mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 23:53:25 +00:00
chore: Merge branch 'main'
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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<protocol::Browser::Bounds> GetBrowserWindowBounds(
|
||||
@@ -72,17 +96,398 @@ std::unique_ptr<protocol::Browser::Bounds> GetBrowserWindowBounds(
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -200,7 +210,7 @@ index 30bd52d09c3fc..2b0bba666f4ae 100644
|
||||
+
|
||||
+Response ResolveTabIdentifier(std::optional<std::string> target_id,
|
||||
+ std::optional<int> tab_id,
|
||||
+ HiddenTabManager* hidden_manager,
|
||||
+ const base::flat_set<int>& 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<HiddenTabManager>()) {
|
||||
// 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<std::string> 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<protocol::Browser::Bounds>* 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<protocol::Browser::TabInfo>* 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<int> 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<int> 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<protocol::Browser::TabInfo>* 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<protocol::Browser::TabInfo>* 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<protocol::Browser::TabInfo>* 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<protocol::Browser::TabInfo>* 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<protocol::Browser::TabInfo>* 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<content::WebContents> 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<protocol::Browser::TabInfo>* 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<content::WebContents> 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);
|
||||
+}
|
||||
+
|
||||
|
||||
@@ -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 <memory>
|
||||
+
|
||||
+#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<std::string> target_id,
|
||||
int* out_window_id,
|
||||
std::unique_ptr<protocol::Browser::Bounds>* out_bounds) override;
|
||||
@@ -31,7 +35,7 @@ index e1424aa52cbf6..2b8da4a31db41 100644
|
||||
protocol::Response GetWindowBounds(
|
||||
int window_id,
|
||||
std::unique_ptr<protocol::Browser::Bounds>* 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<protocol::Browser::TabGroupInfo>* 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<std::string> contexts_with_overridden_permissions_;
|
||||
std::string target_id_;
|
||||
+ std::unique_ptr<HiddenTabManager> hidden_tab_manager_;
|
||||
+ // Window IDs that are positioned off-screen to appear "hidden" while
|
||||
+ // keeping their compositors active for CDP operations.
|
||||
+ base::flat_set<int> hidden_window_ids_;
|
||||
+ base::flat_map<raw_ptr<Profile>, raw_ptr<Browser>> hidden_window_per_profile_;
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_DEVTOOLS_PROTOCOL_BROWSER_HANDLER_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_
|
||||
@@ -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 <Cocoa/Cocoa.h>
|
||||
+
|
||||
+#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<NativeWidgetMacNSWindow>(ns_window)) {
|
||||
+ [native_window setIsHeadless:headless];
|
||||
+ }
|
||||
+}
|
||||
@@ -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 <algorithm>
|
||||
+
|
||||
+#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<content::DevToolsAgentHost> 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<content::WebContents> 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<content::WebContents> result = std::move(*it);
|
||||
+ hidden_web_contents_.erase(it);
|
||||
+ return result;
|
||||
+ }
|
||||
+ }
|
||||
+ return nullptr;
|
||||
+}
|
||||
+
|
||||
+void HiddenTabManager::TakeWebContents(
|
||||
+ std::unique_ptr<content::WebContents> 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);
|
||||
+ }
|
||||
+}
|
||||
@@ -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 <memory>
|
||||
+#include <string>
|
||||
+#include <vector>
|
||||
+
|
||||
+#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<content::WebContents> DetachByTabId(int tab_id);
|
||||
+
|
||||
+ // Take ownership of a WebContents (used by hideTab).
|
||||
+ void TakeWebContents(std::unique_ptr<content::WebContents> web_contents);
|
||||
+
|
||||
+ // Destroy all hidden tabs (called on session disconnect).
|
||||
+ void Clear();
|
||||
+
|
||||
+ const std::vector<std::unique_ptr<content::WebContents>>& hidden_tabs()
|
||||
+ const {
|
||||
+ return hidden_web_contents_;
|
||||
+ }
|
||||
+
|
||||
+ // content::WebContentsDelegate:
|
||||
+ void CloseContents(content::WebContents* source) override;
|
||||
+
|
||||
+ private:
|
||||
+ std::vector<std::unique_ptr<content::WebContents>> hidden_web_contents_;
|
||||
+};
|
||||
+
|
||||
+#endif // CHROME_BROWSER_DEVTOOLS_PROTOCOL_HIDDEN_TAB_MANAGER_H_
|
||||
Reference in New Issue
Block a user