Skip to content

Commit 6238a28

Browse files
committed
panel: Update browser shutdown code to ensure proper sequence of events
When the underlying CEF browser instance is required to close (either because the widget itself is being destroyed or because the associated function was called explicitly) a specific sequence of events needs to be ensured and also needs to block the thread it was called on. This is necessary to ensure that CEF's own shutdown code can run to completion before Qt continues with its own widget destruction and cleanup code. This change splits the procedure into 3 separate steps, each scheduled to run in-order on the local event loop to simulate a "serial dispatch queue": 1. Detach the window used by CEF from the Qt view hierarchy to prevent CEF from closing the main OBS Studio window 2. Initiate the actual browser closure 3. Reset the Qt widget reference kept by the browser client and quit the local event loop. Step 3 is either executed after the "OnBeforeClose" lifetime event is signaled by CEF or automatically after 1000 ms to limit the maximum of time the UI is allowed to keep the user waiting.
1 parent 4056a31 commit 6238a28

File tree

3 files changed

+69
-51
lines changed

3 files changed

+69
-51
lines changed

panel/browser-panel-client.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ bool QCefBrowserClient::OnBeforePopup(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>
220220
void QCefBrowserClient::OnBeforeClose(CefRefPtr<CefBrowser>)
221221
{
222222
if (widget) {
223-
widget->CloseSafely();
223+
widget->finishCloseBrowser();
224224
}
225225
}
226226

panel/browser-panel-internal.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class QCefWidgetInternal : public QCefWidget {
5151
virtual bool zoomPage(int direction) override;
5252
virtual void executeJavaScript(const std::string &script) override;
5353

54-
void CloseSafely();
54+
void finishCloseBrowser();
5555
void Resize();
5656

5757
#ifdef __linux__

panel/browser-panel.cpp

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,36 @@ std::vector<PopupWhitelistInfo> forced_popups;
3535

3636
static int zoomLvls[] = {25, 33, 50, 67, 75, 80, 90, 100, 110, 125, 150, 175, 200, 250, 300, 400};
3737

38+
namespace {
39+
void detachBrowserWindow(CefRefPtr<CefBrowserHost> host)
40+
{
41+
#ifdef _WIN32
42+
HWND hwnd = (HWND)host->GetWindowHandle();
43+
if (hwnd) {
44+
ShowWindow(hwnd, SW_HIDE);
45+
SetParent(hwnd, nullptr);
46+
}
47+
#elif __APPLE__
48+
SEL retain = sel_getUid("retain");
49+
SEL release = sel_getUid("release");
50+
SEL removeFromSuperview = sel_getUid("removeFromSuperview");
51+
void *(*msgSend)(id, SEL) = (void *(*)(id, SEL))objc_msgSend;
52+
53+
id view = static_cast<id>(host->GetWindowHandle());
54+
#pragma clang diagnostic push
55+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
56+
if (view && view->isa) {
57+
msgSend(view, retain);
58+
msgSend(view, removeFromSuperview);
59+
msgSend(view, release);
60+
}
61+
#pragma clang diagnostic pop
62+
#else
63+
UNUSED_PARAMETER(host);
64+
#endif
65+
}
66+
} // namespace
67+
3868
/* ------------------------------------------------------------------------- */
3969

4070
class CookieCheck : public CefCookieVisitor {
@@ -158,61 +188,49 @@ QCefWidgetInternal::~QCefWidgetInternal()
158188

159189
void QCefWidgetInternal::closeBrowser()
160190
{
161-
CefRefPtr<CefBrowser> browser = cefBrowser;
162-
if (!!browser) {
163-
auto destroyBrowser = [=](CefRefPtr<CefBrowser> cefBrowser) {
164-
CefRefPtr<CefClient> client = cefBrowser->GetHost()->GetClient();
165-
QCefBrowserClient *bc = reinterpret_cast<QCefBrowserClient *>(client.get());
191+
if (!cefBrowser) {
192+
return;
193+
}
166194

167-
cefBrowser->GetHost()->CloseBrowser(true);
195+
CefRefPtr<CefBrowserHost> host{cefBrowser->GetHost()};
168196

169-
#if CHROME_VERSION_BUILD >= 6533
170-
QEventLoop loop;
197+
if (!host) {
198+
return;
199+
}
171200

172-
connect(this, &QCefWidgetInternal::readyToClose, &loop, &QEventLoop::quit);
201+
QEventLoop browserCloseLoop;
173202

174-
QTimer::singleShot(1000, &loop, &QEventLoop::quit);
203+
// Ensure that the native window used by CEF is not attached to the widget view hierarchy while the browser
204+
// is closed.
205+
//
206+
// If the host window is not considered "destroyed" by the time CEF destroys the web contents of the associated
207+
// browser object, it will close the host window itself. The "host" window in this case would be OBS Studio's
208+
// main window however. So to ensure this cannot happen, the native window needs to be detached from the Qt
209+
// view hierarchy so there is no associated host window to close.
210+
auto preCloseBrowser = [&host]() {
211+
detachBrowserWindow(host);
212+
};
175213

176-
loop.exec();
177-
#endif
178-
if (bc) {
179-
bc->widget = nullptr;
180-
}
181-
};
182-
183-
/* So you're probably wondering what's going on here. If you
184-
* call CefBrowserHost::CloseBrowser, and it fails to unload
185-
* the web page *before* WM_NCDESTROY is called on the browser
186-
* HWND, it will call an internal CEF function
187-
* CefBrowserPlatformDelegateNativeWin::CloseHostWindow, which
188-
* will attempt to close the browser's main window itself.
189-
* Problem is, this closes the root window containing the
190-
* browser's HWND rather than the browser's specific HWND for
191-
* whatever mysterious reason. If the browser is in a dock
192-
* widget, then the window it closes is, unfortunately, the
193-
* main program's window, causing the entire program to shut
194-
* down.
195-
*
196-
* So, instead, before closing the browser, we need to decouple
197-
* the browser from the widget. To do this, we hide it, then
198-
* remove its parent. */
199-
#ifdef _WIN32
200-
HWND hwnd = (HWND)cefBrowser->GetHost()->GetWindowHandle();
201-
if (hwnd) {
202-
ShowWindow(hwnd, SW_HIDE);
203-
SetParent(hwnd, nullptr);
204-
}
205-
#elif __APPLE__
206-
// felt hacky, might delete later
207-
void *view = (id)cefBrowser->GetHost()->GetWindowHandle();
208-
if (*((bool *)view))
209-
((void (*)(id, SEL))objc_msgSend)((id)view, sel_getUid("removeFromSuperview"));
210-
#endif
214+
auto closeBrowser = [&host]() {
215+
host->CloseBrowser(true);
216+
};
217+
218+
connect(this, &QCefWidgetInternal::readyToClose, &browserCloseLoop, &QEventLoop::quit);
219+
220+
QTimer::singleShot(0, &browserCloseLoop, preCloseBrowser);
221+
QTimer::singleShot(0, &browserCloseLoop, closeBrowser);
222+
QTimer::singleShot(1000, &browserCloseLoop, &QEventLoop::quit);
211223

212-
destroyBrowser(browser);
213-
browser = nullptr;
214-
cefBrowser = nullptr;
224+
browserCloseLoop.exec();
225+
226+
CefRefPtr<CefClient> client{host->GetClient()};
227+
228+
if (client) {
229+
QCefBrowserClient *browserClient{static_cast<QCefBrowserClient *>(client.get())};
230+
browserClient->widget = nullptr;
215231
}
232+
233+
cefBrowser = nullptr;
216234
}
217235

218236
#ifdef __linux__
@@ -388,7 +406,7 @@ void QCefWidgetInternal::Resize()
388406
#endif
389407
}
390408

391-
void QCefWidgetInternal::CloseSafely()
409+
void QCefWidgetInternal::finishCloseBrowser()
392410
{
393411
emit readyToClose();
394412
}

0 commit comments

Comments
 (0)