From ff18ba6e86d3f1aeac8f3ef33a5bead253b35449 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Wed, 9 Apr 2014 18:39:17 +0000 Subject: [PATCH] Mac: Change shutdown-related code to match Chromium (issue #1203) git-svn-id: https://chromiumembedded.googlecode.com/svn/branches/1750@1649 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- tests/cefclient/cefclient_mac.mm | 105 ++++++++++++++++++++--------- tests/cefsimple/cefsimple_mac.mm | 81 ++++++++++++++++------ tests/cefsimple/simple_handler.cpp | 19 +++++- tests/cefsimple/simple_handler.h | 5 ++ 4 files changed, 154 insertions(+), 56 deletions(-) diff --git a/tests/cefclient/cefclient_mac.mm b/tests/cefclient/cefclient_mac.mm index 37d04a6f5..437f3d4fb 100644 --- a/tests/cefclient/cefclient_mac.mm +++ b/tests/cefclient/cefclient_mac.mm @@ -42,6 +42,25 @@ char szWorkingDir[512]; // The current working directory const int kWindowWidth = 800; const int kWindowHeight = 600; +// Receives notifications from the application. Will delete itself when done. +@interface ClientAppDelegate : NSObject +- (void)createApplication:(id)object; +- (void)tryToTerminateApplication:(NSApplication*)app; + +- (IBAction)testGetSource:(id)sender; +- (IBAction)testGetText:(id)sender; +- (IBAction)testPopupWindow:(id)sender; +- (IBAction)testRequest:(id)sender; +- (IBAction)testPluginInfo:(id)sender; +- (IBAction)testZoomIn:(id)sender; +- (IBAction)testZoomOut:(id)sender; +- (IBAction)testZoomReset:(id)sender; +- (IBAction)testBeginTracing:(id)sender; +- (IBAction)testEndTracing:(id)sender; +- (IBAction)testPrint:(id)sender; +- (IBAction)testOtherTests:(id)sender; +@end + // Provide the CefAppProtocol implementation required by CEF. @interface ClientApplication : NSApplication { @private @@ -62,6 +81,50 @@ const int kWindowHeight = 600; CefScopedSendingEvent sendingEventScoper; [super sendEvent:event]; } + +// |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This +// includes the application menu's quit menu item and keyboard equivalent, the +// application's dock icon menu's quit menu item, "quit" (not "force quit") in +// the Activity Monitor, and quits triggered by user logout and system restart +// and shutdown. +// +// The default |-terminate:| implementation ends the process by calling exit(), +// and thus never leaves the main run loop. This is unsuitable for Chromium +// since Chromium depends on leaving the main run loop to perform an orderly +// shutdown. We support the normal |-terminate:| interface by overriding the +// default implementation. Our implementation, which is very specific to the +// needs of Chromium, works by asking the application delegate to terminate +// using its |-tryToTerminateApplication:| method. +// +// |-tryToTerminateApplication:| differs from the standard +// |-applicationShouldTerminate:| in that no special event loop is run in the +// case that immediate termination is not possible (e.g., if dialog boxes +// allowing the user to cancel have to be shown). Instead, this method tries to +// close all browsers by calling CloseBrowser(false) via +// ClientHandler::CloseAllBrowsers. Calling CloseBrowser will result in a call +// to ClientHandler::DoClose and execution of |-performClose:| on the NSWindow. +// DoClose sets a flag that is used to differentiate between new close events +// (e.g., user clicked the window close button) and in-progress close events +// (e.g., user approved the close window dialog). The NSWindowDelegate +// |-windowShouldClose:| method checks this flag and either calls +// CloseBrowser(false) in the case of a new close event or destructs the +// NSWindow in the case of an in-progress close event. +// ClientHandler::OnBeforeClose will be called after the CEF NSView hosted in +// the NSWindow is dealloc'ed. +// +// After the final browser window has closed ClientHandler::OnBeforeClose will +// begin actual tear-down of the application by calling CefQuitMessageLoop. +// This ends the NSApplication event loop and execution then returns to the +// main() function for cleanup before application termination. +// +// The standard |-applicationShouldTerminate:| is not supported, and code paths +// leading to it must be redirected. +- (void)terminate:(id)sender { + ClientAppDelegate* delegate = + static_cast([NSApp delegate]); + [delegate tryToTerminateApplication:self]; + // Return, don't exit. The application is responsible for exiting on its own. +} @end @@ -204,27 +267,10 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { return button; } -// Receives notifications from the application. Will delete itself when done. -@interface ClientAppDelegate : NSObject -- (void)createApp:(id)object; -- (IBAction)testGetSource:(id)sender; -- (IBAction)testGetText:(id)sender; -- (IBAction)testPopupWindow:(id)sender; -- (IBAction)testRequest:(id)sender; -- (IBAction)testPluginInfo:(id)sender; -- (IBAction)testZoomIn:(id)sender; -- (IBAction)testZoomOut:(id)sender; -- (IBAction)testZoomReset:(id)sender; -- (IBAction)testBeginTracing:(id)sender; -- (IBAction)testEndTracing:(id)sender; -- (IBAction)testPrint:(id)sender; -- (IBAction)testOtherTests:(id)sender; -@end - @implementation ClientAppDelegate // Create the application on the UI thread. -- (void)createApp:(id)object { +- (void)createApplication:(id)object { [NSApplication sharedApplication]; [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; @@ -376,6 +422,11 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { [mainWnd setFrame:[mainWnd frameRectForContentRect:r] display:YES]; } +- (void)tryToTerminateApplication:(NSApplication*)app { + if (g_handler.get() && !g_handler->IsClosing()) + g_handler->CloseAllBrowsers(false); +} + - (IBAction)testGetSource:(id)sender { if (g_handler.get() && g_handler->GetBrowserId()) RunGetSourceTest(g_handler->GetBrowser()); @@ -442,22 +493,9 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { RunOtherTests(g_handler->GetBrowser()); } -// Called when the application's Quit menu item is selected. - (NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *)sender { - // Request that all browser windows close. - if (g_handler.get()) - g_handler->CloseAllBrowsers(false); - - // Cancel the termination. The application will exit after all windows have - // closed. - return NSTerminateCancel; -} - -// Sent immediately before the application terminates. This signal should not -// be called because we cancel the termination. -- (void)applicationWillTerminate:(NSNotification *)aNotification { - ASSERT(false); // Not reached. + return NSTerminateNow; } @end @@ -497,7 +535,8 @@ int main(int argc, char* argv[]) { // Create the application delegate and window. NSObject* delegate = [[ClientAppDelegate alloc] init]; - [delegate performSelectorOnMainThread:@selector(createApp:) withObject:nil + [delegate performSelectorOnMainThread:@selector(createApplication:) + withObject:nil waitUntilDone:NO]; // Run the application message loop. diff --git a/tests/cefsimple/cefsimple_mac.mm b/tests/cefsimple/cefsimple_mac.mm index c05d989d5..f8fabce70 100644 --- a/tests/cefsimple/cefsimple_mac.mm +++ b/tests/cefsimple/cefsimple_mac.mm @@ -10,6 +10,12 @@ #include "cefsimple/util.h" #include "include/cef_application_mac.h" +// Receives notifications from the application. +@interface SimpleAppDelegate : NSObject +- (void)createApplication:(id)object; +- (void)tryToTerminateApplication:(NSApplication*)app; +@end + // Provide the CefAppProtocol implementation required by CEF. @interface SimpleApplication : NSApplication { @private @@ -30,18 +36,56 @@ CefScopedSendingEvent sendingEventScoper; [super sendEvent:event]; } -@end - -// Receives notifications from the application. -@interface SimpleAppDelegate : NSObject -- (void)createApp:(id)object; +// |-terminate:| is the entry point for orderly "quit" operations in Cocoa. This +// includes the application menu's quit menu item and keyboard equivalent, the +// application's dock icon menu's quit menu item, "quit" (not "force quit") in +// the Activity Monitor, and quits triggered by user logout and system restart +// and shutdown. +// +// The default |-terminate:| implementation ends the process by calling exit(), +// and thus never leaves the main run loop. This is unsuitable for Chromium +// since Chromium depends on leaving the main run loop to perform an orderly +// shutdown. We support the normal |-terminate:| interface by overriding the +// default implementation. Our implementation, which is very specific to the +// needs of Chromium, works by asking the application delegate to terminate +// using its |-tryToTerminateApplication:| method. +// +// |-tryToTerminateApplication:| differs from the standard +// |-applicationShouldTerminate:| in that no special event loop is run in the +// case that immediate termination is not possible (e.g., if dialog boxes +// allowing the user to cancel have to be shown). Instead, this method tries to +// close all browsers by calling CloseBrowser(false) via +// ClientHandler::CloseAllBrowsers. Calling CloseBrowser will result in a call +// to ClientHandler::DoClose and execution of |-performClose:| on the NSWindow. +// DoClose sets a flag that is used to differentiate between new close events +// (e.g., user clicked the window close button) and in-progress close events +// (e.g., user approved the close window dialog). The NSWindowDelegate +// |-windowShouldClose:| method checks this flag and either calls +// CloseBrowser(false) in the case of a new close event or destructs the +// NSWindow in the case of an in-progress close event. +// ClientHandler::OnBeforeClose will be called after the CEF NSView hosted in +// the NSWindow is dealloc'ed. +// +// After the final browser window has closed ClientHandler::OnBeforeClose will +// begin actual tear-down of the application by calling CefQuitMessageLoop. +// This ends the NSApplication event loop and execution then returns to the +// main() function for cleanup before application termination. +// +// The standard |-applicationShouldTerminate:| is not supported, and code paths +// leading to it must be redirected. +- (void)terminate:(id)sender { + SimpleAppDelegate* delegate = + static_cast([NSApp delegate]); + [delegate tryToTerminateApplication:self]; + // Return, don't exit. The application is responsible for exiting on its own. +} @end @implementation SimpleAppDelegate // Create the application on the UI thread. -- (void)createApp:(id)object { +- (void)createApplication:(id)object { [NSApplication sharedApplication]; [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; @@ -49,24 +93,16 @@ [NSApp setDelegate:self]; } -// Called when the application's Quit menu item is selected. +- (void)tryToTerminateApplication:(NSApplication*)app { + SimpleHandler* handler = SimpleHandler::GetInstance(); + if (handler && !handler->IsClosing()) + handler->CloseAllBrowsers(false); +} + - (NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *)sender { - // Request that all browser windows close. - if (SimpleHandler* handler = SimpleHandler::GetInstance()) - handler->CloseAllBrowsers(false); - - // Cancel the termination. The application will exit after all windows have - // closed. - return NSTerminateCancel; + return NSTerminateNow; } - -// Sent immediately before the application terminates. This signal should not -// be called because we cancel the termination. -- (void)applicationWillTerminate:(NSNotification *)aNotification { - ASSERT(false); // Not reached. -} - @end @@ -93,7 +129,8 @@ int main(int argc, char* argv[]) { // Create the application delegate. NSObject* delegate = [[SimpleAppDelegate alloc] init]; - [delegate performSelectorOnMainThread:@selector(createApp:) withObject:nil + [delegate performSelectorOnMainThread:@selector(createApplication:) + withObject:nil waitUntilDone:NO]; // Run the CEF message loop. This will block until CefQuitMessageLoop() is diff --git a/tests/cefsimple/simple_handler.cpp b/tests/cefsimple/simple_handler.cpp index fcb707540..218e30ede 100644 --- a/tests/cefsimple/simple_handler.cpp +++ b/tests/cefsimple/simple_handler.cpp @@ -17,7 +17,8 @@ SimpleHandler* g_instance = NULL; } // namespace -SimpleHandler::SimpleHandler() { +SimpleHandler::SimpleHandler() + : is_closing_(false) { ASSERT(!g_instance); g_instance = this; } @@ -38,6 +39,22 @@ void SimpleHandler::OnAfterCreated(CefRefPtr browser) { browser_list_.push_back(browser); } +bool SimpleHandler::DoClose(CefRefPtr browser) { + REQUIRE_UI_THREAD(); + + // Closing the main window requires special handling. See the DoClose() + // documentation in the CEF header for a detailed destription of this + // process. + if (browser_list_.size() == 1) { + // Set a flag to indicate that the window close should be allowed. + is_closing_ = true; + } + + // Allow the close. For windowed browsers this will result in the OS close + // event being sent. + return false; +} + void SimpleHandler::OnBeforeClose(CefRefPtr browser) { REQUIRE_UI_THREAD(); diff --git a/tests/cefsimple/simple_handler.h b/tests/cefsimple/simple_handler.h index 184be78b0..1f272ff1f 100644 --- a/tests/cefsimple/simple_handler.h +++ b/tests/cefsimple/simple_handler.h @@ -37,6 +37,7 @@ class SimpleHandler : public CefClient, // CefLifeSpanHandler methods: virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE; + virtual bool DoClose(CefRefPtr browser) OVERRIDE; virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE; // CefLoadHandler methods: @@ -49,11 +50,15 @@ class SimpleHandler : public CefClient, // Request that all existing browser windows close. void CloseAllBrowsers(bool force_close); + bool IsClosing() const { return is_closing_; } + private: // List of existing browser windows. Only accessed on the CEF UI thread. typedef std::list > BrowserList; BrowserList browser_list_; + bool is_closing_; + // Include the default reference counting implementation. IMPLEMENT_REFCOUNTING(SimpleHandler); };