| // Copyright 2018 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/target_handler.h" |
| |
| #include <ranges> |
| #include <string_view> |
| |
| #include "base/notreached.h" |
| #include "chrome/browser/devtools/chrome_devtools_manager_delegate.h" |
| #include "chrome/browser/devtools/devtools_browser_context_manager.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_features.h" |
| #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" |
| #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/common/url_utils.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace { |
| NavigateParams CreateNavigateParams(Profile* profile, |
| const GURL& url, |
| ui::PageTransition transition, |
| bool new_window, |
| bool background, |
| Browser* browser) { |
| DCHECK(new_window || browser); |
| NavigateParams params(profile, url, transition); |
| if (new_window) { |
| params.disposition = WindowOpenDisposition::NEW_WINDOW; |
| if (background) |
| params.window_action = NavigateParams::WindowAction::SHOW_WINDOW_INACTIVE; |
| } else { |
| params.disposition = (background) |
| ? WindowOpenDisposition::NEW_BACKGROUND_TAB |
| : WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| params.browser = browser; |
| } |
| return params; |
| } |
| } // namespace |
| |
| TargetHandler::TargetHandler(protocol::UberDispatcher* dispatcher, |
| bool is_trusted, |
| bool may_read_local_files) |
| : is_trusted_(is_trusted), may_read_local_files_(may_read_local_files) { |
| protocol::Target::Dispatcher::wire(dispatcher, this); |
| } |
| |
| TargetHandler::~TargetHandler() { |
| ChromeDevToolsManagerDelegate* delegate = |
| ChromeDevToolsManagerDelegate::GetInstance(); |
| if (delegate) |
| delegate->UpdateDeviceDiscovery(); |
| } |
| |
| protocol::Response TargetHandler::SetRemoteLocations( |
| std::unique_ptr<protocol::Array<protocol::Target::RemoteLocation>> |
| locations) { |
| remote_locations_.clear(); |
| if (!locations) |
| return protocol::Response::Success(); |
| |
| for (const auto& location : *locations) { |
| remote_locations_.insert( |
| net::HostPortPair(location->GetHost(), location->GetPort())); |
| } |
| |
| ChromeDevToolsManagerDelegate* delegate = |
| ChromeDevToolsManagerDelegate::GetInstance(); |
| if (delegate) |
| delegate->UpdateDeviceDiscovery(); |
| return protocol::Response::Success(); |
| } |
| |
| protocol::Response TargetHandler::CreateTarget( |
| const std::string& url, |
| std::optional<int> left, |
| std::optional<int> top, |
| std::optional<int> width, |
| std::optional<int> height, |
| std::optional<std::string> window_state, |
| std::optional<std::string> browser_context_id, |
| std::optional<bool> enable_begin_frame_control, |
| std::optional<bool> new_window, |
| std::optional<bool> background, |
| std::optional<bool> for_tab, |
| std::optional<bool> hidden, |
| std::string* out_target_id) { |
| if (hidden.value_or(false)) { |
| // Rely on web contents implementation. |
| return protocol::Response::FallThrough(); |
| } |
| Profile* profile = nullptr; |
| if (browser_context_id.has_value()) { |
| std::string profile_id = browser_context_id.value(); |
| profile = |
| DevToolsBrowserContextManager::GetInstance().GetProfileById(profile_id); |
| if (!profile) { |
| return protocol::Response::ServerError( |
| "Failed to find browser context with id " + profile_id); |
| } |
| } else { |
| profile = ProfileManager::GetLastUsedProfile(); |
| DCHECK(profile); |
| } |
| |
| bool create_new_window = new_window.value_or(false); |
| bool create_in_background = background.value_or(false); |
| Browser* target_browser = nullptr; |
| |
| // Must find target_browser if new_window not explicitly true. |
| if (!create_new_window) { |
| // Find a browser to open a new tab. |
| // We shouldn't use browser that is scheduled to close. |
| for (Browser* browser : *BrowserList::GetInstance()) { |
| if (browser->profile() == profile && |
| !browser->IsAttemptingToCloseBrowser()) { |
| target_browser = browser; |
| break; |
| } |
| } |
| } |
| |
| bool explicit_old_window = !new_window.value_or(true); |
| if (explicit_old_window && !target_browser) { |
| return protocol::Response::ServerError( |
| "Failed to open new tab - " |
| "no browser is open"); |
| } |
| |
| GURL gurl(url); |
| if (gurl.is_empty()) { |
| gurl = GURL(url::kAboutBlankURL); |
| } |
| |
| if (!is_trusted_ && gurl.SchemeIs(content::kChromeUIUntrustedScheme)) { |
| return protocol::Response::ServerError( |
| "Refusing to create a target with the specified URL"); |
| } |
| |
| if (!may_read_local_files_ && gurl.SchemeIsFile()) { |
| return protocol::Response::ServerError( |
| "Creating a target with a local URL is not allowed"); |
| } |
| |
| create_new_window = !target_browser; |
| |
| const bool set_window_position = left || top || width || height; |
| if (set_window_position && !create_new_window) { |
| return protocol::Response::InvalidParams( |
| "Target position can only be set for new windows"); |
| } |
| |
| static std::string_view kActionableWindowStates[] = { |
| protocol::Target::WindowStateEnum::Minimized, |
| protocol::Target::WindowStateEnum::Maximized, |
| protocol::Target::WindowStateEnum::Fullscreen, |
| }; |
| |
| bool set_window_state = !!window_state; |
| if (set_window_state) { |
| if (!create_new_window) { |
| return protocol::Response::InvalidParams( |
| "Target window state can only be set for new windows"); |
| } |
| if (*window_state == protocol::Target::WindowStateEnum::Normal) { |
| set_window_state = false; |
| } else if (std::ranges::find(kActionableWindowStates, *window_state) == |
| std::end(kActionableWindowStates)) { |
| return protocol::Response::InvalidParams("Invalid target window state: " + |
| *window_state); |
| } |
| } |
| |
| NavigateParams params = CreateNavigateParams( |
| profile, gurl, ui::PAGE_TRANSITION_AUTO_TOPLEVEL, create_new_window, |
| create_in_background, target_browser); |
| |
| Navigate(¶ms); |
| if (!params.navigated_or_inserted_contents) { |
| return protocol::Response::ServerError("Failed to open a new tab"); |
| } |
| |
| if (set_window_position) { |
| BrowserWindow* browser_window = params.browser->window(); |
| CHECK(browser_window); |
| gfx::Rect bounds = browser_window->GetBounds(); |
| if (left) { |
| bounds.set_x(left.value()); |
| } |
| if (top) { |
| bounds.set_y(top.value()); |
| } |
| if (width) { |
| bounds.set_width(width.value()); |
| } |
| if (height) { |
| bounds.set_height(height.value()); |
| } |
| browser_window->SetBounds(bounds); |
| } |
| |
| if (set_window_state) { |
| if (*window_state == protocol::Target::WindowStateEnum::Minimized) { |
| params.browser->window()->Minimize(); |
| } else if (*window_state == protocol::Target::WindowStateEnum::Maximized) { |
| params.browser->window()->Maximize(); |
| } else if (*window_state == protocol::Target::WindowStateEnum::Fullscreen) { |
| params.browser->GetFeatures() |
| .exclusive_access_manager() |
| ->fullscreen_controller() |
| ->ToggleBrowserFullscreenMode(/*user_initiated=*/false); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| if (!create_in_background) { |
| params.navigated_or_inserted_contents->Focus(); |
| } |
| |
| if (for_tab.value_or(false)) { |
| *out_target_id = content::DevToolsAgentHost::GetOrCreateForTab( |
| params.navigated_or_inserted_contents) |
| ->GetId(); |
| } else { |
| *out_target_id = content::DevToolsAgentHost::GetOrCreateFor( |
| params.navigated_or_inserted_contents) |
| ->GetId(); |
| } |
| return protocol::Response::Success(); |
| } |