diff --git a/cef.gyp b/cef.gyp index dfc626e40..524e761cf 100644 --- a/cef.gyp +++ b/cef.gyp @@ -251,6 +251,7 @@ 'tests/unittests/download_unittest.cc', 'tests/unittests/geolocation_unittest.cc', 'tests/unittests/jsdialog_unittest.cc', + 'tests/unittests/life_span_unittest.cc', 'tests/unittests/navigation_unittest.cc', 'tests/unittests/process_message_unittest.cc', 'tests/unittests/request_unittest.cc', diff --git a/include/capi/cef_browser_capi.h b/include/capi/cef_browser_capi.h index 02dd9136b..c406fbba9 100644 --- a/include/capi/cef_browser_capi.h +++ b/include/capi/cef_browser_capi.h @@ -219,15 +219,24 @@ typedef struct _cef_browser_host_t { /// // Call this function before destroying a contained browser window. This // function performs any internal cleanup that may be needed before the - // browser window is destroyed. + // browser window is destroyed. See cef_life_span_handler_t::do_close() + // documentation for additional usage information. /// void (CEF_CALLBACK *parent_window_will_close)( struct _cef_browser_host_t* self); /// - // Closes this browser window. + // Request that the browser close. The JavaScript 'onbeforeunload' event will + // be fired. If |force_close| is false (0) the event handler, if any, will be + // allowed to prompt the user and the user can optionally cancel the close. If + // |force_close| is true (1) the prompt will not be displayed and the close + // will proceed. Results in a call to cef_life_span_handler_t::do_close() if + // the event handler allows the close or if |force_close| is true (1). See + // cef_life_span_handler_t::do_close() documentation for additional usage + // information. /// - void (CEF_CALLBACK *close_browser)(struct _cef_browser_host_t* self); + void (CEF_CALLBACK *close_browser)(struct _cef_browser_host_t* self, + int force_close); /// // Set focus for the browser window. If |enable| is true (1) focus will be set diff --git a/include/capi/cef_life_span_handler_capi.h b/include/capi/cef_life_span_handler_capi.h index 0aebcf980..0732c6e55 100644 --- a/include/capi/cef_life_span_handler_capi.h +++ b/include/capi/cef_life_span_handler_capi.h @@ -76,7 +76,7 @@ typedef struct _cef_life_span_handler_t { struct _cef_browser_settings_t* settings, int* no_javascript_access); /// - // Called after a new window is created. + // Called after a new browser is created. /// void (CEF_CALLBACK *on_after_created)(struct _cef_life_span_handler_t* self, struct _cef_browser_t* browser); @@ -90,19 +90,73 @@ typedef struct _cef_life_span_handler_t { struct _cef_browser_t* browser); /// - // Called when a window has recieved a request to close. Return false (0) to - // proceed with the window close or true (1) to cancel the window close. If - // this is a modal window and a custom modal loop implementation was provided - // in run_modal() this callback should be used to restore the opener window to - // a usable state. + // Called when a browser has recieved a request to close. This may result + // directly from a call to cef_browser_host_t::close_browser() or indirectly + // if the browser is a top-level OS window created by CEF and the user + // attempts to close the window. This function will be called after the + // JavaScript 'onunload' event has been fired. It will not be called for + // browsers after the associated OS window has been destroyed (for those + // browsers it is no longer possible to cancel the close). + // + // If CEF created an OS window for the browser returning false (0) will send + // an OS close notification to the browser window's top-level owner (e.g. + // WM_CLOSE on Windows, performClose: on OS-X and "delete_event" on Linux). If + // no OS window exists (window rendering disabled) returning false (0) will + // cause the browser object to be destroyed immediately. Return true (1) if + // the browser is parented to another window and that other window needs to + // receive close notification via some non-standard technique. + // + // If an application provides its own top-level window it should handle OS + // close notifications by calling cef_browser_host_t::CloseBrowser(false (0)) + // instead of immediately closing (see the example below). This gives CEF an + // opportunity to process the 'onbeforeunload' event and optionally cancel the + // close before do_close() is called. + // + // The cef_life_span_handler_t::OnBeforeclose() function will be called + // immediately before the browser object is destroyed. The application should + // only exit after OnBeforeclose() has been called for all existing browsers. + // + // If the browser represents a modal window and a custom modal loop + // implementation was provided in cef_life_span_handler_t::run_modal() this + // callback should be used to restore the opener window to a usable state. + // + // By way of example consider what should happen during window close when the + // browser is parented to an application-provided top-level OS window. 1. + // User clicks the window close button which sends an OS close + // notification (e.g. WM_CLOSE on Windows, performClose: on OS-X and + // "delete_event" on Linux). + // 2. Application's top-level window receives the close notification and: + // A. Calls CefBrowserHost::CloseBrowser(false). + // B. Cancels the window close. + // 3. JavaScript 'onbeforeunload' handler executes and shows the close + // confirmation dialog (which can be overridden via + // CefJSDialogHandler::OnBeforeUnloadDialog()). + // 4. User approves the close. 5. JavaScript 'onunload' handler executes. 6. + // Application's do_close() handler is called. Application will: + // A. Call CefBrowserHost::ParentWindowWillClose() to notify CEF that the + // parent window will be closing. + // B. Set a flag to indicate that the next close attempt will be allowed. + // C. Return false. + // 7. CEF sends an OS close notification. 8. Application's top-level window + // receives the OS close notification and + // allows the window to close based on the flag from #6B. + // 9. Browser OS window is destroyed. 10. Application's + // cef_life_span_handler_t::OnBeforeclose() handler is called and + // the browser object is destroyed. + // 11. Application exits by calling cef_quit_message_loop() if no other + // browsers + // exist. /// int (CEF_CALLBACK *do_close)(struct _cef_life_span_handler_t* self, struct _cef_browser_t* browser); /// - // Called just before a window is closed. If this is a modal window and a - // custom modal loop implementation was provided in run_modal() this callback - // should be used to exit the custom modal loop. + // Called just before a browser is destroyed. Release all references to the + // browser object and do not attempt to execute any functions on the browser + // object after this callback returns. If this is a modal window and a custom + // modal loop implementation was provided in run_modal() this callback should + // be used to exit the custom modal loop. See do_close() documentation for + // additional usage information. /// void (CEF_CALLBACK *on_before_close)(struct _cef_life_span_handler_t* self, struct _cef_browser_t* browser); diff --git a/include/cef_browser.h b/include/cef_browser.h index e3fbf03a1..e154e4e3c 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -254,16 +254,24 @@ class CefBrowserHost : public virtual CefBase { /// // Call this method before destroying a contained browser window. This method // performs any internal cleanup that may be needed before the browser window - // is destroyed. + // is destroyed. See CefLifeSpanHandler::DoClose() documentation for + // additional usage information. /// /*--cef()--*/ virtual void ParentWindowWillClose() =0; /// - // Closes this browser window. + // Request that the browser close. The JavaScript 'onbeforeunload' event will + // be fired. If |force_close| is false the event handler, if any, will be + // allowed to prompt the user and the user can optionally cancel the close. + // If |force_close| is true the prompt will not be displayed and the close + // will proceed. Results in a call to CefLifeSpanHandler::DoClose() if the + // event handler allows the close or if |force_close| is true. See + // CefLifeSpanHandler::DoClose() documentation for additional usage + // information. /// /*--cef()--*/ - virtual void CloseBrowser() =0; + virtual void CloseBrowser(bool force_close) =0; /// // Set focus for the browser window. If |enable| is true focus will be set to diff --git a/include/cef_life_span_handler.h b/include/cef_life_span_handler.h index eed9caf4f..5e652716c 100644 --- a/include/cef_life_span_handler.h +++ b/include/cef_life_span_handler.h @@ -77,7 +77,7 @@ class CefLifeSpanHandler : public virtual CefBase { } /// - // Called after a new window is created. + // Called after a new browser is created. /// /*--cef()--*/ virtual void OnAfterCreated(CefRefPtr browser) {} @@ -91,19 +91,73 @@ class CefLifeSpanHandler : public virtual CefBase { virtual bool RunModal(CefRefPtr browser) { return false; } /// - // Called when a window has recieved a request to close. Return false to - // proceed with the window close or true to cancel the window close. If this - // is a modal window and a custom modal loop implementation was provided in - // RunModal() this callback should be used to restore the opener window to a - // usable state. + // Called when a browser has recieved a request to close. This may result + // directly from a call to CefBrowserHost::CloseBrowser() or indirectly if the + // browser is a top-level OS window created by CEF and the user attempts to + // close the window. This method will be called after the JavaScript + // 'onunload' event has been fired. It will not be called for browsers after + // the associated OS window has been destroyed (for those browsers it is no + // longer possible to cancel the close). + // + // If CEF created an OS window for the browser returning false will send an OS + // close notification to the browser window's top-level owner (e.g. WM_CLOSE + // on Windows, performClose: on OS-X and "delete_event" on Linux). If no OS + // window exists (window rendering disabled) returning false will cause the + // browser object to be destroyed immediately. Return true if the browser is + // parented to another window and that other window needs to receive close + // notification via some non-standard technique. + // + // If an application provides its own top-level window it should handle OS + // close notifications by calling CefBrowserHost::CloseBrowser(false) instead + // of immediately closing (see the example below). This gives CEF an + // opportunity to process the 'onbeforeunload' event and optionally cancel the + // close before DoClose() is called. + // + // The CefLifeSpanHandler::OnBeforeClose() method will be called immediately + // before the browser object is destroyed. The application should only exit + // after OnBeforeClose() has been called for all existing browsers. + // + // If the browser represents a modal window and a custom modal loop + // implementation was provided in CefLifeSpanHandler::RunModal() this callback + // should be used to restore the opener window to a usable state. + // + // By way of example consider what should happen during window close when the + // browser is parented to an application-provided top-level OS window. + // 1. User clicks the window close button which sends an OS close + // notification (e.g. WM_CLOSE on Windows, performClose: on OS-X and + // "delete_event" on Linux). + // 2. Application's top-level window receives the close notification and: + // A. Calls CefBrowserHost::CloseBrowser(false). + // B. Cancels the window close. + // 3. JavaScript 'onbeforeunload' handler executes and shows the close + // confirmation dialog (which can be overridden via + // CefJSDialogHandler::OnBeforeUnloadDialog()). + // 4. User approves the close. + // 5. JavaScript 'onunload' handler executes. + // 6. Application's DoClose() handler is called. Application will: + // A. Call CefBrowserHost::ParentWindowWillClose() to notify CEF that the + // parent window will be closing. + // B. Set a flag to indicate that the next close attempt will be allowed. + // C. Return false. + // 7. CEF sends an OS close notification. + // 8. Application's top-level window receives the OS close notification and + // allows the window to close based on the flag from #6B. + // 9. Browser OS window is destroyed. + // 10. Application's CefLifeSpanHandler::OnBeforeClose() handler is called and + // the browser object is destroyed. + // 11. Application exits by calling CefQuitMessageLoop() if no other browsers + // exist. /// /*--cef()--*/ virtual bool DoClose(CefRefPtr browser) { return false; } /// - // Called just before a window is closed. If this is a modal window and a - // custom modal loop implementation was provided in RunModal() this callback - // should be used to exit the custom modal loop. + // Called just before a browser is destroyed. Release all references to the + // browser object and do not attempt to execute any methods on the browser + // object after this callback returns. If this is a modal window and a custom + // modal loop implementation was provided in RunModal() this callback should + // be used to exit the custom modal loop. See DoClose() documentation for + // additional usage information. /// /*--cef()--*/ virtual void OnBeforeClose(CefRefPtr browser) {} diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index 980eba553..cefc4e478 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -459,19 +459,35 @@ CefRefPtr CefBrowserHostImpl::GetBrowser() { return this; } -void CefBrowserHostImpl::CloseBrowser() { +void CefBrowserHostImpl::CloseBrowser(bool force_close) { if (CEF_CURRENTLY_ON_UIT()) { - if (IsWindowRenderingDisabled()) { - if (AllowDestroyBrowser()) { - ParentWindowWillClose(); - DestroyBrowser(); + // Exit early if a close attempt is already pending and this method is + // called again from somewhere other than WindowDestroyed(). + if (destruction_state_ >= DESTRUCTION_STATE_PENDING && + (IsWindowRenderingDisabled() || !window_destroyed_)) { + if (force_close && destruction_state_ == DESTRUCTION_STATE_PENDING) { + // Upgrade the destruction state. + destruction_state_ = DESTRUCTION_STATE_ACCEPTED; } + return; + } + + if (destruction_state_ < DESTRUCTION_STATE_ACCEPTED) { + destruction_state_ = (force_close ? DESTRUCTION_STATE_ACCEPTED : + DESTRUCTION_STATE_PENDING); + } + + content::WebContents* contents = web_contents(); + if (contents && contents->NeedToFireBeforeUnload()) { + // Will result in a call to BeforeUnloadFired() and, if the close isn't + // canceled, CloseContents(). + contents->GetRenderViewHost()->FirePageBeforeUnload(false); } else { - PlatformCloseWindow(); + CloseContents(contents); } } else { CEF_POST_TASK(CEF_UIT, - base::Bind(&CefBrowserHostImpl::CloseBrowser, this)); + base::Bind(&CefBrowserHostImpl::CloseBrowser, this, force_close)); } } @@ -1024,22 +1040,18 @@ bool CefBrowserHostImpl::SendProcessMessage( // CefBrowserHostImpl public methods. // ----------------------------------------------------------------------------- -bool CefBrowserHostImpl::AllowDestroyBrowser() { - if (client_.get()) { - CefRefPtr handler = - client_->GetLifeSpanHandler(); - if (handler.get()) { - // Give the client a chance to handle this one. - return !handler->DoClose(this); - } - } - - return true; +void CefBrowserHostImpl::WindowDestroyed() { + CEF_REQUIRE_UIT(); + DCHECK(!window_destroyed_); + window_destroyed_ = true; + CloseBrowser(true); } void CefBrowserHostImpl::DestroyBrowser() { CEF_REQUIRE_UIT(); + destruction_state_ = DESTRUCTION_STATE_COMPLETED; + if (client_.get()) { CefRefPtr handler = client_->GetLifeSpanHandler(); if (handler.get()) { @@ -1412,7 +1424,41 @@ void CefBrowserHostImpl::LoadingStateChanged(content::WebContents* source) { } void CefBrowserHostImpl::CloseContents(content::WebContents* source) { - PlatformCloseWindow(); + if (destruction_state_ == DESTRUCTION_STATE_COMPLETED) + return; + + bool close_browser = true; + + // If this method is called in response to something other than + // WindowDestroyed() ask the user if the browser should close. + if (IsWindowRenderingDisabled() || !window_destroyed_) { + CefRefPtr handler = + client_->GetLifeSpanHandler(); + if (handler.get()) { + close_browser = !handler->DoClose(this); + } + } + + if (close_browser) { + if (destruction_state_ != DESTRUCTION_STATE_ACCEPTED) + destruction_state_ = DESTRUCTION_STATE_ACCEPTED; + + if (!IsWindowRenderingDisabled() && !window_destroyed_) { + // A window exists so try to close it using the platform method. Will + // result in a call to WindowDestroyed() if/when the window is destroyed + // via the platform window destruction mechanism. + PlatformCloseWindow(); + } else { + // No window exists. Destroy the browser immediately. + DestroyBrowser(); + if (!IsWindowRenderingDisabled()) { + // Release the reference added in PlatformCreateWindow(). + Release(); + } + } + } else if (destruction_state_ != DESTRUCTION_STATE_NONE) { + destruction_state_ = DESTRUCTION_STATE_NONE; + } } void CefBrowserHostImpl::UpdateTargetURL(content::WebContents* source, @@ -1439,6 +1485,17 @@ bool CefBrowserHostImpl::AddMessageToConsole(content::WebContents* source, return false; } +void CefBrowserHostImpl::BeforeUnloadFired(content::WebContents* source, + bool proceed, + bool* proceed_to_fire_unload) { + if (destruction_state_ == DESTRUCTION_STATE_ACCEPTED || proceed) { + *proceed_to_fire_unload = true; + } else if (!proceed) { + *proceed_to_fire_unload = false; + destruction_state_ = DESTRUCTION_STATE_NONE; + } +} + bool CefBrowserHostImpl::TakeFocus(content::WebContents* source, bool reverse) { if (client_.get()) { @@ -1948,6 +2005,8 @@ CefBrowserHostImpl::CefBrowserHostImpl( queue_messages_(true), main_frame_id_(CefFrameHostImpl::kInvalidFrameId), focused_frame_id_(CefFrameHostImpl::kInvalidFrameId), + destruction_state_(DESTRUCTION_STATE_NONE), + window_destroyed_(false), is_in_onsetfocus_(false), focus_on_editable_field_(false), file_chooser_pending_(false) { diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 879cdf822..3b91842c9 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -109,7 +109,7 @@ class CefBrowserHostImpl : public CefBrowserHost, // CefBrowserHost methods. virtual CefRefPtr GetBrowser() OVERRIDE; - virtual void CloseBrowser() OVERRIDE; + virtual void CloseBrowser(bool force_close) OVERRIDE; virtual void ParentWindowWillClose() OVERRIDE; virtual void SetFocus(bool enable) OVERRIDE; virtual CefWindowHandle GetWindowHandle() OVERRIDE; @@ -165,9 +165,8 @@ class CefBrowserHostImpl : public CefBrowserHost, CefProcessId target_process, CefRefPtr message) OVERRIDE; - // Call LifeSpanHandler before destroying. Returns true if destruction - // is allowed at this time. - bool AllowDestroyBrowser(); + // Called when the OS window hosting the browser is destroyed. + void WindowDestroyed(); // Destroy the browser members. This method should only be called after the // native browser window is not longer processing messages. @@ -253,6 +252,14 @@ class CefBrowserHostImpl : public CefBrowserHost, // Returns false if a popup is already pending. bool SetPendingPopupInfo(scoped_ptr info); + enum DestructionState { + DESTRUCTION_STATE_NONE = 0, + DESTRUCTION_STATE_PENDING, + DESTRUCTION_STATE_ACCEPTED, + DESTRUCTION_STATE_COMPLETED + }; + DestructionState destruction_state() const { return destruction_state_; } + private: // content::WebContentsDelegate methods. virtual content::WebContents* OpenURLFromTab( @@ -268,6 +275,9 @@ class CefBrowserHostImpl : public CefBrowserHost, const string16& message, int32 line_no, const string16& source_id) OVERRIDE; + virtual void BeforeUnloadFired(content::WebContents* source, + bool proceed, + bool* proceed_to_fire_unload) OVERRIDE; virtual bool TakeFocus(content::WebContents* source, bool reverse) OVERRIDE; virtual void WebContentsFocused(content::WebContents* contents) OVERRIDE; @@ -483,6 +493,14 @@ class CefBrowserHostImpl : public CefBrowserHost, // Used when no other frame exists. Provides limited functionality. CefRefPtr placeholder_frame_; + // Represents the current browser destruction state. Only accessed on the UI + // thread. + DestructionState destruction_state_; + + // True if the OS window hosting the browser has been destroyed. Only accessed + // on the UI thread. + bool window_destroyed_; + // True if currently in the OnSetFocus callback. Only accessed on the UI // thread. bool is_in_onsetfocus_; diff --git a/libcef/browser/browser_host_impl_gtk.cc b/libcef/browser/browser_host_impl_gtk.cc index 780e38b89..2a7bf96b1 100644 --- a/libcef/browser/browser_host_impl_gtk.cc +++ b/libcef/browser/browser_host_impl_gtk.cc @@ -22,15 +22,38 @@ namespace { void DestroyBrowser(CefRefPtr browser) { - browser->DestroyBrowser(); - browser->Release(); + // Force the browser to be destroyed and release the reference added in + // PlatformCreateWindow(). + browser->WindowDestroyed(); } -void window_destroyed(GtkWidget* widget, CefBrowserHostImpl* browser) { +void browser_destroy(GtkWidget* widget, CefBrowserHostImpl* browser) { // Destroy the browser host after window destruction is complete. CEF_POST_TASK(CEF_UIT, base::Bind(DestroyBrowser, browser)); } +void window_destroy(GtkWidget* widget, gpointer data) { +} + +gboolean window_delete_event(GtkWidget* widget, GdkEvent* event, + CefBrowserHostImpl* browser) { + // Protect against multiple requests to close while the close is pending. + if (browser && browser->destruction_state() <= + CefBrowserHostImpl::DESTRUCTION_STATE_PENDING) { + if (browser->destruction_state() == + CefBrowserHostImpl::DESTRUCTION_STATE_NONE) { + // Request that the browser close. + browser->CloseBrowser(false); + } + + // Cancel the close. + return TRUE; + } + + // Allow the close. + return FALSE; +} + std::string GetDescriptionFromMimeType(const std::string& mime_type) { // Check for wild card mime types and return an appropriate description. static const struct { @@ -225,6 +248,11 @@ bool CefBrowserHostImpl::PlatformCreateWindow() { gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_widget_show_all(GTK_WIDGET(window)); + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(window_destroy), NULL); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(window_delete_event), this); + window_info_.parent_widget = parentView; } @@ -237,7 +265,7 @@ bool CefBrowserHostImpl::PlatformCreateWindow() { window_info_.widget); g_signal_connect(G_OBJECT(window_info_.widget), "destroy", - G_CALLBACK(window_destroyed), this); + G_CALLBACK(browser_destroy), this); // As an additional requirement on Linux, we must set the colors for the // render widgets in webkit. @@ -260,7 +288,14 @@ void CefBrowserHostImpl::PlatformCloseWindow() { if (window_info_.widget != NULL) { GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(window_info_.widget)); - gtk_signal_emit_by_name(GTK_OBJECT(window), "delete_event"); + + // Send the "delete_event" signal. + GdkEvent event; + memset(&event, 0, sizeof(GdkEvent)); + event.any.type = GDK_DELETE; + event.any.send_event = TRUE; + event.any.window = window->window; + gtk_main_do_event(&event); } } diff --git a/libcef/browser/browser_host_impl_mac.mm b/libcef/browser/browser_host_impl_mac.mm index 9ef99c31e..e59512630 100644 --- a/libcef/browser/browser_host_impl_mac.mm +++ b/libcef/browser/browser_host_impl_mac.mm @@ -42,8 +42,9 @@ - (void) dealloc { if (browser_) { - browser_->DestroyBrowser(); - browser_->Release(); + // Force the browser to be destroyed and release the reference added in + // PlatformCreateWindow(). + browser_->WindowDestroyed(); } [super dealloc]; @@ -68,6 +69,51 @@ @end +// Receives notifications from the browser window. Will delete itself when done. +@interface CefWindowDelegate : NSObject { + @private + CefBrowserHostImpl* browser_; // weak +} + +@property (nonatomic, assign) CefBrowserHostImpl* browser; + +@end + +@implementation CefWindowDelegate + +@synthesize browser = browser_; + +- (BOOL)windowShouldClose:(id)window { + // Protect against multiple requests to close while the close is pending. + if (browser_ && browser_->destruction_state() <= + CefBrowserHostImpl::DESTRUCTION_STATE_PENDING) { + if (browser_->destruction_state() == + CefBrowserHostImpl::DESTRUCTION_STATE_NONE) { + // Request that the browser close. + browser_->CloseBrowser(false); + } + + // Cancel the close. + return NO; + } + + // Clean ourselves up after clearing the stack of anything that might have the + // window on it. + [self performSelectorOnMainThread:@selector(cleanup:) + withObject:window + waitUntilDone:NO]; + + // Allow the close. + return YES; +} + +- (void)cleanup:(id)window { + [self release]; +} + +@end + + namespace { // Accept-types to file-types helper. @@ -231,6 +277,10 @@ bool CefBrowserHostImpl::PlatformCreateWindow() { contentRect.size.width = window_rect.size.width; contentRect.size.height = window_rect.size.height; + // Create the delegate for control and browser window events. + CefWindowDelegate* delegate = [[CefWindowDelegate alloc] init]; + delegate.browser = this; + newWnd = [[UnderlayOpenGLHostingWindow alloc] initWithContentRect:window_rect styleMask:(NSTitledWindowMask | @@ -240,6 +290,7 @@ bool CefBrowserHostImpl::PlatformCreateWindow() { NSUnifiedTitleAndToolbarWindowMask ) backing:NSBackingStoreBuffered defer:NO]; + [newWnd setDelegate:delegate]; parentView = [newWnd contentView]; window_info_.parent_view = parentView; } diff --git a/libcef/browser/browser_host_impl_win.cc b/libcef/browser/browser_host_impl_win.cc index cd72ea810..be9ed839a 100644 --- a/libcef/browser/browser_host_impl_win.cc +++ b/libcef/browser/browser_host_impl_win.cc @@ -456,30 +456,24 @@ LPCTSTR CefBrowserHostImpl::GetWndClass() { // static LRESULT CALLBACK CefBrowserHostImpl::WndProc(HWND hwnd, UINT message, - WPARAM wParam, LPARAM lParam) { + WPARAM wParam, LPARAM lParam) { CefBrowserHostImpl* browser = static_cast(ui::GetWindowUserData(hwnd)); switch (message) { case WM_CLOSE: - if (browser) { - bool handled(false); - - if (browser->client_.get()) { - CefRefPtr handler = - browser->client_->GetLifeSpanHandler(); - if (handler.get()) { - // Give the client a chance to handle this one. - handled = handler->DoClose(browser); - } + // Protect against multiple requests to close while the close is pending. + if (browser && browser->destruction_state() <= DESTRUCTION_STATE_PENDING) { + if (browser->destruction_state() == DESTRUCTION_STATE_NONE) { + // Request that the browser close. + browser->CloseBrowser(false); } - if (handled) - return 0; - - // We are our own parent in this case. - browser->ParentWindowWillClose(); + // Cancel the close. + return 0; } + + // Allow the close. break; case WM_DESTROY: @@ -487,11 +481,9 @@ LRESULT CALLBACK CefBrowserHostImpl::WndProc(HWND hwnd, UINT message, // Clear the user data pointer. ui::SetWindowUserData(hwnd, NULL); - // Destroy the browser. - browser->DestroyBrowser(); - - // Release the reference added in PlatformCreateWindow(). - browser->Release(); + // Force the browser to be destroyed and release the reference added in + // PlatformCreateWindow(). + browser->WindowDestroyed(); } return 0; @@ -574,8 +566,10 @@ bool CefBrowserHostImpl::PlatformCreateWindow() { } void CefBrowserHostImpl::PlatformCloseWindow() { - if (window_info_.window != NULL) - PostMessage(window_info_.window, WM_CLOSE, 0, 0); + if (window_info_.window != NULL) { + HWND frameWnd = GetAncestor(window_info_.window, GA_ROOT); + PostMessage(frameWnd, WM_CLOSE, 0, 0); + } } void CefBrowserHostImpl::PlatformSizeTo(int width, int height) { diff --git a/libcef/browser/context.cc b/libcef/browser/context.cc index 4528e6b1a..018f95bd1 100644 --- a/libcef/browser/context.cc +++ b/libcef/browser/context.cc @@ -34,10 +34,6 @@ #include "sandbox/win/src/sandbox_types.h" #endif -// Both the CefContext constuctor and the CefContext::RemoveBrowser method need -// to initialize or reset to the same value. -const int kNextBrowserIdReset = 1; - // Global CefContext pointer CefRefPtr _Context; diff --git a/libcef/browser/javascript_dialog_manager.cc b/libcef/browser/javascript_dialog_manager.cc index d6f9a3d6f..af9ce26d8 100644 --- a/libcef/browser/javascript_dialog_manager.cc +++ b/libcef/browser/javascript_dialog_manager.cc @@ -136,6 +136,14 @@ void CefJavaScriptDialogManager::RunBeforeUnloadDialog( const string16& message_text, bool is_reload, const DialogClosedCallback& callback) { + if (browser_->destruction_state() >= + CefBrowserHostImpl::DESTRUCTION_STATE_ACCEPTED) { + // Currently destroying the browser. Accept the unload without showing + // the prompt. + callback.Run(true, string16()); + return; + } + CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetJSDialogHandler(); diff --git a/libcef_dll/cpptoc/browser_host_cpptoc.cc b/libcef_dll/cpptoc/browser_host_cpptoc.cc index d0ec3a06a..e7f640975 100644 --- a/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -119,7 +119,8 @@ void CEF_CALLBACK browser_host_parent_window_will_close( CefBrowserHostCppToC::Get(self)->ParentWindowWillClose(); } -void CEF_CALLBACK browser_host_close_browser(struct _cef_browser_host_t* self) { +void CEF_CALLBACK browser_host_close_browser(struct _cef_browser_host_t* self, + int force_close) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING DCHECK(self); @@ -127,7 +128,8 @@ void CEF_CALLBACK browser_host_close_browser(struct _cef_browser_host_t* self) { return; // Execute - CefBrowserHostCppToC::Get(self)->CloseBrowser(); + CefBrowserHostCppToC::Get(self)->CloseBrowser( + force_close?true:false); } void CEF_CALLBACK browser_host_set_focus(struct _cef_browser_host_t* self, diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.cc b/libcef_dll/ctocpp/browser_host_ctocpp.cc index e638580a5..babea1a57 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.cc +++ b/libcef_dll/ctocpp/browser_host_ctocpp.cc @@ -81,14 +81,15 @@ void CefBrowserHostCToCpp::ParentWindowWillClose() { struct_->parent_window_will_close(struct_); } -void CefBrowserHostCToCpp::CloseBrowser() { +void CefBrowserHostCToCpp::CloseBrowser(bool force_close) { if (CEF_MEMBER_MISSING(struct_, close_browser)) return; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // Execute - struct_->close_browser(struct_); + struct_->close_browser(struct_, + force_close); } void CefBrowserHostCToCpp::SetFocus(bool enable) { diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.h b/libcef_dll/ctocpp/browser_host_ctocpp.h index 5514e1479..40bdce82c 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.h +++ b/libcef_dll/ctocpp/browser_host_ctocpp.h @@ -39,7 +39,7 @@ class CefBrowserHostCToCpp // CefBrowserHost methods virtual CefRefPtr GetBrowser() OVERRIDE; virtual void ParentWindowWillClose() OVERRIDE; - virtual void CloseBrowser() OVERRIDE; + virtual void CloseBrowser(bool force_close) OVERRIDE; virtual void SetFocus(bool enable) OVERRIDE; virtual CefWindowHandle GetWindowHandle() OVERRIDE; virtual CefWindowHandle GetOpenerWindowHandle() OVERRIDE; diff --git a/tests/cefclient/cefclient.h b/tests/cefclient/cefclient.h index 83b34575d..081926ede 100644 --- a/tests/cefclient/cefclient.h +++ b/tests/cefclient/cefclient.h @@ -36,6 +36,9 @@ void AppGetSettings(CefSettings& settings); // argument. bool AppIsOffScreenRenderingEnabled(); +// Quit the application message loop. +void AppQuitMessageLoop(); + // Implementations for various tests. void RunGetSourceTest(CefRefPtr browser); void RunGetTextTest(CefRefPtr browser); diff --git a/tests/cefclient/cefclient_gtk.cpp b/tests/cefclient/cefclient_gtk.cpp index a443e694b..982cd9c4c 100644 --- a/tests/cefclient/cefclient_gtk.cpp +++ b/tests/cefclient/cefclient_gtk.cpp @@ -24,12 +24,31 @@ char szWorkingDir[512]; // The current working directory // The global ClientHandler reference. extern CefRefPtr g_handler; -void destroy(void) { - CefQuitMessageLoop(); +void destroy(GtkWidget* widget, gpointer data) { + // Quitting CEF is handled in ClientHandler::OnBeforeClose(). +} + +gboolean delete_event(GtkWidget* widget, GdkEvent* event, + GtkWindow* window) { + if (g_handler.get() && !g_handler->IsClosing()) { + CefRefPtr browser = g_handler->GetBrowser(); + if (browser.get()) { + // Notify the browser window that we would like to close it. This + // will result in a call to ClientHandler::DoClose() if the + // JavaScript 'onbeforeunload' event handler allows it. + browser->GetHost()->CloseBrowser(false); + + // Allow the close. + return TRUE; + } + } + + // Cancel the close. + return FALSE; } void TerminationSignalHandler(int signatl) { - destroy(); + AppQuitMessageLoop(); } // Callback for Debug > Get Source... menu item. @@ -404,10 +423,10 @@ int main(int argc, char* argv[]) { gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(gtk_widget_destroyed), &window); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL); + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(delete_event), window); // Create the handler. g_handler = new ClientHandler(); @@ -444,4 +463,8 @@ int main(int argc, char* argv[]) { std::string AppGetWorkingDirectory() { return szWorkingDir; -} \ No newline at end of file +} + +void AppQuitMessageLoop() { + CefQuitMessageLoop(); +} diff --git a/tests/cefclient/cefclient_mac.mm b/tests/cefclient/cefclient_mac.mm index 729940757..d040b1425 100644 --- a/tests/cefclient/cefclient_mac.mm +++ b/tests/cefclient/cefclient_mac.mm @@ -155,16 +155,30 @@ static NSAutoreleasePool* g_autopool = nil; // Called when the window is about to close. Perform the self-destruction // sequence by getting rid of the window. By returning YES, we allow the window // to be removed from the screen. -- (BOOL)windowShouldClose:(id)window { +- (BOOL)windowShouldClose:(id)window { + if (g_handler.get() && !g_handler->IsClosing()) { + CefRefPtr browser = g_handler->GetBrowser(); + if (browser.get()) { + // Notify the browser window that we would like to close it. This + // will result in a call to ClientHandler::DoClose() if the + // JavaScript 'onbeforeunload' event handler allows it. + browser->GetHost()->CloseBrowser(false); + + // Cancel the close. + return NO; + } + } + // Try to make the window go away. [window autorelease]; - + // Clean ourselves up after clearing the stack of anything that might have the // window on it. [self performSelectorOnMainThread:@selector(cleanup:) withObject:window waitUntilDone:NO]; - + + // Allow the close. return YES; } @@ -500,11 +514,10 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { } // Sent by the default notification center immediately before the application -// terminates. +// terminates. Quitting CEF is handled in ClientHandler::OnBeforeClose(). - (void)applicationWillTerminate:(NSNotification *)aNotification { - // Shut down CEF. + // Release the handler. g_handler = NULL; - CefShutdown(); [self release]; @@ -555,7 +568,9 @@ int main(int argc, char* argv[]) { // Run the application message loop. CefRunMessageLoop(); - // Don't put anything below this line because it won't be executed. + // Shut down CEF. + CefShutdown(); + return 0; } @@ -565,3 +580,7 @@ int main(int argc, char* argv[]) { std::string AppGetWorkingDirectory() { return szWorkingDir; } + +void AppQuitMessageLoop() { + CefQuitMessageLoop(); +} diff --git a/tests/cefclient/cefclient_win.cpp b/tests/cefclient/cefclient_win.cpp index 38ba12969..ce0fcf1bf 100644 --- a/tests/cefclient/cefclient_win.cpp +++ b/tests/cefclient/cefclient_win.cpp @@ -42,6 +42,12 @@ BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); +// Used for processing messages on the main application thread while running +// in multi-threaded message loop mode. +HWND hMessageWnd = NULL; +HWND CreateMessageWindow(HINSTANCE hInstance); +LRESULT CALLBACK MessageWndProc(HWND, UINT, WPARAM, LPARAM); + // The global ClientHandler reference. extern CefRefPtr g_handler; @@ -115,6 +121,10 @@ int APIENTRY wWinMain(HINSTANCE hInstance, // recieves a WM_QUIT message. CefRunMessageLoop(); } else { + // Create a hidden window for message processing. + hMessageWnd = CreateMessageWindow(hInstance); + ASSERT(hMessageWnd); + MSG msg; // Run the application message loop. @@ -125,6 +135,9 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } } + DestroyWindow(hMessageWnd); + hMessageWnd = NULL; + result = static_cast(msg.wParam); } @@ -541,17 +554,24 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, break; case WM_CLOSE: - if (g_handler.get()) { + if (g_handler.get() && !g_handler->IsClosing()) { CefRefPtr browser = g_handler->GetBrowser(); if (browser.get()) { - // Let the browser window know we are about to destroy it. - browser->GetHost()->ParentWindowWillClose(); + // Notify the browser window that we would like to close it. This + // will result in a call to ClientHandler::DoClose() if the + // JavaScript 'onbeforeunload' event handler allows it. + browser->GetHost()->CloseBrowser(false); + + // Cancel the close. + return 0; } } + + // Allow the close. break; case WM_DESTROY: - PostQuitMessage(0); + // Quitting CEF is handled in ClientHandler::OnBeforeClose(). return 0; } @@ -576,9 +596,50 @@ INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { return (INT_PTR)FALSE; } +HWND CreateMessageWindow(HINSTANCE hInstance) { + static const wchar_t kWndClass[] = L"ClientMessageWindow"; + + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = MessageWndProc; + wc.hInstance = hInstance; + wc.lpszClassName = kWndClass; + RegisterClassEx(&wc); + + return CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, + hInstance, 0); +} + +LRESULT CALLBACK MessageWndProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) { + switch (message) { + case WM_COMMAND: { + int wmId = LOWORD(wParam); + switch (wmId) { + case ID_QUIT: + PostQuitMessage(0); + return 0; + } + } + } + return DefWindowProc(hWnd, message, wParam, lParam); +} + // Global functions std::string AppGetWorkingDirectory() { return szWorkingDir; } + +void AppQuitMessageLoop() { + CefRefPtr command_line = AppGetCommandLine(); + if (command_line->HasSwitch(cefclient::kMultiThreadedMessageLoop)) { + // Running in multi-threaded message loop mode. Need to execute + // PostQuitMessage on the main application thread. + ASSERT(hMessageWnd); + PostMessage(hMessageWnd, WM_COMMAND, ID_QUIT, 0); + } else { + CefQuitMessageLoop(); + } +} diff --git a/tests/cefclient/client_handler.cpp b/tests/cefclient/client_handler.cpp index f9024fc8b..68678f236 100644 --- a/tests/cefclient/client_handler.cpp +++ b/tests/cefclient/client_handler.cpp @@ -35,9 +35,12 @@ enum client_menu_ids { CLIENT_ID_TESTMENU_RADIOITEM3, }; +int ClientHandler::m_BrowserCount = 0; + ClientHandler::ClientHandler() : m_MainHwnd(NULL), m_BrowserId(0), + m_bIsClosing(false), m_EditHwnd(NULL), m_BackHwnd(NULL), m_ForwardHwnd(NULL), @@ -257,24 +260,26 @@ void ClientHandler::OnAfterCreated(CefRefPtr browser) { m_Browser = browser; m_BrowserId = browser->GetIdentifier(); } + + m_BrowserCount++; } bool ClientHandler::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 (m_BrowserId == browser->GetIdentifier()) { - // Since the main window contains the browser window, we need to close - // the parent window instead of the browser window. - CloseMainWindow(); + // Notify the browser that the parent window is about to close. + browser->GetHost()->ParentWindowWillClose(); - // Return true here so that we can skip closing the browser window - // in this pass. (It will be destroyed due to the call to close - // the parent above.) - return true; + // Set a flag to indicate that the window close should be allowed. + m_bIsClosing = true; } - // A popup browser window is not contained in another window, so we can let - // these windows close by themselves. + // Allow the close. For windowed browsers this will result in the OS close + // event being sent. return false; } @@ -296,6 +301,11 @@ void ClientHandler::OnBeforeClose(CefRefPtr browser) { if (it != m_OpenDevToolsURLs.end()) m_OpenDevToolsURLs.erase(it); } + + if (--m_BrowserCount == 0) { + // All browser windows have closed. Quit the application message loop. + AppQuitMessageLoop(); + } } void ClientHandler::OnLoadStart(CefRefPtr browser, diff --git a/tests/cefclient/client_handler.h b/tests/cefclient/client_handler.h index 980b01d17..c1dc04526 100644 --- a/tests/cefclient/client_handler.h +++ b/tests/cefclient/client_handler.h @@ -236,6 +236,11 @@ class ClientHandler : public CefClient, CefRefPtr GetBrowser() { return m_Browser; } int GetBrowserId() { return m_BrowserId; } + // Returns true if the main browser window is currently closing. Used in + // combination with DoClose() and the OS close notification to properly handle + // 'onbeforeunload' JavaScript events during window close. + bool IsClosing() { return m_bIsClosing; } + std::string GetLogFile(); void SetLastDownloadFile(const std::string& fileName); @@ -249,7 +254,6 @@ class ClientHandler : public CefClient, NOTIFY_DOWNLOAD_ERROR, }; void SendNotification(NotificationType type); - void CloseMainWindow(); void ShowDevTools(CefRefPtr browser); @@ -297,6 +301,9 @@ class ClientHandler : public CefClient, // The child browser id int m_BrowserId; + // True if the main browser window is currently closing. + bool m_bIsClosing; + // The edit window handle CefWindowHandle m_EditHwnd; @@ -330,6 +337,10 @@ class ClientHandler : public CefClient, // The startup URL. std::string m_StartupURL; + // Number of currently existing browser windows. The application will exit + // when the number of windows reaches 0. + static int m_BrowserCount; + // Include the default reference counting implementation. IMPLEMENT_REFCOUNTING(ClientHandler); // Include the default locking implementation. diff --git a/tests/cefclient/client_handler_gtk.cpp b/tests/cefclient/client_handler_gtk.cpp index a35b04e61..2cd10e4c3 100644 --- a/tests/cefclient/client_handler_gtk.cpp +++ b/tests/cefclient/client_handler_gtk.cpp @@ -54,10 +54,6 @@ void ClientHandler::SetNavState(bool canGoBack, bool canGoForward) { gtk_widget_set_sensitive(GTK_WIDGET(m_ForwardHwnd), false); } -void ClientHandler::CloseMainWindow() { - // TODO(port): Close main window. -} - std::string ClientHandler::GetDownloadPath(const std::string& file_name) { return std::string(); } diff --git a/tests/cefclient/client_handler_mac.mm b/tests/cefclient/client_handler_mac.mm index fba1ddd2c..0a6f91a40 100644 --- a/tests/cefclient/client_handler_mac.mm +++ b/tests/cefclient/client_handler_mac.mm @@ -65,10 +65,6 @@ void ClientHandler::SetNavState(bool canGoBack, bool canGoForward) { // TODO(port): Change button status. } -void ClientHandler::CloseMainWindow() { - // TODO(port): Close window -} - std::string ClientHandler::GetDownloadPath(const std::string& file_name) { return std::string(); } diff --git a/tests/cefclient/client_handler_win.cpp b/tests/cefclient/client_handler_win.cpp index fe9944255..99bde8d47 100644 --- a/tests/cefclient/client_handler_win.cpp +++ b/tests/cefclient/client_handler_win.cpp @@ -67,10 +67,6 @@ void ClientHandler::SetNavState(bool canGoBack, bool canGoForward) { EnableWindow(m_ForwardHwnd, canGoForward); } -void ClientHandler::CloseMainWindow() { - ::PostMessage(m_MainHwnd, WM_CLOSE, 0, 0); -} - std::string ClientHandler::GetDownloadPath(const std::string& file_name) { TCHAR szFolderPath[MAX_PATH]; std::string path; diff --git a/tests/cefclient/process_helper_mac.cpp b/tests/cefclient/process_helper_mac.cpp index 7354b036a..412519d3d 100644 --- a/tests/cefclient/process_helper_mac.cpp +++ b/tests/cefclient/process_helper_mac.cpp @@ -15,6 +15,8 @@ std::string AppGetWorkingDirectory() { CefWindowHandle AppGetMainHwnd() { return NULL; } +void AppQuitMessageLoop() { +} // Process entry point. int main(int argc, char* argv[]) { diff --git a/tests/cefclient/resource.h b/tests/cefclient/resource.h index cd4c924f9..4077a1d4d 100644 --- a/tests/cefclient/resource.h +++ b/tests/cefclient/resource.h @@ -25,6 +25,7 @@ #define ID_WARN_CONSOLEMESSAGE 32000 #define ID_WARN_DOWNLOADCOMPLETE 32001 #define ID_WARN_DOWNLOADERROR 32002 +#define ID_QUIT 32500 #define ID_TESTS_GETSOURCE 32760 #define ID_TESTS_GETTEXT 32761 #define ID_TESTS_POPUP 32762 diff --git a/tests/unittests/display_unittest.cc b/tests/unittests/display_unittest.cc index 9149e29ee..3f77978a4 100644 --- a/tests/unittests/display_unittest.cc +++ b/tests/unittests/display_unittest.cc @@ -83,7 +83,7 @@ class TitleTestHandler : public TestHandler { } private: - virtual void DestroyTest() { + virtual void DestroyTest() OVERRIDE { for (int i = 0; i < 5; ++i) EXPECT_TRUE(got_title_[i]) << "step " << i; diff --git a/tests/unittests/life_span_unittest.cc b/tests/unittests/life_span_unittest.cc new file mode 100644 index 000000000..2ac45d179 --- /dev/null +++ b/tests/unittests/life_span_unittest.cc @@ -0,0 +1,306 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#include "include/cef_runnable.h" +#include "tests/unittests/test_handler.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kLifeSpanUrl[] = "http://tests-life-span/test.html"; +const char kUnloadDialogText[] = "Are you sure?"; +const char kUnloadMsg[] = "LifeSpanTestHandler.Unload"; + +// Browser side. +class LifeSpanTestHandler : public TestHandler { + public: + struct Settings { + Settings() + : force_close(false), + add_onunload_handler(false), + allow_do_close(true), + accept_before_unload_dialog(true) {} + + bool force_close; + bool add_onunload_handler; + bool allow_do_close; + bool accept_before_unload_dialog; + }; + + explicit LifeSpanTestHandler(const Settings& settings) + : settings_(settings), + executing_delay_close_(false) {} + + virtual void RunTest() OVERRIDE { + // Add the resources that we will navigate to/from. + std::string page = "Page"; + AddResource(kLifeSpanUrl, page, "text/html"); + + // Create the browser. + CreateBrowser(kLifeSpanUrl); + } + + virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE { + got_after_created_.yes(); + TestHandler::OnAfterCreated(browser); + } + + virtual bool DoClose(CefRefPtr browser) OVERRIDE { + if (executing_delay_close_) + return false; + + EXPECT_TRUE(browser->IsSame(GetBrowser())); + + got_do_close_.yes(); + + if (!settings_.allow_do_close) { + // The close will be canceled. + ScheduleDelayClose(); + } + + return !settings_.allow_do_close; + } + + virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE { + if (!executing_delay_close_) { + got_before_close_.yes(); + EXPECT_TRUE(browser->IsSame(GetBrowser())); + } + + TestHandler::OnBeforeClose(browser); + } + + virtual bool OnBeforeUnloadDialog( + CefRefPtr browser, + const CefString& message_text, + bool is_reload, + CefRefPtr callback) OVERRIDE { + if (executing_delay_close_) { + callback->Continue(true, CefString()); + return true; + } + + EXPECT_TRUE(browser->IsSame(GetBrowser())); + EXPECT_STREQ(kUnloadDialogText, message_text.ToString().c_str()); + EXPECT_FALSE(is_reload); + EXPECT_TRUE(callback.get()); + + if (!settings_.accept_before_unload_dialog) { + // The close will be canceled. + ScheduleDelayClose(); + } + + got_before_unload_dialog_.yes(); + callback->Continue(settings_.accept_before_unload_dialog, CefString()); + return true; + } + + virtual void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) OVERRIDE { + got_load_end_.yes(); + EXPECT_TRUE(browser->IsSame(GetBrowser())); + + // Attempt to close the browser. + browser->GetHost()->CloseBrowser(settings_.force_close); + } + + virtual bool OnProcessMessageReceived( + CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) OVERRIDE { + const std::string& message_name = message->GetName(); + if (message_name == kUnloadMsg) { + if (!executing_delay_close_) + got_unload_message_.yes(); + return true; + } + + return false; + } + + TrackCallback got_after_created_; + TrackCallback got_do_close_; + TrackCallback got_before_close_; + TrackCallback got_before_unload_dialog_; + TrackCallback got_unload_message_; + TrackCallback got_load_end_; + TrackCallback got_delay_close_; + + private: + // Wait a bit to make sure no additional events are received and then close + // the window. + void ScheduleDelayClose() { + CefPostDelayedTask(TID_UI, + NewCefRunnableMethod(this, &LifeSpanTestHandler::DelayClose), 100); + } + + void DelayClose() { + got_delay_close_.yes(); + executing_delay_close_ = true; + DestroyTest(); + } + + Settings settings_; + + // Forces the window to close (bypasses test conditions). + bool executing_delay_close_; +}; + +} // namespace + +TEST(LifeSpanTest, DoCloseAllow) { + LifeSpanTestHandler::Settings settings; + settings.allow_do_close = true; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_TRUE(handler->got_before_close_); + EXPECT_FALSE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_FALSE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, DoCloseAllowForce) { + LifeSpanTestHandler::Settings settings; + settings.allow_do_close = true; + settings.force_close = true; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_TRUE(handler->got_before_close_); + EXPECT_FALSE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_FALSE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, DoCloseDisallow) { + LifeSpanTestHandler::Settings settings; + settings.allow_do_close = false; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_FALSE(handler->got_before_close_); + EXPECT_FALSE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_TRUE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, DoCloseDisallowForce) { + LifeSpanTestHandler::Settings settings; + settings.allow_do_close = false; + settings.force_close = true; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_FALSE(handler->got_before_close_); + EXPECT_FALSE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_TRUE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, DoCloseDisallowWithOnUnloadAllow) { + LifeSpanTestHandler::Settings settings; + settings.allow_do_close = false; + settings.add_onunload_handler = true; + settings.accept_before_unload_dialog = true; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_FALSE(handler->got_before_close_); + EXPECT_TRUE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_TRUE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, DoCloseAllowWithOnUnloadForce) { + LifeSpanTestHandler::Settings settings; + settings.allow_do_close = true; + settings.add_onunload_handler = true; + settings.force_close = true; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_TRUE(handler->got_before_close_); + EXPECT_FALSE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_FALSE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, DoCloseDisallowWithOnUnloadForce) { + LifeSpanTestHandler::Settings settings; + settings.allow_do_close = false; + settings.add_onunload_handler = true; + settings.force_close = true; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_FALSE(handler->got_before_close_); + EXPECT_FALSE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_TRUE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, OnUnloadAllow) { + LifeSpanTestHandler::Settings settings; + settings.add_onunload_handler = true; + settings.accept_before_unload_dialog = true; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_TRUE(handler->got_do_close_); + EXPECT_TRUE(handler->got_before_close_); + EXPECT_TRUE(handler->got_before_unload_dialog_); + EXPECT_TRUE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_FALSE(handler->got_delay_close_); +} + +TEST(LifeSpanTest, OnUnloadDisallow) { + LifeSpanTestHandler::Settings settings; + settings.add_onunload_handler = true; + settings.accept_before_unload_dialog = false; + CefRefPtr handler = new LifeSpanTestHandler(settings); + handler->ExecuteTest(); + + EXPECT_TRUE(handler->got_after_created_); + EXPECT_FALSE(handler->got_do_close_); + EXPECT_FALSE(handler->got_before_close_); + EXPECT_TRUE(handler->got_before_unload_dialog_); + EXPECT_FALSE(handler->got_unload_message_); + EXPECT_TRUE(handler->got_load_end_); + EXPECT_TRUE(handler->got_delay_close_); +} diff --git a/tests/unittests/navigation_unittest.cc b/tests/unittests/navigation_unittest.cc index b3d2ef184..ba2c99a5b 100644 --- a/tests/unittests/navigation_unittest.cc +++ b/tests/unittests/navigation_unittest.cc @@ -1018,7 +1018,7 @@ class OrderNavTestHandler : public TestHandler { "window.open('" + std::string(KONav2) + "');", CefString(), 0); } else { // Close the popup window. - browser_popup_->GetHost()->CloseBrowser(); + browser_popup_->GetHost()->CloseBrowser(false); } } @@ -1502,7 +1502,7 @@ class PopupNavTestHandler : public TestHandler { } else if (url == kPopupNavPopupUrl) { if (allow_) { got_popup_load_end_.yes(); - browser->GetHost()->CloseBrowser(); + browser->GetHost()->CloseBrowser(false); DestroyTest(); } else { EXPECT_FALSE(true); // Not reached. @@ -1513,7 +1513,7 @@ class PopupNavTestHandler : public TestHandler { } private: - virtual void DestroyTest() { + virtual void DestroyTest() OVERRIDE { EXPECT_TRUE(got_on_before_popup_); if (allow_) EXPECT_TRUE(got_popup_load_end_); diff --git a/tests/unittests/run_all_unittests.cc b/tests/unittests/run_all_unittests.cc index 10a7f9db3..1ea97f3af 100644 --- a/tests/unittests/run_all_unittests.cc +++ b/tests/unittests/run_all_unittests.cc @@ -5,6 +5,7 @@ #include "include/cef_app.h" #include "include/cef_task.h" #include "tests/cefclient/client_app.h" +#include "tests/unittests/test_handler.h" #include "tests/unittests/test_suite.h" #include "base/bind.h" #include "base/command_line.h" @@ -27,6 +28,10 @@ class CefTestThread : public base::Thread { // Run the test suite. retval_ = test_suite_->Run(); + // Wait for all browsers to exit. + while (TestHandler::HasBrowser()) + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + // Quit the CEF message loop. CefPostTask(TID_UI, NewCefRunnableFunction(CefQuitMessageLoop)); } diff --git a/tests/unittests/test_handler.cc b/tests/unittests/test_handler.cc index cf6051a66..854fe822f 100644 --- a/tests/unittests/test_handler.cc +++ b/tests/unittests/test_handler.cc @@ -19,6 +19,8 @@ void NotifyEvent(base::WaitableEvent* event) { // TestHandler +int TestHandler::browser_count_ = 0; + TestHandler::TestHandler() : browser_id_(0), completion_event_(true, false) { @@ -28,6 +30,8 @@ TestHandler::~TestHandler() { } void TestHandler::OnAfterCreated(CefRefPtr browser) { + browser_count_++; + AutoLock lock_scope(this); if (!browser->IsPopup()) { // Keep the main child window, but not popup windows @@ -37,15 +41,19 @@ void TestHandler::OnAfterCreated(CefRefPtr browser) { } void TestHandler::OnBeforeClose(CefRefPtr browser) { - AutoLock lock_scope(this); - if (browser_id_ == browser->GetIdentifier()) { - // Free the browser pointer so that the browser can be destroyed - browser_ = NULL; - browser_id_ = 0; + { + AutoLock lock_scope(this); + if (browser_id_ == browser->GetIdentifier()) { + // Free the browser pointer so that the browser can be destroyed + browser_ = NULL; + browser_id_ = 0; - // Signal that the test is now complete. - completion_event_.Signal(); + // Signal that the test is now complete. + completion_event_.Signal(); + } } + + browser_count_--; } CefRefPtr TestHandler::GetResourceHandler( @@ -91,7 +99,7 @@ void TestHandler::ExecuteTest() { void TestHandler::DestroyTest() { AutoLock lock_scope(this); if (browser_id_ != 0) - browser_->GetHost()->CloseBrowser(); + browser_->GetHost()->CloseBrowser(false); } void TestHandler::CreateBrowser(const CefString& url) { diff --git a/tests/unittests/test_handler.h b/tests/unittests/test_handler.h index adec312b7..8ead855cd 100644 --- a/tests/unittests/test_handler.h +++ b/tests/unittests/test_handler.h @@ -97,6 +97,9 @@ class TestHandler : public CefClient, // returns. void ExecuteTest(); + // Returns true if a browser currently exists. + static bool HasBrowser() { return browser_count_ > 0; } + protected: // Destroy the browser window. Once the window is destroyed test completion // will be signaled. @@ -128,6 +131,9 @@ class TestHandler : public CefClient, IMPLEMENT_REFCOUNTING(TestHandler); // Include the default locking implementation. IMPLEMENT_LOCKING(TestHandler); + + // Used to track the number of currently existing browser windows. + static int browser_count_; }; diff --git a/tests/unittests/test_suite.cc b/tests/unittests/test_suite.cc index 55f59c71d..0ce3b4da1 100644 --- a/tests/unittests/test_suite.cc +++ b/tests/unittests/test_suite.cc @@ -10,7 +10,7 @@ #if defined(OS_MACOSX) #include "base/debug/stack_trace.h" -#include "base/file_path.h" +#include "base/files/file_path.h" #include "base/i18n/icu_util.h" #include "base/path_service.h" #include "base/process_util.h"