From 866fe88acd64d1f3d327f9bdd55fd75e2e60f9c3 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Tue, 10 Mar 2026 18:21:10 -0700 Subject: [PATCH] feat: fix hidden window and tab tools (#417) --- .../chrome/browser/devtools/BUILD.gn | 21 +- .../devtools/protocol/browser_handler.cc | 269 +++++++++++------- .../devtools/protocol/browser_handler.h | 23 +- .../devtools/protocol/browser_handler_mac.h | 22 ++ .../devtools/protocol/browser_handler_mac.mm | 25 ++ .../devtools/protocol/hidden_tab_manager.cc | 104 ------- .../devtools/protocol/hidden_tab_manager.h | 65 ----- 7 files changed, 251 insertions(+), 278 deletions(-) create mode 100644 packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.h create mode 100644 packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.mm delete mode 100644 packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.cc delete mode 100644 packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.h diff --git a/packages/browseros/chromium_patches/chrome/browser/devtools/BUILD.gn b/packages/browseros/chromium_patches/chrome/browser/devtools/BUILD.gn index 654d00457..b723e9440 100644 --- a/packages/browseros/chromium_patches/chrome/browser/devtools/BUILD.gn +++ b/packages/browseros/chromium_patches/chrome/browser/devtools/BUILD.gn @@ -1,5 +1,5 @@ diff --git a/chrome/browser/devtools/BUILD.gn b/chrome/browser/devtools/BUILD.gn -index 4fc39e878028e..9a7fb91ab49e8 100644 +index 3ba666bf1e264..3655114cdaae9 100644 --- a/chrome/browser/devtools/BUILD.gn +++ b/chrome/browser/devtools/BUILD.gn @@ -34,6 +34,10 @@ if (enable_devtools_frontend) { @@ -13,7 +13,20 @@ index 4fc39e878028e..9a7fb91ab49e8 100644 "protocol/pwa.cc", "protocol/pwa.h", "protocol/security.cc", -@@ -372,7 +376,10 @@ static_library("devtools") { +@@ -362,7 +366,11 @@ static_library("devtools") { + } + + if (is_mac) { +- sources += [ "devtools_dock_tile_mac.mm" ] ++ sources += [ ++ "devtools_dock_tile_mac.mm", ++ "protocol/browser_handler_mac.h", ++ "protocol/browser_handler_mac.mm", ++ ] + } else { + sources += [ "devtools_dock_tile.cc" ] + } +@@ -379,7 +387,10 @@ static_library("devtools") { "//components/media_router/browser", "//components/media_router/common/mojom:media_router", "//components/payments/content", @@ -24,7 +37,7 @@ index 4fc39e878028e..9a7fb91ab49e8 100644 "//components/security_state/content", "//components/subresource_filter/content/browser", "//components/web_package", -@@ -386,8 +393,14 @@ static_library("devtools") { +@@ -393,8 +404,12 @@ static_library("devtools") { sources += [ "protocol/autofill_handler.cc", "protocol/autofill_handler.h", @@ -32,8 +45,6 @@ index 4fc39e878028e..9a7fb91ab49e8 100644 + "protocol/bookmarks_handler.h", "protocol/browser_handler.cc", "protocol/browser_handler.h", -+ "protocol/hidden_tab_manager.cc", -+ "protocol/hidden_tab_manager.h", + "protocol/history_handler.cc", + "protocol/history_handler.h", "protocol/cast_handler.cc", diff --git a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.cc b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.cc index 8ab8f3706..3e386e554 100644 --- a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.cc +++ b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.cc @@ -1,5 +1,5 @@ diff --git a/chrome/browser/devtools/protocol/browser_handler.cc b/chrome/browser/devtools/protocol/browser_handler.cc -index 30bd52d09c3fc..2b0bba666f4ae 100644 +index 30bd52d09c3fc..2df7c861f26aa 100644 --- a/chrome/browser/devtools/protocol/browser_handler.cc +++ b/chrome/browser/devtools/protocol/browser_handler.cc @@ -8,19 +8,32 @@ @@ -9,10 +9,10 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 +#include "base/memory/raw_ptr.h" #include "base/memory/ref_counted_memory.h" +#include "base/strings/utf_string_conversions.h" ++#include "build/build_config.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/devtools/chrome_devtools_manager_delegate.h" #include "chrome/browser/devtools/devtools_dock_tile.h" -+#include "chrome/browser/devtools/protocol/hidden_tab_manager.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/browser.h" @@ -35,19 +35,29 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/devtools_agent_host.h" -@@ -34,6 +47,11 @@ using protocol::Response; +@@ -30,10 +43,21 @@ + #include "ui/gfx/image/image.h" + #include "ui/gfx/image/image_png_rep.h" + ++#if BUILDFLAG(IS_MAC) ++#include "chrome/browser/devtools/protocol/browser_handler_mac.h" ++#endif ++ + using protocol::Response; namespace { ++#if !BUILDFLAG(IS_MAC) +// Off-screen position used to hide windows while keeping their compositors +// active. This enables CDP operations like Page.captureScreenshot on hidden +// windows. Uses cross-platform ui::BaseWindow::SetBounds/ShowInactive APIs. +constexpr int kOffScreenPosition = -32000; ++#endif + BrowserWindow* GetBrowserWindow(int window_id) { BrowserWindow* result = nullptr; ForEachCurrentBrowserWindowInterfaceOrderedByActivation( -@@ -72,11 +90,405 @@ std::unique_ptr GetBrowserWindowBounds( +@@ -72,17 +96,398 @@ std::unique_ptr GetBrowserWindowBounds( .Build(); } @@ -200,7 +210,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + +Response ResolveTabIdentifier(std::optional target_id, + std::optional tab_id, -+ HiddenTabManager* hidden_manager, ++ const base::flat_set& hidden_window_ids, + TabLookupResult* result) { + if (target_id.has_value() && tab_id.has_value()) { + return Response::InvalidParams( @@ -219,15 +229,6 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + if (!wc) + return Response::ServerError("No web contents in the target"); + -+ if (hidden_manager) { -+ SessionID sid = sessions::SessionTabHelper::IdForTab(wc); -+ if (sid.is_valid() && hidden_manager->IsHidden(sid.id())) { -+ result->web_contents = wc; -+ result->is_hidden = true; -+ return Response::Success(); -+ } -+ } -+ + BrowserWindowInterface* found_bwi = nullptr; + int found_index = -1; + ForEachCurrentBrowserWindowInterfaceOrderedByActivation( @@ -248,21 +249,14 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + result->web_contents = wc; + result->bwi = found_bwi; + result->tab_index = found_index; ++ result->is_hidden = ++ hidden_window_ids.contains(found_bwi->GetSessionID().id()); + return Response::Success(); + } + + // tab_id provided + int tid = tab_id.value(); + -+ if (hidden_manager) { -+ content::WebContents* hidden_wc = hidden_manager->FindByTabId(tid); -+ if (hidden_wc) { -+ result->web_contents = hidden_wc; -+ result->is_hidden = true; -+ return Response::Success(); -+ } -+ } -+ + BrowserWindowInterface* found_bwi = nullptr; + content::WebContents* found_wc = nullptr; + int found_index = -1; @@ -289,6 +283,8 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + result->web_contents = found_wc; + result->bwi = found_bwi; + result->tab_index = found_index; ++ result->is_hidden = ++ hidden_window_ids.contains(found_bwi->GetSessionID().id()); + return Response::Success(); +} + @@ -448,13 +444,21 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 BrowserHandler::BrowserHandler(protocol::UberDispatcher* dispatcher, const std::string& target_id) -- : target_id_(target_id) { -+ : target_id_(target_id), -+ hidden_tab_manager_(std::make_unique()) { - // Dispatcher can be null in tests. + : target_id_(target_id) { +- // Dispatcher can be null in tests. if (dispatcher) protocol::Browser::Dispatcher::wire(dispatcher, this); -@@ -120,6 +532,65 @@ Response BrowserHandler::GetWindowForTarget( + } + +-BrowserHandler::~BrowserHandler() = default; ++BrowserHandler::~BrowserHandler() { ++ hidden_window_per_profile_.clear(); ++ hidden_window_ids_.clear(); ++} + + Response BrowserHandler::GetWindowForTarget( + std::optional target_id, +@@ -120,6 +525,65 @@ Response BrowserHandler::GetWindowForTarget( return Response::Success(); } @@ -520,7 +524,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 Response BrowserHandler::GetWindowBounds( int window_id, std::unique_ptr* out_bounds) { -@@ -297,3 +768,828 @@ protocol::Response BrowserHandler::AddPrivacySandboxEnrollmentOverride( +@@ -297,3 +761,901 @@ protocol::Response BrowserHandler::AddPrivacySandboxEnrollmentOverride( net::SchemefulSite(url_to_add)); return Response::Success(); } @@ -589,17 +593,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + chrome::AddTabAt(browser, navigate_url, -1, true); + + if (hidden.value_or(false)) { -+ // Move the window off-screen, then show it without stealing focus. -+ // This keeps the compositor active (so CDP operations like -+ // Page.captureScreenshot work) while making the window invisible. -+ // Widget::Hide() cannot be used because it disconnects the compositor -+ // at the platform level on all OSes. -+ gfx::Rect offscreen_bounds = browser->window()->GetBounds(); -+ offscreen_bounds.set_origin( -+ gfx::Point(kOffScreenPosition, kOffScreenPosition)); -+ browser->window()->SetBounds(offscreen_bounds); -+ browser->window()->ShowInactive(); -+ hidden_window_ids_.insert(browser->session_id().id()); ++ MakeWindowHidden(browser); + } else { + browser->window()->Show(); + } @@ -620,6 +614,15 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + return Response::ServerError("Browser window not found"); + } + hidden_window_ids_.erase(window_id); ++ // Clean up hidden_window_per_profile_ if this was a hidden window. ++ Browser* browser = bwi->GetBrowserForMigrationOnly(); ++ for (auto it = hidden_window_per_profile_.begin(); ++ it != hidden_window_per_profile_.end(); ++it) { ++ if (it->second == browser) { ++ hidden_window_per_profile_.erase(it); ++ break; ++ } ++ } + bwi->GetTabStripModel()->CloseAllTabs(); + bwi->GetWindow()->Close(); + return Response::Success(); @@ -640,10 +643,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + return Response::ServerError("Browser window not found"); + } + if (hidden_window_ids_.contains(window_id)) { -+ gfx::Rect bounds = bwi->GetWindow()->GetBounds(); -+ bounds.set_origin(gfx::Point(100, 100)); -+ bwi->GetWindow()->SetBounds(bounds); -+ hidden_window_ids_.erase(window_id); ++ MakeWindowVisible(bwi); + } + bwi->GetWindow()->Show(); + return Response::Success(); @@ -654,11 +654,8 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + if (!bwi) { + return Response::ServerError("Browser window not found"); + } -+ gfx::Rect offscreen_bounds = bwi->GetWindow()->GetBounds(); -+ offscreen_bounds.set_origin( -+ gfx::Point(kOffScreenPosition, kOffScreenPosition)); -+ bwi->GetWindow()->SetBounds(offscreen_bounds); -+ hidden_window_ids_.insert(window_id); ++ Browser* browser = bwi->GetBrowserForMigrationOnly(); ++ MakeWindowHidden(browser); + return Response::Success(); +} + @@ -677,14 +674,20 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + if (!bwi) { + return Response::ServerError("Browser window not found"); + } ++ bool is_hidden = hidden_window_ids_.contains(bwi->GetSessionID().id()); + TabStripModel* tab_strip = bwi->GetTabStripModel(); + for (int i = 0; i < tab_strip->count(); ++i) { + tabs->push_back( -+ BuildTabInfo(tab_strip->GetWebContentsAt(i), bwi, i, false)); ++ BuildTabInfo(tab_strip->GetWebContentsAt(i), bwi, i, is_hidden)); + } + } else { + ForEachCurrentBrowserWindowInterfaceOrderedByActivation( -+ [&tabs](BrowserWindowInterface* bwi) { ++ [&tabs, this](BrowserWindowInterface* bwi) { ++ bool is_hidden = ++ hidden_window_ids_.contains(bwi->GetSessionID().id()); ++ if (is_hidden) { ++ return true; ++ } + TabStripModel* tab_strip = bwi->GetTabStripModel(); + for (int i = 0; i < tab_strip->count(); ++i) { + tabs->push_back( @@ -695,9 +698,18 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + } + + if (include_hidden.value_or(false)) { -+ for (const auto& wc : hidden_tab_manager_->hidden_tabs()) { -+ tabs->push_back(BuildTabInfo(wc.get(), nullptr, -1, true)); -+ } ++ ForEachCurrentBrowserWindowInterfaceOrderedByActivation( ++ [&tabs, this](BrowserWindowInterface* bwi) { ++ if (!hidden_window_ids_.contains(bwi->GetSessionID().id())) { ++ return true; ++ } ++ TabStripModel* tab_strip = bwi->GetTabStripModel(); ++ for (int i = 0; i < tab_strip->count(); ++i) { ++ tabs->push_back( ++ BuildTabInfo(tab_strip->GetWebContentsAt(i), bwi, i, true)); ++ } ++ return true; ++ }); + } + + *out_tabs = std::move(tabs); @@ -734,7 +746,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::unique_ptr* out_tab) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -755,10 +767,6 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + bool is_hidden = hidden.value_or(false); + + if (is_hidden) { -+ if (window_id.has_value()) { -+ return Response::InvalidParams( -+ "Cannot specify windowId for hidden tabs"); -+ } + if (pinned.value_or(false)) { + return Response::InvalidParams("Cannot pin a hidden tab"); + } @@ -773,15 +781,24 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + return Response::ServerError("No profile available"); + } + ++ Browser* hidden_browser = GetOrCreateHiddenWindow(profile); ++ if (!hidden_browser) { ++ return Response::ServerError("Failed to create hidden window for tab"); ++ } ++ + GURL navigate_url = url.has_value() ? GURL(url.value()) : GURL(); -+ int tab_id = -+ hidden_tab_manager_->CreateHiddenTab(navigate_url, profile); -+ content::WebContents* wc = hidden_tab_manager_->FindByTabId(tab_id); ++ chrome::AddTabAt(hidden_browser, navigate_url, -1, false); ++ ++ TabStripModel* tab_strip = hidden_browser->tab_strip_model(); ++ int new_index = tab_strip->count() - 1; ++ content::WebContents* wc = tab_strip->GetWebContentsAt(new_index); + if (!wc) { + return Response::ServerError("Failed to create hidden tab"); + } + -+ *out_tab = BuildTabInfo(wc, nullptr, -1, true); ++ BrowserWindowInterface* bwi = GetBrowserWindowInterface( ++ hidden_browser->session_id().id()); ++ *out_tab = BuildTabInfo(wc, bwi, new_index, true); + return Response::Success(); + } + @@ -825,19 +842,10 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::optional tab_id) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + -+ if (lookup.is_hidden) { -+ SessionID sid = -+ sessions::SessionTabHelper::IdForTab(lookup.web_contents); -+ if (sid.is_valid()) { -+ hidden_tab_manager_->DetachByTabId(sid.id()); -+ } -+ return Response::Success(); -+ } -+ + TabStripModel* tab_strip = lookup.bwi->GetTabStripModel(); + tab_strip->CloseWebContentsAt(lookup.tab_index, + TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB); @@ -848,7 +856,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::optional tab_id) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -870,7 +878,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::unique_ptr* out_tab) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -929,7 +937,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::unique_ptr* out_tab) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -956,7 +964,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::unique_ptr* out_tab) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -977,7 +985,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::unique_ptr* out_tab) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -1001,7 +1009,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::unique_ptr* out_tab) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -1009,32 +1017,37 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + return Response::InvalidParams("Tab is not hidden"); + } + -+ SessionID sid = -+ sessions::SessionTabHelper::IdForTab(lookup.web_contents); -+ if (!sid.is_valid()) { -+ return Response::ServerError("Hidden tab has invalid tab id"); -+ } -+ ++ // Detach from the hidden window. ++ TabStripModel* source_strip = lookup.bwi->GetTabStripModel(); + std::unique_ptr detached = -+ hidden_tab_manager_->DetachByTabId(sid.id()); ++ source_strip->DetachWebContentsAtForInsertion(lookup.tab_index); + if (!detached) { + return Response::ServerError("Failed to detach hidden tab"); + } + ++ // Find target visible window. + BrowserWindowInterface* target_bwi = nullptr; + if (window_id.has_value()) { + target_bwi = GetBrowserWindowInterface(window_id.value()); + if (!target_bwi) { -+ // Put it back if the window wasn't found. -+ hidden_tab_manager_->TakeWebContents(std::move(detached)); ++ // Put it back on the hidden window. ++ source_strip->InsertWebContentsAt(-1, std::move(detached), ++ AddTabTypes::ADD_NONE); + return Response::ServerError("Browser window not found"); + } + } else { -+ target_bwi = GetLastActiveBrowserWindowInterfaceWithAnyProfile(); ++ // Find last active non-hidden window. ++ ForEachCurrentBrowserWindowInterfaceOrderedByActivation( ++ [this, &target_bwi](BrowserWindowInterface* bwi) { ++ if (!hidden_window_ids_.contains(bwi->GetSessionID().id())) { ++ target_bwi = bwi; ++ return false; ++ } ++ return true; ++ }); + } + + if (!target_bwi) { -+ // No windows exist — create one. + Profile* profile = + Profile::FromBrowserContext(detached->GetBrowserContext()); + Browser::CreateParams params( @@ -1043,7 +1056,8 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + browser->window()->Show(); + target_bwi = GetBrowserWindowInterface(browser->session_id().id()); + if (!target_bwi) { -+ hidden_tab_manager_->TakeWebContents(std::move(detached)); ++ source_strip->InsertWebContentsAt(-1, std::move(detached), ++ AddTabTypes::ADD_NONE); + return Response::ServerError("Failed to create window for tab"); + } + } @@ -1069,7 +1083,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + std::unique_ptr* out_tab) { + TabLookupResult lookup; + Response response = ResolveTabIdentifier(target_id, tab_id, -+ hidden_tab_manager_.get(), &lookup); ++ hidden_window_ids_, &lookup); + if (!response.IsSuccess()) + return response; + @@ -1077,14 +1091,28 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + return Response::InvalidParams("Tab is already hidden"); + } + -+ TabStripModel* tab_strip = lookup.bwi->GetTabStripModel(); ++ // Detach from visible window. ++ TabStripModel* source_strip = lookup.bwi->GetTabStripModel(); + std::unique_ptr detached = -+ tab_strip->DetachWebContentsAtForInsertion(lookup.tab_index); ++ source_strip->DetachWebContentsAtForInsertion(lookup.tab_index); ++ if (!detached) { ++ return Response::ServerError("Failed to detach tab"); ++ } ++ ++ // Insert into hidden window. ++ Profile* profile = ++ Profile::FromBrowserContext(detached->GetBrowserContext()); ++ Browser* hidden_browser = GetOrCreateHiddenWindow(profile); + + content::WebContents* raw_wc = detached.get(); -+ hidden_tab_manager_->TakeWebContents(std::move(detached)); ++ hidden_browser->tab_strip_model()->InsertWebContentsAt( ++ -1, std::move(detached), AddTabTypes::ADD_NONE); + -+ *out_tab = BuildTabInfo(raw_wc, nullptr, -1, true); ++ BrowserWindowInterface* hidden_bwi = GetBrowserWindowInterface( ++ hidden_browser->session_id().id()); ++ int new_index = ++ hidden_browser->tab_strip_model()->GetIndexOfWebContents(raw_wc); ++ *out_tab = BuildTabInfo(raw_wc, hidden_bwi, new_index, true); + return Response::Success(); +} + @@ -1349,3 +1377,52 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644 + return Response::Success(); +} + ++// --- Hidden Window Helpers --- ++ ++Browser* BrowserHandler::GetOrCreateHiddenWindow(Profile* profile) { ++ auto it = hidden_window_per_profile_.find(profile); ++ if (it != hidden_window_per_profile_.end()) { ++ return it->second; ++ } ++ ++ Browser::CreateParams params(Browser::TYPE_NORMAL, profile, true); ++ Browser* browser = Browser::Create(params); ++ ++ // Add a blank tab so ShowInactive has content to composite. ++ chrome::AddTabAt(browser, GURL(), -1, false); ++ MakeWindowHidden(browser); ++ ++ hidden_window_per_profile_[profile] = browser; ++ return browser; ++} ++ ++void BrowserHandler::MakeWindowHidden(Browser* browser) { ++#if BUILDFLAG(IS_MAC) ++ SetWindowHeadless(browser->window(), true); ++ browser->window()->ShowInactive(); ++#else ++ gfx::Rect offscreen_bounds = browser->window()->GetBounds(); ++ offscreen_bounds.set_origin( ++ gfx::Point(kOffScreenPosition, kOffScreenPosition)); ++ browser->window()->SetBounds(offscreen_bounds); ++ browser->window()->ShowInactive(); ++#endif ++ hidden_window_ids_.insert(browser->session_id().id()); ++} ++ ++void BrowserHandler::MakeWindowVisible(BrowserWindowInterface* bwi) { ++#if BUILDFLAG(IS_MAC) ++ Browser* browser = bwi->GetBrowserForMigrationOnly(); ++ SetWindowHeadless(browser->window(), false); ++#else ++ gfx::Rect bounds = bwi->GetWindow()->GetBounds(); ++ bounds.set_origin(gfx::Point(100, 100)); ++ bwi->GetWindow()->SetBounds(bounds); ++#endif ++ hidden_window_ids_.erase(bwi->GetSessionID().id()); ++} ++ ++bool BrowserHandler::IsHiddenWindow(int window_id) const { ++ return hidden_window_ids_.contains(window_id); ++} ++ diff --git a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.h b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.h index c94338b59..90af22f11 100644 --- a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.h +++ b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler.h @@ -1,22 +1,26 @@ diff --git a/chrome/browser/devtools/protocol/browser_handler.h b/chrome/browser/devtools/protocol/browser_handler.h -index e1424aa52cbf6..2b8da4a31db41 100644 +index e1424aa52cbf6..ffd1c86c5aed9 100644 --- a/chrome/browser/devtools/protocol/browser_handler.h +++ b/chrome/browser/devtools/protocol/browser_handler.h -@@ -5,9 +5,13 @@ +@@ -5,9 +5,17 @@ #ifndef CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_H_ #define CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_H_ +#include + ++#include "base/containers/flat_map.h" #include "base/containers/flat_set.h" ++#include "base/memory/raw_ptr.h" #include "chrome/browser/devtools/protocol/browser.h" -+class HiddenTabManager; ++class Browser; ++class BrowserWindowInterface; ++class Profile; + class BrowserHandler : public protocol::Browser::Backend { public: BrowserHandler(protocol::UberDispatcher* dispatcher, -@@ -23,6 +27,14 @@ class BrowserHandler : public protocol::Browser::Backend { +@@ -23,6 +31,14 @@ class BrowserHandler : public protocol::Browser::Backend { std::optional target_id, int* out_window_id, std::unique_ptr* out_bounds) override; @@ -31,7 +35,7 @@ index e1424aa52cbf6..2b8da4a31db41 100644 protocol::Response GetWindowBounds( int window_id, std::unique_ptr* out_bounds) override; -@@ -41,9 +53,115 @@ class BrowserHandler : public protocol::Browser::Backend { +@@ -41,9 +57,118 @@ class BrowserHandler : public protocol::Browser::Backend { protocol::Response AddPrivacySandboxEnrollmentOverride( const std::string& in_url) override; @@ -138,12 +142,15 @@ index e1424aa52cbf6..2b8da4a31db41 100644 + std::unique_ptr* out_group) override; + private: ++ Browser* GetOrCreateHiddenWindow(Profile* profile); ++ void MakeWindowHidden(Browser* browser); ++ void MakeWindowVisible(BrowserWindowInterface* bwi); ++ bool IsHiddenWindow(int window_id) const; ++ base::flat_set contexts_with_overridden_permissions_; std::string target_id_; -+ std::unique_ptr hidden_tab_manager_; -+ // Window IDs that are positioned off-screen to appear "hidden" while -+ // keeping their compositors active for CDP operations. + base::flat_set hidden_window_ids_; ++ base::flat_map, raw_ptr> hidden_window_per_profile_; }; #endif // CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_H_ diff --git a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.h b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.h new file mode 100644 index 000000000..348647b2a --- /dev/null +++ b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.h @@ -0,0 +1,22 @@ +diff --git a/chrome/browser/devtools/protocol/browser_handler_mac.h b/chrome/browser/devtools/protocol/browser_handler_mac.h +new file mode 100644 +index 0000000000000..52e1b27fbfb0a +--- /dev/null ++++ b/chrome/browser/devtools/protocol/browser_handler_mac.h +@@ -0,0 +1,16 @@ ++// Copyright 2026 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#ifndef CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_MAC_H_ ++#define CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_MAC_H_ ++ ++class BrowserWindow; ++ ++// Sets per-window headless mode on macOS. When headless, the window's ++// NSWindow swizzles AppKit methods to fake visibility while keeping the ++// compositor active. This bypasses constrainFrameRect:toScreen: which ++// otherwise clamps off-screen windows back on-screen. ++void SetWindowHeadless(BrowserWindow* window, bool headless); ++ ++#endif // CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_MAC_H_ diff --git a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.mm b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.mm new file mode 100644 index 000000000..f698af2d8 --- /dev/null +++ b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/browser_handler_mac.mm @@ -0,0 +1,25 @@ +diff --git a/chrome/browser/devtools/protocol/browser_handler_mac.mm b/chrome/browser/devtools/protocol/browser_handler_mac.mm +new file mode 100644 +index 0000000000000..cd806bae50afd +--- /dev/null ++++ b/chrome/browser/devtools/protocol/browser_handler_mac.mm +@@ -0,0 +1,19 @@ ++// Copyright 2026 The Chromium Authors ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include "chrome/browser/devtools/protocol/browser_handler_mac.h" ++ ++#import ++ ++#include "chrome/browser/ui/browser_window.h" ++#include "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h" ++#include "ui/gfx/native_ui_types.h" ++ ++void SetWindowHeadless(BrowserWindow* window, bool headless) { ++ NSWindow* ns_window = window->GetNativeWindow().GetNativeNSWindow(); ++ if (auto* native_window = ++ base::apple::ObjCCast(ns_window)) { ++ [native_window setIsHeadless:headless]; ++ } ++} diff --git a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.cc b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.cc deleted file mode 100644 index 252de221d..000000000 --- a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.cc +++ /dev/null @@ -1,104 +0,0 @@ -diff --git a/chrome/browser/devtools/protocol/hidden_tab_manager.cc b/chrome/browser/devtools/protocol/hidden_tab_manager.cc -new file mode 100644 -index 0000000000000..c6de538ee4d90 ---- /dev/null -+++ b/chrome/browser/devtools/protocol/hidden_tab_manager.cc -@@ -0,0 +1,98 @@ -+// Copyright 2026 The Chromium Authors -+// Use of this source code is governed by a BSD-style license that can be -+// found in the LICENSE file. -+ -+#include "chrome/browser/devtools/protocol/hidden_tab_manager.h" -+ -+#include -+ -+#include "chrome/browser/sessions/session_tab_helper_factory.h" -+#include "components/sessions/content/session_tab_helper.h" -+#include "content/public/browser/devtools_agent_host.h" -+#include "content/public/browser/navigation_controller.h" -+#include "content/public/browser/web_contents.h" -+#include "url/gurl.h" -+ -+HiddenTabManager::HiddenTabManager() = default; -+ -+HiddenTabManager::~HiddenTabManager() = default; -+ -+int HiddenTabManager::CreateHiddenTab( -+ const GURL& url, -+ content::BrowserContext* browser_context) { -+ content::WebContents::CreateParams params(browser_context); -+ auto web_contents = content::WebContents::Create(params); -+ web_contents->SetDelegate(this); -+ -+ CreateSessionServiceTabHelper(web_contents.get()); -+ content::DevToolsAgentHost::GetOrCreateFor(web_contents.get()); -+ -+ if (!url.is_empty()) { -+ content::NavigationController::LoadURLParams load_params(url); -+ web_contents->GetController().LoadURLWithParams(load_params); -+ } -+ -+ int tab_id = -+ sessions::SessionTabHelper::IdForTab(web_contents.get()).id(); -+ hidden_web_contents_.push_back(std::move(web_contents)); -+ return tab_id; -+} -+ -+content::WebContents* HiddenTabManager::FindByTabId(int tab_id) { -+ for (const auto& wc : hidden_web_contents_) { -+ SessionID sid = sessions::SessionTabHelper::IdForTab(wc.get()); -+ if (sid.is_valid() && sid.id() == tab_id) { -+ return wc.get(); -+ } -+ } -+ return nullptr; -+} -+ -+content::WebContents* HiddenTabManager::FindByTargetId( -+ const std::string& target_id) { -+ for (const auto& wc : hidden_web_contents_) { -+ scoped_refptr host = -+ content::DevToolsAgentHost::GetOrCreateFor(wc.get()); -+ if (host && host->GetId() == target_id) { -+ return wc.get(); -+ } -+ } -+ return nullptr; -+} -+ -+bool HiddenTabManager::IsHidden(int tab_id) { -+ return FindByTabId(tab_id) != nullptr; -+} -+ -+std::unique_ptr HiddenTabManager::DetachByTabId( -+ int tab_id) { -+ for (auto it = hidden_web_contents_.begin(); -+ it != hidden_web_contents_.end(); ++it) { -+ SessionID sid = sessions::SessionTabHelper::IdForTab(it->get()); -+ if (sid.is_valid() && sid.id() == tab_id) { -+ std::unique_ptr result = std::move(*it); -+ hidden_web_contents_.erase(it); -+ return result; -+ } -+ } -+ return nullptr; -+} -+ -+void HiddenTabManager::TakeWebContents( -+ std::unique_ptr web_contents) { -+ web_contents->SetDelegate(this); -+ hidden_web_contents_.push_back(std::move(web_contents)); -+} -+ -+void HiddenTabManager::Clear() { -+ hidden_web_contents_.clear(); -+} -+ -+void HiddenTabManager::CloseContents(content::WebContents* source) { -+ auto it = std::find_if( -+ hidden_web_contents_.begin(), hidden_web_contents_.end(), -+ [source](const auto& wc) { return wc.get() == source; }); -+ if (it != hidden_web_contents_.end()) { -+ hidden_web_contents_.erase(it); -+ } -+} diff --git a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.h b/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.h deleted file mode 100644 index b869333f1..000000000 --- a/packages/browseros/chromium_patches/chrome/browser/devtools/protocol/hidden_tab_manager.h +++ /dev/null @@ -1,65 +0,0 @@ -diff --git a/chrome/browser/devtools/protocol/hidden_tab_manager.h b/chrome/browser/devtools/protocol/hidden_tab_manager.h -new file mode 100644 -index 0000000000000..52cb68e094c67 ---- /dev/null -+++ b/chrome/browser/devtools/protocol/hidden_tab_manager.h -@@ -0,0 +1,59 @@ -+// Copyright 2026 The Chromium Authors -+// Use of this source code is governed by a BSD-style license that can be -+// found in the LICENSE file. -+ -+#ifndef CHROME_BROWSER_DEVTOOLS_PROTOCOL_HIDDEN_TAB_MANAGER_H_ -+#define CHROME_BROWSER_DEVTOOLS_PROTOCOL_HIDDEN_TAB_MANAGER_H_ -+ -+#include -+#include -+#include -+ -+#include "content/public/browser/web_contents_delegate.h" -+ -+namespace content { -+class BrowserContext; -+class WebContents; -+} // namespace content -+ -+class GURL; -+ -+// Manages hidden tab WebContents that are not attached to any browser window. -+// Hidden tabs are first-class CDP targets and are destroyed on CDP disconnect. -+class HiddenTabManager : public content::WebContentsDelegate { -+ public: -+ HiddenTabManager(); -+ ~HiddenTabManager() override; -+ HiddenTabManager(const HiddenTabManager&) = delete; -+ HiddenTabManager& operator=(const HiddenTabManager&) = delete; -+ -+ // Create a hidden tab, optionally navigated to |url|. Returns the tab ID. -+ int CreateHiddenTab(const GURL& url, -+ content::BrowserContext* browser_context); -+ -+ content::WebContents* FindByTabId(int tab_id); -+ content::WebContents* FindByTargetId(const std::string& target_id); -+ bool IsHidden(int tab_id); -+ -+ // Remove a hidden tab from management, returning ownership. -+ std::unique_ptr DetachByTabId(int tab_id); -+ -+ // Take ownership of a WebContents (used by hideTab). -+ void TakeWebContents(std::unique_ptr web_contents); -+ -+ // Destroy all hidden tabs (called on session disconnect). -+ void Clear(); -+ -+ const std::vector>& hidden_tabs() -+ const { -+ return hidden_web_contents_; -+ } -+ -+ // content::WebContentsDelegate: -+ void CloseContents(content::WebContents* source) override; -+ -+ private: -+ std::vector> hidden_web_contents_; -+}; -+ -+#endif // CHROME_BROWSER_DEVTOOLS_PROTOCOL_HIDDEN_TAB_MANAGER_H_