You need a shutdown loop, I suppose.
CloseBrowser is not synchronous (someone correct me if I'm wrong), it's the OnBeforeClose callback that tells you the browser is *about to* close.
So this is how I did it:
- DoClose. If browser window was reparented to a tab, main window etc. reparent browser window to something dummy, or remove the child window style (GWL_STYLE: & ~WS_CHILD, | WS_POPUP)
- OnBeforeClose: notify parent object (in my example, I'm calling OnBrowserClosed(browser))
- OnBrowserClosed is implemented in main window (main form in C#) which does specific cleanup, then check if can close or not.
Here are the implementation excerpts.
Client handler:
- Code: Select all
LONG ClientHandler::BrowsersAddRef()
{
CEF_REQUIRE_UI_THREAD();
return ++m_BrowsersCount;
}
LONG ClientHandler::BrowsersRelease()
{
CEF_REQUIRE_UI_THREAD();
return --m_BrowsersCount;
}
bool ClientHandler::HaveBrowsers() const
{
CEF_REQUIRE_UI_THREAD();
return m_BrowsersCount != 0;
}
LONG ClientHandler::BrowsersCount() const
{
CEF_REQUIRE_UI_THREAD();
return m_BrowsersCount;
}
Note. Call BrowsersAddRef() in OnAfterCreated, BrowsersRelease() in OnBeforeClose.
bool ClientHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
if(browser.get() != 0)
{
HWND hwndHost = browser->GetHost()->GetWindowHandle();
::SetParent(hwndHost, NULL);
DWORD dwHostStyle = (DWORD)::GetWindowLongPtrW(hwndHost, GWL_STYLE);
if(dwHostStyle & WS_CHILD)
{
dwHostStyle &= ~WS_CHILD;
dwHostStyle |= WS_POPUP;
}
::SetWindowLongPtrW(hwndHost, GWL_STYLE, (LONG_PTR)dwHostStyle);
}
return false;
}
void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
... cleanup
BrowsersRelease();
_NotifyBrowserClosed(browser);
}
void ClientHandler::_NotifyBrowserClosed(CefRefPtr<CefBrowser> browser)
{
if(!CefCurrentlyOn(TID_UI)) {
// Execute this method on the main thread.
CefCreateClosureTask(base::Bind(&ClientHandler::_NotifyBrowserClosed, this, browser));
return;
}
m_pParent->OnBrowserClosed(browser);
}
Note: m_pParent is the main application window.
Main app window:
- Code: Select all
void MainWindow::OnBrowserClosed(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
...
OnBrowserWindowDestroyed(browser);
return;
}
void MainWindow::OnBrowserWindowDestroyed(CefRefPtr<CefBrowser> browser)
{
... specific cleanup
browsers_destroyed_ = !_handler->HaveBrowsers(); // browser count is NOT > 0 ?
if(browsers_destroyed_) { // yes, we have 0 browsers
if (!window_destroyed_) { // main window not destroyed
if(_handler->isClosing()) { // isClosing returns true if we entered the shutdown phase, for example click the Close button on main window
CloseIfNeeded(true); // force close main window
}
}
}
NotifyDestroyedIfDone();
}
Note. browsers_destroyed_ and window_destroyed_ are bools, first indicates "all browsers are destroyed, browser count is 0" and the second means "main window is destroyed".
void MainWindow::CloseIfNeeded(bool force)
{
if (GetSafeHwnd()) {
if (force) {
::DestroyWindow(GetSafeHwnd());
}
else {
::PostMessage(GetSafeHwnd(), WM_CLOSE, 0, 0);
}
}
}
void MainWindow::OnDestroyed()
{
window_destroyed_ = true;
NotifyDestroyedIfDone();
}
Note. OnDestroyed is called from WM_NCDESTROY.
void MainWindow::NotifyDestroyedIfDone()
{
// (re)check if browsers destroyed
browsers_destroyed_ = !_handler->HaveBrowsers();
if(window_destroyed_ && browsers_destroyed_) {
OnWindowDestroyed();
}
}
void MainWindow::OnWindowDestroyed()
{
Application::QuitMessageLoop();
}
void Application::QuitMessageLoop()
{
CefDoMessageLoopWork();
CefQuitMessageLoop();
}
The basic idea is this (also consider that not all things might be helpful for you, I'm describing my particular case):
- when WM_CLOSE is first sent, we don't close (return TRUE from WM_CLOSE), mark the client handler as closing (ClientHandler::isClosing will return true from now on), mark main window as closing (we enter in a shutdown phase), then post UWM_CLOSE to the main window (UWM_CLOSE is an user-defined message)
- UWM_CLOSE arrives; do the cleanup (hide main window, dispose resources etc.) and arm an one time shutdown timer
- WM_TIMER: if timer ID == shutdown timer ID, check the ClientHandler::BrowserCount(); if 0, destroy main window and call Application::QuitMessageLoop(), which consists of two calls, a last CefDoMessageLoopWork() + CefQuitMessageLoop()
- if browser count is not 0, rearm the one time shutdown timer and next timer will recheck if browser count dropped to 0.
How you pick how much time or how many times you rearm the shutdown timer is up to you.