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/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, | |
149 GURL(), extensions::EventFilteringInfo()); | |
150 } | |
151 | |
152 bool ExtensionIdleQueryStateFunction::RunImpl() { | |
153 int threshold; | |
154 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &threshold)); | |
155 threshold = CheckThresholdBounds(threshold); | |
156 | |
157 IdleState state = ExtensionIdleCache::CalculateIdleState(threshold); | |
158 if (state != IDLE_STATE_UNKNOWN) { | |
159 SetResult(CreateIdleValue(state)); | |
160 SendResponse(true); | |
161 return true; | |
162 } | |
163 | |
164 CalculateIdleState(threshold, | |
165 base::Bind(&ExtensionIdleQueryStateFunction::IdleStateCallback, | |
166 this, threshold)); | |
167 // Don't send the response, it'll be sent by our callback | |
168 return true; | |
169 } | |
170 | |
171 void ExtensionIdleQueryStateFunction::IdleStateCallback(int threshold, | |
172 IdleState state) { | |
173 // If our state is not active, make sure we're running a polling task to check | |
174 // for active state and report it when it changes to active. | |
175 if (state != IDLE_STATE_ACTIVE) { | |
176 ExtensionIdlePollingTask::CreateNewPollTask(threshold, state, profile_); | |
177 } | |
178 | |
179 SetResult(CreateIdleValue(state)); | |
180 | |
181 ExtensionIdleCache::UpdateCache(threshold, state); | |
182 | |
183 SendResponse(true); | |
184 } | |
185 | |
186 ExtensionIdleCache::CacheData ExtensionIdleCache::cached_data = | |
187 {-1, -1, -1, -1}; | |
188 | |
189 IdleState ExtensionIdleCache::CalculateIdleState(int threshold) { | |
190 return CalculateState(threshold, base::Time::Now().ToDoubleT()); | |
191 } | |
192 | |
193 void ExtensionIdleCache::UpdateCache(int threshold, IdleState state) { | |
194 Update(threshold, state, base::Time::Now().ToDoubleT()); | |
195 } | |
196 | |
197 IdleState ExtensionIdleCache::CalculateState(int threshold, double now) { | |
198 if (threshold < kMinThreshold) | |
199 return IDLE_STATE_UNKNOWN; | |
200 double threshold_moment = now - static_cast<double>(threshold); | |
201 double throttle_interval = static_cast<double>(kThrottleInterval); | |
202 | |
203 // We test for IDEL_STATE_LOCKED first, because the result should be | |
204 // independent of the data for idle and active state. If last state was | |
205 // LOCKED and test for LOCKED is satisfied we should always return LOCKED. | |
206 if (cached_data.latest_locked > 0 && | |
207 now - cached_data.latest_locked < throttle_interval) | |
208 return IDLE_STATE_LOCKED; | |
209 | |
210 // If thershold moment is beyond the moment after whih we are certain we have | |
211 // been active, return active state. We allow kThrottleInterval error. | |
212 if (cached_data.latest_known_active > 0 && | |
213 threshold_moment - cached_data.latest_known_active < throttle_interval) | |
214 return IDLE_STATE_ACTIVE; | |
215 | |
216 // If total error that query interval has in respect to last recorded idle | |
217 // interval is less than kThrottleInterval, return IDLE state. | |
218 // query interval is the interval [now, now - threshold] and the error is | |
219 // defined as amount of query interval that is outside of idle interval. | |
220 double error_from_idle = | |
221 QueryErrorFromIdle(cached_data.idle_interval_start, | |
222 cached_data.idle_interval_end, threshold_moment, now); | |
223 if (cached_data.idle_interval_end > 0 && | |
224 error_from_idle < throttle_interval) | |
225 return IDLE_STATE_IDLE; | |
226 | |
227 return IDLE_STATE_UNKNOWN; | |
228 } | |
229 | |
230 void ExtensionIdleCache::Update(int threshold, IdleState state, double now) { | |
231 if (threshold < kMinThreshold) | |
232 return; | |
233 double threshold_moment = now - static_cast<double>(threshold); | |
234 switch (state) { | |
235 case IDLE_STATE_IDLE: | |
236 if (threshold_moment > cached_data.idle_interval_end) { | |
237 // Cached and new interval don't overlap. We disregard the cached one. | |
238 cached_data.idle_interval_start = threshold_moment; | |
239 } else { | |
240 // Cached and new interval overlap, so we can combine them. We set | |
241 // the cached interval begining to less recent one. | |
242 cached_data.idle_interval_start = | |
243 std::min(cached_data.idle_interval_start, threshold_moment); | |
244 } | |
245 cached_data.idle_interval_end = now; | |
246 // Reset data for LOCKED state, since the last query result is not | |
247 // LOCKED. | |
248 cached_data.latest_locked = -1; | |
249 break; | |
250 case IDLE_STATE_ACTIVE: | |
251 if (threshold_moment > cached_data.latest_known_active) | |
252 cached_data.latest_known_active = threshold_moment; | |
253 // Reset data for LOCKED state, since the last query result is not | |
254 // LOCKED. | |
255 cached_data.latest_locked = -1; | |
256 break; | |
257 case IDLE_STATE_LOCKED: | |
258 if (threshold_moment > cached_data.latest_locked) | |
259 cached_data.latest_locked = now; | |
260 break; | |
261 default: | |
262 return; | |
263 } | |
264 } | |
265 | |
266 int ExtensionIdleCache::get_min_threshold() { | |
267 return kMinThreshold; | |
268 } | |
269 | |
270 double ExtensionIdleCache::get_throttle_interval() { | |
271 return static_cast<double>(kThrottleInterval); | |
272 } | |
OLD | NEW |