|
1 // Copyright (c) 2008 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 "base/message_pump_mac.h" |
|
6 |
|
7 #import <AppKit/AppKit.h> |
|
8 #import <Foundation/Foundation.h> |
|
9 #include <IOKit/IOMessage.h> |
|
10 #include <IOKit/pwr_mgt/IOPMLib.h> |
|
11 |
|
12 #include <limits> |
|
13 |
|
14 #import "base/chrome_application_mac.h" |
|
15 #include "base/logging.h" |
|
16 #include "base/time.h" |
|
17 |
|
18 namespace { |
|
19 |
|
20 void NoOp(void* info) { |
|
21 } |
|
22 |
|
23 const CFTimeInterval kCFTimeIntervalMax = |
|
24 std::numeric_limits<CFTimeInterval>::max(); |
|
25 |
|
26 } // namespace |
|
27 |
|
28 namespace base { |
|
29 |
|
30 // A scoper for autorelease pools created from message pump run loops. |
|
31 // Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare |
|
32 // case where an autorelease pool needs to be passed in. |
|
33 class MessagePumpScopedAutoreleasePool { |
|
34 public: |
|
35 explicit MessagePumpScopedAutoreleasePool(MessagePumpCFRunLoopBase* pump) : |
|
36 pool_(pump->CreateAutoreleasePool()) { |
|
37 } |
|
38 ~MessagePumpScopedAutoreleasePool() { |
|
39 [pool_ drain]; |
|
40 } |
|
41 |
|
42 private: |
|
43 NSAutoreleasePool* pool_; |
|
44 DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); |
|
45 }; |
|
46 |
|
47 // Must be called on the run loop thread. |
|
48 MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() |
|
49 : delegate_(NULL), |
|
50 delayed_work_fire_time_(kCFTimeIntervalMax), |
|
51 nesting_level_(0), |
|
52 run_nesting_level_(0), |
|
53 deepest_nesting_level_(0), |
|
54 delegateless_work_(false), |
|
55 delegateless_delayed_work_(false), |
|
56 delegateless_idle_work_(false) { |
|
57 run_loop_ = CFRunLoopGetCurrent(); |
|
58 CFRetain(run_loop_); |
|
59 |
|
60 // Set a repeating timer with a preposterous firing time and interval. The |
|
61 // timer will effectively never fire as-is. The firing time will be adjusted |
|
62 // as needed when ScheduleDelayedWork is called. |
|
63 CFRunLoopTimerContext timer_context = CFRunLoopTimerContext(); |
|
64 timer_context.info = this; |
|
65 delayed_work_timer_ = CFRunLoopTimerCreate(NULL, // allocator |
|
66 kCFTimeIntervalMax, // fire time |
|
67 kCFTimeIntervalMax, // interval |
|
68 0, // flags |
|
69 0, // priority |
|
70 RunDelayedWorkTimer, |
|
71 &timer_context); |
|
72 CFRunLoopAddTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); |
|
73 |
|
74 CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); |
|
75 source_context.info = this; |
|
76 source_context.perform = RunWorkSource; |
|
77 work_source_ = CFRunLoopSourceCreate(NULL, // allocator |
|
78 1, // priority |
|
79 &source_context); |
|
80 CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes); |
|
81 |
|
82 source_context.perform = RunDelayedWorkSource; |
|
83 delayed_work_source_ = CFRunLoopSourceCreate(NULL, // allocator |
|
84 2, // priority |
|
85 &source_context); |
|
86 CFRunLoopAddSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes); |
|
87 |
|
88 source_context.perform = RunIdleWorkSource; |
|
89 idle_work_source_ = CFRunLoopSourceCreate(NULL, // allocator |
|
90 3, // priority |
|
91 &source_context); |
|
92 CFRunLoopAddSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); |
|
93 |
|
94 source_context.perform = RunNestingDeferredWorkSource; |
|
95 nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL, // allocator |
|
96 0, // priority |
|
97 &source_context); |
|
98 CFRunLoopAddSource(run_loop_, nesting_deferred_work_source_, |
|
99 kCFRunLoopCommonModes); |
|
100 |
|
101 CFRunLoopObserverContext observer_context = CFRunLoopObserverContext(); |
|
102 observer_context.info = this; |
|
103 pre_wait_observer_ = CFRunLoopObserverCreate(NULL, // allocator |
|
104 kCFRunLoopBeforeWaiting, |
|
105 true, // repeat |
|
106 0, // priority |
|
107 PreWaitObserver, |
|
108 &observer_context); |
|
109 CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes); |
|
110 |
|
111 pre_source_observer_ = CFRunLoopObserverCreate(NULL, // allocator |
|
112 kCFRunLoopBeforeSources, |
|
113 true, // repeat |
|
114 0, // priority |
|
115 PreSourceObserver, |
|
116 &observer_context); |
|
117 CFRunLoopAddObserver(run_loop_, pre_source_observer_, kCFRunLoopCommonModes); |
|
118 |
|
119 enter_exit_observer_ = CFRunLoopObserverCreate(NULL, // allocator |
|
120 kCFRunLoopEntry | |
|
121 kCFRunLoopExit, |
|
122 true, // repeat |
|
123 0, // priority |
|
124 EnterExitObserver, |
|
125 &observer_context); |
|
126 CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes); |
|
127 |
|
128 root_power_domain_ = IORegisterForSystemPower(this, |
|
129 &power_notification_port_, |
|
130 PowerStateNotification, |
|
131 &power_notification_object_); |
|
132 if (root_power_domain_ != MACH_PORT_NULL) { |
|
133 CFRunLoopAddSource( |
|
134 run_loop_, |
|
135 IONotificationPortGetRunLoopSource(power_notification_port_), |
|
136 kCFRunLoopCommonModes); |
|
137 } |
|
138 } |
|
139 |
|
140 // Ideally called on the run loop thread. If other run loops were running |
|
141 // lower on the run loop thread's stack when this object was created, the |
|
142 // same number of run loops must be running when this object is destroyed. |
|
143 MessagePumpCFRunLoopBase::~MessagePumpCFRunLoopBase() { |
|
144 if (root_power_domain_ != MACH_PORT_NULL) { |
|
145 CFRunLoopRemoveSource( |
|
146 run_loop_, |
|
147 IONotificationPortGetRunLoopSource(power_notification_port_), |
|
148 kCFRunLoopCommonModes); |
|
149 IODeregisterForSystemPower(&power_notification_object_); |
|
150 IOServiceClose(root_power_domain_); |
|
151 IONotificationPortDestroy(power_notification_port_); |
|
152 } |
|
153 |
|
154 CFRunLoopRemoveObserver(run_loop_, enter_exit_observer_, |
|
155 kCFRunLoopCommonModes); |
|
156 CFRelease(enter_exit_observer_); |
|
157 |
|
158 CFRunLoopRemoveObserver(run_loop_, pre_source_observer_, |
|
159 kCFRunLoopCommonModes); |
|
160 CFRelease(pre_source_observer_); |
|
161 |
|
162 CFRunLoopRemoveObserver(run_loop_, pre_wait_observer_, |
|
163 kCFRunLoopCommonModes); |
|
164 CFRelease(pre_wait_observer_); |
|
165 |
|
166 CFRunLoopRemoveSource(run_loop_, nesting_deferred_work_source_, |
|
167 kCFRunLoopCommonModes); |
|
168 CFRelease(nesting_deferred_work_source_); |
|
169 |
|
170 CFRunLoopRemoveSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes); |
|
171 CFRelease(idle_work_source_); |
|
172 |
|
173 CFRunLoopRemoveSource(run_loop_, delayed_work_source_, kCFRunLoopCommonModes); |
|
174 CFRelease(delayed_work_source_); |
|
175 |
|
176 CFRunLoopRemoveSource(run_loop_, work_source_, kCFRunLoopCommonModes); |
|
177 CFRelease(work_source_); |
|
178 |
|
179 CFRunLoopRemoveTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes); |
|
180 CFRelease(delayed_work_timer_); |
|
181 |
|
182 CFRelease(run_loop_); |
|
183 } |
|
184 |
|
185 // Must be called on the run loop thread. |
|
186 void MessagePumpCFRunLoopBase::Run(Delegate* delegate) { |
|
187 // nesting_level_ will be incremented in EnterExitRunLoop, so set |
|
188 // run_nesting_level_ accordingly. |
|
189 int last_run_nesting_level = run_nesting_level_; |
|
190 run_nesting_level_ = nesting_level_ + 1; |
|
191 |
|
192 Delegate* last_delegate = delegate_; |
|
193 delegate_ = delegate; |
|
194 |
|
195 if (delegate) { |
|
196 // If any work showed up but could not be dispatched for want of a |
|
197 // delegate, set it up for dispatch again now that a delegate is |
|
198 // available. |
|
199 if (delegateless_work_) { |
|
200 CFRunLoopSourceSignal(work_source_); |
|
201 delegateless_work_ = false; |
|
202 } |
|
203 if (delegateless_delayed_work_) { |
|
204 CFRunLoopSourceSignal(delayed_work_source_); |
|
205 delegateless_delayed_work_ = false; |
|
206 } |
|
207 if (delegateless_idle_work_) { |
|
208 CFRunLoopSourceSignal(idle_work_source_); |
|
209 delegateless_idle_work_ = false; |
|
210 } |
|
211 } |
|
212 |
|
213 DoRun(delegate); |
|
214 |
|
215 // Restore the previous state of the object. |
|
216 delegate_ = last_delegate; |
|
217 run_nesting_level_ = last_run_nesting_level; |
|
218 } |
|
219 |
|
220 // May be called on any thread. |
|
221 void MessagePumpCFRunLoopBase::ScheduleWork() { |
|
222 CFRunLoopSourceSignal(work_source_); |
|
223 CFRunLoopWakeUp(run_loop_); |
|
224 } |
|
225 |
|
226 // Must be called on the run loop thread. |
|
227 void MessagePumpCFRunLoopBase::ScheduleDelayedWork( |
|
228 const TimeTicks& delayed_work_time) { |
|
229 TimeDelta delta = delayed_work_time - TimeTicks::Now(); |
|
230 delayed_work_fire_time_ = CFAbsoluteTimeGetCurrent() + delta.InSecondsF(); |
|
231 CFRunLoopTimerSetNextFireDate(delayed_work_timer_, delayed_work_fire_time_); |
|
232 } |
|
233 |
|
234 // Called from the run loop. |
|
235 // static |
|
236 void MessagePumpCFRunLoopBase::RunDelayedWorkTimer(CFRunLoopTimerRef timer, |
|
237 void* info) { |
|
238 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
239 |
|
240 // The timer won't fire again until it's reset. |
|
241 self->delayed_work_fire_time_ = kCFTimeIntervalMax; |
|
242 |
|
243 // CFRunLoopTimers fire outside of the priority scheme for CFRunLoopSources. |
|
244 // In order to establish the proper priority where delegate_->DoDelayedWork |
|
245 // can only be called if delegate_->DoWork returns false, the timer used |
|
246 // to schedule delayed work must signal a CFRunLoopSource set at a lower |
|
247 // priority than the one used for delegate_->DoWork. |
|
248 CFRunLoopSourceSignal(self->delayed_work_source_); |
|
249 } |
|
250 |
|
251 // Called from the run loop. |
|
252 // static |
|
253 void MessagePumpCFRunLoopBase::RunWorkSource(void* info) { |
|
254 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
255 self->RunWork(); |
|
256 } |
|
257 |
|
258 // Called by MessagePumpCFRunLoopBase::RunWorkSource. |
|
259 bool MessagePumpCFRunLoopBase::RunWork() { |
|
260 if (!delegate_) { |
|
261 // This point can be reached with a NULL delegate_ if Run is not on the |
|
262 // stack but foreign code is spinning the CFRunLoop. Arrange to come back |
|
263 // here when a delegate is available. |
|
264 delegateless_work_ = true; |
|
265 return false; |
|
266 } |
|
267 |
|
268 // The NSApplication-based run loop only drains the autorelease pool at each |
|
269 // UI event (NSEvent). The autorelease pool is not drained for each |
|
270 // CFRunLoopSource target that's run. Use a local pool for any autoreleased |
|
271 // objects if the app is not currently handling a UI event to ensure they're |
|
272 // released promptly even in the absence of UI events. |
|
273 MessagePumpScopedAutoreleasePool autorelease_pool(this); |
|
274 |
|
275 // Call DoWork once, and if something was done, arrange to come back here |
|
276 // again as long as the loop is still running. |
|
277 bool did_work = delegate_->DoWork(); |
|
278 if (did_work) { |
|
279 CFRunLoopSourceSignal(work_source_); |
|
280 } |
|
281 |
|
282 return did_work; |
|
283 } |
|
284 |
|
285 // Called from the run loop. |
|
286 // static |
|
287 void MessagePumpCFRunLoopBase::RunDelayedWorkSource(void* info) { |
|
288 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
289 self->RunDelayedWork(); |
|
290 } |
|
291 |
|
292 // Called by MessagePumpCFRunLoopBase::RunDelayedWorkSource. |
|
293 bool MessagePumpCFRunLoopBase::RunDelayedWork() { |
|
294 if (!delegate_) { |
|
295 // This point can be reached with a NULL delegate_ if Run is not on the |
|
296 // stack but foreign code is spinning the CFRunLoop. Arrange to come back |
|
297 // here when a delegate is available. |
|
298 delegateless_delayed_work_ = true; |
|
299 return false; |
|
300 } |
|
301 |
|
302 // The NSApplication-based run loop only drains the autorelease pool at each |
|
303 // UI event (NSEvent). The autorelease pool is not drained for each |
|
304 // CFRunLoopSource target that's run. Use a local pool for any autoreleased |
|
305 // objects if the app is not currently handling a UI event to ensure they're |
|
306 // released promptly even in the absence of UI events. |
|
307 MessagePumpScopedAutoreleasePool autorelease_pool(this); |
|
308 |
|
309 TimeTicks next_time; |
|
310 delegate_->DoDelayedWork(&next_time); |
|
311 |
|
312 bool more_work = !next_time.is_null(); |
|
313 if (more_work) { |
|
314 TimeDelta delay = next_time - TimeTicks::Now(); |
|
315 if (delay > TimeDelta()) { |
|
316 // There's more delayed work to be done in the future. |
|
317 ScheduleDelayedWork(next_time); |
|
318 } else { |
|
319 // There's more delayed work to be done, and its time is in the past. |
|
320 // Arrange to come back here directly as long as the loop is still |
|
321 // running. |
|
322 CFRunLoopSourceSignal(delayed_work_source_); |
|
323 } |
|
324 } |
|
325 |
|
326 return more_work; |
|
327 } |
|
328 |
|
329 // Called from the run loop. |
|
330 // static |
|
331 void MessagePumpCFRunLoopBase::RunIdleWorkSource(void* info) { |
|
332 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
333 self->RunIdleWork(); |
|
334 } |
|
335 |
|
336 // Called by MessagePumpCFRunLoopBase::RunIdleWorkSource. |
|
337 bool MessagePumpCFRunLoopBase::RunIdleWork() { |
|
338 if (!delegate_) { |
|
339 // This point can be reached with a NULL delegate_ if Run is not on the |
|
340 // stack but foreign code is spinning the CFRunLoop. Arrange to come back |
|
341 // here when a delegate is available. |
|
342 delegateless_idle_work_ = true; |
|
343 return false; |
|
344 } |
|
345 |
|
346 // The NSApplication-based run loop only drains the autorelease pool at each |
|
347 // UI event (NSEvent). The autorelease pool is not drained for each |
|
348 // CFRunLoopSource target that's run. Use a local pool for any autoreleased |
|
349 // objects if the app is not currently handling a UI event to ensure they're |
|
350 // released promptly even in the absence of UI events. |
|
351 MessagePumpScopedAutoreleasePool autorelease_pool(this); |
|
352 |
|
353 // Call DoIdleWork once, and if something was done, arrange to come back here |
|
354 // again as long as the loop is still running. |
|
355 bool did_work = delegate_->DoIdleWork(); |
|
356 if (did_work) { |
|
357 CFRunLoopSourceSignal(idle_work_source_); |
|
358 } |
|
359 |
|
360 return did_work; |
|
361 } |
|
362 |
|
363 // Called from the run loop. |
|
364 // static |
|
365 void MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource(void* info) { |
|
366 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
367 self->RunNestingDeferredWork(); |
|
368 } |
|
369 |
|
370 // Called by MessagePumpCFRunLoopBase::RunNestingDeferredWorkSource. |
|
371 bool MessagePumpCFRunLoopBase::RunNestingDeferredWork() { |
|
372 if (!delegate_) { |
|
373 // This point can be reached with a NULL delegate_ if Run is not on the |
|
374 // stack but foreign code is spinning the CFRunLoop. There's no sense in |
|
375 // attempting to do any work or signalling the work sources because |
|
376 // without a delegate, work is not possible. |
|
377 return false; |
|
378 } |
|
379 |
|
380 // Immediately try work in priority order. |
|
381 if (!RunWork()) { |
|
382 if (!RunDelayedWork()) { |
|
383 if (!RunIdleWork()) { |
|
384 return false; |
|
385 } |
|
386 } else { |
|
387 // There was no work, and delayed work was done. Arrange for the loop |
|
388 // to try non-nestable idle work on a subsequent pass. |
|
389 CFRunLoopSourceSignal(idle_work_source_); |
|
390 } |
|
391 } else { |
|
392 // Work was done. Arrange for the loop to try non-nestable delayed and |
|
393 // idle work on a subsequent pass. |
|
394 CFRunLoopSourceSignal(delayed_work_source_); |
|
395 CFRunLoopSourceSignal(idle_work_source_); |
|
396 } |
|
397 |
|
398 return true; |
|
399 } |
|
400 |
|
401 // Called before the run loop goes to sleep or exits, or processes sources. |
|
402 void MessagePumpCFRunLoopBase::MaybeScheduleNestingDeferredWork() { |
|
403 // deepest_nesting_level_ is set as run loops are entered. If the deepest |
|
404 // level encountered is deeper than the current level, a nested loop |
|
405 // (relative to the current level) ran since the last time nesting-deferred |
|
406 // work was scheduled. When that situation is encountered, schedule |
|
407 // nesting-deferred work in case any work was deferred because nested work |
|
408 // was disallowed. |
|
409 if (deepest_nesting_level_ > nesting_level_) { |
|
410 deepest_nesting_level_ = nesting_level_; |
|
411 CFRunLoopSourceSignal(nesting_deferred_work_source_); |
|
412 } |
|
413 } |
|
414 |
|
415 // Called from the run loop. |
|
416 // static |
|
417 void MessagePumpCFRunLoopBase::PreWaitObserver(CFRunLoopObserverRef observer, |
|
418 CFRunLoopActivity activity, |
|
419 void* info) { |
|
420 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
421 |
|
422 // Attempt to do some idle work before going to sleep. |
|
423 self->RunIdleWork(); |
|
424 |
|
425 // The run loop is about to go to sleep. If any of the work done since it |
|
426 // started or woke up resulted in a nested run loop running, |
|
427 // nesting-deferred work may have accumulated. Schedule it for processing |
|
428 // if appropriate. |
|
429 self->MaybeScheduleNestingDeferredWork(); |
|
430 } |
|
431 |
|
432 // Called from the run loop. |
|
433 // static |
|
434 void MessagePumpCFRunLoopBase::PreSourceObserver(CFRunLoopObserverRef observer, |
|
435 CFRunLoopActivity activity, |
|
436 void* info) { |
|
437 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
438 |
|
439 // The run loop has reached the top of the loop and is about to begin |
|
440 // processing sources. If the last iteration of the loop at this nesting |
|
441 // level did not sleep or exit, nesting-deferred work may have accumulated |
|
442 // if a nested loop ran. Schedule nesting-deferred work for processing if |
|
443 // appropriate. |
|
444 self->MaybeScheduleNestingDeferredWork(); |
|
445 } |
|
446 |
|
447 // Called from the run loop. |
|
448 // static |
|
449 void MessagePumpCFRunLoopBase::EnterExitObserver(CFRunLoopObserverRef observer, |
|
450 CFRunLoopActivity activity, |
|
451 void* info) { |
|
452 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
453 |
|
454 switch (activity) { |
|
455 case kCFRunLoopEntry: |
|
456 ++self->nesting_level_; |
|
457 if (self->nesting_level_ > self->deepest_nesting_level_) { |
|
458 self->deepest_nesting_level_ = self->nesting_level_; |
|
459 } |
|
460 break; |
|
461 |
|
462 case kCFRunLoopExit: |
|
463 // Not all run loops go to sleep. If a run loop is stopped before it |
|
464 // goes to sleep due to a CFRunLoopStop call, or if the timeout passed |
|
465 // to CFRunLoopRunInMode expires, the run loop may proceed directly from |
|
466 // handling sources to exiting without any sleep. This most commonly |
|
467 // occurs when CFRunLoopRunInMode is passed a timeout of 0, causing it |
|
468 // to make a single pass through the loop and exit without sleep. Some |
|
469 // native loops use CFRunLoop in this way. Because PreWaitObserver will |
|
470 // not be called in these case, MaybeScheduleNestingDeferredWork needs |
|
471 // to be called here, as the run loop exits. |
|
472 // |
|
473 // MaybeScheduleNestingDeferredWork consults self->nesting_level_ |
|
474 // to determine whether to schedule nesting-deferred work. It expects |
|
475 // the nesting level to be set to the depth of the loop that is going |
|
476 // to sleep or exiting. It must be called before decrementing the |
|
477 // value so that the value still corresponds to the level of the exiting |
|
478 // loop. |
|
479 self->MaybeScheduleNestingDeferredWork(); |
|
480 --self->nesting_level_; |
|
481 break; |
|
482 |
|
483 default: |
|
484 break; |
|
485 } |
|
486 |
|
487 self->EnterExitRunLoop(activity); |
|
488 } |
|
489 |
|
490 // Called from the run loop. |
|
491 // static |
|
492 void MessagePumpCFRunLoopBase::PowerStateNotification(void* info, |
|
493 io_service_t service, |
|
494 uint32_t message_type, |
|
495 void* message_argument) { |
|
496 // CFRunLoopTimer (NSTimer) is scheduled in terms of CFAbsoluteTime, which |
|
497 // measures the number of seconds since 2001-01-01 00:00:00.0 Z. It is |
|
498 // implemented in terms of kernel ticks, as in mach_absolute_time. While an |
|
499 // offset and scale factor can be applied to convert between the two time |
|
500 // bases at any time after boot, the kernel clock stops while the system is |
|
501 // asleep, altering the offset. (The offset will also change when the |
|
502 // real-time clock is adjusted.) CFRunLoopTimers are not readjusted to take |
|
503 // this into account when the system wakes up, so any timers that were |
|
504 // pending while the system was asleep will be delayed by the sleep |
|
505 // duration. |
|
506 // |
|
507 // The MessagePump interface assumes that scheduled delayed work will be |
|
508 // performed at the time ScheduleDelayedWork was asked to perform it. The |
|
509 // delay caused by the CFRunLoopTimer not firing at the appropriate time |
|
510 // results in a stall of queued delayed work when the system wakes up. |
|
511 // With this limitation, scheduled work would not be performed until |
|
512 // (system wake time + scheduled work time - system sleep time), while it |
|
513 // would be expected to be performed at (scheduled work time). |
|
514 // |
|
515 // To work around this problem, when the system wakes up from sleep, if a |
|
516 // delayed work timer is pending, it is rescheduled to fire at the original |
|
517 // time that it was scheduled to fire. |
|
518 // |
|
519 // This mechanism is not resilient if the real-time clock does not maintain |
|
520 // stable time while the system is sleeping, but it matches the behavior of |
|
521 // the various other MessagePump implementations, and MessageLoop seems to |
|
522 // be limited in the same way. |
|
523 // |
|
524 // References |
|
525 // - Chris Kane, "NSTimer and deep sleep," cocoa-dev@lists.apple.com, |
|
526 // http://lists.apple.com/archives/Cocoa-dev/2002/May/msg01547.html |
|
527 // - Apple Technical Q&A QA1340, "Registering and unregistering for sleep |
|
528 // and wake notifications," |
|
529 // http://developer.apple.com/mac/library/qa/qa2004/qa1340.html |
|
530 // - Core Foundation source code, CF-550/CFRunLoop.c and CF-550/CFDate.c, |
|
531 // http://www.opensource.apple.com/ |
|
532 |
|
533 MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info); |
|
534 |
|
535 switch (message_type) { |
|
536 case kIOMessageSystemWillPowerOn: |
|
537 if (self->delayed_work_fire_time_ != kCFTimeIntervalMax) { |
|
538 CFRunLoopTimerSetNextFireDate(self->delayed_work_timer_, |
|
539 self->delayed_work_fire_time_); |
|
540 } |
|
541 break; |
|
542 |
|
543 case kIOMessageSystemWillSleep: |
|
544 case kIOMessageCanSystemSleep: |
|
545 // The system will wait for 30 seconds before entering sleep if neither |
|
546 // IOAllowPowerChange nor IOCancelPowerChange are called. That would be |
|
547 // pretty antisocial. |
|
548 IOAllowPowerChange(self->root_power_domain_, |
|
549 reinterpret_cast<long>(message_argument)); |
|
550 break; |
|
551 |
|
552 default: |
|
553 break; |
|
554 } |
|
555 } |
|
556 |
|
557 // Called by MessagePumpCFRunLoopBase::EnterExitRunLoop. The default |
|
558 // implementation is a no-op. |
|
559 void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) { |
|
560 } |
|
561 |
|
562 // Base version returns a standard NSAutoreleasePool. |
|
563 NSAutoreleasePool* MessagePumpCFRunLoopBase::CreateAutoreleasePool() { |
|
564 return [[NSAutoreleasePool alloc] init]; |
|
565 } |
|
566 |
|
567 MessagePumpCFRunLoop::MessagePumpCFRunLoop() |
|
568 : quit_pending_(false) { |
|
569 } |
|
570 |
|
571 // Called by MessagePumpCFRunLoopBase::DoRun. If other CFRunLoopRun loops were |
|
572 // running lower on the run loop thread's stack when this object was created, |
|
573 // the same number of CFRunLoopRun loops must be running for the outermost call |
|
574 // to Run. Run/DoRun are reentrant after that point. |
|
575 void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { |
|
576 // This is completely identical to calling CFRunLoopRun(), except autorelease |
|
577 // pool management is introduced. |
|
578 int result; |
|
579 do { |
|
580 MessagePumpScopedAutoreleasePool autorelease_pool(this); |
|
581 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, |
|
582 kCFTimeIntervalMax, |
|
583 false); |
|
584 } while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished); |
|
585 } |
|
586 |
|
587 // Must be called on the run loop thread. |
|
588 void MessagePumpCFRunLoop::Quit() { |
|
589 // Stop the innermost run loop managed by this MessagePumpCFRunLoop object. |
|
590 if (nesting_level() == run_nesting_level()) { |
|
591 // This object is running the innermost loop, just stop it. |
|
592 CFRunLoopStop(run_loop()); |
|
593 } else { |
|
594 // There's another loop running inside the loop managed by this object. |
|
595 // In other words, someone else called CFRunLoopRunInMode on the same |
|
596 // thread, deeper on the stack than the deepest Run call. Don't preempt |
|
597 // other run loops, just mark this object to quit the innermost Run as |
|
598 // soon as the other inner loops not managed by Run are done. |
|
599 quit_pending_ = true; |
|
600 } |
|
601 } |
|
602 |
|
603 // Called by MessagePumpCFRunLoopBase::EnterExitObserver. |
|
604 void MessagePumpCFRunLoop::EnterExitRunLoop(CFRunLoopActivity activity) { |
|
605 if (activity == kCFRunLoopExit && |
|
606 nesting_level() == run_nesting_level() && |
|
607 quit_pending_) { |
|
608 // Quit was called while loops other than those managed by this object |
|
609 // were running further inside a run loop managed by this object. Now |
|
610 // that all unmanaged inner run loops are gone, stop the loop running |
|
611 // just inside Run. |
|
612 CFRunLoopStop(run_loop()); |
|
613 quit_pending_ = false; |
|
614 } |
|
615 } |
|
616 |
|
617 MessagePumpNSRunLoop::MessagePumpNSRunLoop() |
|
618 : keep_running_(true) { |
|
619 CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); |
|
620 source_context.perform = NoOp; |
|
621 quit_source_ = CFRunLoopSourceCreate(NULL, // allocator |
|
622 0, // priority |
|
623 &source_context); |
|
624 CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes); |
|
625 } |
|
626 |
|
627 MessagePumpNSRunLoop::~MessagePumpNSRunLoop() { |
|
628 CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes); |
|
629 CFRelease(quit_source_); |
|
630 } |
|
631 |
|
632 void MessagePumpNSRunLoop::DoRun(Delegate* delegate) { |
|
633 while (keep_running_) { |
|
634 // NSRunLoop manages autorelease pools itself. |
|
635 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode |
|
636 beforeDate:[NSDate distantFuture]]; |
|
637 } |
|
638 |
|
639 keep_running_ = true; |
|
640 } |
|
641 |
|
642 void MessagePumpNSRunLoop::Quit() { |
|
643 keep_running_ = false; |
|
644 CFRunLoopSourceSignal(quit_source_); |
|
645 CFRunLoopWakeUp(run_loop()); |
|
646 } |
|
647 |
|
648 MessagePumpNSApplication::MessagePumpNSApplication() |
|
649 : keep_running_(true), |
|
650 running_own_loop_(false) { |
|
651 } |
|
652 |
|
653 void MessagePumpNSApplication::DoRun(Delegate* delegate) { |
|
654 bool last_running_own_loop_ = running_own_loop_; |
|
655 |
|
656 // TODO(dmaclach): Get rid of this gratuitous sharedApplication. |
|
657 // Tests should be setting up their applications on their own. |
|
658 [CrApplication sharedApplication]; |
|
659 |
|
660 if (![NSApp isRunning]) { |
|
661 running_own_loop_ = false; |
|
662 // NSApplication manages autorelease pools itself when run this way. |
|
663 [NSApp run]; |
|
664 } else { |
|
665 running_own_loop_ = true; |
|
666 NSDate* distant_future = [NSDate distantFuture]; |
|
667 while (keep_running_) { |
|
668 MessagePumpScopedAutoreleasePool autorelease_pool(this); |
|
669 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask |
|
670 untilDate:distant_future |
|
671 inMode:NSDefaultRunLoopMode |
|
672 dequeue:YES]; |
|
673 if (event) { |
|
674 [NSApp sendEvent:event]; |
|
675 } |
|
676 } |
|
677 keep_running_ = true; |
|
678 } |
|
679 |
|
680 running_own_loop_ = last_running_own_loop_; |
|
681 } |
|
682 |
|
683 void MessagePumpNSApplication::Quit() { |
|
684 if (!running_own_loop_) { |
|
685 [NSApp stop:nil]; |
|
686 } else { |
|
687 keep_running_ = false; |
|
688 } |
|
689 |
|
690 // Send a fake event to wake the loop up. |
|
691 [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined |
|
692 location:NSMakePoint(0, 0) |
|
693 modifierFlags:0 |
|
694 timestamp:0 |
|
695 windowNumber:0 |
|
696 context:NULL |
|
697 subtype:0 |
|
698 data1:0 |
|
699 data2:0] |
|
700 atStart:NO]; |
|
701 } |
|
702 |
|
703 // Prevents an autorelease pool from being created if the app is in the midst of |
|
704 // handling a UI event because various parts of AppKit depend on objects that |
|
705 // are created while handling a UI event to be autoreleased in the event loop. |
|
706 // An example of this is NSWindowController. When a window with a window |
|
707 // controller is closed it goes through a stack like this: |
|
708 // (Several stack frames elided for clarity) |
|
709 // |
|
710 // #0 [NSWindowController autorelease] |
|
711 // #1 DoAClose |
|
712 // #2 MessagePumpCFRunLoopBase::DoWork() |
|
713 // #3 [NSRunLoop run] |
|
714 // #4 [NSButton performClick:] |
|
715 // #5 [NSWindow sendEvent:] |
|
716 // #6 [NSApp sendEvent:] |
|
717 // #7 [NSApp run] |
|
718 // |
|
719 // -performClick: spins a nested run loop. If the pool created in DoWork was a |
|
720 // standard NSAutoreleasePool, it would release the objects that were |
|
721 // autoreleased into it once DoWork released it. This would cause the window |
|
722 // controller, which autoreleased itself in frame #0, to release itself, and |
|
723 // possibly free itself. Unfortunately this window controller controls the |
|
724 // window in frame #5. When the stack is unwound to frame #5, the window would |
|
725 // no longer exists and crashes may occur. Apple gets around this by never |
|
726 // releasing the pool it creates in frame #4, and letting frame #7 clean it up |
|
727 // when it cleans up the pool that wraps frame #7. When an autorelease pool is |
|
728 // released it releases all other pools that were created after it on the |
|
729 // autorelease pool stack. |
|
730 // |
|
731 // CrApplication is responsible for setting handlingSendEvent to true just |
|
732 // before it sends the event throught the event handling mechanism, and |
|
733 // returning it to its previous value once the event has been sent. |
|
734 NSAutoreleasePool* MessagePumpNSApplication::CreateAutoreleasePool() { |
|
735 NSAutoreleasePool* pool = nil; |
|
736 DCHECK([NSApp isKindOfClass:[CrApplication class]]); |
|
737 if (![static_cast<CrApplication*>(NSApp) isHandlingSendEvent]) { |
|
738 pool = MessagePumpCFRunLoopBase::CreateAutoreleasePool(); |
|
739 } |
|
740 return pool; |
|
741 } |
|
742 |
|
743 // static |
|
744 MessagePump* MessagePumpMac::Create() { |
|
745 if ([NSThread isMainThread]) { |
|
746 return new MessagePumpNSApplication; |
|
747 } |
|
748 |
|
749 return new MessagePumpNSRunLoop; |
|
750 } |
|
751 |
|
752 } // namespace base |