| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/devtools/chrome_devtools_manager_delegate.h" |
| |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/devtools/device/android_device_manager.h" |
| #include "chrome/browser/devtools/device/tcp_device_provider.h" |
| #include "chrome/browser/devtools/devtools_network_protocol_handler.h" |
| #include "chrome/browser/devtools/devtools_protocol_constants.h" |
| #include "chrome/browser/devtools/devtools_window.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" |
| #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/grit/browser_resources.h" |
| #include "components/guest_view/browser/guest_view_base.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/extension_host.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/process_manager.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| using content::DevToolsAgentHost; |
| |
| char ChromeDevToolsManagerDelegate::kTypeApp[] = "app"; |
| char ChromeDevToolsManagerDelegate::kTypeBackgroundPage[] = "background_page"; |
| char ChromeDevToolsManagerDelegate::kTypeWebView[] = "webview"; |
| |
| namespace { |
| |
| char kLocationsParam[] = "locations"; |
| char kHostParam[] = "host"; |
| char kPortParam[] = "port"; |
| |
| BrowserWindow* GetBrowserWindow(int window_id) { |
| for (auto* b : *BrowserList::GetInstance()) { |
| if (b->session_id().id() == window_id) |
| return b->window(); |
| } |
| return nullptr; |
| } |
| |
| // Get the bounds and state of the browser window. The bounds is for the |
| // restored window when the window is minimized. Otherwise, it is for the actual |
| // window. |
| std::unique_ptr<base::DictionaryValue> GetBounds(BrowserWindow* window) { |
| gfx::Rect bounds; |
| if (window->IsMinimized()) |
| bounds = window->GetRestoredBounds(); |
| else |
| bounds = window->GetBounds(); |
| |
| auto bounds_object = base::MakeUnique<base::DictionaryValue>(); |
| |
| bounds_object->SetInteger("left", bounds.x()); |
| bounds_object->SetInteger("top", bounds.y()); |
| bounds_object->SetInteger("width", bounds.width()); |
| bounds_object->SetInteger("height", bounds.height()); |
| |
| std::string window_state = "normal"; |
| if (window->IsMinimized()) |
| window_state = "minimized"; |
| if (window->IsMaximized()) |
| window_state = "maximized"; |
| if (window->IsFullscreen()) |
| window_state = "fullscreen"; |
| bounds_object->SetString("windowState", window_state); |
| |
| return bounds_object; |
| } |
| |
| bool GetExtensionInfo(content::RenderFrameHost* host, |
| std::string* name, |
| std::string* type) { |
| content::WebContents* wc = content::WebContents::FromRenderFrameHost(host); |
| if (!wc) |
| return false; |
| Profile* profile = Profile::FromBrowserContext(wc->GetBrowserContext()); |
| if (!profile) |
| return false; |
| const extensions::Extension* extension = |
| extensions::ProcessManager::Get(profile)->GetExtensionForRenderFrameHost( |
| host); |
| if (!extension) |
| return false; |
| extensions::ExtensionHost* extension_host = |
| extensions::ProcessManager::Get(profile)->GetBackgroundHostForExtension( |
| extension->id()); |
| if (extension_host && extension_host->host_contents() == wc) { |
| *name = extension->name(); |
| *type = ChromeDevToolsManagerDelegate::kTypeBackgroundPage; |
| return true; |
| } else if (extension->is_hosted_app() || |
| extension->is_legacy_packaged_app() || |
| extension->is_platform_app()) { |
| *name = extension->name(); |
| *type = ChromeDevToolsManagerDelegate::kTypeApp; |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<base::DictionaryValue> |
| ChromeDevToolsManagerDelegate::GetWindowForTarget( |
| int id, |
| base::DictionaryValue* params) { |
| std::string target_id; |
| if (!params->GetString("targetId", &target_id)) |
| return DevToolsProtocol::CreateInvalidParamsResponse(id, "targetId"); |
| |
| Browser* browser = nullptr; |
| scoped_refptr<DevToolsAgentHost> host = |
| DevToolsAgentHost::GetForId(target_id); |
| if (!host) |
| return DevToolsProtocol::CreateErrorResponse(id, "No target with given id"); |
| content::WebContents* web_contents = host->GetWebContents(); |
| if (!web_contents) { |
| return DevToolsProtocol::CreateErrorResponse( |
| id, "No web contents in the target"); |
| } |
| for (auto* b : *BrowserList::GetInstance()) { |
| int tab_index = b->tab_strip_model()->GetIndexOfWebContents(web_contents); |
| if (tab_index != TabStripModel::kNoTab) |
| browser = b; |
| } |
| if (!browser) { |
| return DevToolsProtocol::CreateErrorResponse(id, |
| "Browser window not found"); |
| } |
| |
| auto result = base::MakeUnique<base::DictionaryValue>(); |
| result->SetInteger("windowId", browser->session_id().id()); |
| result->Set("bounds", GetBounds(browser->window())); |
| return DevToolsProtocol::CreateSuccessResponse(id, std::move(result)); |
| } |
| |
| // static |
| std::unique_ptr<base::DictionaryValue> |
| ChromeDevToolsManagerDelegate::GetWindowBounds(int id, |
| base::DictionaryValue* params) { |
| int window_id; |
| if (!params->GetInteger("windowId", &window_id)) |
| return DevToolsProtocol::CreateInvalidParamsResponse(id, "windowId"); |
| BrowserWindow* window = GetBrowserWindow(window_id); |
| if (!window) { |
| return DevToolsProtocol::CreateErrorResponse(id, |
| "Browser window not found"); |
| } |
| |
| auto result = base::MakeUnique<base::DictionaryValue>(); |
| result->Set("bounds", GetBounds(window)); |
| return DevToolsProtocol::CreateSuccessResponse(id, std::move(result)); |
| } |
| |
| // static |
| std::unique_ptr<base::DictionaryValue> |
| ChromeDevToolsManagerDelegate::SetWindowBounds(int id, |
| base::DictionaryValue* params) { |
| int window_id; |
| if (!params->GetInteger("windowId", &window_id)) |
| return DevToolsProtocol::CreateInvalidParamsResponse(id, "windowId"); |
| BrowserWindow* window = GetBrowserWindow(window_id); |
| if (!window) { |
| return DevToolsProtocol::CreateErrorResponse(id, |
| "Browser window not found"); |
| } |
| |
| const base::Value* value = nullptr; |
| const base::DictionaryValue* bounds_dict = nullptr; |
| if (!params->Get("bounds", &value) || !value->GetAsDictionary(&bounds_dict)) |
| return DevToolsProtocol::CreateInvalidParamsResponse(id, "bounds"); |
| |
| std::string window_state; |
| if (!bounds_dict->GetString("windowState", &window_state)) |
| window_state = "normal"; |
| else if (window_state != "normal" && window_state != "minimized" && |
| window_state != "maximized" && window_state != "fullscreen") |
| return DevToolsProtocol::CreateInvalidParamsResponse(id, "windowState"); |
| |
| // Compute updated bounds when window state is normal. |
| bool set_bounds = false; |
| gfx::Rect bounds = window->GetBounds(); |
| int left, top, width, height; |
| if (bounds_dict->GetInteger("left", &left)) { |
| bounds.set_x(left); |
| set_bounds = true; |
| } |
| if (bounds_dict->GetInteger("top", &top)) { |
| bounds.set_y(top); |
| set_bounds = true; |
| } |
| if (bounds_dict->GetInteger("width", &width)) { |
| if (width < 0) |
| return DevToolsProtocol::CreateInvalidParamsResponse(id, "width"); |
| bounds.set_width(width); |
| set_bounds = true; |
| } |
| if (bounds_dict->GetInteger("height", &height)) { |
| if (height < 0) |
| return DevToolsProtocol::CreateInvalidParamsResponse(id, "height"); |
| bounds.set_height(height); |
| set_bounds = true; |
| } |
| |
| if (set_bounds && window_state != "normal") { |
| return DevToolsProtocol::CreateErrorResponse( |
| id, |
| "The 'minimized', 'maximized' and 'fullscreen' states cannot be " |
| "combined with 'left', 'top', 'width' or 'height'"); |
| } |
| |
| if (set_bounds && (window->IsMinimized() || window->IsMaximized() || |
| window->IsFullscreen())) { |
| return DevToolsProtocol::CreateErrorResponse( |
| id, |
| "To resize minimized/maximized/fullscreen window, restore it to normal " |
| "state first."); |
| } |
| |
| if (window_state == "fullscreen") { |
| if (window->IsMinimized()) { |
| return DevToolsProtocol::CreateErrorResponse(id, |
| "To make minimized window " |
| "fullscreen, restore it to " |
| "normal state first."); |
| } |
| window->GetExclusiveAccessContext()->EnterFullscreen( |
| GURL(), EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE); |
| } |
| |
| if (window_state == "maximized") { |
| if (window->IsMinimized() || window->IsFullscreen()) { |
| return DevToolsProtocol::CreateErrorResponse( |
| id, |
| "To maximize a minimized or fullscreen window, restore it to normal " |
| "state first."); |
| } |
| window->Maximize(); |
| } |
| |
| if (window_state == "minimized") { |
| if (window->IsFullscreen()) { |
| return DevToolsProtocol::CreateErrorResponse( |
| id, |
| "To minimize a fullscreen window, restore it to normal " |
| "state first."); |
| } |
| window->Minimize(); |
| } |
| |
| if (window_state == "normal") { |
| if (window->IsFullscreen()) { |
| window->GetExclusiveAccessContext()->ExitFullscreen(); |
| } else if (window->IsMinimized()) { |
| window->Show(); |
| } else if (window->IsMaximized()) { |
| window->Restore(); |
| } else if (set_bounds) { |
| window->SetBounds(bounds); |
| } |
| } |
| |
| return DevToolsProtocol::CreateSuccessResponse(id, nullptr); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> |
| ChromeDevToolsManagerDelegate::HandleBrowserCommand( |
| int id, |
| std::string method, |
| base::DictionaryValue* params) { |
| if (method == chrome::devtools::Browser::getWindowForTarget::kName) |
| return GetWindowForTarget(id, params); |
| if (method == chrome::devtools::Browser::getWindowBounds::kName) |
| return GetWindowBounds(id, params); |
| if (method == chrome::devtools::Browser::setWindowBounds::kName) |
| return SetWindowBounds(id, params); |
| return nullptr; |
| } |
| |
| class ChromeDevToolsManagerDelegate::HostData { |
| public: |
| HostData() {} |
| ~HostData() {} |
| |
| RemoteLocations& remote_locations() { return remote_locations_; } |
| |
| void set_remote_locations(RemoteLocations& locations) { |
| remote_locations_.swap(locations); |
| } |
| |
| private: |
| RemoteLocations remote_locations_; |
| }; |
| |
| ChromeDevToolsManagerDelegate::ChromeDevToolsManagerDelegate() |
| : network_protocol_handler_(new DevToolsNetworkProtocolHandler()) { |
| content::DevToolsAgentHost::AddObserver(this); |
| } |
| |
| ChromeDevToolsManagerDelegate::~ChromeDevToolsManagerDelegate() { |
| content::DevToolsAgentHost::RemoveObserver(this); |
| } |
| |
| void ChromeDevToolsManagerDelegate::Inspect( |
| content::DevToolsAgentHost* agent_host) { |
| DevToolsWindow::OpenDevToolsWindow(agent_host, nullptr); |
| } |
| |
| base::DictionaryValue* ChromeDevToolsManagerDelegate::HandleCommand( |
| DevToolsAgentHost* agent_host, |
| base::DictionaryValue* command_dict) { |
| |
| int id = 0; |
| std::string method; |
| base::DictionaryValue* params = nullptr; |
| if (!DevToolsProtocol::ParseCommand(command_dict, &id, &method, ¶ms)) |
| return nullptr; |
| |
| if (agent_host->GetType() == DevToolsAgentHost::kTypeBrowser && |
| method.find("Browser.") == 0) |
| return HandleBrowserCommand(id, method, params).release(); |
| |
| if (method == chrome::devtools::Target::setRemoteLocations::kName) |
| return SetRemoteLocations(agent_host, id, params).release(); |
| |
| return network_protocol_handler_->HandleCommand(agent_host, command_dict); |
| } |
| |
| std::string ChromeDevToolsManagerDelegate::GetTargetType( |
| content::RenderFrameHost* host) { |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderFrameHost(host); |
| |
| guest_view::GuestViewBase* guest = |
| guest_view::GuestViewBase::FromWebContents(web_contents); |
| content::WebContents* guest_contents = |
| guest ? guest->embedder_web_contents() : nullptr; |
| if (guest_contents) |
| return kTypeWebView; |
| |
| if (host->GetParent()) |
| return DevToolsAgentHost::kTypeFrame; |
| |
| for (TabContentsIterator it; !it.done(); it.Next()) { |
| if (*it == web_contents) |
| return DevToolsAgentHost::kTypePage; |
| } |
| |
| std::string extension_name; |
| std::string extension_type; |
| if (!GetExtensionInfo(host, &extension_name, &extension_type)) |
| return DevToolsAgentHost::kTypeOther; |
| return extension_type; |
| } |
| |
| std::string ChromeDevToolsManagerDelegate::GetTargetTitle( |
| content::RenderFrameHost* host) { |
| std::string extension_name; |
| std::string extension_type; |
| if (!GetExtensionInfo(host, &extension_name, &extension_type)) |
| return std::string(); |
| return extension_name; |
| } |
| |
| scoped_refptr<DevToolsAgentHost> |
| ChromeDevToolsManagerDelegate::CreateNewTarget(const GURL& url) { |
| chrome::NavigateParams params(ProfileManager::GetLastUsedProfile(), |
| url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL); |
| params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| chrome::Navigate(¶ms); |
| if (!params.target_contents) |
| return nullptr; |
| return DevToolsAgentHost::GetOrCreateFor(params.target_contents); |
| } |
| |
| std::string ChromeDevToolsManagerDelegate::GetDiscoveryPageHTML() { |
| return ResourceBundle::GetSharedInstance().GetRawDataResource( |
| IDR_DEVTOOLS_DISCOVERY_PAGE_HTML).as_string(); |
| } |
| |
| std::string ChromeDevToolsManagerDelegate::GetFrontendResource( |
| const std::string& path) { |
| return content::DevToolsFrontendHost::GetFrontendResource(path).as_string(); |
| } |
| |
| void ChromeDevToolsManagerDelegate::DevToolsAgentHostAttached( |
| content::DevToolsAgentHost* agent_host) { |
| network_protocol_handler_->DevToolsAgentStateChanged(agent_host, true); |
| |
| DCHECK(host_data_.find(agent_host) == host_data_.end()); |
| host_data_[agent_host].reset(new ChromeDevToolsManagerDelegate::HostData()); |
| } |
| |
| void ChromeDevToolsManagerDelegate::DevToolsAgentHostDetached( |
| content::DevToolsAgentHost* agent_host) { |
| network_protocol_handler_->DevToolsAgentStateChanged(agent_host, false); |
| // This class is created lazily, so it may not know about some attached hosts. |
| if (host_data_.find(agent_host) != host_data_.end()) { |
| host_data_.erase(agent_host); |
| UpdateDeviceDiscovery(); |
| } |
| } |
| |
| void ChromeDevToolsManagerDelegate::DevicesAvailable( |
| const DevToolsDeviceDiscovery::CompleteDevices& devices) { |
| DevToolsAgentHost::List remote_targets; |
| for (const auto& complete : devices) { |
| for (const auto& browser : complete.second->browsers()) { |
| for (const auto& page : browser->pages()) |
| remote_targets.push_back(page->CreateTarget()); |
| } |
| } |
| remote_agent_hosts_.swap(remote_targets); |
| } |
| |
| void ChromeDevToolsManagerDelegate::UpdateDeviceDiscovery() { |
| RemoteLocations remote_locations; |
| for (const auto& pair : host_data_) { |
| RemoteLocations& locations = pair.second->remote_locations(); |
| remote_locations.insert(locations.begin(), locations.end()); |
| } |
| |
| bool equals = remote_locations.size() == remote_locations_.size(); |
| if (equals) { |
| RemoteLocations::iterator it1 = remote_locations.begin(); |
| RemoteLocations::iterator it2 = remote_locations_.begin(); |
| while (it1 != remote_locations.end()) { |
| DCHECK(it2 != remote_locations_.end()); |
| if (!(*it1).Equals(*it2)) |
| equals = false; |
| ++it1; |
| ++it2; |
| } |
| DCHECK(it2 == remote_locations_.end()); |
| } |
| |
| if (equals) |
| return; |
| |
| if (remote_locations.empty()) { |
| device_discovery_.reset(); |
| remote_agent_hosts_.clear(); |
| } else { |
| if (!device_manager_) |
| device_manager_ = AndroidDeviceManager::Create(); |
| |
| AndroidDeviceManager::DeviceProviders providers; |
| providers.push_back(new TCPDeviceProvider(remote_locations)); |
| device_manager_->SetDeviceProviders(providers); |
| |
| device_discovery_.reset(new DevToolsDeviceDiscovery(device_manager_.get(), |
| base::Bind(&ChromeDevToolsManagerDelegate::DevicesAvailable, |
| base::Unretained(this)))); |
| } |
| remote_locations_.swap(remote_locations); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> |
| ChromeDevToolsManagerDelegate::SetRemoteLocations( |
| content::DevToolsAgentHost* agent_host, |
| int command_id, |
| base::DictionaryValue* params) { |
| // Could have been created late. |
| if (host_data_.find(agent_host) == host_data_.end()) |
| DevToolsAgentHostAttached(agent_host); |
| |
| std::set<net::HostPortPair> tcp_locations; |
| base::ListValue* locations; |
| if (!params->GetList(kLocationsParam, &locations)) |
| return DevToolsProtocol::CreateInvalidParamsResponse(command_id, |
| kLocationsParam); |
| for (const auto& item : *locations) { |
| if (!item.IsType(base::Value::Type::DICTIONARY)) { |
| return DevToolsProtocol::CreateInvalidParamsResponse(command_id, |
| kLocationsParam); |
| } |
| const base::DictionaryValue* dictionary = |
| static_cast<const base::DictionaryValue*>(&item); |
| std::string host; |
| if (!dictionary->GetStringWithoutPathExpansion(kHostParam, &host)) { |
| return DevToolsProtocol::CreateInvalidParamsResponse(command_id, |
| kLocationsParam); |
| } |
| int port = 0; |
| if (!dictionary->GetIntegerWithoutPathExpansion(kPortParam, &port)) { |
| return DevToolsProtocol::CreateInvalidParamsResponse(command_id, |
| kLocationsParam); |
| } |
| tcp_locations.insert(net::HostPortPair(host, port)); |
| } |
| |
| host_data_[agent_host]->set_remote_locations(tcp_locations); |
| UpdateDeviceDiscovery(); |
| |
| return DevToolsProtocol::CreateSuccessResponse(command_id, nullptr); |
| } |