From b4d93f24cdc05e1cd82617a6951159e58d9f1359 Mon Sep 17 00:00:00 2001 From: Andrew Zambazos <62979495+Bobbybear007@users.noreply.github.com> Date: Mon, 18 May 2026 18:35:18 +1200 Subject: [PATCH] Force close browsers and handle bigpicture exit Call CloseBrowser(true) for chrome, popup and tab browsers to force shutdown. Immediately destroy the top-level window and call MaybeFinishShutdown() to avoid hangs when WM_CLOSE is not resent by Alloy child-window paths. Route "close" and new "exit-bigpicture" chrome commands through OnWindowCloseRequested so they trigger the same shutdown flow. Add IsBigPictureFrame and permit the "exit-bigpicture" process message from bigpicture internal frames. --- src/app/nebula_controller.cpp | 22 ++++++++++++++-------- src/cef/browser_client.cpp | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/app/nebula_controller.cpp b/src/app/nebula_controller.cpp index 81f0f57..c209920 100644 --- a/src/app/nebula_controller.cpp +++ b/src/app/nebula_controller.cpp @@ -162,10 +162,6 @@ void NebulaController::OnWindowCloseRequested() { } if (closing_) { - // CEF re-sends WM_CLOSE to the top-level window after each Alloy - // child browser finishes its JS unload + DoClose phase. Destroy the - // Nebula window now so CEF can tear down the child browser HWNDs and - // fire OnBeforeClose; MaybeFinishShutdown will then quit the loop. if (window_ && window_->native_handle()) { nebula::platform::DestroyTopLevelWindow(window_->native_handle()); } @@ -180,16 +176,24 @@ void NebulaController::OnWindowCloseRequested() { } if (chrome_browser_) { - chrome_browser_->GetHost()->CloseBrowser(false); + chrome_browser_->GetHost()->CloseBrowser(true); } if (menu_popup_browser_) { - menu_popup_browser_->GetHost()->CloseBrowser(false); + menu_popup_browser_->GetHost()->CloseBrowser(true); } for (const auto& tab : tabs_.Tabs()) { if (tab.browser) { - tab.browser->GetHost()->CloseBrowser(false); + tab.browser->GetHost()->CloseBrowser(true); } } + + // Do not wait for CEF to re-send WM_CLOSE to the host window. On some + // Alloy child-window paths that message never arrives, leaving the app + // alive with all close affordances disabled until the process is killed. + if (window_ && window_->native_handle()) { + nebula::platform::DestroyTopLevelWindow(window_->native_handle()); + } + MaybeFinishShutdown(); } void NebulaController::OnActiveTabChanged(const nebula::browser::NebulaTab& tab) { @@ -307,7 +311,9 @@ void NebulaController::OnChromeCommand(const std::string& command, const std::st } else if (command == "maximize" && window_) { window_->ToggleMaximize(); } else if (command == "close" && window_) { - window_->Close(); + OnWindowCloseRequested(); + } else if (command == "exit-bigpicture" && window_) { + OnWindowCloseRequested(); } else if (command == "drag" && window_) { window_->BeginDrag(); } diff --git a/src/cef/browser_client.cpp b/src/cef/browser_client.cpp index f1228e2..01520f2 100644 --- a/src/cef/browser_client.cpp +++ b/src/cef/browser_client.cpp @@ -25,6 +25,16 @@ bool IsSettingsFrame(CefRefPtr frame) { return nebula::ui::ToInternalUrl(frame->GetURL().ToString()).starts_with("nebula://settings"); } +bool IsBigPictureFrame(CefRefPtr frame) { + if (!frame) { + return false; + } + + const std::string url = nebula::ui::ToInternalUrl(frame->GetURL().ToString()); + return url.starts_with("nebula://bigpicture") || + url.starts_with("nebula://big-picture"); +} + std::vector ToStringVector(const std::vector& values) { std::vector result; result.reserve(values.size()); @@ -67,7 +77,10 @@ bool NebulaBrowserClient::OnProcessMessageReceived(CefRefPtr browser command == "new-tab" || command == "clear-site-history" || command == "clear-search-history"); - if (!allowed_insecure_command && !allowed_settings_command) { + const bool allowed_big_picture_command = + IsBigPictureFrame(frame) && command == "exit-bigpicture"; + if (!allowed_insecure_command && !allowed_settings_command && + !allowed_big_picture_command) { return false; } }