| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/extensions/extension_idle_api.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <map> | |
| 9 #include <string> | |
| 10 | |
| 11 #include "base/bind.h" | |
| 12 #include "base/callback.h" | |
| 13 #include "base/json/json_writer.h" | |
| 14 #include "base/message_loop.h" | |
| 15 #include "base/stl_util.h" | |
| 16 #include "base/time.h" | |
| 17 #include "chrome/browser/extensions/extension_event_router.h" | |
| 18 #include "chrome/browser/extensions/extension_host.h" | |
| 19 #include "chrome/browser/extensions/extension_idle_api_constants.h" | |
| 20 #include "chrome/browser/extensions/extension_service.h" | |
| 21 #include "chrome/browser/profiles/profile.h" | |
| 22 #include "chrome/common/extensions/extension.h" | |
| 23 #include "content/public/browser/render_view_host.h" | |
| 24 | |
| 25 namespace keys = extension_idle_api_constants; | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 const int kIdlePollInterval = 1; // Number of seconds between status checks | |
| 30 // when polling for active. | |
| 31 const int kThrottleInterval = 1; // Number of seconds to throttle idle checks | |
| 32 // for. Return the previously checked idle | |
| 33 // state if the next check is faster than this | |
| 34 const int kMinThreshold = 15; // In seconds. Set >1 sec for security concerns. | |
| 35 const int kMaxThreshold = 4*60*60; // Four hours, in seconds. Not set | |
| 36 // arbitrarily high for security concerns. | |
| 37 const unsigned int kMaxCacheSize = 100; // Number of state queries to cache. | |
| 38 | |
| 39 // Calculates the error query interval has in respect to idle interval. | |
| 40 // The error is defined as amount of the query interval that is not part of the | |
| 41 // idle interval. | |
| 42 double QueryErrorFromIdle(double idle_start, | |
| 43 double idle_end, | |
| 44 double query_start, | |
| 45 double query_end) { | |
| 46 return query_end - idle_end + std::max(0., idle_start - query_start); | |
| 47 } | |
| 48 | |
| 49 // Internal class which is used to poll for changes in the system idle state. | |
| 50 class ExtensionIdlePollingTask { | |
| 51 public: | |
| 52 explicit ExtensionIdlePollingTask(int threshold, IdleState last_state, | |
| 53 Profile* profile) : threshold_(threshold), last_state_(last_state), | |
| 54 profile_(profile) {} | |
| 55 | |
| 56 // Check if we're active; then restart the polling task. Do this till we are | |
| 57 // are in active state. | |
| 58 void CheckIdleState(); | |
| 59 void IdleStateCallback(IdleState state); | |
| 60 | |
| 61 // Create a poll task to check for Idle state | |
| 62 static void CreateNewPollTask(int threshold, IdleState state, | |
| 63 Profile* profile); | |
| 64 | |
| 65 private: | |
| 66 int threshold_; | |
| 67 IdleState last_state_; | |
| 68 Profile* profile_; | |
| 69 | |
| 70 static bool poll_task_running_; | |
| 71 | |
| 72 DISALLOW_COPY_AND_ASSIGN(ExtensionIdlePollingTask); | |
| 73 }; | |
| 74 | |
| 75 // Implementation of ExtensionIdlePollingTask. | |
| 76 bool ExtensionIdlePollingTask::poll_task_running_ = false; | |
| 77 | |
| 78 void ExtensionIdlePollingTask::IdleStateCallback(IdleState current_state) { | |
| 79 // If we just came into an active state, notify the extension. | |
| 80 if (IDLE_STATE_ACTIVE == current_state && last_state_ != current_state) | |
| 81 ExtensionIdleEventRouter::OnIdleStateChange(profile_, current_state); | |
| 82 | |
| 83 ExtensionIdlePollingTask::poll_task_running_ = false; | |
| 84 | |
| 85 ExtensionIdleCache::UpdateCache(threshold_, current_state); | |
| 86 | |
| 87 // Startup another polling task as we exit. | |
| 88 if (current_state != IDLE_STATE_ACTIVE) | |
| 89 ExtensionIdlePollingTask::CreateNewPollTask(threshold_, current_state, | |
| 90 profile_); | |
| 91 | |
| 92 // This instance won't be needed anymore. | |
| 93 delete this; | |
| 94 } | |
| 95 | |
| 96 void ExtensionIdlePollingTask::CheckIdleState() { | |
| 97 CalculateIdleState(threshold_, | |
| 98 base::Bind(&ExtensionIdlePollingTask::IdleStateCallback, | |
| 99 base::Unretained(this))); | |
| 100 } | |
| 101 | |
| 102 // static | |
| 103 void ExtensionIdlePollingTask::CreateNewPollTask(int threshold, IdleState state, | |
| 104 Profile* profile) { | |
| 105 if (ExtensionIdlePollingTask::poll_task_running_) return; | |
| 106 | |
| 107 ExtensionIdlePollingTask::poll_task_running_ = true; | |
| 108 MessageLoop::current()->PostDelayedTask( | |
| 109 FROM_HERE, | |
| 110 base::Bind(&ExtensionIdlePollingTask::CheckIdleState, base::Unretained( | |
| 111 new ExtensionIdlePollingTask(threshold, state, profile))), | |
| 112 base::TimeDelta::FromSeconds(kIdlePollInterval)); | |
| 113 } | |
| 114 | |
| 115 | |
| 116 const char* IdleStateToDescription(IdleState state) { | |
| 117 if (IDLE_STATE_ACTIVE == state) | |
| 118 return keys::kStateActive; | |
| 119 if (IDLE_STATE_IDLE == state) | |
| 120 return keys::kStateIdle; | |
| 121 return keys::kStateLocked; | |
| 122 }; | |
| 123 | |
| 124 // Helper function for reporting the idle state. The lifetime of the object | |
| 125 // returned is controlled by the caller. | |
| 126 StringValue* CreateIdleValue(IdleState idle_state) { | |
| 127 StringValue* result = new StringValue(IdleStateToDescription(idle_state)); | |
| 128 return result; | |
| 129 } | |
| 130 | |
| 131 int CheckThresholdBounds(int timeout) { | |
| 132 if (timeout < kMinThreshold) return kMinThreshold; | |
| 133 if (timeout > kMaxThreshold) return kMaxThreshold; | |
| 134 return timeout; | |
| 135 } | |
| 136 | |
| 137 }; // namespace | |
| 138 | |
| 139 void ExtensionIdleEventRouter::OnIdleStateChange(Profile* profile, | |
| 140 IdleState state) { | |
| 141 // Prepare the single argument of the current state. | |
| 142 ListValue args; | |
| 143 args.Append(CreateIdleValue(state)); | |
| 144 std::string json_args; | |
| 145 base::JSONWriter::Write(&args, &json_args); | |
| 146 | |
| 147 profile->GetExtensionEventRouter()->DispatchEventToRenderers( | |
| 148 keys::kOnStateChanged, json_args, profile, GURL(), EventFilteringInfo()); | |
| 149 } | |
| 150 | |
| 151 bool ExtensionIdleQueryStateFunction::RunImpl() { | |
| 152 int threshold; | |
| 153 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &threshold)); | |
| 154 threshold = CheckThresholdBounds(threshold); | |
| 155 | |
| 156 IdleState state = ExtensionIdleCache::CalculateIdleState(threshold); | |
| 157 if (state != IDLE_STATE_UNKNOWN) { | |
| 158 result_.reset(CreateIdleValue(state)); | |
| 159 SendResponse(true); | |
| 160 return true; | |
| 161 } | |
| 162 | |
| 163 CalculateIdleState(threshold, | |
| 164 base::Bind(&ExtensionIdleQueryStateFunction::IdleStateCallback, | |
| 165 this, threshold)); | |
| 166 // Don't send the response, it'll be sent by our callback | |
| 167 return true; | |
| 168 } | |
| 169 | |
| 170 void ExtensionIdleQueryStateFunction::IdleStateCallback(int threshold, | |
| 171 IdleState state) { | |
| 172 // If our state is not active, make sure we're running a polling task to check | |
| 173 // for active state and report it when it changes to active. | |
| 174 if (state != IDLE_STATE_ACTIVE) { | |
| 175 ExtensionIdlePollingTask::CreateNewPollTask(threshold, state, profile_); | |
| 176 } | |
| 177 | |
| 178 result_.reset(CreateIdleValue(state)); | |
| 179 | |
| 180 ExtensionIdleCache::UpdateCache(threshold, state); | |
| 181 | |
| 182 SendResponse(true); | |
| 183 } | |
| 184 | |
| 185 ExtensionIdleCache::CacheData ExtensionIdleCache::cached_data = | |
| 186 {-1, -1, -1, -1}; | |
| 187 | |
| 188 IdleState ExtensionIdleCache::CalculateIdleState(int threshold) { | |
| 189 return CalculateState(threshold, base::Time::Now().ToDoubleT()); | |
| 190 } | |
| 191 | |
| 192 void ExtensionIdleCache::UpdateCache(int threshold, IdleState state) { | |
| 193 Update(threshold, state, base::Time::Now().ToDoubleT()); | |
| 194 } | |
| 195 | |
| 196 IdleState ExtensionIdleCache::CalculateState(int threshold, double now) { | |
| 197 if (threshold < kMinThreshold) | |
| 198 return IDLE_STATE_UNKNOWN; | |
| 199 double threshold_moment = now - static_cast<double>(threshold); | |
| 200 double throttle_interval = static_cast<double>(kThrottleInterval); | |
| 201 | |
| 202 // We test for IDEL_STATE_LOCKED first, because the result should be | |
| 203 // independent of the data for idle and active state. If last state was | |
| 204 // LOCKED and test for LOCKED is satisfied we should always return LOCKED. | |
| 205 if (cached_data.latest_locked > 0 && | |
| 206 now - cached_data.latest_locked < throttle_interval) | |
| 207 return IDLE_STATE_LOCKED; | |
| 208 | |
| 209 // If thershold moment is beyond the moment after whih we are certain we have | |
| 210 // been active, return active state. We allow kThrottleInterval error. | |
| 211 if (cached_data.latest_known_active > 0 && | |
| 212 threshold_moment - cached_data.latest_known_active < throttle_interval) | |
| 213 return IDLE_STATE_ACTIVE; | |
| 214 | |
| 215 // If total error that query interval has in respect to last recorded idle | |
| 216 // interval is less than kThrottleInterval, return IDLE state. | |
| 217 // query interval is the interval [now, now - threshold] and the error is | |
| 218 // defined as amount of query interval that is outside of idle interval. | |
| 219 double error_from_idle = | |
| 220 QueryErrorFromIdle(cached_data.idle_interval_start, | |
| 221 cached_data.idle_interval_end, threshold_moment, now); | |
| 222 if (cached_data.idle_interval_end > 0 && | |
| 223 error_from_idle < throttle_interval) | |
| 224 return IDLE_STATE_IDLE; | |
| 225 | |
| 226 return IDLE_STATE_UNKNOWN; | |
| 227 } | |
| 228 | |
| 229 void ExtensionIdleCache::Update(int threshold, IdleState state, double now) { | |
| 230 if (threshold < kMinThreshold) | |
| 231 return; | |
| 232 double threshold_moment = now - static_cast<double>(threshold); | |
| 233 switch (state) { | |
| 234 case IDLE_STATE_IDLE: | |
| 235 if (threshold_moment > cached_data.idle_interval_end) { | |
| 236 // Cached and new interval don't overlap. We disregard the cached one. | |
| 237 cached_data.idle_interval_start = threshold_moment; | |
| 238 } else { | |
| 239 // Cached and new interval overlap, so we can combine them. We set | |
| 240 // the cached interval begining to less recent one. | |
| 241 cached_data.idle_interval_start = | |
| 242 std::min(cached_data.idle_interval_start, threshold_moment); | |
| 243 } | |
| 244 cached_data.idle_interval_end = now; | |
| 245 // Reset data for LOCKED state, since the last query result is not | |
| 246 // LOCKED. | |
| 247 cached_data.latest_locked = -1; | |
| 248 break; | |
| 249 case IDLE_STATE_ACTIVE: | |
| 250 if (threshold_moment > cached_data.latest_known_active) | |
| 251 cached_data.latest_known_active = threshold_moment; | |
| 252 // Reset data for LOCKED state, since the last query result is not | |
| 253 // LOCKED. | |
| 254 cached_data.latest_locked = -1; | |
| 255 break; | |
| 256 case IDLE_STATE_LOCKED: | |
| 257 if (threshold_moment > cached_data.latest_locked) | |
| 258 cached_data.latest_locked = now; | |
| 259 break; | |
| 260 default: | |
| 261 return; | |
| 262 } | |
| 263 } | |
| 264 | |
| 265 int ExtensionIdleCache::get_min_threshold() { | |
| 266 return kMinThreshold; | |
| 267 } | |
| 268 | |
| 269 double ExtensionIdleCache::get_throttle_interval() { | |
| 270 return static_cast<double>(kThrottleInterval); | |
| 271 } | |
| OLD | NEW |