diff options
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gyp | 2 | ||||
-rw-r--r-- | base/chrome_application_mac.h | 40 | ||||
-rw-r--r-- | base/chrome_application_mac.mm | 49 | ||||
-rw-r--r-- | base/message_pump_mac.mm | 91 |
4 files changed, 169 insertions, 13 deletions
diff --git a/base/base.gyp b/base/base.gyp index 47d2438..71ff640 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -85,6 +85,8 @@ 'base_switches.h', 'basictypes.h', 'bzip2_error_handler.cc', + 'chrome_application_mac.h', + 'chrome_application_mac.mm', 'cocoa_protocols_mac.h', 'command_line.cc', 'command_line.h', diff --git a/base/chrome_application_mac.h b/base/chrome_application_mac.h new file mode 100644 index 0000000..39ef8b3 --- /dev/null +++ b/base/chrome_application_mac.h @@ -0,0 +1,40 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_CHROME_APPLICATION_MAC_H_ +#define BASE_CHROME_APPLICATION_MAC_H_ + +#import <AppKit/AppKit.h> + +#include "base/basictypes.h" + +@interface CrApplication : NSApplication { + @private + BOOL handlingSendEvent_; +} +@property(readonly, + getter=isHandlingSendEvent, + nonatomic) BOOL handlingSendEvent; + ++ (NSApplication*)sharedApplication; +@end + +namespace chrome_application_mac { + +// Controls the state of |handlingSendEvent_| in the event loop so that it is +// reset properly. +class ScopedSendingEvent { + public: + explicit ScopedSendingEvent(CrApplication* app); + ~ScopedSendingEvent(); + + private: + CrApplication* app_; + BOOL handling_; + DISALLOW_COPY_AND_ASSIGN(ScopedSendingEvent); +}; + +} // chrome_application_mac + +#endif // BASE_CHROME_APPLICATION_MAC_H_ diff --git a/base/chrome_application_mac.mm b/base/chrome_application_mac.mm new file mode 100644 index 0000000..d2d8f9f --- /dev/null +++ b/base/chrome_application_mac.mm @@ -0,0 +1,49 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome_application_mac.h" + +#include "base/logging.h" + +@interface CrApplication () +@property(readwrite, + getter=isHandlingSendEvent, + nonatomic) BOOL handlingSendEvent; +@end + +@implementation CrApplication +@synthesize handlingSendEvent = handlingSendEvent_; + +// Initialize NSApplication using the custom subclass. Check whether NSApp +// was already initialized using another class, because that would break +// some things. ++ (NSApplication*)sharedApplication { + NSApplication* app = [super sharedApplication]; + if (![NSApp isKindOfClass:self]) { + LOG(ERROR) << "NSApp should be of type " << [[self className] UTF8String] + << ", not " << [[NSApp className] UTF8String]; + DCHECK(false) << "NSApp is of wrong type"; + } + return app; +} + +- (void)sendEvent:(NSEvent*)event { + chrome_application_mac::ScopedSendingEvent sendingEventScoper(self); + [super sendEvent:event]; +} + +@end + +namespace chrome_application_mac { + +ScopedSendingEvent::ScopedSendingEvent(CrApplication* app) : app_(app) { + handling_ = [app_ isHandlingSendEvent]; + [app_ setHandlingSendEvent:YES]; +} + +ScopedSendingEvent::~ScopedSendingEvent() { + [app_ setHandlingSendEvent:handling_]; +} + +} // namespace chrome_application_mac diff --git a/base/message_pump_mac.mm b/base/message_pump_mac.mm index b08bdad..16a9a59 100644 --- a/base/message_pump_mac.mm +++ b/base/message_pump_mac.mm @@ -11,7 +11,8 @@ #include <limits> -#include "base/scoped_nsautorelease_pool.h" +#import "base/chrome_application_mac.h" +#include "base/logging.h" #include "base/time.h" namespace { @@ -26,6 +27,23 @@ const CFTimeInterval kCFTimeIntervalMax = namespace base { +// A scoper for autorelease pools created from message pump run loops. +// Avoids dirtying up the ScopedNSAutoreleasePool interface for the rare +// case where an autorelease pool needs to be passed in. +class MessagePumpScopedAutoreleasePool { + public: + explicit MessagePumpScopedAutoreleasePool(MessagePumpCFRunLoopBase* pump) : + pool_(pump->CreateAutoreleasePool()) { + } + ~MessagePumpScopedAutoreleasePool() { + [pool_ drain]; + } + + private: + NSAutoreleasePool* pool_; + DISALLOW_COPY_AND_ASSIGN(MessagePumpScopedAutoreleasePool); +}; + // Must be called on the run loop thread. MessagePumpCFRunLoopBase::MessagePumpCFRunLoopBase() : delegate_(NULL), @@ -264,9 +282,9 @@ bool MessagePumpCFRunLoopBase::RunWork() { // The NSApplication-based run loop only drains the autorelease pool at each // UI event (NSEvent). The autorelease pool is not drained for each // CFRunLoopSource target that's run. Use a local pool for any autoreleased - // objects to ensure they're released promptly even in the absence of UI - // events. - ScopedNSAutoreleasePool autorelease_pool; + // objects if the app is not currently handling a UI event to ensure they're + // released promptly even in the absence of UI events. + MessagePumpScopedAutoreleasePool autorelease_pool(this); // Call DoWork once, and if something was done, arrange to come back here // again as long as the loop is still running. @@ -298,9 +316,9 @@ bool MessagePumpCFRunLoopBase::RunDelayedWork() { // The NSApplication-based run loop only drains the autorelease pool at each // UI event (NSEvent). The autorelease pool is not drained for each // CFRunLoopSource target that's run. Use a local pool for any autoreleased - // objects to ensure they're released promptly even in the absence of UI - // events. - ScopedNSAutoreleasePool autorelease_pool; + // objects if the app is not currently handling a UI event to ensure they're + // released promptly even in the absence of UI events. + MessagePumpScopedAutoreleasePool autorelease_pool(this); Time next_time; delegate_->DoDelayedWork(&next_time); @@ -342,9 +360,9 @@ bool MessagePumpCFRunLoopBase::RunIdleWork() { // The NSApplication-based run loop only drains the autorelease pool at each // UI event (NSEvent). The autorelease pool is not drained for each // CFRunLoopSource target that's run. Use a local pool for any autoreleased - // objects to ensure they're released promptly even in the absence of UI - // events. - ScopedNSAutoreleasePool autorelease_pool; + // objects if the app is not currently handling a UI event to ensure they're + // released promptly even in the absence of UI events. + MessagePumpScopedAutoreleasePool autorelease_pool(this); // Call DoIdleWork once, and if something was done, arrange to come back here // again as long as the loop is still running. @@ -555,6 +573,11 @@ void MessagePumpCFRunLoopBase::PowerStateNotification(void* info, void MessagePumpCFRunLoopBase::EnterExitRunLoop(CFRunLoopActivity activity) { } +// Base version returns a standard NSAutoreleasePool. +NSAutoreleasePool* MessagePumpCFRunLoopBase::CreateAutoreleasePool() { + return [[NSAutoreleasePool alloc] init]; +} + MessagePumpCFRunLoop::MessagePumpCFRunLoop() : quit_pending_(false) { } @@ -568,7 +591,7 @@ void MessagePumpCFRunLoop::DoRun(Delegate* delegate) { // pool management is introduced. int result; do { - ScopedNSAutoreleasePool autorelease_pool; + MessagePumpScopedAutoreleasePool autorelease_pool(this); result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, kCFTimeIntervalMax, false); @@ -644,7 +667,9 @@ MessagePumpNSApplication::MessagePumpNSApplication() void MessagePumpNSApplication::DoRun(Delegate* delegate) { bool last_running_own_loop_ = running_own_loop_; - [NSApplication sharedApplication]; + // TODO(dmaclach): Get rid of this gratuitous sharedApplication. + // Tests should be setting up their applications on their own. + [CrApplication sharedApplication]; if (![NSApp isRunning]) { running_own_loop_ = false; @@ -654,7 +679,7 @@ void MessagePumpNSApplication::DoRun(Delegate* delegate) { running_own_loop_ = true; NSDate* distant_future = [NSDate distantFuture]; while (keep_running_) { - ScopedNSAutoreleasePool autorelease_pool; + MessagePumpScopedAutoreleasePool autorelease_pool(this); NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distant_future inMode:NSDefaultRunLoopMode @@ -689,6 +714,46 @@ void MessagePumpNSApplication::Quit() { atStart:NO]; } +// Prevents an autorelease pool from being created if the app is in the midst of +// handling a UI event because various parts of AppKit depend on objects that +// are created while handling a UI event to be autoreleased in the event loop. +// An example of this is NSWindowController. When a window with a window +// controller is closed it goes through a stack like this: +// (Several stack frames elided for clarity) +// +// #0 [NSWindowController autorelease] +// #1 DoAClose +// #2 MessagePumpCFRunLoopBase::DoWork() +// #3 [NSRunLoop run] +// #4 [NSButton performClick:] +// #5 [NSWindow sendEvent:] +// #6 [NSApp sendEvent:] +// #7 [NSApp run] +// +// -performClick: spins a nested run loop. If the pool created in DoWork was a +// standard NSAutoreleasePool, it would release the objects that were +// autoreleased into it once DoWork released it. This would cause the window +// controller, which autoreleased itself in frame #0, to release itself, and +// possibly free itself. Unfortunately this window controller controls the +// window in frame #5. When the stack is unwound to frame #5, the window would +// no longer exists and crashes may occur. Apple gets around this by never +// releasing the pool it creates in frame #4, and letting frame #7 clean it up +// when it cleans up the pool that wraps frame #7. When an autorelease pool is +// released it releases all other pools that were created after it on the +// autorelease pool stack. +// +// CrApplication is responsible for setting handlingSendEvent to true just +// before it sends the event throught the event handling mechanism, and +// returning it to its previous value once the event has been sent. +NSAutoreleasePool* MessagePumpNSApplication::CreateAutoreleasePool() { + NSAutoreleasePool* pool = nil; + DCHECK([NSApp isKindOfClass:[CrApplication class]]); + if (![static_cast<CrApplication*>(NSApp) isHandlingSendEvent]) { + pool = MessagePumpCFRunLoopBase::CreateAutoreleasePool(); + } + return pool; +} + // static MessagePump* MessagePumpMac::Create() { if ([NSThread isMainThread]) { |