diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-09-12 10:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-09-12 08:34:13 +0000 |
commit | 53d399fe6415a96ea6986ec0d402a9c07da72453 (patch) | |
tree | ce7dbd5d170326a7d1c5f69f5dcd841c178e0dc0 /chromium/chrome/browser/devtools | |
parent | a3ee7849e3b0ad3d5f9595fa1cfd694c22dcee2a (diff) |
BASELINE: Update Chromium to 60.0.3112.116 and Ninja to 1.8.2
Also adds a few devtools and webui files needed for new features.
Change-Id: I431976cc9f4c209d062a925ab6a5d63ec61abcfe
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/chrome/browser/devtools')
93 files changed, 20942 insertions, 0 deletions
diff --git a/chromium/chrome/browser/devtools/OWNERS b/chromium/chrome/browser/devtools/OWNERS new file mode 100644 index 00000000000..8d426acaa95 --- /dev/null +++ b/chromium/chrome/browser/devtools/OWNERS @@ -0,0 +1,7 @@ +dgozman@chromium.org +pfeldman@chromium.org + +per-file devtools_embedder_message_dispatcher.*=set noparent +per-file devtools_embedder_message_dispatcher.*=file://ipc/SECURITY_OWNERS + +# COMPONENT: Platform>DevTools diff --git a/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc new file mode 100644 index 00000000000..75b04a7bff9 --- /dev/null +++ b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc @@ -0,0 +1,494 @@ +// 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"; + +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::WebContents* wc, + std::string* name, + std::string* type) { + Profile* profile = Profile::FromBrowserContext(wc->GetBrowserContext()); + if (!profile) + return false; + const extensions::Extension* extension = + extensions::ProcessManager::Get(profile)->GetExtensionForWebContents(wc); + 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::WebContents* web_contents) { + for (TabContentsIterator it; !it.done(); it.Next()) { + if (*it == web_contents) + return DevToolsAgentHost::kTypePage; + } + + std::string extension_name; + std::string extension_type; + if (!GetExtensionInfo(web_contents, &extension_name, &extension_type)) + return DevToolsAgentHost::kTypeOther; + return extension_type; +} + +std::string ChromeDevToolsManagerDelegate::GetTargetTitle( + content::WebContents* web_contents) { + std::string extension_name; + std::string extension_type; + if (!GetExtensionInfo(web_contents, &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); +} diff --git a/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h new file mode 100644 index 00000000000..3042a2216a0 --- /dev/null +++ b/chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h @@ -0,0 +1,89 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_CHROME_DEVTOOLS_MANAGER_DELEGATE_H_ +#define CHROME_BROWSER_DEVTOOLS_CHROME_DEVTOOLS_MANAGER_DELEGATE_H_ + +#include <map> +#include <memory> +#include <set> +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "chrome/browser/devtools/device/devtools_device_discovery.h" +#include "content/public/browser/devtools_agent_host_observer.h" +#include "content/public/browser/devtools_manager_delegate.h" +#include "net/base/host_port_pair.h" + +class DevToolsNetworkProtocolHandler; + +class ChromeDevToolsManagerDelegate : + public content::DevToolsManagerDelegate, + public content::DevToolsAgentHostObserver { + public: + static char kTypeApp[]; + static char kTypeBackgroundPage[]; + + ChromeDevToolsManagerDelegate(); + ~ChromeDevToolsManagerDelegate() override; + + private: + class HostData; + friend class DevToolsManagerDelegateTest; + using RemoteLocations = std::set<net::HostPortPair>; + + // content::DevToolsManagerDelegate implementation. + void Inspect(content::DevToolsAgentHost* agent_host) override; + base::DictionaryValue* HandleCommand( + content::DevToolsAgentHost* agent_host, + base::DictionaryValue* command_dict) override; + std::string GetTargetType(content::WebContents* web_contents) override; + std::string GetTargetTitle(content::WebContents* web_contents) override; + scoped_refptr<content::DevToolsAgentHost> CreateNewTarget( + const GURL& url) override; + std::string GetDiscoveryPageHTML() override; + std::string GetFrontendResource(const std::string& path) override; + + // content::DevToolsAgentHostObserver overrides. + void DevToolsAgentHostAttached( + content::DevToolsAgentHost* agent_host) override; + void DevToolsAgentHostDetached( + content::DevToolsAgentHost* agent_host) override; + + void UpdateDeviceDiscovery(); + void DevicesAvailable( + const DevToolsDeviceDiscovery::CompleteDevices& devices); + + std::unique_ptr<base::DictionaryValue> SetRemoteLocations( + content::DevToolsAgentHost* agent_host, + int command_id, + base::DictionaryValue* params); + + std::unique_ptr<base::DictionaryValue> HandleBrowserCommand( + int id, + std::string method, + base::DictionaryValue* params); + static std::unique_ptr<base::DictionaryValue> GetWindowForTarget( + int id, + base::DictionaryValue* params); + static std::unique_ptr<base::DictionaryValue> GetWindowBounds( + int id, + base::DictionaryValue* params); + static std::unique_ptr<base::DictionaryValue> SetWindowBounds( + int id, + base::DictionaryValue* params); + + std::unique_ptr<DevToolsNetworkProtocolHandler> network_protocol_handler_; + std::map<content::DevToolsAgentHost*, std::unique_ptr<HostData>> host_data_; + + std::unique_ptr<AndroidDeviceManager> device_manager_; + std::unique_ptr<DevToolsDeviceDiscovery> device_discovery_; + content::DevToolsAgentHost::List remote_agent_hosts_; + RemoteLocations remote_locations_; + + DISALLOW_COPY_AND_ASSIGN(ChromeDevToolsManagerDelegate); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_CHROME_DEVTOOLS_MANAGER_DELEGATE_H_ diff --git a/chromium/chrome/browser/devtools/device/adb/adb_client_socket.cc b/chromium/chrome/browser/devtools/device/adb/adb_client_socket.cc new file mode 100644 index 00000000000..3ba639762a9 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/adb/adb_client_socket.cc @@ -0,0 +1,304 @@ +// 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/device/adb/adb_client_socket.h" + +#include <stddef.h> + +#include <utility> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "net/base/ip_address.h" +#include "net/base/net_errors.h" +#include "net/log/net_log_source.h" +#include "net/socket/tcp_client_socket.h" + +namespace { + +const int kBufferSize = 16 * 1024; +const char kOkayResponse[] = "OKAY"; +const char kHostTransportCommand[] = "host:transport:%s"; +const char kLocalhost[] = "127.0.0.1"; + +std::string EncodeMessage(const std::string& message) { + static const char kHexChars[] = "0123456789ABCDEF"; + + size_t length = message.length(); + std::string result(4, '\0'); + char b = reinterpret_cast<const char*>(&length)[1]; + result[0] = kHexChars[(b >> 4) & 0xf]; + result[1] = kHexChars[b & 0xf]; + b = reinterpret_cast<const char*>(&length)[0]; + result[2] = kHexChars[(b >> 4) & 0xf]; + result[3] = kHexChars[b & 0xf]; + return result + message; +} + +class AdbTransportSocket : public AdbClientSocket { + public: + AdbTransportSocket(int port, + const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) + : AdbClientSocket(port), + serial_(serial), + socket_name_(socket_name), + callback_(callback) { + Connect(base::Bind(&AdbTransportSocket::OnConnected, + base::Unretained(this))); + } + + private: + ~AdbTransportSocket() {} + + void OnConnected(int result) { + if (!CheckNetResultOrDie(result)) + return; + SendCommand(base::StringPrintf(kHostTransportCommand, serial_.c_str()), + true, base::Bind(&AdbTransportSocket::SendLocalAbstract, + base::Unretained(this))); + } + + void SendLocalAbstract(int result, const std::string& response) { + if (!CheckNetResultOrDie(result)) + return; + SendCommand(socket_name_, true, + base::Bind(&AdbTransportSocket::OnSocketAvailable, + base::Unretained(this))); + } + + void OnSocketAvailable(int result, const std::string& response) { + if (!CheckNetResultOrDie(result)) + return; + callback_.Run(net::OK, std::move(socket_)); + delete this; + } + + bool CheckNetResultOrDie(int result) { + if (result >= 0) + return true; + callback_.Run(result, base::WrapUnique<net::StreamSocket>(NULL)); + delete this; + return false; + } + + std::string serial_; + std::string socket_name_; + SocketCallback callback_; +}; + +class AdbQuerySocket : AdbClientSocket { + public: + AdbQuerySocket(int port, + const std::string& query, + const CommandCallback& callback) + : AdbClientSocket(port), + current_query_(0), + callback_(callback) { + queries_ = base::SplitString(query, "|", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + if (queries_.empty()) { + CheckNetResultOrDie(net::ERR_INVALID_ARGUMENT); + return; + } + Connect(base::Bind(&AdbQuerySocket::SendNextQuery, + base::Unretained(this))); + } + + private: + ~AdbQuerySocket() { + } + + void SendNextQuery(int result) { + if (!CheckNetResultOrDie(result)) + return; + std::string query = queries_[current_query_]; + if (query.length() > 0xFFFF) { + CheckNetResultOrDie(net::ERR_MSG_TOO_BIG); + return; + } + bool is_void = current_query_ < queries_.size() - 1; + SendCommand(query, is_void, + base::Bind(&AdbQuerySocket::OnResponse, base::Unretained(this))); + } + + void OnResponse(int result, const std::string& response) { + if (++current_query_ < queries_.size()) { + SendNextQuery(net::OK); + } else { + callback_.Run(result, response); + delete this; + } + } + + bool CheckNetResultOrDie(int result) { + if (result >= 0) + return true; + callback_.Run(result, std::string()); + delete this; + return false; + } + + std::vector<std::string> queries_; + size_t current_query_; + CommandCallback callback_; +}; + +} // namespace + +// static +void AdbClientSocket::AdbQuery(int port, + const std::string& query, + const CommandCallback& callback) { + new AdbQuerySocket(port, query, callback); +} + +// static +void AdbClientSocket::TransportQuery(int port, + const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) { + new AdbTransportSocket(port, serial, socket_name, callback); +} + +AdbClientSocket::AdbClientSocket(int port) + : host_(kLocalhost), port_(port) { +} + +AdbClientSocket::~AdbClientSocket() { +} + +void AdbClientSocket::Connect(const net::CompletionCallback& callback) { + net::IPAddress ip_address; + if (!ip_address.AssignFromIPLiteral(host_)) { + callback.Run(net::ERR_FAILED); + return; + } + + net::AddressList address_list = + net::AddressList::CreateFromIPAddress(ip_address, port_); + socket_.reset(new net::TCPClientSocket(address_list, NULL, NULL, + net::NetLogSource())); + int result = socket_->Connect(callback); + if (result != net::ERR_IO_PENDING) + callback.Run(result); +} + +void AdbClientSocket::SendCommand(const std::string& command, + bool is_void, + const CommandCallback& callback) { + scoped_refptr<net::StringIOBuffer> request_buffer = + new net::StringIOBuffer(EncodeMessage(command)); + int result = socket_->Write(request_buffer.get(), + request_buffer->size(), + base::Bind(&AdbClientSocket::ReadResponse, + base::Unretained(this), + callback, + is_void)); + if (result != net::ERR_IO_PENDING) + ReadResponse(callback, is_void, result); +} + +void AdbClientSocket::ReadResponse(const CommandCallback& callback, + bool is_void, + int result) { + if (result < 0) { + callback.Run(result, "IO error"); + return; + } + scoped_refptr<net::IOBuffer> response_buffer = + new net::IOBuffer(kBufferSize); + result = socket_->Read(response_buffer.get(), + kBufferSize, + base::Bind(&AdbClientSocket::OnResponseHeader, + base::Unretained(this), + callback, + is_void, + response_buffer)); + if (result != net::ERR_IO_PENDING) + OnResponseHeader(callback, is_void, response_buffer, result); +} + +void AdbClientSocket::OnResponseHeader( + const CommandCallback& callback, + bool is_void, + scoped_refptr<net::IOBuffer> response_buffer, + int result) { + if (result <= 0) { + callback.Run(result == 0 ? net::ERR_CONNECTION_CLOSED : result, + "IO error"); + return; + } + + std::string data = std::string(response_buffer->data(), result); + if (result < 4) { + callback.Run(net::ERR_FAILED, "Response is too short: " + data); + return; + } + + std::string status = data.substr(0, 4); + if (status != kOkayResponse) { + callback.Run(net::ERR_FAILED, data); + return; + } + + // Trim OKAY. + data = data.substr(4); + if (!is_void) + OnResponseData(callback, data, response_buffer, -1, 0); + else + callback.Run(net::OK, data); +} + +void AdbClientSocket::OnResponseData( + const CommandCallback& callback, + const std::string& response, + scoped_refptr<net::IOBuffer> response_buffer, + int bytes_left, + int result) { + if (result < 0) { + callback.Run(result, "IO error"); + return; + } + + std::string new_response = response + + std::string(response_buffer->data(), result); + + if (bytes_left == -1) { + // First read the response header. + int payload_length = 0; + if (new_response.length() >= 4 && + base::HexStringToInt(new_response.substr(0, 4), &payload_length)) { + new_response = new_response.substr(4); + bytes_left = payload_length - new_response.size(); + } + } else { + bytes_left -= result; + } + + if (bytes_left == 0) { + callback.Run(net::OK, new_response); + return; + } + + // Read tail + result = socket_->Read(response_buffer.get(), + kBufferSize, + base::Bind(&AdbClientSocket::OnResponseData, + base::Unretained(this), + callback, + new_response, + response_buffer, + bytes_left)); + if (result > 0) + OnResponseData(callback, new_response, response_buffer, bytes_left, result); + else if (result != net::ERR_IO_PENDING) + callback.Run(net::OK, new_response); +} diff --git a/chromium/chrome/browser/devtools/device/adb/adb_client_socket.h b/chromium/chrome/browser/devtools/device/adb/adb_client_socket.h new file mode 100644 index 00000000000..f7578e71957 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/adb/adb_client_socket.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_ADB_CLIENT_SOCKET_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_ADB_CLIENT_SOCKET_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "net/base/io_buffer.h" +#include "net/socket/stream_socket.h" + +class AdbClientSocket { + public: + typedef base::Callback<void(int, const std::string&)> CommandCallback; + typedef base::Callback<void(int result, std::unique_ptr<net::StreamSocket>)> + SocketCallback; + + static void AdbQuery(int port, + const std::string& query, + const CommandCallback& callback); + + + static void TransportQuery(int port, + const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback); + + protected: + explicit AdbClientSocket(int port); + ~AdbClientSocket(); + + void Connect(const net::CompletionCallback& callback); + + void SendCommand(const std::string& command, + bool is_void, + const CommandCallback& callback); + + std::unique_ptr<net::StreamSocket> socket_; + + private: + void ReadResponse(const CommandCallback& callback, bool is_void, int result); + + void OnResponseHeader(const CommandCallback& callback, + bool is_void, + scoped_refptr<net::IOBuffer> response_buffer, + int result); + + void OnResponseData(const CommandCallback& callback, + const std::string& response, + scoped_refptr<net::IOBuffer> response_buffer, + int bytes_left, + int result); + + std::string host_; + int port_; + + DISALLOW_COPY_AND_ASSIGN(AdbClientSocket); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_ADB_CLIENT_SOCKET_H_ diff --git a/chromium/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc b/chromium/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc new file mode 100644 index 00000000000..3da5dc89d18 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc @@ -0,0 +1,159 @@ +// 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 "base/message_loop/message_loop.h" +#include "chrome/browser/devtools/device/adb/adb_device_provider.h" +#include "chrome/browser/devtools/device/adb/mock_adb_server.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_utils.h" + +using content::BrowserThread; + +static scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> +FindBrowserByDisplayName(DevToolsAndroidBridge::RemoteBrowsers browsers, + const std::string& name) { + for (DevToolsAndroidBridge::RemoteBrowsers::iterator it = browsers.begin(); + it != browsers.end(); ++it) + if ((*it)->display_name() == name) + return *it; + return NULL; +} + +class AdbClientSocketTest : public InProcessBrowserTest, + public DevToolsAndroidBridge::DeviceListListener { + + public: + void StartTest() { + Profile* profile = browser()->profile(); + android_bridge_ = DevToolsAndroidBridge::Factory::GetForProfile(profile); + AndroidDeviceManager::DeviceProviders device_providers; + device_providers.push_back(new AdbDeviceProvider()); + android_bridge_->set_device_providers_for_test(device_providers); + android_bridge_->AddDeviceListListener(this); + content::RunMessageLoop(); + } + + void DeviceListChanged( + const DevToolsAndroidBridge::RemoteDevices& devices) override { + devices_ = devices; + android_bridge_->RemoveDeviceListListener(this); + base::MessageLoop::current()->QuitWhenIdle(); + } + + void CheckDevices() { + ASSERT_EQ(2U, devices_.size()); + + scoped_refptr<DevToolsAndroidBridge::RemoteDevice> connected = + devices_[0]->is_connected() ? devices_[0] : devices_[1]; + + scoped_refptr<DevToolsAndroidBridge::RemoteDevice> not_connected = + devices_[0]->is_connected() ? devices_[1] : devices_[0]; + + ASSERT_TRUE(connected->is_connected()); + ASSERT_FALSE(not_connected->is_connected()); + + ASSERT_EQ(720, connected->screen_size().width()); + ASSERT_EQ(1184, connected->screen_size().height()); + + ASSERT_EQ("01498B321301A00A", connected->serial()); + ASSERT_EQ("Nexus 6", connected->model()); + + ASSERT_EQ("01498B2B0D01300E", not_connected->serial()); + ASSERT_EQ("Offline", not_connected->model()); + + const DevToolsAndroidBridge::RemoteBrowsers& browsers = + connected->browsers(); + ASSERT_EQ(4U, browsers.size()); + + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> chrome = + FindBrowserByDisplayName(browsers, "Chrome"); + ASSERT_TRUE(chrome.get()); + + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> chrome_beta = + FindBrowserByDisplayName(browsers, "Chrome Beta"); + ASSERT_TRUE(chrome_beta.get()); + + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> chromium = + FindBrowserByDisplayName(browsers, "Chromium"); + ASSERT_FALSE(chromium.get()); + + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> webview = + FindBrowserByDisplayName(browsers, "WebView in com.sample.feed"); + ASSERT_TRUE(webview.get()); + + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> noprocess = + FindBrowserByDisplayName(browsers, "Noprocess"); + ASSERT_TRUE(noprocess.get()); + + ASSERT_EQ("32.0.1679.0", chrome->version()); + ASSERT_EQ("31.0.1599.0", chrome_beta->version()); + ASSERT_EQ("4.0", webview->version()); + + ASSERT_EQ("Test User", chrome->user()); + ASSERT_EQ("Test User : 2", chrome_beta->user()); + ASSERT_EQ("Test User", webview->user()); + + DevToolsAndroidBridge::RemotePages chrome_pages = + chrome->pages(); + DevToolsAndroidBridge::RemotePages chrome_beta_pages = + chrome_beta->pages(); + DevToolsAndroidBridge::RemotePages webview_pages = + webview->pages(); + + ASSERT_EQ(1U, chrome_pages.size()); + ASSERT_EQ(1U, chrome_beta_pages.size()); + ASSERT_EQ(2U, webview_pages.size()); + + scoped_refptr<content::DevToolsAgentHost> chrome_target( + chrome_pages[0]->CreateTarget()); + scoped_refptr<content::DevToolsAgentHost> chrome_beta_target( + chrome_beta_pages[0]->CreateTarget()); + scoped_refptr<content::DevToolsAgentHost> webview_target_0( + webview_pages[0]->CreateTarget()); + scoped_refptr<content::DevToolsAgentHost> webview_target_1( + webview_pages[1]->CreateTarget()); + + // Check that we have non-empty description for webview pages. + ASSERT_EQ(0U, chrome_target->GetDescription().size()); + ASSERT_EQ(0U, chrome_beta_target->GetDescription().size()); + ASSERT_NE(0U, webview_target_0->GetDescription().size()); + ASSERT_NE(0U, webview_target_1->GetDescription().size()); + + ASSERT_EQ(GURL("http://www.chromium.org/"), + chrome_target->GetURL()); + ASSERT_EQ("The Chromium Projects", + chrome_target->GetTitle()); + } + + private: + DevToolsAndroidBridge* android_bridge_; + DevToolsAndroidBridge::RemoteDevices devices_; +}; + +// Flaky due to failure to bind a hardcoded port. crbug.com/566057 +IN_PROC_BROWSER_TEST_F(AdbClientSocketTest, DISABLED_TestFlushWithoutSize) { + StartMockAdbServer(FlushWithoutSize); + StartTest(); + CheckDevices(); + StopMockAdbServer(); +} + +// Flaky due to failure to bind a hardcoded port. crbug.com/566057 +IN_PROC_BROWSER_TEST_F(AdbClientSocketTest, DISABLED_TestFlushWithSize) { + StartMockAdbServer(FlushWithSize); + StartTest(); + CheckDevices(); + StopMockAdbServer(); +} + +// Flaky due to failure to bind a hardcoded port. crbug.com/566057 +IN_PROC_BROWSER_TEST_F(AdbClientSocketTest, DISABLED_TestFlushWithData) { + StartMockAdbServer(FlushWithData); + StartTest(); + CheckDevices(); + StopMockAdbServer(); +} diff --git a/chromium/chrome/browser/devtools/device/adb/adb_device_provider.cc b/chromium/chrome/browser/devtools/device/adb/adb_device_provider.cc new file mode 100644 index 00000000000..fda0496597c --- /dev/null +++ b/chromium/chrome/browser/devtools/device/adb/adb_device_provider.cc @@ -0,0 +1,69 @@ +// 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/device/adb/adb_device_provider.h" + +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" +#include "chrome/browser/devtools/device/adb/adb_client_socket.h" + +namespace { + +const char kHostDevicesCommand[] = "host:devices"; +const char kHostTransportCommand[] = "host:transport:%s|%s"; +const char kLocalAbstractCommand[] = "localabstract:%s"; + +const int kAdbPort = 5037; + +static void RunCommand(const std::string& serial, + const std::string& command, + const AdbDeviceProvider::CommandCallback& callback) { + std::string query = base::StringPrintf( + kHostTransportCommand, serial.c_str(), command.c_str()); + AdbClientSocket::AdbQuery(kAdbPort, query, callback); +} + +static void ReceivedAdbDevices( + const AdbDeviceProvider::SerialsCallback& callback, + int result_code, + const std::string& response) { + std::vector<std::string> result; + if (result_code < 0) { + callback.Run(result); + return; + } + for (const base::StringPiece& line : + base::SplitStringPiece(response, "\n", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY)) { + std::vector<base::StringPiece> tokens = + base::SplitStringPiece(line, "\t ", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + result.push_back(tokens[0].as_string()); + } + callback.Run(result); +} + +} // namespace + +void AdbDeviceProvider::QueryDevices(const SerialsCallback& callback) { + AdbClientSocket::AdbQuery( + kAdbPort, kHostDevicesCommand, base::Bind(&ReceivedAdbDevices, callback)); +} + +void AdbDeviceProvider::QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) { + AndroidDeviceManager::QueryDeviceInfo(base::Bind(&RunCommand, serial), + callback); +} + +void AdbDeviceProvider::OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) { + std::string request = + base::StringPrintf(kLocalAbstractCommand, socket_name.c_str()); + AdbClientSocket::TransportQuery(kAdbPort, serial, request, callback); +} + +AdbDeviceProvider::~AdbDeviceProvider() { +} diff --git a/chromium/chrome/browser/devtools/device/adb/adb_device_provider.h b/chromium/chrome/browser/devtools/device/adb/adb_device_provider.h new file mode 100644 index 00000000000..56b7fb96b72 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/adb/adb_device_provider.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_ADB_DEVICE_PROVIDER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_ADB_DEVICE_PROVIDER_H_ + +#include "chrome/browser/devtools/device/android_device_manager.h" + +class AdbDeviceProvider : public AndroidDeviceManager::DeviceProvider { + public: + void QueryDevices(const SerialsCallback& callback) override; + + void QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) override; + + void OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) override; + + private: + ~AdbDeviceProvider() override; +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_ADB_DEVICE_PROVIDER_H_ diff --git a/chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc b/chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc new file mode 100644 index 00000000000..88658a38902 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc @@ -0,0 +1,625 @@ +// 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/device/adb/mock_adb_server.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/non_thread_safe.h" +#include "base/threading/thread_task_runner_handle.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/test_utils.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_address.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/log/net_log_source.h" +#include "net/socket/stream_socket.h" +#include "net/socket/tcp_server_socket.h" + +using content::BrowserThread; + +namespace { + +const char kHostTransportPrefix[] = "host:transport:"; +const char kLocalAbstractPrefix[] = "localabstract:"; + +const char kShellPrefix[] = "shell:"; +const char kOpenedUnixSocketsCommand[] = "cat /proc/net/unix"; +const char kDeviceModelCommand[] = "getprop ro.product.model"; +const char kDumpsysCommand[] = "dumpsys window policy"; +const char kListProcessesCommand[] = "ps"; +const char kListUsersCommand[] = "dumpsys user"; +const char kEchoCommandPrefix[] = "echo "; + +const char kSerialOnline[] = "01498B321301A00A"; +const char kSerialOffline[] = "01498B2B0D01300E"; +const char kDeviceModel[] = "Nexus 6"; + +const char kJsonVersionPath[] = "/json/version"; +const char kJsonPath[] = "/json"; +const char kJsonListPath[] = "/json/list"; + +const char kHttpRequestTerminator[] = "\r\n\r\n"; + +const char kHttpResponse[] = + "HTTP/1.1 200 OK\r\n" + "Content-Length:%d\r\n" + "Content-Type:application/json; charset=UTF-8\r\n\r\n%s"; + +const char kSampleOpenedUnixSockets[] = + "Num RefCount Protocol Flags Type St Inode Path\n" + "00000000: 00000004 00000000" + " 00000000 0002 01 3328 /dev/socket/wpa_wlan0\n" + "00000000: 00000002 00000000" + " 00010000 0001 01 5394 /dev/socket/vold\n" + "00000000: 00000002 00000000" + " 00010000 0001 01 11810 @webview_devtools_remote_2425\n" + "00000000: 00000002 00000000" + " 00010000 0001 01 20893 @chrome_devtools_remote\n" + "00000000: 00000002 00000000" + " 00010000 0001 01 20894 @chrome_devtools_remote_1002\n" + "00000000: 00000002 00000000" + " 00010000 0001 01 20895 @noprocess_devtools_remote\n"; + +const char kSampleListProcesses[] = + "USER PID PPID VSIZE RSS WCHAN PC NAME\n" + "root 1 0 688 508 ffffffff 00000000 S /init\r\n" + "u0_a75 2425 123 933736 193024 ffffffff 00000000 S com.sample.feed\r\n" + "nfc 741 123 706448 26316 ffffffff 00000000 S com.android.nfc\r\n" + "u0_a76 1001 124 111111 222222 ffffffff 00000000 S com.android.chrome\r\n" + "u10_a77 1002 125 111111 222222 ffffffff 00000000 S com.chrome.beta\r\n" + "u0_a78 1003 126 111111 222222 ffffffff 00000000 S com.noprocess.app\r\n"; + +const char kSampleDumpsys[] = + "WINDOW MANAGER POLICY STATE (dumpsys window policy)\r\n" + " mSafeMode=false mSystemReady=true mSystemBooted=true\r\n" + " mStable=(0,50)-(720,1184)\r\n" // Only mStable parameter is parsed + " mForceStatusBar=false mForceStatusBarFromKeyguard=false\r\n"; + +const char kSampleListUsers[] = + "Users:\r\n" + " UserInfo{0:Test User:13} serialNo=0\r\n" + " Created: <unknown>\r\n" + " Last logged in: +17m18s871ms ago\r\n" + " UserInfo{10:Test User : 2:10} serialNo=10\r\n" + " Created: +3d4h35m1s139ms ago\r\n" + " Last logged in: +17m26s287ms ago\r\n"; + +char kSampleChromeVersion[] = "{\n" + " \"Browser\": \"Chrome/32.0.1679.0\",\n" + " \"Protocol-Version\": \"1.0\",\n" + " \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/32.0.1679.0 Safari/537.36\",\n" + " \"WebKit-Version\": \"537.36 (@160162)\"\n" + "}"; + +char kSampleChromeBetaVersion[] = "{\n" + " \"Browser\": \"Chrome/31.0.1599.0\",\n" + " \"Protocol-Version\": \"1.0\",\n" + " \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/32.0.1679.0 Safari/537.36\",\n" + " \"WebKit-Version\": \"537.36 (@160162)\"\n" + "}"; + +char kSampleWebViewVersion[] = "{\n" + " \"Browser\": \"Version/4.0\",\n" + " \"Protocol-Version\": \"1.0\",\n" + " \"User-Agent\": \"Mozilla/5.0 (Linux; Android 4.3; Build/KRS74B) " + "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Safari/537.36\",\n" + " \"WebKit-Version\": \"537.36 (@157588)\"\n" + "}"; + +char kSampleChromePages[] = "[ {\n" + " \"description\": \"\",\n" + " \"devtoolsFrontendUrl\": \"/devtools/devtools.html?" + "ws=/devtools/page/0\",\n" + " \"id\": \"0\",\n" + " \"title\": \"The Chromium Projects\",\n" + " \"type\": \"page\",\n" + " \"url\": \"http://www.chromium.org/\",\n" + " \"webSocketDebuggerUrl\": \"" + "ws:///devtools/page/0\"\n" + "} ]"; + +char kSampleChromeBetaPages[] = "[ {\n" + " \"description\": \"\",\n" + " \"devtoolsFrontendUrl\": \"/devtools/devtools.html?" + "ws=/devtools/page/0\",\n" + " \"id\": \"0\",\n" + " \"title\": \"The Chromium Projects\",\n" + " \"type\": \"page\",\n" + " \"url\": \"http://www.chromium.org/\",\n" + " \"webSocketDebuggerUrl\": \"" + "ws:///devtools/page/0\"\n" + "} ]"; + +char kSampleWebViewPages[] = "[ {\n" + " \"description\": \"{\\\"attached\\\":false,\\\"empty\\\":false," + "\\\"height\\\":1173,\\\"screenX\\\":0,\\\"screenY\\\":0," + "\\\"visible\\\":true,\\\"width\\\":800}\",\n" + " \"devtoolsFrontendUrl\": \"http://chrome-devtools-frontend.appspot.com/" + "serve_rev/@157588/devtools.html?ws=" + "/devtools/page/3E962D4D-B676-182D-3BE8-FAE7CE224DE7\",\n" + " \"faviconUrl\": \"http://chromium.org/favicon.ico\",\n" + " \"id\": \"3E962D4D-B676-182D-3BE8-FAE7CE224DE7\",\n" + " \"title\": \"Blink - The Chromium Projects\",\n" + " \"type\": \"page\",\n" + " \"url\": \"http://www.chromium.org/blink\",\n" + " \"webSocketDebuggerUrl\": \"ws:///devtools/" + "page/3E962D4D-B676-182D-3BE8-FAE7CE224DE7\"\n" + "}, {\n" + " \"description\": \"{\\\"attached\\\":true,\\\"empty\\\":true," + "\\\"screenX\\\":0,\\\"screenY\\\":33,\\\"visible\\\":false}\",\n" + " \"devtoolsFrontendUrl\": \"http://chrome-devtools-frontend.appspot.com/" + "serve_rev/@157588/devtools.html?ws=" + "/devtools/page/44681551-ADFD-2411-076B-3AB14C1C60E2\",\n" + " \"faviconUrl\": \"\",\n" + " \"id\": \"44681551-ADFD-2411-076B-3AB14C1C60E2\",\n" + " \"title\": \"More Activity\",\n" + " \"type\": \"page\",\n" + " \"url\": \"about:blank\",\n" + " \"webSocketDebuggerUrl\": \"ws:///devtools/page/" + "44681551-ADFD-2411-076B-3AB14C1C60E2\"\n" + "}]"; + +static const int kBufferSize = 16*1024; +static const uint16_t kAdbPort = 5037; + +static const int kAdbMessageHeaderSize = 4; + +class SimpleHttpServer : base::NonThreadSafe { + public: + class Parser { + public: + virtual int Consume(const char* data, int size) = 0; + virtual ~Parser() {} + }; + + using SendCallback = base::Callback<void(const std::string&)>; + using ParserFactory = base::Callback<Parser*(const SendCallback&)>; + + SimpleHttpServer(const ParserFactory& factory, net::IPEndPoint endpoint); + virtual ~SimpleHttpServer(); + + private: + class Connection : base::NonThreadSafe { + public: + Connection(net::StreamSocket* socket, const ParserFactory& factory); + virtual ~Connection(); + + private: + void Send(const std::string& message); + void ReadData(); + void OnDataRead(int count); + void WriteData(); + void OnDataWritten(int count); + + std::unique_ptr<net::StreamSocket> socket_; + std::unique_ptr<Parser> parser_; + scoped_refptr<net::GrowableIOBuffer> input_buffer_; + scoped_refptr<net::GrowableIOBuffer> output_buffer_; + int bytes_to_write_; + bool read_closed_; + base::WeakPtrFactory<Connection> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(Connection); + }; + + void OnConnect(); + void OnAccepted(int result); + + ParserFactory factory_; + std::unique_ptr<net::TCPServerSocket> socket_; + std::unique_ptr<net::StreamSocket> client_socket_; + base::WeakPtrFactory<SimpleHttpServer> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SimpleHttpServer); +}; + +SimpleHttpServer::SimpleHttpServer(const ParserFactory& factory, + net::IPEndPoint endpoint) + : factory_(factory), + socket_(new net::TCPServerSocket(nullptr, net::NetLogSource())), + weak_factory_(this) { + socket_->Listen(endpoint, 5); + OnConnect(); +} + +SimpleHttpServer::~SimpleHttpServer() { +} + +SimpleHttpServer::Connection::Connection(net::StreamSocket* socket, + const ParserFactory& factory) + : socket_(socket), + parser_(factory.Run(base::Bind(&Connection::Send, + base::Unretained(this)))), + input_buffer_(new net::GrowableIOBuffer()), + output_buffer_(new net::GrowableIOBuffer()), + bytes_to_write_(0), + read_closed_(false), + weak_factory_(this) { + input_buffer_->SetCapacity(kBufferSize); + ReadData(); +} + +SimpleHttpServer::Connection::~Connection() { +} + +void SimpleHttpServer::Connection::Send(const std::string& message) { + CHECK(CalledOnValidThread()); + const char* data = message.c_str(); + int size = message.size(); + + if ((output_buffer_->offset() + bytes_to_write_ + size) > + output_buffer_->capacity()) { + // If not enough space without relocation + if (output_buffer_->capacity() < (bytes_to_write_ + size)) { + // If even buffer is not enough + int new_size = std::max(output_buffer_->capacity() * 2, size * 2); + output_buffer_->SetCapacity(new_size); + } + memmove(output_buffer_->StartOfBuffer(), + output_buffer_->data(), + bytes_to_write_); + output_buffer_->set_offset(0); + } + + memcpy(output_buffer_->data() + bytes_to_write_, data, size); + bytes_to_write_ += size; + + if (bytes_to_write_ == size) + // If write loop wasn't yet started, then start it + WriteData(); +} + +void SimpleHttpServer::Connection::ReadData() { + CHECK(CalledOnValidThread()); + + if (input_buffer_->RemainingCapacity() == 0) + input_buffer_->SetCapacity(input_buffer_->capacity() * 2); + + int read_result = socket_->Read( + input_buffer_.get(), + input_buffer_->RemainingCapacity(), + base::Bind(&Connection::OnDataRead, base::Unretained(this))); + + if (read_result != net::ERR_IO_PENDING) + OnDataRead(read_result); +} + +void SimpleHttpServer::Connection::OnDataRead(int count) { + CHECK(CalledOnValidThread()); + if (count <= 0) { + if (bytes_to_write_ == 0) + delete this; + else + read_closed_ = true; + return; + } + input_buffer_->set_offset(input_buffer_->offset() + count); + int bytes_processed; + + do { + char* data = input_buffer_->StartOfBuffer(); + int data_size = input_buffer_->offset(); + bytes_processed = parser_->Consume(data, data_size); + + if (bytes_processed) { + memmove(data, data + bytes_processed, data_size - bytes_processed); + input_buffer_->set_offset(data_size - bytes_processed); + } + } while (bytes_processed); + // Posting to avoid deep recursion in case of synchronous IO + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&Connection::ReadData, weak_factory_.GetWeakPtr())); +} + +void SimpleHttpServer::Connection::WriteData() { + CHECK(CalledOnValidThread()); + CHECK_GE(output_buffer_->capacity(), + output_buffer_->offset() + bytes_to_write_) << "Overflow"; + + int write_result = socket_->Write( + output_buffer_.get(), + bytes_to_write_, + base::Bind(&Connection::OnDataWritten, base::Unretained(this))); + + if (write_result != net::ERR_IO_PENDING) + OnDataWritten(write_result); +} + +void SimpleHttpServer::Connection::OnDataWritten(int count) { + CHECK(CalledOnValidThread()); + if (count < 0) { + delete this; + return; + } + CHECK_GT(count, 0); + CHECK_GE(output_buffer_->capacity(), + output_buffer_->offset() + bytes_to_write_) << "Overflow"; + + bytes_to_write_ -= count; + output_buffer_->set_offset(output_buffer_->offset() + count); + + if (bytes_to_write_ != 0) + // Posting to avoid deep recursion in case of synchronous IO + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&Connection::WriteData, weak_factory_.GetWeakPtr())); + else if (read_closed_) + delete this; +} + +void SimpleHttpServer::OnConnect() { + CHECK(CalledOnValidThread()); + + int accept_result = socket_->Accept(&client_socket_, + base::Bind(&SimpleHttpServer::OnAccepted, base::Unretained(this))); + + if (accept_result != net::ERR_IO_PENDING) + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&SimpleHttpServer::OnAccepted, + weak_factory_.GetWeakPtr(), accept_result)); +} + +void SimpleHttpServer::OnAccepted(int result) { + CHECK(CalledOnValidThread()); + ASSERT_EQ(result, 0); // Fails if the socket is already in use. + new Connection(client_socket_.release(), factory_); + OnConnect(); +} + +class AdbParser : public SimpleHttpServer::Parser, + public base::NonThreadSafe, + public MockAndroidConnection::Delegate { + public: + static Parser* Create(FlushMode flush_mode, + const SimpleHttpServer::SendCallback& callback) { + return new AdbParser(flush_mode, callback); + } + + ~AdbParser() override {} + private: + explicit AdbParser(FlushMode flush_mode, + const SimpleHttpServer::SendCallback& callback) + : flush_mode_(flush_mode), + callback_(callback) { + } + + int Consume(const char* data, int size) override { + CHECK(CalledOnValidThread()); + if (mock_connection_) { + mock_connection_->Receive(std::string(data, size)); + return size; + } + if (size >= kAdbMessageHeaderSize) { + std::string message_header(data, kAdbMessageHeaderSize); + int message_size; + + EXPECT_TRUE(base::HexStringToInt(message_header, &message_size)); + + if (size >= message_size + kAdbMessageHeaderSize) { + std::string message_body(data + kAdbMessageHeaderSize, message_size); + ProcessCommand(message_body); + return kAdbMessageHeaderSize + message_size; + } + } + return 0; + } + + void ProcessCommand(const std::string& command) { + CHECK(CalledOnValidThread()); + if (command == "host:devices") { + SendSuccess(base::StringPrintf("%s\tdevice\n%s\toffline", + kSerialOnline, + kSerialOffline)); + } else if (base::StartsWith(command, kHostTransportPrefix, + base::CompareCase::SENSITIVE)) { + serial_ = command.substr(sizeof(kHostTransportPrefix) - 1); + SendSuccess(std::string()); + } else if (serial_ != kSerialOnline) { + Send("FAIL", "device offline (x)"); + } else { + mock_connection_ = + base::MakeUnique<MockAndroidConnection>(this, serial_, command); + } + } + + void SendSuccess(const std::string& response) override { + Send("OKAY", response); + } + + void SendRaw(const std::string& data) override { + callback_.Run(data); + } + + void Send(const std::string& status, const std::string& response) { + CHECK(CalledOnValidThread()); + CHECK_EQ(4U, status.size()); + std::string buffer = status; + if (flush_mode_ == FlushWithoutSize) { + callback_.Run(buffer); + buffer = std::string(); + } + + int size = response.size(); + if (size > 0) { + static const char kHexChars[] = "0123456789ABCDEF"; + for (int i = 3; i >= 0; i--) + buffer += kHexChars[ (size >> 4*i) & 0x0f ]; + if (flush_mode_ == FlushWithSize) { + callback_.Run(buffer); + buffer = std::string(); + } + buffer += response; + callback_.Run(buffer); + } else if (flush_mode_ != FlushWithoutSize) { + callback_.Run(buffer); + } + } + + FlushMode flush_mode_; + SimpleHttpServer::SendCallback callback_; + std::string serial_; + std::unique_ptr<MockAndroidConnection> mock_connection_; +}; + +static SimpleHttpServer* mock_adb_server_ = NULL; + +void StartMockAdbServerOnIOThread(FlushMode flush_mode) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + CHECK(mock_adb_server_ == NULL); + net::IPEndPoint endpoint(net::IPAddress(127, 0, 0, 1), kAdbPort); + mock_adb_server_ = new SimpleHttpServer( + base::Bind(&AdbParser::Create, flush_mode), endpoint); +} + +void StopMockAdbServerOnIOThread() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + CHECK(mock_adb_server_ != NULL); + delete mock_adb_server_; + mock_adb_server_ = NULL; +} + +} // namespace + +MockAndroidConnection::MockAndroidConnection( + Delegate* delegate, + const std::string& serial, + const std::string& command) + : delegate_(delegate), + serial_(serial) { + ProcessCommand(command); +} + +MockAndroidConnection::~MockAndroidConnection() { +} + +void MockAndroidConnection::Receive(const std::string& data) { + request_ += data; + size_t request_end_pos = data.find(kHttpRequestTerminator); + if (request_end_pos == std::string::npos) + return; + + std::string request(request_.substr(0, request_end_pos)); + std::vector<std::string> tokens = + base::SplitString(request, " ", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + CHECK_EQ(3U, tokens.size()); + CHECK_EQ("GET", tokens[0]); + CHECK_EQ("HTTP/1.1", tokens[2]); + + std::string path(tokens[1]); + if (path == kJsonPath) + path = kJsonListPath; + + if (socket_name_ == "chrome_devtools_remote") { + if (path == kJsonVersionPath) + SendHTTPResponse(kSampleChromeVersion); + else if (path == kJsonListPath) + SendHTTPResponse(kSampleChromePages); + else + NOTREACHED() << "Unknown command " << request; + } else if (socket_name_ == "chrome_devtools_remote_1002") { + if (path == kJsonVersionPath) + SendHTTPResponse(kSampleChromeBetaVersion); + else if (path == kJsonListPath) + SendHTTPResponse(kSampleChromeBetaPages); + else + NOTREACHED() << "Unknown command " << request; + } else if (base::StartsWith(socket_name_, "noprocess_devtools_remote", + base::CompareCase::SENSITIVE)) { + if (path == kJsonVersionPath) + SendHTTPResponse("{}"); + else if (path == kJsonListPath) + SendHTTPResponse("[]"); + else + NOTREACHED() << "Unknown command " << request; + } else if (socket_name_ == "webview_devtools_remote_2425") { + if (path == kJsonVersionPath) + SendHTTPResponse(kSampleWebViewVersion); + else if (path == kJsonListPath) + SendHTTPResponse(kSampleWebViewPages); + else + NOTREACHED() << "Unknown command " << request; + } else { + NOTREACHED() << "Unknown socket " << socket_name_; + } +} + +void MockAndroidConnection::ProcessCommand(const std::string& command) { + if (base::StartsWith(command, kLocalAbstractPrefix, + base::CompareCase::SENSITIVE)) { + socket_name_ = command.substr(sizeof(kLocalAbstractPrefix) - 1); + delegate_->SendSuccess(std::string()); + return; + } + + if (base::StartsWith(command, kShellPrefix, base::CompareCase::SENSITIVE)) { + std::string result; + for (const auto& line : + base::SplitString(command.substr(sizeof(kShellPrefix) - 1), "\n", + base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { + if (line == kDeviceModelCommand) { + result += kDeviceModel; + result += "\r\n"; + } else if (line == kOpenedUnixSocketsCommand) { + result += kSampleOpenedUnixSockets; + } else if (line == kDumpsysCommand) { + result += kSampleDumpsys; + } else if (line == kListProcessesCommand) { + result += kSampleListProcesses; + } else if (line == kListUsersCommand) { + result += kSampleListUsers; + } else if (base::StartsWith(line, kEchoCommandPrefix, + base::CompareCase::SENSITIVE)) { + result += line.substr(sizeof(kEchoCommandPrefix) - 1); + result += "\r\n"; + } else { + NOTREACHED() << "Unknown shell command - " << command; + } + } + delegate_->SendSuccess(result); + } else { + NOTREACHED() << "Unknown command - " << command; + } + delegate_->Close(); +} + +void MockAndroidConnection::SendHTTPResponse(const std::string& body) { + std::string response_data(base::StringPrintf(kHttpResponse, + static_cast<int>(body.size()), + body.c_str())); + delegate_->SendRaw(response_data); +} + +void StartMockAdbServer(FlushMode flush_mode) { + BrowserThread::PostTaskAndReply( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&StartMockAdbServerOnIOThread, flush_mode), + base::MessageLoop::QuitWhenIdleClosure()); + content::RunMessageLoop(); +} + +void StopMockAdbServer() { + BrowserThread::PostTaskAndReply(BrowserThread::IO, FROM_HERE, + base::BindOnce(&StopMockAdbServerOnIOThread), + base::MessageLoop::QuitWhenIdleClosure()); + content::RunMessageLoop(); +} + diff --git a/chromium/chrome/browser/devtools/device/adb/mock_adb_server.h b/chromium/chrome/browser/devtools/device/adb/mock_adb_server.h new file mode 100644 index 00000000000..d18c5a27b26 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/adb/mock_adb_server.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_MOCK_ADB_SERVER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_MOCK_ADB_SERVER_H_ + +#include <string> + +#include "base/callback.h" + +// Single instance mock ADB server for use in browser tests. Runs on IO thread. + +// These methods can be called from any thread. +enum FlushMode { + FlushWithoutSize, + FlushWithSize, + FlushWithData +}; + +void StartMockAdbServer(FlushMode flush_mode); +void StopMockAdbServer(); + +// Part of mock server independent of transport. +class MockAndroidConnection { + public: + class Delegate { + public: + virtual void SendSuccess(const std::string& message) {} + virtual void SendRaw(const std::string& data) {} + virtual void Close() {} + virtual ~Delegate() {} + }; + using Callback = base::Callback<void(const std::string&)>; + MockAndroidConnection(Delegate* delegate, + const std::string& serial, + const std::string& command); + virtual ~MockAndroidConnection(); + + void Receive(const std::string& data); + + private: + void ProcessCommand(const std::string& command); + void SendHTTPResponse(const std::string& body); + + Delegate* delegate_; + std::string serial_; + std::string socket_name_; + std::string request_; +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_ADB_MOCK_ADB_SERVER_H_ diff --git a/chromium/chrome/browser/devtools/device/android_device_info_query.cc b/chromium/chrome/browser/devtools/device/android_device_info_query.cc new file mode 100644 index 00000000000..e16000b47e9 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/android_device_info_query.cc @@ -0,0 +1,370 @@ +// 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 <stddef.h> + +#include "base/macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "chrome/browser/devtools/device/android_device_manager.h" + +namespace { + +#define SEPARATOR "======== output separator ========" + +const char kAllCommands[] = "shell:" + "getprop ro.product.model\n" + "echo " SEPARATOR "\n" + "dumpsys window policy\n" + "echo " SEPARATOR "\n" + "ps\n" + "echo " SEPARATOR "\n" + "cat /proc/net/unix\n" + "echo " SEPARATOR "\n" + "dumpsys user\n"; + +const char kSeparator[] = SEPARATOR; + +#undef SEPARATOR + +const char kScreenSizePrefix[] = "mStable="; +const char kUserInfoPrefix[] = "UserInfo{"; + +const char kDevToolsSocketSuffix[] = "_devtools_remote"; + +const char kChromeDefaultName[] = "Chrome"; +const char kChromeDefaultSocket[] = "chrome_devtools_remote"; + +const char kWebViewSocketPrefix[] = "webview_devtools_remote"; +const char kWebViewNameTemplate[] = "WebView in %s"; + +struct BrowserDescriptor { + const char* package; + const char* socket; + const char* display_name; +}; + +const BrowserDescriptor kBrowserDescriptors[] = { + { + "com.google.android.apps.chrome", + kChromeDefaultSocket, + "Chromium" + }, + { + "com.chrome.canary", + kChromeDefaultSocket, + "Chrome Canary" + }, + { + "com.chrome.dev", + kChromeDefaultSocket, + "Chrome Dev" + }, + { + "com.chrome.beta", + kChromeDefaultSocket, + "Chrome Beta" + }, + { + "com.android.chrome", + kChromeDefaultSocket, + kChromeDefaultName + }, + { + "com.chrome.work", + kChromeDefaultSocket, + "Work Chrome" + }, + { + "org.chromium.android_webview.shell", + "webview_devtools_remote", + "WebView Test Shell" + }, + { + "org.chromium.content_shell_apk", + "content_shell_devtools_remote", + "Content Shell" + }, + { + "org.chromium.chrome", + kChromeDefaultSocket, + "Chromium" + }, +}; + +const BrowserDescriptor* FindBrowserDescriptor(const std::string& package) { + size_t count = arraysize(kBrowserDescriptors); + for (size_t i = 0; i < count; i++) { + if (kBrowserDescriptors[i].package == package) + return &kBrowserDescriptors[i]; + } + return nullptr; +} + +bool BrowserCompare(const AndroidDeviceManager::BrowserInfo& a, + const AndroidDeviceManager::BrowserInfo& b) { + size_t count = arraysize(kBrowserDescriptors); + for (size_t i = 0; i < count; i++) { + bool isA = kBrowserDescriptors[i].display_name == a.display_name; + bool isB = kBrowserDescriptors[i].display_name == b.display_name; + if (isA != isB) + return isA; + if (isA && isB) + break; + } + return a.socket_name < b.socket_name; +} + +using StringMap = std::map<std::string, std::string>; + +void MapProcessesToPackages(const std::string& response, + StringMap* pid_to_package, + StringMap* pid_to_user) { + // Parse 'ps' output which on Android looks like this: + // + // USER PID PPID VSIZE RSS WCHAN PC ? NAME + // + for (const base::StringPiece& line : + base::SplitStringPiece(response, "\n", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY)) { + std::vector<std::string> fields = + base::SplitString(line, " \r", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + if (fields.size() < 9) + continue; + std::string pid = fields[1]; + (*pid_to_package)[pid] = fields[8]; + (*pid_to_user)[pid] = fields[0]; + } +} + +StringMap MapSocketsToProcesses(const std::string& response) { + // Parse 'cat /proc/net/unix' output which on Android looks like this: + // + // Num RefCount Protocol Flags Type St Inode Path + // 00000000: 00000002 00000000 00010000 0001 01 331813 /dev/socket/zygote + // 00000000: 00000002 00000000 00010000 0001 01 358606 @xxx_devtools_remote + // 00000000: 00000002 00000000 00010000 0001 01 347300 @yyy_devtools_remote + // + // We need to find records with paths starting from '@' (abstract socket) + // and containing the channel pattern ("_devtools_remote"). + StringMap socket_to_pid; + for (const base::StringPiece& line : + base::SplitStringPiece(response, "\n", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY)) { + std::vector<std::string> fields = + base::SplitString(line, " \r", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + if (fields.size() < 8) + continue; + if (fields[3] != "00010000" || fields[5] != "01") + continue; + std::string path_field = fields[7]; + if (path_field.size() < 1 || path_field[0] != '@') + continue; + size_t socket_name_pos = path_field.find(kDevToolsSocketSuffix); + if (socket_name_pos == std::string::npos) + continue; + + std::string socket = path_field.substr(1); + + std::string pid; + size_t socket_name_end = socket_name_pos + strlen(kDevToolsSocketSuffix); + if (socket_name_end < path_field.size() && + path_field[socket_name_end] == '_') { + pid = path_field.substr(socket_name_end + 1); + } + socket_to_pid[socket] = pid; + } + return socket_to_pid; +} + +gfx::Size ParseScreenSize(base::StringPiece str) { + std::vector<base::StringPiece> pairs = + base::SplitStringPiece(str, "-", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + if (pairs.size() != 2) + return gfx::Size(); + + int width; + int height; + std::vector<base::StringPiece> numbers = + base::SplitStringPiece(pairs[1].substr(1, pairs[1].size() - 2), ",", + base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (numbers.size() != 2 || + !base::StringToInt(numbers[0], &width) || + !base::StringToInt(numbers[1], &height)) + return gfx::Size(); + + return gfx::Size(width, height); +} + +gfx::Size ParseWindowPolicyResponse(const std::string& response) { + for (const base::StringPiece& line : + base::SplitStringPiece(response, "\r", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY)) { + size_t pos = line.find(kScreenSizePrefix); + if (pos != base::StringPiece::npos) { + return ParseScreenSize( + line.substr(pos + strlen(kScreenSizePrefix))); + } + } + return gfx::Size(); +} + +StringMap MapIdsToUsers(const std::string& response) { + // Parse 'dumpsys user' output which looks like this: + // Users: + // UserInfo{0:Test User:13} serialNo=0 + // Created: <unknown> + // Last logged in: +17m18s871ms ago + // UserInfo{10:User with : (colon):10} serialNo=10 + // Created: +3d4h35m1s139ms ago + // Last logged in: +17m26s287ms ago + StringMap id_to_username; + for (const base::StringPiece& line : + base::SplitStringPiece(response, "\r", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY)) { + size_t pos = line.find(kUserInfoPrefix); + if (pos != std::string::npos) { + base::StringPiece fields = line.substr(pos + strlen(kUserInfoPrefix)); + size_t first_pos = fields.find_first_of(":"); + size_t last_pos = fields.find_last_of(":"); + if (first_pos != std::string::npos && last_pos != std::string::npos) { + std::string id = fields.substr(0, first_pos).as_string(); + std::string name = fields.substr(first_pos + 1, + last_pos - first_pos - 1).as_string(); + id_to_username[id] = name; + } + } + } + return id_to_username; +} + +std::string GetUserName(const std::string& unix_user, + const StringMap id_to_username) { + // Parse username as returned by ps which looks like 'u0_a31' + // where '0' is user id and '31' is app id. + if (!unix_user.empty() && unix_user[0] == 'u') { + size_t pos = unix_user.find('_'); + if (pos != std::string::npos) { + StringMap::const_iterator it = + id_to_username.find(unix_user.substr(1, pos - 1)); + if (it != id_to_username.end()) + return it->second; + } + } + return std::string(); +} + +AndroidDeviceManager::BrowserInfo::Type +GetBrowserType(const std::string& socket) { + if (base::StartsWith(socket, kChromeDefaultSocket, + base::CompareCase::SENSITIVE)) { + return AndroidDeviceManager::BrowserInfo::kTypeChrome; + } + + if (base::StartsWith(socket, kWebViewSocketPrefix, + base::CompareCase::SENSITIVE)) { + return AndroidDeviceManager::BrowserInfo::kTypeWebView; + } + + return AndroidDeviceManager::BrowserInfo::kTypeOther; +} + +void ReceivedResponse(const AndroidDeviceManager::DeviceInfoCallback& callback, + int result, + const std::string& response) { + AndroidDeviceManager::DeviceInfo device_info; + if (result < 0) { + callback.Run(device_info); + return; + } + std::vector<std::string> outputs = base::SplitStringUsingSubstr( + response, kSeparator, base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (outputs.size() != 5) { + callback.Run(device_info); + return; + } + device_info.connected = true; + device_info.model = outputs[0]; + device_info.screen_size = ParseWindowPolicyResponse(outputs[1]); + StringMap pid_to_package; + StringMap pid_to_user; + MapProcessesToPackages(outputs[2], &pid_to_package, &pid_to_user); + StringMap socket_to_pid = MapSocketsToProcesses(outputs[3]); + StringMap id_to_username = MapIdsToUsers(outputs[4]); + std::set<std::string> used_pids; + for (const auto& pair : socket_to_pid) + used_pids.insert(pair.second); + + for (const auto& pair : pid_to_package) { + std::string pid = pair.first; + std::string package = pair.second; + if (used_pids.find(pid) == used_pids.end()) { + const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); + if (descriptor) + socket_to_pid[descriptor->socket] = pid; + } + } + + for (const auto& pair : socket_to_pid) { + std::string socket = pair.first; + std::string pid = pair.second; + std::string package; + StringMap::iterator pit = pid_to_package.find(pid); + if (pit != pid_to_package.end()) + package = pit->second; + + AndroidDeviceManager::BrowserInfo browser_info; + browser_info.socket_name = socket; + browser_info.type = GetBrowserType(socket); + browser_info.display_name = + AndroidDeviceManager::GetBrowserName(socket, package); + + StringMap::iterator uit = pid_to_user.find(pid); + if (uit != pid_to_user.end()) + browser_info.user = GetUserName(uit->second, id_to_username); + + device_info.browser_info.push_back(browser_info); + } + std::sort(device_info.browser_info.begin(), + device_info.browser_info.end(), + &BrowserCompare); + callback.Run(device_info); +} + +} // namespace + +// static +std::string AndroidDeviceManager::GetBrowserName(const std::string& socket, + const std::string& package) { + if (package.empty()) { + // Derive a fallback display name from the socket name. + std::string name = socket.substr(0, socket.find(kDevToolsSocketSuffix)); + name[0] = base::ToUpperASCII(name[0]); + return name; + } + + const BrowserDescriptor* descriptor = FindBrowserDescriptor(package); + if (descriptor) + return descriptor->display_name; + + if (GetBrowserType(socket) == + AndroidDeviceManager::BrowserInfo::kTypeWebView) + return base::StringPrintf(kWebViewNameTemplate, package.c_str()); + + return package; +} + +// static +void AndroidDeviceManager::QueryDeviceInfo( + const RunCommandCallback& command_callback, + const DeviceInfoCallback& callback) { + command_callback.Run( + kAllCommands, + base::Bind(&ReceivedResponse, callback)); +} diff --git a/chromium/chrome/browser/devtools/device/android_device_manager.cc b/chromium/chrome/browser/devtools/device/android_device_manager.cc new file mode 100644 index 00000000000..86901ff0007 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/android_device_manager.cc @@ -0,0 +1,569 @@ +// 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/device/android_device_manager.h" + +#include <stddef.h> +#include <string.h> + +#include <utility> + +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/socket/stream_socket.h" + +using content::BrowserThread; + +namespace { + +const char kDevToolsAdbBridgeThreadName[] = "Chrome_DevToolsADBThread"; + +const int kBufferSize = 16 * 1024; + +static const char kModelOffline[] = "Offline"; + +static const char kHttpGetRequest[] = "GET %s HTTP/1.1\r\n\r\n"; + +static const char kWebSocketUpgradeRequest[] = "GET %s HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "%s" + "\r\n"; + +static void PostDeviceInfoCallback( + scoped_refptr<base::SingleThreadTaskRunner> response_task_runner, + const AndroidDeviceManager::DeviceInfoCallback& callback, + const AndroidDeviceManager::DeviceInfo& device_info) { + response_task_runner->PostTask(FROM_HERE, + base::BindOnce(callback, device_info)); +} + +static void PostCommandCallback( + scoped_refptr<base::SingleThreadTaskRunner> response_task_runner, + const AndroidDeviceManager::CommandCallback& callback, + int result, + const std::string& response) { + response_task_runner->PostTask(FROM_HERE, + base::BindOnce(callback, result, response)); +} + +static void PostHttpUpgradeCallback( + scoped_refptr<base::SingleThreadTaskRunner> response_task_runner, + const AndroidDeviceManager::HttpUpgradeCallback& callback, + int result, + const std::string& extensions, + const std::string& body_head, + std::unique_ptr<net::StreamSocket> socket) { + response_task_runner->PostTask( + FROM_HERE, base::BindOnce(callback, result, extensions, body_head, + base::Passed(&socket))); +} + +class HttpRequest { + public: + typedef AndroidDeviceManager::CommandCallback CommandCallback; + typedef AndroidDeviceManager::HttpUpgradeCallback HttpUpgradeCallback; + + static void CommandRequest(const std::string& request, + const CommandCallback& callback, + int result, + std::unique_ptr<net::StreamSocket> socket) { + if (result != net::OK) { + callback.Run(result, std::string()); + return; + } + new HttpRequest(std::move(socket), request, callback); + } + + static void HttpUpgradeRequest(const std::string& request, + const HttpUpgradeCallback& callback, + int result, + std::unique_ptr<net::StreamSocket> socket) { + if (result != net::OK) { + callback.Run(result, std::string(), std::string(), + base::WrapUnique<net::StreamSocket>(nullptr)); + return; + } + new HttpRequest(std::move(socket), request, callback); + } + + private: + HttpRequest(std::unique_ptr<net::StreamSocket> socket, + const std::string& request, + const CommandCallback& callback) + : socket_(std::move(socket)), + command_callback_(callback), + expected_total_size_(0), + header_size_(std::string::npos) { + SendRequest(request); + } + + HttpRequest(std::unique_ptr<net::StreamSocket> socket, + const std::string& request, + const HttpUpgradeCallback& callback) + : socket_(std::move(socket)), + http_upgrade_callback_(callback), + expected_total_size_(0), + header_size_(std::string::npos) { + SendRequest(request); + } + + ~HttpRequest() { + } + + void DoSendRequest(int result) { + while (result != net::ERR_IO_PENDING) { + if (!CheckNetResultOrDie(result)) + return; + + if (result > 0) + request_->DidConsume(result); + + if (request_->BytesRemaining() == 0) { + request_ = nullptr; + ReadResponse(net::OK); + return; + } + + result = socket_->Write( + request_.get(), + request_->BytesRemaining(), + base::Bind(&HttpRequest::DoSendRequest, base::Unretained(this))); + } + } + + void SendRequest(const std::string& request) { + scoped_refptr<net::IOBuffer> base_buffer = + new net::IOBuffer(request.size()); + memcpy(base_buffer->data(), request.data(), request.size()); + request_ = new net::DrainableIOBuffer(base_buffer.get(), request.size()); + + DoSendRequest(net::OK); + } + + void ReadResponse(int result) { + if (!CheckNetResultOrDie(result)) + return; + + response_buffer_ = new net::IOBuffer(kBufferSize); + + result = socket_->Read( + response_buffer_.get(), + kBufferSize, + base::Bind(&HttpRequest::OnResponseData, base::Unretained(this))); + if (result != net::ERR_IO_PENDING) + OnResponseData(result); + } + + void OnResponseData(int result) { + do { + if (!CheckNetResultOrDie(result)) + return; + if (result == 0) { + CheckNetResultOrDie(net::ERR_CONNECTION_CLOSED); + return; + } + + response_.append(response_buffer_->data(), result); + + if (header_size_ == std::string::npos) { + header_size_ = response_.find("\r\n\r\n"); + + if (header_size_ != std::string::npos) { + header_size_ += 4; + + int expected_body_size = 0; + + // TODO(kaznacheev): Use net::HttpResponseHeader to parse the header. + std::string content_length = ExtractHeader("Content-Length:"); + if (!content_length.empty()) { + if (!base::StringToInt(content_length, &expected_body_size)) { + CheckNetResultOrDie(net::ERR_FAILED); + return; + } + } + + expected_total_size_ = header_size_ + expected_body_size; + } + } + + // WebSocket handshake doesn't contain the Content-Length header. For this + // case, |expected_total_size_| is set to the size of the header (opening + // handshake). + // + // Some (part of) WebSocket frames can be already received into + // |response_|. + if (header_size_ != std::string::npos && + response_.length() >= expected_total_size_) { + const std::string& body = response_.substr(header_size_); + + if (!command_callback_.is_null()) { + command_callback_.Run(net::OK, body); + } else { + http_upgrade_callback_.Run(net::OK, + ExtractHeader("Sec-WebSocket-Extensions:"), + body, std::move(socket_)); + } + + delete this; + return; + } + + result = socket_->Read( + response_buffer_.get(), kBufferSize, + base::Bind(&HttpRequest::OnResponseData, base::Unretained(this))); + } while (result != net::ERR_IO_PENDING); + } + + std::string ExtractHeader(const std::string& header) { + size_t start_pos = response_.find(header); + if (start_pos == std::string::npos) + return std::string(); + + size_t endline_pos = response_.find("\n", start_pos); + if (endline_pos == std::string::npos) + return std::string(); + + std::string value = response_.substr( + start_pos + header.length(), endline_pos - start_pos - header.length()); + base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value); + return value; + } + + bool CheckNetResultOrDie(int result) { + if (result >= 0) + return true; + if (!command_callback_.is_null()) { + command_callback_.Run(result, std::string()); + } else { + http_upgrade_callback_.Run(result, std::string(), std::string(), + base::WrapUnique<net::StreamSocket>(nullptr)); + } + delete this; + return false; + } + + std::unique_ptr<net::StreamSocket> socket_; + scoped_refptr<net::DrainableIOBuffer> request_; + std::string response_; + CommandCallback command_callback_; + HttpUpgradeCallback http_upgrade_callback_; + + scoped_refptr<net::IOBuffer> response_buffer_; + + // Initially set to 0. Once the end of the header is seen: + // - If the Content-Length header is included, set to the sum of the header + // size (including the last two CRLFs) and the value of + // the header. + // - Otherwise, this variable is set to the size of the header (including the + // last two CRLFs). + size_t expected_total_size_; + // Initially set to std::string::npos. Once the end of the header is seen, + // set to the size of the header part in |response_| including the two CRLFs + // at the end. + size_t header_size_; +}; + +class DevicesRequest : public base::RefCountedThreadSafe<DevicesRequest> { + public: + typedef AndroidDeviceManager::DeviceInfo DeviceInfo; + typedef AndroidDeviceManager::DeviceProvider DeviceProvider; + typedef AndroidDeviceManager::DeviceProviders DeviceProviders; + typedef AndroidDeviceManager::DeviceDescriptors DeviceDescriptors; + typedef base::Callback<void(std::unique_ptr<DeviceDescriptors>)> + DescriptorsCallback; + + static void Start( + scoped_refptr<base::SingleThreadTaskRunner> device_task_runner, + const DeviceProviders& providers, + const DescriptorsCallback& callback) { + // Don't keep counted reference on calling thread; + DevicesRequest* request = new DevicesRequest(callback); + // Avoid destruction while sending requests + request->AddRef(); + for (DeviceProviders::const_iterator it = providers.begin(); + it != providers.end(); ++it) { + device_task_runner->PostTask( + FROM_HERE, base::BindOnce(&DeviceProvider::QueryDevices, *it, + base::Bind(&DevicesRequest::ProcessSerials, + request, *it))); + } + device_task_runner->ReleaseSoon(FROM_HERE, request); + } + + private: + explicit DevicesRequest(const DescriptorsCallback& callback) + : response_task_runner_(base::ThreadTaskRunnerHandle::Get()), + callback_(callback), + descriptors_(new DeviceDescriptors()) {} + + friend class base::RefCountedThreadSafe<DevicesRequest>; + ~DevicesRequest() { + response_task_runner_->PostTask( + FROM_HERE, base::BindOnce(callback_, base::Passed(&descriptors_))); + } + + typedef std::vector<std::string> Serials; + + void ProcessSerials(scoped_refptr<DeviceProvider> provider, + const Serials& serials) { + for (Serials::const_iterator it = serials.begin(); it != serials.end(); + ++it) { + descriptors_->resize(descriptors_->size() + 1); + descriptors_->back().provider = provider; + descriptors_->back().serial = *it; + } + } + + scoped_refptr<base::SingleThreadTaskRunner> response_task_runner_; + DescriptorsCallback callback_; + std::unique_ptr<DeviceDescriptors> descriptors_; +}; + +void ReleaseDeviceAndProvider( + AndroidDeviceManager::DeviceProvider* provider, + const std::string& serial) { + provider->ReleaseDevice(serial); + provider->Release(); +} + +} // namespace + +AndroidDeviceManager::BrowserInfo::BrowserInfo() + : type(kTypeOther) { +} + +AndroidDeviceManager::BrowserInfo::BrowserInfo(const BrowserInfo& other) = + default; + +AndroidDeviceManager::DeviceInfo::DeviceInfo() + : model(kModelOffline), connected(false) { +} + +AndroidDeviceManager::DeviceInfo::DeviceInfo(const DeviceInfo& other) = default; + +AndroidDeviceManager::DeviceInfo::~DeviceInfo() { +} + +AndroidDeviceManager::DeviceDescriptor::DeviceDescriptor() { +} + +AndroidDeviceManager::DeviceDescriptor::DeviceDescriptor( + const DeviceDescriptor& other) = default; + +AndroidDeviceManager::DeviceDescriptor::~DeviceDescriptor() { +} + +void AndroidDeviceManager::DeviceProvider::SendJsonRequest( + const std::string& serial, + const std::string& socket_name, + const std::string& request, + const CommandCallback& callback) { + OpenSocket(serial, + socket_name, + base::Bind(&HttpRequest::CommandRequest, + base::StringPrintf(kHttpGetRequest, request.c_str()), + callback)); +} + +void AndroidDeviceManager::DeviceProvider::HttpUpgrade( + const std::string& serial, + const std::string& socket_name, + const std::string& path, + const std::string& extensions, + const HttpUpgradeCallback& callback) { + std::string extensions_with_new_line = + extensions.empty() ? std::string() : extensions + "\r\n"; + OpenSocket( + serial, + socket_name, + base::Bind(&HttpRequest::HttpUpgradeRequest, + base::StringPrintf(kWebSocketUpgradeRequest, + path.c_str(), + extensions_with_new_line.c_str()), + callback)); +} + +void AndroidDeviceManager::DeviceProvider::ReleaseDevice( + const std::string& serial) { +} + +AndroidDeviceManager::DeviceProvider::DeviceProvider() { +} + +AndroidDeviceManager::DeviceProvider::~DeviceProvider() { +} + +void AndroidDeviceManager::Device::QueryDeviceInfo( + const DeviceInfoCallback& callback) { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + &DeviceProvider::QueryDeviceInfo, provider_, serial_, + base::Bind(&PostDeviceInfoCallback, + base::ThreadTaskRunnerHandle::Get(), callback))); +} + +void AndroidDeviceManager::Device::OpenSocket(const std::string& socket_name, + const SocketCallback& callback) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&DeviceProvider::OpenSocket, provider_, serial_, + socket_name, callback)); +} + +void AndroidDeviceManager::Device::SendJsonRequest( + const std::string& socket_name, + const std::string& request, + const CommandCallback& callback) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&DeviceProvider::SendJsonRequest, provider_, + serial_, socket_name, request, + base::Bind(&PostCommandCallback, + base::ThreadTaskRunnerHandle::Get(), + callback))); +} + +void AndroidDeviceManager::Device::HttpUpgrade( + const std::string& socket_name, + const std::string& path, + const std::string& extensions, + const HttpUpgradeCallback& callback) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&DeviceProvider::HttpUpgrade, provider_, + serial_, socket_name, path, extensions, + base::Bind(&PostHttpUpgradeCallback, + base::ThreadTaskRunnerHandle::Get(), + callback))); +} + +AndroidDeviceManager::Device::Device( + scoped_refptr<base::SingleThreadTaskRunner> device_task_runner, + scoped_refptr<DeviceProvider> provider, + const std::string& serial) + : task_runner_(device_task_runner), + provider_(provider), + serial_(serial), + weak_factory_(this) { +} + +AndroidDeviceManager::Device::~Device() { + provider_->AddRef(); + DeviceProvider* raw_ptr = provider_.get(); + provider_ = nullptr; + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&ReleaseDeviceAndProvider, + base::Unretained(raw_ptr), serial_)); +} + +AndroidDeviceManager::HandlerThread* +AndroidDeviceManager::HandlerThread::instance_ = nullptr; + +// static +scoped_refptr<AndroidDeviceManager::HandlerThread> +AndroidDeviceManager::HandlerThread::GetInstance() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (!instance_) + new HandlerThread(); + return instance_; +} + +AndroidDeviceManager::HandlerThread::HandlerThread() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + instance_ = this; + thread_ = new base::Thread(kDevToolsAdbBridgeThreadName); + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + if (!thread_->StartWithOptions(options)) { + delete thread_; + thread_ = nullptr; + } +} + +scoped_refptr<base::SingleThreadTaskRunner> +AndroidDeviceManager::HandlerThread::message_loop() { + return thread_ ? thread_->task_runner() : nullptr; +} + +// static +void AndroidDeviceManager::HandlerThread::StopThread( + base::Thread* thread) { + thread->Stop(); + delete thread; +} + +AndroidDeviceManager::HandlerThread::~HandlerThread() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + instance_ = nullptr; + if (!thread_) + return; + // Shut down thread on FILE thread to join into IO. + content::BrowserThread::PostTask( + content::BrowserThread::FILE, FROM_HERE, + base::BindOnce(&HandlerThread::StopThread, thread_)); +} + +// static +std::unique_ptr<AndroidDeviceManager> AndroidDeviceManager::Create() { + return base::WrapUnique(new AndroidDeviceManager()); +} + +void AndroidDeviceManager::SetDeviceProviders( + const DeviceProviders& providers) { + for (DeviceProviders::iterator it = providers_.begin(); + it != providers_.end(); ++it) { + (*it)->AddRef(); + DeviceProvider* raw_ptr = it->get(); + *it = nullptr; + handler_thread_->message_loop()->ReleaseSoon(FROM_HERE, raw_ptr); + } + providers_ = providers; +} + +void AndroidDeviceManager::QueryDevices(const DevicesCallback& callback) { + DevicesRequest::Start(handler_thread_->message_loop(), providers_, + base::Bind(&AndroidDeviceManager::UpdateDevices, + weak_factory_.GetWeakPtr(), callback)); +} + +AndroidDeviceManager::AndroidDeviceManager() + : handler_thread_(HandlerThread::GetInstance()), + weak_factory_(this) { +} + +AndroidDeviceManager::~AndroidDeviceManager() { + SetDeviceProviders(DeviceProviders()); +} + +void AndroidDeviceManager::UpdateDevices( + const DevicesCallback& callback, + std::unique_ptr<DeviceDescriptors> descriptors) { + Devices response; + DeviceWeakMap new_devices; + for (DeviceDescriptors::const_iterator it = descriptors->begin(); + it != descriptors->end(); + ++it) { + DeviceWeakMap::iterator found = devices_.find(it->serial); + scoped_refptr<Device> device; + if (found == devices_.end() || !found->second || + found->second->provider_.get() != it->provider.get()) { + device = + new Device(handler_thread_->message_loop(), it->provider, it->serial); + } else { + device = found->second.get(); + } + response.push_back(device); + new_devices[it->serial] = device->weak_factory_.GetWeakPtr(); + } + devices_.swap(new_devices); + callback.Run(response); +} diff --git a/chromium/chrome/browser/devtools/device/android_device_manager.h b/chromium/chrome/browser/devtools/device/android_device_manager.h new file mode 100644 index 00000000000..0838c1d788b --- /dev/null +++ b/chromium/chrome/browser/devtools/device/android_device_manager.h @@ -0,0 +1,249 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_ANDROID_DEVICE_MANAGER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_ANDROID_DEVICE_MANAGER_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/browser_thread.h" +#include "ui/gfx/geometry/size.h" + +namespace net { +class StreamSocket; +} + +class AndroidDeviceManager : public base::NonThreadSafe { + public: + using CommandCallback = + base::Callback<void(int, const std::string&)>; + using SocketCallback = + base::Callback<void(int result, std::unique_ptr<net::StreamSocket>)>; + // |body_head| should contain the body (WebSocket frame data) part that has + // been read during processing the header (WebSocket handshake). + using HttpUpgradeCallback = + base::Callback<void(int result, + const std::string& extensions, + const std::string& body_head, + std::unique_ptr<net::StreamSocket>)>; + using SerialsCallback = + base::Callback<void(const std::vector<std::string>&)>; + + struct BrowserInfo { + BrowserInfo(); + BrowserInfo(const BrowserInfo& other); + + enum Type { + kTypeChrome, + kTypeWebView, + kTypeOther + }; + + std::string socket_name; + std::string display_name; + std::string user; + Type type; + }; + + struct DeviceInfo { + DeviceInfo(); + DeviceInfo(const DeviceInfo& other); + ~DeviceInfo(); + + std::string model; + bool connected; + gfx::Size screen_size; + std::vector<BrowserInfo> browser_info; + }; + + typedef base::Callback<void(const DeviceInfo&)> DeviceInfoCallback; + class Device; + + class AndroidWebSocket { + public: + class Delegate { + public: + virtual void OnSocketOpened() = 0; + virtual void OnFrameRead(const std::string& message) = 0; + virtual void OnSocketClosed() = 0; + + protected: + virtual ~Delegate() {} + }; + + ~AndroidWebSocket(); + + void SendFrame(const std::string& message); + + private: + friend class Device; + class WebSocketImpl; + + AndroidWebSocket( + scoped_refptr<Device> device, + const std::string& socket_name, + const std::string& path, + AndroidWebSocket::Delegate* delegate); + void Connected(int result, + const std::string& extensions, + const std::string& body_head, + std::unique_ptr<net::StreamSocket> socket); + void OnFrameRead(const std::string& message); + void OnSocketClosed(); + + scoped_refptr<Device> device_; + WebSocketImpl* socket_impl_; + Delegate* delegate_; + base::WeakPtrFactory<AndroidWebSocket> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(AndroidWebSocket); + }; + + class DeviceProvider; + + class Device : public base::RefCountedThreadSafe<Device>, + public base::NonThreadSafe { + public: + void QueryDeviceInfo(const DeviceInfoCallback& callback); + + void OpenSocket(const std::string& socket_name, + const SocketCallback& callback); + + void SendJsonRequest(const std::string& socket_name, + const std::string& request, + const CommandCallback& callback); + + void HttpUpgrade(const std::string& socket_name, + const std::string& path, + const std::string& extensions, + const HttpUpgradeCallback& callback); + AndroidWebSocket* CreateWebSocket( + const std::string& socket_name, + const std::string& path, + AndroidWebSocket::Delegate* delegate); + + std::string serial() { return serial_; } + + private: + friend class base::RefCountedThreadSafe<Device>; + friend class AndroidDeviceManager; + friend class AndroidWebSocket; + + Device(scoped_refptr<base::SingleThreadTaskRunner> device_task_runner, + scoped_refptr<DeviceProvider> provider, + const std::string& serial); + + virtual ~Device(); + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + scoped_refptr<DeviceProvider> provider_; + std::string serial_; + base::WeakPtrFactory<Device> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(Device); + }; + + typedef std::vector<scoped_refptr<Device> > Devices; + typedef base::Callback<void(const Devices&)> DevicesCallback; + + class DeviceProvider : public base::RefCountedThreadSafe<DeviceProvider> { + public: + typedef AndroidDeviceManager::SerialsCallback SerialsCallback; + typedef AndroidDeviceManager::DeviceInfoCallback DeviceInfoCallback; + typedef AndroidDeviceManager::SocketCallback SocketCallback; + typedef AndroidDeviceManager::CommandCallback CommandCallback; + + virtual void QueryDevices(const SerialsCallback& callback) = 0; + + virtual void QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) = 0; + + virtual void OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) = 0; + + virtual void SendJsonRequest(const std::string& serial, + const std::string& socket_name, + const std::string& request, + const CommandCallback& callback); + + virtual void HttpUpgrade(const std::string& serial, + const std::string& socket_name, + const std::string& path, + const std::string& extensions, + const HttpUpgradeCallback& callback); + + virtual void ReleaseDevice(const std::string& serial); + + protected: + friend class base::RefCountedThreadSafe<DeviceProvider>; + DeviceProvider(); + virtual ~DeviceProvider(); + }; + + typedef std::vector<scoped_refptr<DeviceProvider> > DeviceProviders; + + virtual ~AndroidDeviceManager(); + + static std::unique_ptr<AndroidDeviceManager> Create(); + + void SetDeviceProviders(const DeviceProviders& providers); + + void QueryDevices(const DevicesCallback& callback); + + static std::string GetBrowserName(const std::string& socket, + const std::string& package); + using RunCommandCallback = + base::Callback<void(const std::string&, const CommandCallback&)>; + + static void QueryDeviceInfo(const RunCommandCallback& command_callback, + const DeviceInfoCallback& callback); + + struct DeviceDescriptor { + DeviceDescriptor(); + DeviceDescriptor(const DeviceDescriptor& other); + ~DeviceDescriptor(); + + scoped_refptr<DeviceProvider> provider; + std::string serial; + }; + + typedef std::vector<DeviceDescriptor> DeviceDescriptors; + + private: + class HandlerThread : public base::RefCountedThreadSafe<HandlerThread> { + public: + static scoped_refptr<HandlerThread> GetInstance(); + scoped_refptr<base::SingleThreadTaskRunner> message_loop(); + + private: + friend class base::RefCountedThreadSafe<HandlerThread>; + static HandlerThread* instance_; + static void StopThread(base::Thread* thread); + + HandlerThread(); + virtual ~HandlerThread(); + base::Thread* thread_; + }; + + AndroidDeviceManager(); + + void UpdateDevices(const DevicesCallback& callback, + std::unique_ptr<DeviceDescriptors> descriptors); + + typedef std::map<std::string, base::WeakPtr<Device> > DeviceWeakMap; + + scoped_refptr<HandlerThread> handler_thread_; + DeviceProviders providers_; + DeviceWeakMap devices_; + + base::WeakPtrFactory<AndroidDeviceManager> weak_factory_; +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_ANDROID_DEVICE_MANAGER_H_ diff --git a/chromium/chrome/browser/devtools/device/android_web_socket.cc b/chromium/chrome/browser/devtools/device/android_web_socket.cc new file mode 100644 index 00000000000..8a57aa364a6 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/android_web_socket.cc @@ -0,0 +1,226 @@ +// 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 <utility> + +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/rand_util.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/devtools/device/android_device_manager.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/server/web_socket_encoder.h" +#include "net/socket/stream_socket.h" + +using content::BrowserThread; +using net::WebSocket; + +namespace { + +const int kBufferSize = 16 * 1024; +const char kCloseResponse[] = "\x88\x80\x2D\x0E\x1E\xFA"; + +} // namespace + +class AndroidDeviceManager::AndroidWebSocket::WebSocketImpl { + public: + WebSocketImpl( + scoped_refptr<base::SingleThreadTaskRunner> response_task_runner, + base::WeakPtr<AndroidWebSocket> weak_socket, + const std::string& extensions, + const std::string& body_head, + std::unique_ptr<net::StreamSocket> socket) + : response_task_runner_(response_task_runner), + weak_socket_(weak_socket), + socket_(std::move(socket)), + encoder_(net::WebSocketEncoder::CreateClient(extensions)), + response_buffer_(body_head) { + thread_checker_.DetachFromThread(); + } + + void StartListening() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(socket_); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize)); + + if (response_buffer_.size() > 0) + ProcessResponseBuffer(buffer); + else + Read(buffer); + } + + void SendFrame(const std::string& message) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!socket_) + return; + int mask = base::RandInt(0, 0x7FFFFFFF); + std::string encoded_frame; + encoder_->EncodeFrame(message, mask, &encoded_frame); + SendData(encoded_frame); + } + + private: + void Read(scoped_refptr<net::IOBuffer> io_buffer) { + int result = socket_->Read( + io_buffer.get(), + kBufferSize, + base::Bind(&WebSocketImpl::OnBytesRead, + base::Unretained(this), io_buffer)); + if (result != net::ERR_IO_PENDING) + OnBytesRead(io_buffer, result); + } + + void OnBytesRead(scoped_refptr<net::IOBuffer> io_buffer, int result) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (result <= 0) { + Disconnect(); + return; + } + response_buffer_.append(io_buffer->data(), result); + + ProcessResponseBuffer(io_buffer); + } + + void ProcessResponseBuffer(scoped_refptr<net::IOBuffer> io_buffer) { + int bytes_consumed; + std::string output; + WebSocket::ParseResult parse_result = encoder_->DecodeFrame( + response_buffer_, &bytes_consumed, &output); + + while (parse_result == WebSocket::FRAME_OK) { + response_buffer_ = response_buffer_.substr(bytes_consumed); + response_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&AndroidWebSocket::OnFrameRead, weak_socket_, output)); + parse_result = encoder_->DecodeFrame( + response_buffer_, &bytes_consumed, &output); + } + if (parse_result == WebSocket::FRAME_CLOSE) + SendData(kCloseResponse); + + if (parse_result == WebSocket::FRAME_ERROR) { + Disconnect(); + return; + } + Read(io_buffer); + } + + void SendData(const std::string& data) { + request_buffer_ += data; + if (request_buffer_.length() == data.length()) + SendPendingRequests(0); + } + + void SendPendingRequests(int result) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (result < 0) { + Disconnect(); + return; + } + request_buffer_ = request_buffer_.substr(result); + if (request_buffer_.empty()) + return; + + scoped_refptr<net::StringIOBuffer> buffer = + new net::StringIOBuffer(request_buffer_); + result = socket_->Write(buffer.get(), buffer->size(), + base::Bind(&WebSocketImpl::SendPendingRequests, + base::Unretained(this))); + if (result != net::ERR_IO_PENDING) + SendPendingRequests(result); + } + + void Disconnect() { + DCHECK(thread_checker_.CalledOnValidThread()); + socket_.reset(); + response_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&AndroidWebSocket::OnSocketClosed, weak_socket_)); + } + + scoped_refptr<base::SingleThreadTaskRunner> response_task_runner_; + base::WeakPtr<AndroidWebSocket> weak_socket_; + std::unique_ptr<net::StreamSocket> socket_; + std::unique_ptr<net::WebSocketEncoder> encoder_; + std::string response_buffer_; + std::string request_buffer_; + base::ThreadChecker thread_checker_; + DISALLOW_COPY_AND_ASSIGN(WebSocketImpl); +}; + +AndroidDeviceManager::AndroidWebSocket::AndroidWebSocket( + scoped_refptr<Device> device, + const std::string& socket_name, + const std::string& path, + Delegate* delegate) + : device_(device), + socket_impl_(nullptr), + delegate_(delegate), + weak_factory_(this) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(delegate_); + DCHECK(device_); + device_->HttpUpgrade( + socket_name, path, net::WebSocketEncoder::kClientExtensions, + base::Bind(&AndroidWebSocket::Connected, weak_factory_.GetWeakPtr())); +} + +AndroidDeviceManager::AndroidWebSocket::~AndroidWebSocket() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (socket_impl_) + device_->task_runner_->DeleteSoon(FROM_HERE, socket_impl_); +} + +void AndroidDeviceManager::AndroidWebSocket::SendFrame( + const std::string& message) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(socket_impl_); + DCHECK(device_); + device_->task_runner_->PostTask( + FROM_HERE, base::BindOnce(&WebSocketImpl::SendFrame, + base::Unretained(socket_impl_), message)); +} + +void AndroidDeviceManager::AndroidWebSocket::Connected( + int result, + const std::string& extensions, + const std::string& body_head, + std::unique_ptr<net::StreamSocket> socket) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (result != net::OK || !socket.get()) { + OnSocketClosed(); + return; + } + socket_impl_ = new WebSocketImpl(base::ThreadTaskRunnerHandle::Get(), + weak_factory_.GetWeakPtr(), extensions, + body_head, std::move(socket)); + device_->task_runner_->PostTask( + FROM_HERE, base::BindOnce(&WebSocketImpl::StartListening, + base::Unretained(socket_impl_))); + delegate_->OnSocketOpened(); +} + +void AndroidDeviceManager::AndroidWebSocket::OnFrameRead( + const std::string& message) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + delegate_->OnFrameRead(message); +} + +void AndroidDeviceManager::AndroidWebSocket::OnSocketClosed() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + delegate_->OnSocketClosed(); +} + +AndroidDeviceManager::AndroidWebSocket* +AndroidDeviceManager::Device::CreateWebSocket( + const std::string& socket_name, + const std::string& path, + AndroidWebSocket::Delegate* delegate) { + return new AndroidWebSocket(this, socket_name, path, delegate); +} diff --git a/chromium/chrome/browser/devtools/device/cast_device_provider.cc b/chromium/chrome/browser/devtools/device/cast_device_provider.cc new file mode 100644 index 00000000000..8ee6714b2c5 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/cast_device_provider.cc @@ -0,0 +1,206 @@ +// Copyright 2015 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/device/cast_device_provider.h" + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/local_discovery/service_discovery_shared_client.h" +#include "net/base/host_port_pair.h" +#include "net/base/ip_address.h" + +using local_discovery::ServiceDescription; +using local_discovery::ServiceDiscoveryDeviceLister; +using local_discovery::ServiceDiscoverySharedClient; + +namespace { + +const int kCastInspectPort = 9222; +const char kCastServiceType[] = "_googlecast._tcp.local"; +const char kUnknownCastDevice[] = "Unknown Cast Device"; + +using ServiceTxtRecordMap = std::map<std::string, std::string>; + +// Parses TXT record strings into a map. TXT key-value strings are assumed to +// follow the form "$key(=$value)?", where $key must contain at least one +// character, and $value may be empty. +std::unique_ptr<ServiceTxtRecordMap> ParseServiceTxtRecord( + const std::vector<std::string>& record) { + std::unique_ptr<ServiceTxtRecordMap> record_map(new ServiceTxtRecordMap()); + for (const auto& key_value_str : record) { + if (key_value_str.empty()) + continue; + + size_t index = key_value_str.find("=", 0); + if (index == std::string::npos) { + // Some strings may only define a key (no '=' in the key/value string). + // The chosen behavior is to assume the value is the empty string. + record_map->insert(std::make_pair(key_value_str, "")); + } else { + std::string key = key_value_str.substr(0, index); + std::string value = key_value_str.substr(index + 1); + record_map->insert(std::make_pair(key, value)); + } + } + return record_map; +} + +AndroidDeviceManager::DeviceInfo ServiceDescriptionToDeviceInfo( + const ServiceDescription& service_description) { + std::unique_ptr<ServiceTxtRecordMap> record_map = + ParseServiceTxtRecord(service_description.metadata); + + AndroidDeviceManager::DeviceInfo device_info; + device_info.connected = true; + const auto it = record_map->find("md"); + if (it != record_map->end() && !it->second.empty()) + device_info.model = it->second; + else + device_info.model = kUnknownCastDevice; + + AndroidDeviceManager::BrowserInfo browser_info; + browser_info.socket_name = base::IntToString(kCastInspectPort); + browser_info.display_name = + base::SplitString(service_description.service_name, ".", + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)[0]; + + browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeChrome; + device_info.browser_info.push_back(browser_info); + return device_info; +} + +} // namespace + +// The purpose of this class is to route lister delegate signals from +// ServiceDiscoveryDeviceLister (on the UI thread) to CastDeviceProvider (on the +// DevTools ADB thread). Cancellable callbacks are necessary since +// CastDeviceProvider and ServiceDiscoveryDeviceLister are destroyed on +// different threads in undefined order. +class CastDeviceProvider::DeviceListerDelegate + : public ServiceDiscoveryDeviceLister::Delegate, + public base::SupportsWeakPtr<DeviceListerDelegate> { + public: + DeviceListerDelegate(base::WeakPtr<CastDeviceProvider> provider, + scoped_refptr<base::SingleThreadTaskRunner> runner) + : provider_(provider), runner_(runner) {} + + virtual ~DeviceListerDelegate() {} + + void StartDiscovery() { + // This must be called on the UI thread; ServiceDiscoverySharedClient and + // ServiceDiscoveryDeviceLister are thread protected. + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (device_lister_) + return; + service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance(); + device_lister_.reset(new ServiceDiscoveryDeviceLister( + this, service_discovery_client_.get(), kCastServiceType)); + device_lister_->Start(); + device_lister_->DiscoverNewDevices(true); + } + + // ServiceDiscoveryDeviceLister::Delegate implementation: + void OnDeviceChanged(bool added, + const ServiceDescription& service_description) override { + runner_->PostTask(FROM_HERE, + base::BindOnce(&CastDeviceProvider::OnDeviceChanged, + provider_, added, service_description)); + } + + void OnDeviceRemoved(const std::string& service_name) override { + runner_->PostTask(FROM_HERE, + base::BindOnce(&CastDeviceProvider::OnDeviceRemoved, + provider_, service_name)); + } + + void OnDeviceCacheFlushed() override { + runner_->PostTask( + FROM_HERE, + base::BindOnce(&CastDeviceProvider::OnDeviceCacheFlushed, provider_)); + } + + private: + // The device provider to notify of device changes. + base::WeakPtr<CastDeviceProvider> provider_; + // Runner for the thread the WeakPtr was created on (this is where device + // messages will be posted). + scoped_refptr<base::SingleThreadTaskRunner> runner_; + scoped_refptr<ServiceDiscoverySharedClient> service_discovery_client_; + std::unique_ptr<ServiceDiscoveryDeviceLister> device_lister_; +}; + +CastDeviceProvider::CastDeviceProvider() : weak_factory_(this) {} + +CastDeviceProvider::~CastDeviceProvider() {} + +void CastDeviceProvider::QueryDevices(const SerialsCallback& callback) { + if (!lister_delegate_) { + lister_delegate_.reset(new DeviceListerDelegate( + weak_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get())); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::BindOnce(&DeviceListerDelegate::StartDiscovery, + lister_delegate_->AsWeakPtr())); + } + std::set<net::HostPortPair> targets; + for (const auto& device_entry : device_info_map_) + targets.insert(net::HostPortPair(device_entry.first, kCastInspectPort)); + tcp_provider_ = new TCPDeviceProvider(targets); + tcp_provider_->QueryDevices(callback); +} + +void CastDeviceProvider::QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) { + auto it_device = device_info_map_.find(serial); + if (it_device == device_info_map_.end()) + return; + callback.Run(it_device->second); +} + +void CastDeviceProvider::OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) { + tcp_provider_->OpenSocket(serial, socket_name, callback); +} + +void CastDeviceProvider::OnDeviceChanged( + bool added, + const ServiceDescription& service_description) { + VLOG(1) << "Device " << (added ? "added: " : "changed: ") + << service_description.service_name; + if (service_description.service_type() != kCastServiceType) + return; + const net::IPAddress& ip_address = service_description.ip_address; + if (!ip_address.IsValid()) { + // An invalid IP address is not queryable. + return; + } + const std::string& name = service_description.service_name; + std::string host = ip_address.ToString(); + service_hostname_map_[name] = host; + device_info_map_[host] = ServiceDescriptionToDeviceInfo(service_description); +} + +void CastDeviceProvider::OnDeviceRemoved(const std::string& service_name) { + VLOG(1) << "Device removed: " << service_name; + auto it = service_hostname_map_.find(service_name); + if (it == service_hostname_map_.end()) + return; + const std::string& hostname = it->second; + device_info_map_.erase(hostname); + service_hostname_map_.erase(it); +} + +void CastDeviceProvider::OnDeviceCacheFlushed() { + VLOG(1) << "Device cache flushed"; + service_hostname_map_.clear(); + device_info_map_.clear(); +} diff --git a/chromium/chrome/browser/devtools/device/cast_device_provider.h b/chromium/chrome/browser/devtools/device/cast_device_provider.h new file mode 100644 index 00000000000..f0462cabaa8 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/cast_device_provider.h @@ -0,0 +1,63 @@ +// Copyright 2015 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_CAST_DEVICE_PROVIDER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_CAST_DEVICE_PROVIDER_H_ + +#include <map> +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/devtools/device/android_device_manager.h" +#include "chrome/browser/devtools/device/tcp_device_provider.h" +#include "chrome/browser/local_discovery/service_discovery_device_lister.h" +#include "content/public/browser/browser_thread.h" + +// Supplies Cast device information for the purposes of remote debugging Cast +// applications over ADB. +class CastDeviceProvider + : public AndroidDeviceManager::DeviceProvider, + public local_discovery::ServiceDiscoveryDeviceLister::Delegate { + public: + CastDeviceProvider(); + + // DeviceProvider implementation: + void QueryDevices(const SerialsCallback& callback) override; + void QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) override; + void OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) override; + + // ServiceDiscoveryDeviceLister::Delegate implementation: + void OnDeviceChanged( + bool added, + const local_discovery::ServiceDescription& service_description) override; + void OnDeviceRemoved(const std::string& service_name) override; + void OnDeviceCacheFlushed() override; + + private: + class DeviceListerDelegate; + + ~CastDeviceProvider() override; + + scoped_refptr<TCPDeviceProvider> tcp_provider_; + std::unique_ptr<DeviceListerDelegate, + content::BrowserThread::DeleteOnUIThread> + lister_delegate_; + + // Keyed on the hostname (IP address). + std::map<std::string, AndroidDeviceManager::DeviceInfo> device_info_map_; + // Maps a service name to the hostname (IP address). + std::map<std::string, std::string> service_hostname_map_; + + base::WeakPtrFactory<CastDeviceProvider> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CastDeviceProvider); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_CAST_DEVICE_PROVIDER_H_ diff --git a/chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc b/chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc new file mode 100644 index 00000000000..936d7e56099 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc @@ -0,0 +1,109 @@ +// Copyright 2015 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/device/cast_device_provider.h" + +#include "chrome/browser/devtools/device/android_device_manager.h" +#include "net/base/host_port_pair.h" +#include "testing/gtest/include/gtest/gtest.h" + +using local_discovery::ServiceDescription; +using DeviceInfo = AndroidDeviceManager::DeviceInfo; +using BrowserInfo = AndroidDeviceManager::BrowserInfo; + +namespace { + +void CompareDeviceInfo(bool* was_run, + const DeviceInfo& expected, + const DeviceInfo& actual) { + EXPECT_EQ(expected.model, actual.model); + EXPECT_EQ(expected.connected, actual.connected); + + const BrowserInfo& exp_br_info = expected.browser_info[0]; + const BrowserInfo& act_br_info = actual.browser_info[0]; + EXPECT_EQ(exp_br_info.socket_name, act_br_info.socket_name); + EXPECT_EQ(exp_br_info.display_name, act_br_info.display_name); + EXPECT_EQ(exp_br_info.type, act_br_info.type); + + *was_run = true; +} + +void DummyCallback(bool* was_run, const DeviceInfo& device_info) { + *was_run = true; +} + +} // namespace + +TEST(CastDeviceProviderTest, ServiceDiscovery) { + scoped_refptr<CastDeviceProvider> device_provider_ = new CastDeviceProvider(); + + // Create a cast service. + const std::string cast_service_display_name = "FakeCast1337"; + const std::string cast_service_type = "_googlecast._tcp.local"; + const std::string cast_service_model = "Fake Cast Device"; + + ServiceDescription cast_service; + cast_service.service_name = + cast_service_display_name + "." + cast_service_type; + cast_service.address = net::HostPortPair("192.168.1.101", 8009); + cast_service.metadata.push_back("id=0123456789abcdef0123456789abcdef"); + cast_service.metadata.push_back("ve=00"); + cast_service.metadata.push_back("md=" + cast_service_model); + ASSERT_TRUE(cast_service.ip_address.AssignFromIPLiteral("192.168.1.101")); + + device_provider_->OnDeviceChanged(true, cast_service); + + BrowserInfo exp_browser_info; + exp_browser_info.socket_name = "9222"; + exp_browser_info.display_name = cast_service_display_name; + exp_browser_info.type = BrowserInfo::kTypeChrome; + + DeviceInfo expected; + expected.model = cast_service_model; // From metadata::md + expected.connected = true; + expected.browser_info.push_back(exp_browser_info); + + bool was_run = false; + // Callback should be run, and the queried service should match the expected. + device_provider_->QueryDeviceInfo( + cast_service.address.host(), + base::Bind(&CompareDeviceInfo, &was_run, expected)); + ASSERT_TRUE(was_run); + was_run = false; + + // Create a non-cast service. + const std::string other_service_display_name = "OtherDevice"; + const std::string other_service_type = "_other._tcp.local"; + const std::string other_service_model = "Some Other Device"; + + ServiceDescription other_service; + other_service.service_name = + other_service_display_name + "." + other_service_type; + other_service.address = net::HostPortPair("10.64.1.101", 1234); + other_service.metadata.push_back("id=0123456789abcdef0123456789abcdef"); + other_service.metadata.push_back("ve=00"); + other_service.metadata.push_back("md=" + other_service_model); + ASSERT_TRUE(other_service.ip_address.AssignFromIPLiteral("10.64.1.101")); + + // Callback should not be run, since this service is not yet discovered. + device_provider_->QueryDeviceInfo(other_service.address.host(), + base::Bind(&DummyCallback, &was_run)); + ASSERT_FALSE(was_run); + + device_provider_->OnDeviceChanged(true, other_service); + + // Callback should not be run, since non-cast services are not discovered by + // this device provider. + device_provider_->QueryDeviceInfo(other_service.address.host(), + base::Bind(&DummyCallback, &was_run)); + ASSERT_FALSE(was_run); + + // Remove the cast service. + device_provider_->OnDeviceRemoved(cast_service.service_name); + + // Callback should not be run, since the cast service has been removed. + device_provider_->QueryDeviceInfo(cast_service.address.host(), + base::Bind(&DummyCallback, &was_run)); + ASSERT_FALSE(was_run); +} diff --git a/chromium/chrome/browser/devtools/device/devtools_android_bridge.cc b/chromium/chrome/browser/devtools/device/devtools_android_bridge.cc new file mode 100644 index 00000000000..a8ad1f01a8b --- /dev/null +++ b/chromium/chrome/browser/devtools/device/devtools_android_bridge.cc @@ -0,0 +1,406 @@ +// 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/device/devtools_android_bridge.h" + +#include <stddef.h> +#include <algorithm> +#include <map> +#include <set> +#include <utility> +#include <vector> + +#include "base/base64.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/json/json_reader.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/singleton.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread.h" +#include "base/values.h" +#include "chrome/browser/devtools/device/adb/adb_device_provider.h" +#include "chrome/browser/devtools/device/port_forwarding_controller.h" +#include "chrome/browser/devtools/device/tcp_device_provider.h" +#include "chrome/browser/devtools/device/usb/usb_device_provider.h" +#include "chrome/browser/devtools/devtools_protocol.h" +#include "chrome/browser/devtools/devtools_window.h" +#include "chrome/browser/devtools/remote_debugging_server.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/features.h" +#include "chrome/common/pref_names.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/prefs/pref_service.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "components/signin/core/browser/signin_manager.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/devtools_external_agent_proxy.h" +#include "content/public/browser/devtools_external_agent_proxy_delegate.h" +#include "net/base/escape.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" + +#if BUILDFLAG(ENABLE_SERVICE_DISCOVERY) +#include "chrome/browser/devtools/device/cast_device_provider.h" +#endif + +using content::BrowserThread; +using content::DevToolsAgentHost; + +namespace { + +const char kNewPageRequestWithURL[] = "/json/new?%s"; +const char kChromeDiscoveryURL[] = "localhost:9222"; +const char kNodeDiscoveryURL[] = "localhost:9229"; + +bool BrowserIdFromString(const std::string& browser_id_str, + std::string* serial, + std::string* browser_id) { + size_t colon_pos = browser_id_str.find(':'); + if (colon_pos == std::string::npos) + return false; + *serial = browser_id_str.substr(0, colon_pos); + *browser_id = browser_id_str.substr(colon_pos + 1); + return true; +} + +static void NoOp(int, const std::string&) {} + +} // namespace + +// static +DevToolsAndroidBridge::Factory* DevToolsAndroidBridge::Factory::GetInstance() { + return base::Singleton<DevToolsAndroidBridge::Factory>::get(); +} + +// static +DevToolsAndroidBridge* DevToolsAndroidBridge::Factory::GetForProfile( + Profile* profile) { + return static_cast<DevToolsAndroidBridge*>(GetInstance()-> + GetServiceForBrowserContext(profile->GetOriginalProfile(), true)); +} + +DevToolsAndroidBridge::Factory::Factory() + : BrowserContextKeyedServiceFactory( + "DevToolsAndroidBridge", + BrowserContextDependencyManager::GetInstance()) { +} + +DevToolsAndroidBridge::Factory::~Factory() {} + +KeyedService* DevToolsAndroidBridge::Factory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + Profile* profile = Profile::FromBrowserContext(context); + + return new DevToolsAndroidBridge(profile); +} + +scoped_refptr<content::DevToolsAgentHost> +DevToolsAndroidBridge::GetBrowserAgentHost( + scoped_refptr<RemoteBrowser> browser) { + DeviceMap::iterator it = device_map_.find(browser->serial()); + if (it == device_map_.end()) + return nullptr; + + return DevToolsDeviceDiscovery::CreateBrowserAgentHost(it->second, browser); +} + +void DevToolsAndroidBridge::SendJsonRequest( + const std::string& browser_id_str, + const std::string& url, + const JsonRequestCallback& callback) { + std::string serial; + std::string browser_id; + if (!BrowserIdFromString(browser_id_str, &serial, &browser_id)) { + callback.Run(net::ERR_FAILED, std::string()); + return; + } + DeviceMap::iterator it = device_map_.find(serial); + if (it == device_map_.end()) { + callback.Run(net::ERR_FAILED, std::string()); + return; + } + it->second->SendJsonRequest(browser_id, url, callback); +} + +void DevToolsAndroidBridge::OpenRemotePage(scoped_refptr<RemoteBrowser> browser, + const std::string& input_url) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + GURL gurl(input_url); + if (!gurl.is_valid()) { + gurl = GURL("http://" + input_url); + if (!gurl.is_valid()) + return; + } + std::string url = gurl.spec(); + RemoteBrowser::ParsedVersion parsed_version = browser->GetParsedVersion(); + + std::string query = net::EscapeQueryParamValue(url, false /* use_plus */); + std::string request = + base::StringPrintf(kNewPageRequestWithURL, query.c_str()); + SendJsonRequest(browser->GetId(), request, base::Bind(&NoOp)); +} + +DevToolsAndroidBridge::DevToolsAndroidBridge( + Profile* profile) + : profile_(profile), + device_manager_(AndroidDeviceManager::Create()), + port_forwarding_controller_(new PortForwardingController(profile)), + weak_factory_(this) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + pref_change_registrar_.Init(profile_->GetPrefs()); + pref_change_registrar_.Add(prefs::kDevToolsDiscoverUsbDevicesEnabled, + base::Bind(&DevToolsAndroidBridge::CreateDeviceProviders, + base::Unretained(this))); + pref_change_registrar_.Add(prefs::kDevToolsTCPDiscoveryConfig, + base::Bind(&DevToolsAndroidBridge::CreateDeviceProviders, + base::Unretained(this))); + pref_change_registrar_.Add(prefs::kDevToolsDiscoverTCPTargetsEnabled, + base::Bind(&DevToolsAndroidBridge::CreateDeviceProviders, + base::Unretained(this))); + base::ListValue* target_discovery = new base::ListValue(); + target_discovery->AppendString(kChromeDiscoveryURL); + target_discovery->AppendString(kNodeDiscoveryURL); + profile->GetPrefs()->SetDefaultPrefValue( + prefs::kDevToolsTCPDiscoveryConfig, target_discovery); + CreateDeviceProviders(); +} + +void DevToolsAndroidBridge::AddDeviceListListener( + DeviceListListener* listener) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + bool polling_was_off = !NeedsDeviceListPolling(); + device_list_listeners_.push_back(listener); + if (polling_was_off) + StartDeviceListPolling(); +} + +void DevToolsAndroidBridge::RemoveDeviceListListener( + DeviceListListener* listener) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DeviceListListeners::iterator it = std::find( + device_list_listeners_.begin(), device_list_listeners_.end(), listener); + DCHECK(it != device_list_listeners_.end()); + device_list_listeners_.erase(it); + if (!NeedsDeviceListPolling()) + StopDeviceListPolling(); +} + +void DevToolsAndroidBridge::AddDeviceCountListener( + DeviceCountListener* listener) { + device_count_listeners_.push_back(listener); + if (device_count_listeners_.size() == 1) + StartDeviceCountPolling(); +} + +void DevToolsAndroidBridge::RemoveDeviceCountListener( + DeviceCountListener* listener) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DeviceCountListeners::iterator it = std::find( + device_count_listeners_.begin(), device_count_listeners_.end(), listener); + DCHECK(it != device_count_listeners_.end()); + device_count_listeners_.erase(it); + if (device_count_listeners_.empty()) + StopDeviceCountPolling(); +} + +void DevToolsAndroidBridge::AddPortForwardingListener( + PortForwardingListener* listener) { + bool polling_was_off = !NeedsDeviceListPolling(); + port_forwarding_listeners_.push_back(listener); + if (polling_was_off) + StartDeviceListPolling(); +} + +void DevToolsAndroidBridge::RemovePortForwardingListener( + PortForwardingListener* listener) { + PortForwardingListeners::iterator it = std::find( + port_forwarding_listeners_.begin(), + port_forwarding_listeners_.end(), + listener); + DCHECK(it != port_forwarding_listeners_.end()); + port_forwarding_listeners_.erase(it); + if (!NeedsDeviceListPolling()) + StopDeviceListPolling(); +} + +DevToolsAndroidBridge::~DevToolsAndroidBridge() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(device_list_listeners_.empty()); + DCHECK(device_count_listeners_.empty()); + DCHECK(port_forwarding_listeners_.empty()); +} + +void DevToolsAndroidBridge::StartDeviceListPolling() { + device_discovery_.reset(new DevToolsDeviceDiscovery(device_manager_.get(), + base::Bind(&DevToolsAndroidBridge::ReceivedDeviceList, + base::Unretained(this)))); + if (!task_scheduler_.is_null()) + device_discovery_->SetScheduler(task_scheduler_); +} + +void DevToolsAndroidBridge::StopDeviceListPolling() { + device_discovery_.reset(); + device_map_.clear(); + port_forwarding_controller_->CloseAllConnections(); +} + +bool DevToolsAndroidBridge::NeedsDeviceListPolling() { + return !device_list_listeners_.empty() || !port_forwarding_listeners_.empty(); +} + +void DevToolsAndroidBridge::ReceivedDeviceList( + const CompleteDevices& complete_devices) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + device_map_.clear(); + RemoteDevices remote_devices; + for (const auto& pair : complete_devices) { + device_map_[pair.first->serial()] = pair.first; + remote_devices.push_back(pair.second); + } + + DeviceListListeners copy(device_list_listeners_); + for (DeviceListListeners::iterator it = copy.begin(); it != copy.end(); ++it) + (*it)->DeviceListChanged(remote_devices); + + ForwardingStatus status = + port_forwarding_controller_->DeviceListChanged(complete_devices); + PortForwardingListeners forwarding_listeners(port_forwarding_listeners_); + for (PortForwardingListeners::iterator it = forwarding_listeners.begin(); + it != forwarding_listeners.end(); ++it) { + (*it)->PortStatusChanged(status); + } +} + +void DevToolsAndroidBridge::StartDeviceCountPolling() { + device_count_callback_.Reset( + base::Bind(&DevToolsAndroidBridge::ReceivedDeviceCount, AsWeakPtr())); + RequestDeviceCount(device_count_callback_.callback()); +} + +void DevToolsAndroidBridge::StopDeviceCountPolling() { + device_count_callback_.Cancel(); +} + +void DevToolsAndroidBridge::RequestDeviceCount( + const base::Callback<void(int)>& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (device_count_listeners_.empty() || + !callback.Equals(device_count_callback_.callback())) + return; + + UsbDeviceProvider::CountDevices(callback); +} + +void DevToolsAndroidBridge::ReceivedDeviceCount(int count) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + DeviceCountListeners copy(device_count_listeners_); + for (DeviceCountListeners::iterator it = copy.begin(); it != copy.end(); ++it) + (*it)->DeviceCountChanged(count); + + if (device_count_listeners_.empty()) + return; + + task_scheduler_.Run( + base::Bind(&DevToolsAndroidBridge::RequestDeviceCount, + AsWeakPtr(), device_count_callback_.callback())); +} + +static std::set<net::HostPortPair> ParseTargetDiscoveryPreferenceValue( + const base::ListValue* preferenceValue) { + std::set<net::HostPortPair> targets; + if (!preferenceValue || preferenceValue->empty()) + return targets; + std::string address; + for (size_t i = 0; i < preferenceValue->GetSize(); i++) { + if (!preferenceValue->GetString(i, &address)) + continue; + net::HostPortPair target = net::HostPortPair::FromString(address); + if (target.IsEmpty()) { + LOG(WARNING) << "Invalid target: " << address; + continue; + } + targets.insert(target); + } + return targets; +} + +static scoped_refptr<TCPDeviceProvider> CreateTCPDeviceProvider( + const base::ListValue* targetDiscoveryConfig) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + std::set<net::HostPortPair> targets = + ParseTargetDiscoveryPreferenceValue(targetDiscoveryConfig); + if (targets.empty() && + !command_line->HasSwitch(switches::kRemoteDebuggingTargets)) + return nullptr; + std::string value = + command_line->GetSwitchValueASCII(switches::kRemoteDebuggingTargets); + std::vector<std::string> addresses = base::SplitString( + value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (const std::string& address : addresses) { + net::HostPortPair target = net::HostPortPair::FromString(address); + if (target.IsEmpty()) { + LOG(WARNING) << "Invalid target: " << address; + continue; + } + targets.insert(target); + } + if (targets.empty()) + return nullptr; + return new TCPDeviceProvider(targets); +} + +void DevToolsAndroidBridge::CreateDeviceProviders() { + AndroidDeviceManager::DeviceProviders device_providers; + PrefService* service = profile_->GetPrefs(); + const base::ListValue* targets = + service->GetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled) + ? service->GetList(prefs::kDevToolsTCPDiscoveryConfig) + : nullptr; + scoped_refptr<TCPDeviceProvider> provider = CreateTCPDeviceProvider(targets); + if (tcp_provider_callback_) + tcp_provider_callback_.Run(provider); + + if (provider) + device_providers.push_back(provider); + +#if BUILDFLAG(ENABLE_SERVICE_DISCOVERY) + device_providers.push_back(new CastDeviceProvider()); +#endif + + device_providers.push_back(new AdbDeviceProvider()); + + const PrefService::Preference* pref = + service->FindPreference(prefs::kDevToolsDiscoverUsbDevicesEnabled); + const base::Value* pref_value = pref->GetValue(); + + bool enabled; + if (pref_value->GetAsBoolean(&enabled) && enabled) { + device_providers.push_back(new UsbDeviceProvider(profile_)); + } + + device_manager_->SetDeviceProviders(device_providers); + if (NeedsDeviceListPolling()) { + StopDeviceListPolling(); + StartDeviceListPolling(); + } +} + +void DevToolsAndroidBridge::set_tcp_provider_callback_for_test( + TCPProviderCallback callback) { + tcp_provider_callback_ = callback; + CreateDeviceProviders(); +} diff --git a/chromium/chrome/browser/devtools/device/devtools_android_bridge.h b/chromium/chrome/browser/devtools/device/devtools_android_bridge.h new file mode 100644 index 00000000000..78736498ba4 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/devtools_android_bridge.h @@ -0,0 +1,193 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_DEVTOOLS_ANDROID_BRIDGE_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_DEVTOOLS_ANDROID_BRIDGE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/cancelable_callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/devtools/device/android_device_manager.h" +#include "chrome/browser/devtools/device/devtools_device_discovery.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/prefs/pref_change_registrar.h" +#include "content/public/browser/devtools_agent_host.h" +#include "ui/gfx/geometry/size.h" + +namespace base { +template<typename T> struct DefaultSingletonTraits; +} // namespace base + +namespace content { +class BrowserContext; +} + +class PortForwardingController; +class Profile; +class TCPDeviceProvider; + +class DevToolsAndroidBridge : public KeyedService { + public: + class Factory : public BrowserContextKeyedServiceFactory { + public: + // Returns singleton instance of DevToolsAndroidBridge. + static Factory* GetInstance(); + + // Returns DevToolsAndroidBridge associated with |profile|. + static DevToolsAndroidBridge* GetForProfile(Profile* profile); + + private: + friend struct base::DefaultSingletonTraits<Factory>; + + Factory(); + ~Factory() override; + + // BrowserContextKeyedServiceFactory overrides: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + DISALLOW_COPY_AND_ASSIGN(Factory); + }; + + using RemotePage = DevToolsDeviceDiscovery::RemotePage; + using RemotePages = DevToolsDeviceDiscovery::RemotePages; + using RemoteBrowser = DevToolsDeviceDiscovery::RemoteBrowser; + using RemoteBrowsers = DevToolsDeviceDiscovery::RemoteBrowsers; + using RemoteDevice = DevToolsDeviceDiscovery::RemoteDevice; + using RemoteDevices = DevToolsDeviceDiscovery::RemoteDevices; + using CompleteDevice = DevToolsDeviceDiscovery::CompleteDevice; + using CompleteDevices = DevToolsDeviceDiscovery::CompleteDevices; + using DeviceListCallback = DevToolsDeviceDiscovery::DeviceListCallback; + + using JsonRequestCallback = base::Callback<void(int, const std::string&)>; + + class DeviceListListener { + public: + virtual void DeviceListChanged(const RemoteDevices& devices) = 0; + protected: + virtual ~DeviceListListener() {} + }; + + explicit DevToolsAndroidBridge(Profile* profile); + void AddDeviceListListener(DeviceListListener* listener); + void RemoveDeviceListListener(DeviceListListener* listener); + + class DeviceCountListener { + public: + virtual void DeviceCountChanged(int count) = 0; + protected: + virtual ~DeviceCountListener() {} + }; + + void AddDeviceCountListener(DeviceCountListener* listener); + void RemoveDeviceCountListener(DeviceCountListener* listener); + + using PortStatus = int; + using PortStatusMap = std::map<int, PortStatus>; + using BrowserStatus = std::pair<scoped_refptr<RemoteBrowser>, PortStatusMap>; + using ForwardingStatus = std::vector<BrowserStatus>; + + class PortForwardingListener { + public: + using PortStatusMap = DevToolsAndroidBridge::PortStatusMap; + using BrowserStatus = DevToolsAndroidBridge::BrowserStatus; + using ForwardingStatus = DevToolsAndroidBridge::ForwardingStatus; + + virtual void PortStatusChanged(const ForwardingStatus&) = 0; + protected: + virtual ~PortForwardingListener() {} + }; + + void AddPortForwardingListener(PortForwardingListener* listener); + void RemovePortForwardingListener(PortForwardingListener* listener); + + void set_device_providers_for_test( + const AndroidDeviceManager::DeviceProviders& device_providers) { + device_manager_->SetDeviceProviders(device_providers); + } + + void set_task_scheduler_for_test( + base::Callback<void(const base::Closure&)> scheduler) { + task_scheduler_ = scheduler; + } + + using RemotePageCallback = base::Callback<void(scoped_refptr<RemotePage>)>; + void OpenRemotePage(scoped_refptr<RemoteBrowser> browser, + const std::string& url); + + scoped_refptr<content::DevToolsAgentHost> GetBrowserAgentHost( + scoped_refptr<RemoteBrowser> browser); + + void SendJsonRequest(const std::string& browser_id_str, + const std::string& url, + const JsonRequestCallback& callback); + + using TCPProviderCallback = + base::Callback<void(scoped_refptr<TCPDeviceProvider>)>; + void set_tcp_provider_callback_for_test(TCPProviderCallback callback); + + private: + friend struct content::BrowserThread::DeleteOnThread< + content::BrowserThread::UI>; + friend class base::DeleteHelper<DevToolsAndroidBridge>; + + ~DevToolsAndroidBridge() override; + + void StartDeviceListPolling(); + void StopDeviceListPolling(); + bool NeedsDeviceListPolling(); + + void RequestDeviceList(const DeviceListCallback& callback); + void ReceivedDeviceList(const CompleteDevices& complete_devices); + + void StartDeviceCountPolling(); + void StopDeviceCountPolling(); + void RequestDeviceCount(const base::Callback<void(int)>& callback); + void ReceivedDeviceCount(int count); + + static void ScheduleTaskDefault(const base::Closure& task); + + void CreateDeviceProviders(); + + base::WeakPtr<DevToolsAndroidBridge> AsWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + Profile* const profile_; + const std::unique_ptr<AndroidDeviceManager> device_manager_; + + using DeviceMap = + std::map<std::string, scoped_refptr<AndroidDeviceManager::Device> >; + DeviceMap device_map_; + + using DeviceListListeners = std::vector<DeviceListListener*>; + DeviceListListeners device_list_listeners_; + + using DeviceCountListeners = std::vector<DeviceCountListener*>; + DeviceCountListeners device_count_listeners_; + base::CancelableCallback<void(int)> device_count_callback_; + base::Callback<void(const base::Closure&)> task_scheduler_; + + using PortForwardingListeners = std::vector<PortForwardingListener*>; + PortForwardingListeners port_forwarding_listeners_; + std::unique_ptr<PortForwardingController> port_forwarding_controller_; + + PrefChangeRegistrar pref_change_registrar_; + + TCPProviderCallback tcp_provider_callback_; + + std::unique_ptr<DevToolsDeviceDiscovery> device_discovery_; + + base::WeakPtrFactory<DevToolsAndroidBridge> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsAndroidBridge); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_DEVTOOLS_ANDROID_BRIDGE_H_ diff --git a/chromium/chrome/browser/devtools/device/devtools_android_bridge_browsertest.cc b/chromium/chrome/browser/devtools/device/devtools_android_bridge_browsertest.cc new file mode 100644 index 00000000000..376a9bbf539 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/devtools_android_bridge_browsertest.cc @@ -0,0 +1,144 @@ +// Copyright 2016 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 <algorithm> +#include <array> + +#include "base/values.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" +#include "chrome/browser/devtools/device/tcp_device_provider.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/prefs/pref_service.h" + +class DevToolsAndroidBridgeTest : public InProcessBrowserTest { +}; + +static void assign_from_callback(scoped_refptr<TCPDeviceProvider>* store, + int* invocation_counter, + scoped_refptr<TCPDeviceProvider> value) { + (*invocation_counter)++; + *store = value; +} + +static std::string SetToString(const std::set<std::string>& values) { + std::ostringstream result; + std::copy(values.begin(), values.end(), + std::ostream_iterator<std::string>(result, ", ")); + std::string result_string = result.str(); + return result_string.substr(0, result_string.length() - 2); +} + +static std::string AllTargetsString( + scoped_refptr<TCPDeviceProvider> provider) { + std::set<std::string> actual; + for (const net::HostPortPair& hostport : provider->get_targets_for_test()) + actual.insert(hostport.ToString()); + return SetToString(actual); +} + +IN_PROC_BROWSER_TEST_F(DevToolsAndroidBridgeTest, DiscoveryListChanges) { + Profile* profile = browser()->profile(); + + PrefService* service = profile->GetPrefs(); + service->ClearPref(prefs::kDevToolsTCPDiscoveryConfig); + service->SetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled, true); + + DevToolsAndroidBridge* bridge = + DevToolsAndroidBridge::Factory::GetForProfile(profile); + + scoped_refptr<TCPDeviceProvider> provider; + int called = 0; + bridge->set_tcp_provider_callback_for_test( + base::Bind(assign_from_callback, &provider, &called)); + + EXPECT_LT(0, called); + EXPECT_NE(nullptr, provider); + + EXPECT_STREQ("localhost:9222, localhost:9229", + AllTargetsString(provider).c_str()); + + int invocations = called; + base::ListValue list; + list.AppendString("somehost:2000"); + + service->Set(prefs::kDevToolsTCPDiscoveryConfig, list); + + EXPECT_LT(invocations, called); + EXPECT_NE(nullptr, provider); + EXPECT_STREQ("somehost:2000", AllTargetsString(provider).c_str()); + + invocations = called; + list.Clear(); + service->Set(prefs::kDevToolsTCPDiscoveryConfig, list); + + EXPECT_LT(invocations, called); + EXPECT_EQ(nullptr, provider); + invocations = called; + + list.AppendString("b:1"); + list.AppendString("c:2"); + list.AppendString("<not really a good address."); + list.AppendString("d:3"); + list.AppendString("c:2"); + service->Set(prefs::kDevToolsTCPDiscoveryConfig, list); + + EXPECT_LT(invocations, called); + EXPECT_NE(nullptr, provider); + EXPECT_STREQ("b:1, c:2, d:3", AllTargetsString(provider).c_str()); +} + +IN_PROC_BROWSER_TEST_F(DevToolsAndroidBridgeTest, DefaultValues) { + Profile* profile = browser()->profile(); + + PrefService* service = profile->GetPrefs(); + DevToolsAndroidBridge::Factory::GetForProfile(profile); + service->ClearPref(prefs::kDevToolsDiscoverTCPTargetsEnabled); + service->ClearPref(prefs::kDevToolsTCPDiscoveryConfig); + + const base::ListValue* targets = + service->GetList(prefs::kDevToolsTCPDiscoveryConfig); + EXPECT_NE(nullptr, targets); + EXPECT_EQ(2ul, targets->GetSize()); + + std::set<std::string> actual; + for (size_t i = 0; i < targets->GetSize(); i++) { + std::string value; + targets->GetString(i, &value); + actual.insert(value); + } + EXPECT_STREQ("localhost:9222, localhost:9229", SetToString(actual).c_str()); + EXPECT_TRUE(service->GetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled)); +} + +IN_PROC_BROWSER_TEST_F(DevToolsAndroidBridgeTest, TCPEnableChange) { + Profile* profile = browser()->profile(); + + PrefService* service = profile->GetPrefs(); + service->ClearPref(prefs::kDevToolsTCPDiscoveryConfig); + service->ClearPref(prefs::kDevToolsDiscoverTCPTargetsEnabled); + + DevToolsAndroidBridge* bridge = + DevToolsAndroidBridge::Factory::GetForProfile(profile); + + scoped_refptr<TCPDeviceProvider> provider; + int called = 0; + bridge->set_tcp_provider_callback_for_test( + base::Bind(assign_from_callback, &provider, &called)); + + EXPECT_NE(nullptr, provider); + + service->SetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled, true); + + EXPECT_NE(nullptr, provider); + EXPECT_STREQ("localhost:9222, localhost:9229", + AllTargetsString(provider).c_str()); + + service->SetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled, false); + + EXPECT_EQ(nullptr, provider); +} diff --git a/chromium/chrome/browser/devtools/device/devtools_device_discovery.cc b/chromium/chrome/browser/devtools/device/devtools_device_discovery.cc new file mode 100644 index 00000000000..fb9901e38d8 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/devtools_device_discovery.cc @@ -0,0 +1,626 @@ +// 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/device/devtools_device_discovery.h" + +#include "base/bind.h" +#include "base/json/json_reader.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/user_metrics.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/devtools/devtools_protocol.h" +#include "chrome/browser/devtools/devtools_window.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/devtools_external_agent_proxy.h" +#include "content/public/browser/devtools_external_agent_proxy_delegate.h" +#include "net/base/escape.h" + +using content::BrowserThread; +using content::DevToolsAgentHost; +using RemoteBrowser = DevToolsDeviceDiscovery::RemoteBrowser; +using RemoteDevice = DevToolsDeviceDiscovery::RemoteDevice; +using RemotePage = DevToolsDeviceDiscovery::RemotePage; + +namespace { + +const char kPageListRequest[] = "/json"; +const char kVersionRequest[] = "/json/version"; +const char kClosePageRequest[] = "/json/close/%s"; +const char kActivatePageRequest[] = "/json/activate/%s"; +const char kBrowserTargetSocket[] = "/devtools/browser"; +const int kPollingIntervalMs = 1000; + +const char kPageReloadCommand[] = "Page.reload"; + +const char kWebViewSocketPrefix[] = "webview_devtools_remote"; + +static void NoOp(int, const std::string&) {} + +static void ScheduleTaskDefault(const base::Closure& task) { + BrowserThread::PostDelayedTask( + BrowserThread::UI, + FROM_HERE, + task, + base::TimeDelta::FromMilliseconds(kPollingIntervalMs)); +} + +// ProtocolCommand ------------------------------------------------------------ + +class ProtocolCommand + : public AndroidDeviceManager::AndroidWebSocket::Delegate { + public: + ProtocolCommand( + scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& socket, + const std::string& target_path, + const std::string& command, + const base::Closure callback); + + private: + void OnSocketOpened() override; + void OnFrameRead(const std::string& message) override; + void OnSocketClosed() override; + ~ProtocolCommand() override; + + const std::string command_; + const base::Closure callback_; + std::unique_ptr<AndroidDeviceManager::AndroidWebSocket> web_socket_; + + DISALLOW_COPY_AND_ASSIGN(ProtocolCommand); +}; + +ProtocolCommand::ProtocolCommand( + scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& socket, + const std::string& target_path, + const std::string& command, + const base::Closure callback) + : command_(command), + callback_(callback), + web_socket_(device->CreateWebSocket(socket, target_path, this)) { +} + +void ProtocolCommand::OnSocketOpened() { + web_socket_->SendFrame(command_); +} + +void ProtocolCommand::OnFrameRead(const std::string& message) { + delete this; +} + +void ProtocolCommand::OnSocketClosed() { + delete this; +} + +ProtocolCommand::~ProtocolCommand() { + if (!callback_.is_null()) + callback_.Run(); +} + +// AgentHostDelegate ---------------------------------------------------------- + +class AgentHostDelegate + : public content::DevToolsExternalAgentProxyDelegate, + public AndroidDeviceManager::AndroidWebSocket::Delegate { + public: + static scoped_refptr<content::DevToolsAgentHost> GetOrCreateAgentHost( + scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& browser_id, + const std::string& local_id, + const std::string& target_path, + const std::string& type, + base::DictionaryValue* value); + ~AgentHostDelegate() override; + + private: + AgentHostDelegate( + scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& browser_id, + const std::string& local_id, + const std::string& target_path, + const std::string& type, + base::DictionaryValue* value); + // DevToolsExternalAgentProxyDelegate overrides. + void Attach(content::DevToolsExternalAgentProxy* proxy) override; + void Detach() override; + std::string GetType() override; + std::string GetTitle() override; + std::string GetDescription() override; + GURL GetURL() override; + GURL GetFaviconURL() override; + std::string GetFrontendURL() override; + bool Activate() override; + void Reload() override; + bool Close() override; + base::TimeTicks GetLastActivityTime() override; + void SendMessageToBackend(const std::string& message) override; + + void OnSocketOpened() override; + void OnFrameRead(const std::string& message) override; + void OnSocketClosed() override; + + void SendProtocolCommand(const std::string& target_path, + const std::string& method, + std::unique_ptr<base::DictionaryValue> params, + const base::Closure callback); + + scoped_refptr<AndroidDeviceManager::Device> device_; + std::string browser_id_; + std::string local_id_; + std::string target_path_; + std::string remote_type_; + std::string remote_id_; + std::string frontend_url_; + std::string title_; + std::string description_; + GURL url_; + GURL favicon_url_; + bool socket_opened_; + std::vector<std::string> pending_messages_; + std::unique_ptr<AndroidDeviceManager::AndroidWebSocket> web_socket_; + content::DevToolsAgentHost* agent_host_; + content::DevToolsExternalAgentProxy* proxy_; + DISALLOW_COPY_AND_ASSIGN(AgentHostDelegate); +}; + +static std::string GetStringProperty(base::DictionaryValue* value, + const std::string& name) { + std::string result; + value->GetString(name, &result); + return result; +} + +static std::string BuildUniqueTargetId( + const std::string& serial, + const std::string& browser_id, + base::DictionaryValue* value) { + return base::StringPrintf("%s:%s:%s", serial.c_str(), + browser_id.c_str(), GetStringProperty(value, "id").c_str()); +} + +static std::string GetFrontendURLFromValue(base::DictionaryValue* value) { + std::string frontend_url = GetStringProperty(value, "devtoolsFrontendUrl"); + size_t ws_param = frontend_url.find("?ws"); + if (ws_param != std::string::npos) + frontend_url = frontend_url.substr(0, ws_param); + if (base::StartsWith(frontend_url, "http:", base::CompareCase::SENSITIVE)) + frontend_url = "https:" + frontend_url.substr(5); + return frontend_url; +} + +static std::string GetTargetPath(base::DictionaryValue* value) { + std::string target_path = GetStringProperty(value, "webSocketDebuggerUrl"); + + if (base::StartsWith(target_path, "ws://", base::CompareCase::SENSITIVE)) { + size_t pos = target_path.find("/", 5); + if (pos == std::string::npos) + pos = 5; + target_path = target_path.substr(pos); + } else { + target_path = std::string(); + } + return target_path; +} + +// static +scoped_refptr<content::DevToolsAgentHost> +AgentHostDelegate::GetOrCreateAgentHost( + scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& browser_id, + const std::string& local_id, + const std::string& target_path, + const std::string& type, + base::DictionaryValue* value) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + scoped_refptr<DevToolsAgentHost> result = + DevToolsAgentHost::GetForId(local_id); + if (result) + return result; + + AgentHostDelegate* delegate = new AgentHostDelegate( + device, browser_id, local_id, target_path, type, value); + result = content::DevToolsAgentHost::Forward( + local_id, base::WrapUnique(delegate)); + delegate->agent_host_ = result.get(); + return result; +} + +AgentHostDelegate::AgentHostDelegate( + scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& browser_id, + const std::string& local_id, + const std::string& target_path, + const std::string& type, + base::DictionaryValue* value) + : device_(device), + browser_id_(browser_id), + local_id_(local_id), + target_path_(target_path), + remote_type_(type), + remote_id_(value ? GetStringProperty(value, "id") : ""), + frontend_url_(value ? GetFrontendURLFromValue(value) : ""), + title_(value ? base::UTF16ToUTF8(net::UnescapeForHTML(base::UTF8ToUTF16( + GetStringProperty(value, "title")))) : ""), + description_(value ? GetStringProperty(value, "description") : ""), + url_(GURL(value ? GetStringProperty(value, "url") : "")), + favicon_url_(GURL(value ? GetStringProperty(value, "faviconUrl") : "")), + socket_opened_(false), + agent_host_(nullptr), + proxy_(nullptr) { +} + +AgentHostDelegate::~AgentHostDelegate() { +} + +void AgentHostDelegate::Attach(content::DevToolsExternalAgentProxy* proxy) { + proxy_ = proxy; + base::RecordAction( + base::StartsWith(browser_id_, kWebViewSocketPrefix, + base::CompareCase::SENSITIVE) + ? base::UserMetricsAction("DevTools_InspectAndroidWebView") + : base::UserMetricsAction("DevTools_InspectAndroidPage")); + web_socket_.reset( + device_->CreateWebSocket(browser_id_, target_path_, this)); +} + +void AgentHostDelegate::Detach() { + web_socket_.reset(); + proxy_ = nullptr; +} + +std::string AgentHostDelegate::GetType() { + return remote_type_; +} + +std::string AgentHostDelegate::GetTitle() { + return title_; +} + +std::string AgentHostDelegate::GetDescription() { + return description_; +} + +GURL AgentHostDelegate::GetURL() { + return url_; +} + +GURL AgentHostDelegate::GetFaviconURL() { + return favicon_url_; +} + +std::string AgentHostDelegate::GetFrontendURL() { + return frontend_url_; +} + +bool AgentHostDelegate::Activate() { + std::string request = base::StringPrintf(kActivatePageRequest, + remote_id_.c_str()); + device_->SendJsonRequest(browser_id_, request, base::Bind(&NoOp)); + return true; +} + +void AgentHostDelegate::Reload() { + SendProtocolCommand(target_path_, kPageReloadCommand, nullptr, + base::Closure()); +} + +bool AgentHostDelegate::Close() { + std::string request = base::StringPrintf(kClosePageRequest, + remote_id_.c_str()); + device_->SendJsonRequest(browser_id_, request, base::Bind(&NoOp)); + return true; +} + +base::TimeTicks AgentHostDelegate::GetLastActivityTime() { + return base::TimeTicks(); +} + +void AgentHostDelegate::SendMessageToBackend(const std::string& message) { + // We could have detached due to physical connection being closed. + if (!proxy_) + return; + if (socket_opened_) + web_socket_->SendFrame(message); + else + pending_messages_.push_back(message); +} + +void AgentHostDelegate::OnSocketOpened() { + socket_opened_ = true; + for (std::vector<std::string>::iterator it = pending_messages_.begin(); + it != pending_messages_.end(); ++it) { + SendMessageToBackend(*it); + } + pending_messages_.clear(); +} + +void AgentHostDelegate::OnFrameRead(const std::string& message) { + if (proxy_) + proxy_->DispatchOnClientHost(message); +} + +void AgentHostDelegate::OnSocketClosed() { + content::DevToolsExternalAgentProxy* proxy = proxy_; + if (proxy) { + std::string message = "{ \"method\": \"Inspector.detached\", " + "\"params\": { \"reason\": \"Connection lost.\"} }"; + proxy->DispatchOnClientHost(message); + Detach(); + proxy->ConnectionClosed(); // May delete |this|. + } +} + +void AgentHostDelegate::SendProtocolCommand( + const std::string& target_path, + const std::string& method, + std::unique_ptr<base::DictionaryValue> params, + const base::Closure callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (target_path.empty()) + return; + new ProtocolCommand( + device_, browser_id_, target_path, + DevToolsProtocol::SerializeCommand(1, method, std::move(params)), + callback); +} + +} // namespace + +// DevToolsDeviceDiscovery::DiscoveryRequest ---------------------------------- + +class DevToolsDeviceDiscovery::DiscoveryRequest + : public base::RefCountedThreadSafe<DiscoveryRequest, + BrowserThread::DeleteOnUIThread> { + public: + DiscoveryRequest(AndroidDeviceManager* device_manager, + const DevToolsDeviceDiscovery::DeviceListCallback& callback); + private: + friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; + friend class base::DeleteHelper<DiscoveryRequest>; + virtual ~DiscoveryRequest(); + + void ReceivedDevices(const AndroidDeviceManager::Devices& devices); + void ReceivedDeviceInfo(scoped_refptr<AndroidDeviceManager::Device> device, + const AndroidDeviceManager::DeviceInfo& device_info); + void ReceivedVersion(scoped_refptr<RemoteBrowser>, + int result, + const std::string& response); + void ReceivedPages(scoped_refptr<AndroidDeviceManager::Device> device, + scoped_refptr<RemoteBrowser>, + int result, + const std::string& response); + + DevToolsDeviceDiscovery::DeviceListCallback callback_; + DevToolsDeviceDiscovery::CompleteDevices complete_devices_; +}; + +DevToolsDeviceDiscovery::DiscoveryRequest::DiscoveryRequest( + AndroidDeviceManager* device_manager, + const DevToolsDeviceDiscovery::DeviceListCallback& callback) + : callback_(callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + device_manager->QueryDevices( + base::Bind(&DiscoveryRequest::ReceivedDevices, this)); +} + +DevToolsDeviceDiscovery::DiscoveryRequest::~DiscoveryRequest() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + callback_.Run(complete_devices_); +} + +void DevToolsDeviceDiscovery::DiscoveryRequest::ReceivedDevices( + const AndroidDeviceManager::Devices& devices) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + for (const auto& device : devices) { + device->QueryDeviceInfo( + base::Bind(&DiscoveryRequest::ReceivedDeviceInfo, this, device)); + } +} + +void DevToolsDeviceDiscovery::DiscoveryRequest::ReceivedDeviceInfo( + scoped_refptr<AndroidDeviceManager::Device> device, + const AndroidDeviceManager::DeviceInfo& device_info) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + scoped_refptr<RemoteDevice> remote_device = + new RemoteDevice(device->serial(), device_info); + complete_devices_.push_back(std::make_pair(device, remote_device)); + for (RemoteBrowsers::iterator it = remote_device->browsers().begin(); + it != remote_device->browsers().end(); ++it) { + device->SendJsonRequest( + (*it)->socket(), + kVersionRequest, + base::Bind(&DiscoveryRequest::ReceivedVersion, this, *it)); + device->SendJsonRequest( + (*it)->socket(), + kPageListRequest, + base::Bind(&DiscoveryRequest::ReceivedPages, this, device, *it)); + } +} + +void DevToolsDeviceDiscovery::DiscoveryRequest::ReceivedVersion( + scoped_refptr<RemoteBrowser> browser, + int result, + const std::string& response) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (result < 0) + return; + // Parse version, append to package name if available, + std::unique_ptr<base::Value> value = base::JSONReader::Read(response); + base::DictionaryValue* dict; + if (value && value->GetAsDictionary(&dict)) { + std::string browser_name; + if (dict->GetString("Browser", &browser_name)) { + std::vector<std::string> parts = base::SplitString( + browser_name, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (parts.size() == 2) + browser->version_ = parts[1]; + else + browser->version_ = browser_name; + } + std::string package; + if (dict->GetString("Android-Package", &package)) { + browser->display_name_ = + AndroidDeviceManager::GetBrowserName(browser->socket(), package); + } + } +} + +void DevToolsDeviceDiscovery::DiscoveryRequest::ReceivedPages( + scoped_refptr<AndroidDeviceManager::Device> device, + scoped_refptr<RemoteBrowser> browser, + int result, + const std::string& response) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (result < 0) + return; + std::unique_ptr<base::Value> value = base::JSONReader::Read(response); + base::ListValue* list_value; + if (value && value->GetAsList(&list_value)) { + for (const auto& page_value : *list_value) { + const base::DictionaryValue* dict; + if (page_value.GetAsDictionary(&dict)) + browser->pages_.push_back( + new RemotePage(device, browser->browser_id_, *dict)); + } + } +} + +// DevToolsDeviceDiscovery::RemotePage ---------------------------------------- + +DevToolsDeviceDiscovery::RemotePage::RemotePage( + scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& browser_id, + const base::DictionaryValue& dict) + : device_(device), + browser_id_(browser_id), + dict_(dict.DeepCopy()) { +} + +DevToolsDeviceDiscovery::RemotePage::~RemotePage() { +} + +scoped_refptr<content::DevToolsAgentHost> +DevToolsDeviceDiscovery::RemotePage::CreateTarget() { + std::string local_id = BuildUniqueTargetId(device_->serial(), + browser_id_, + dict_.get()); + std::string target_path = GetTargetPath(dict_.get()); + std::string type = GetStringProperty(dict_.get(), "type"); + agent_host_ = AgentHostDelegate::GetOrCreateAgentHost( + device_, browser_id_, local_id, target_path, type, dict_.get()); + return agent_host_; +} + +// DevToolsDeviceDiscovery::RemoteBrowser ------------------------------------- + +DevToolsDeviceDiscovery::RemoteBrowser::RemoteBrowser( + const std::string& serial, + const AndroidDeviceManager::BrowserInfo& browser_info) + : serial_(serial), + browser_id_(browser_info.socket_name), + display_name_(browser_info.display_name), + user_(browser_info.user), + type_(browser_info.type) { +} + +bool DevToolsDeviceDiscovery::RemoteBrowser::IsChrome() { + return type_ == AndroidDeviceManager::BrowserInfo::kTypeChrome; +} + +std::string DevToolsDeviceDiscovery::RemoteBrowser::GetId() { + return serial() + ":" + socket(); +} + +DevToolsDeviceDiscovery::RemoteBrowser::ParsedVersion +DevToolsDeviceDiscovery::RemoteBrowser::GetParsedVersion() { + ParsedVersion result; + for (const base::StringPiece& part : + base::SplitStringPiece( + version_, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { + int value = 0; + base::StringToInt(part, &value); + result.push_back(value); + } + return result; +} + +DevToolsDeviceDiscovery::RemoteBrowser::~RemoteBrowser() { +} + +// DevToolsDeviceDiscovery::RemoteDevice -------------------------------------- + +DevToolsDeviceDiscovery::RemoteDevice::RemoteDevice( + const std::string& serial, + const AndroidDeviceManager::DeviceInfo& device_info) + : serial_(serial), + model_(device_info.model), + connected_(device_info.connected), + screen_size_(device_info.screen_size) { + for (std::vector<AndroidDeviceManager::BrowserInfo>::const_iterator it = + device_info.browser_info.begin(); + it != device_info.browser_info.end(); + ++it) { + browsers_.push_back(new RemoteBrowser(serial, *it)); + } +} + +DevToolsDeviceDiscovery::RemoteDevice::~RemoteDevice() { +} + +// DevToolsDeviceDiscovery ---------------------------------------------------- + +DevToolsDeviceDiscovery::DevToolsDeviceDiscovery( + AndroidDeviceManager* device_manager, + const DeviceListCallback& callback) + : device_manager_(device_manager), + callback_(callback), + task_scheduler_(base::Bind(&ScheduleTaskDefault)), + weak_factory_(this) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + RequestDeviceList(); +} + +DevToolsDeviceDiscovery::~DevToolsDeviceDiscovery() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); +} + +void DevToolsDeviceDiscovery::SetScheduler( + base::Callback<void(const base::Closure&)> scheduler) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + task_scheduler_ = scheduler; +} + +// static +scoped_refptr<content::DevToolsAgentHost> +DevToolsDeviceDiscovery::CreateBrowserAgentHost( + scoped_refptr<AndroidDeviceManager::Device> device, + scoped_refptr<RemoteBrowser> browser) { + return AgentHostDelegate::GetOrCreateAgentHost( + device, + browser->browser_id_, + "adb:" + browser->serial() + ":" + browser->socket(), + kBrowserTargetSocket, DevToolsAgentHost::kTypeBrowser, nullptr); +} + +void DevToolsDeviceDiscovery::RequestDeviceList() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + new DiscoveryRequest( + device_manager_, + base::Bind(&DevToolsDeviceDiscovery::ReceivedDeviceList, + weak_factory_.GetWeakPtr())); +} + +void DevToolsDeviceDiscovery::ReceivedDeviceList( + const CompleteDevices& complete_devices) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + task_scheduler_.Run(base::Bind(&DevToolsDeviceDiscovery::RequestDeviceList, + weak_factory_.GetWeakPtr())); + // |callback_| should be run last as it may destroy |this|. + callback_.Run(complete_devices); +} diff --git a/chromium/chrome/browser/devtools/device/devtools_device_discovery.h b/chromium/chrome/browser/devtools/device/devtools_device_discovery.h new file mode 100644 index 00000000000..6a2e66dc617 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/devtools_device_discovery.h @@ -0,0 +1,145 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_DEVTOOLS_DEVICE_DISCOVERY_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_DEVTOOLS_DEVICE_DISCOVERY_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/devtools/device/android_device_manager.h" +#include "content/public/browser/devtools_agent_host.h" + +class DevToolsDeviceDiscovery { + public: + class RemotePage : public base::RefCountedThreadSafe<RemotePage> { + public: + scoped_refptr<AndroidDeviceManager::Device> device() { return device_; } + const std::string& socket() { return browser_id_; } + const std::string& frontend_url() { return frontend_url_; } + scoped_refptr<content::DevToolsAgentHost> CreateTarget(); + + private: + friend class base::RefCountedThreadSafe<RemotePage>; + friend class DevToolsDeviceDiscovery; + + RemotePage(scoped_refptr<AndroidDeviceManager::Device> device, + const std::string& browser_id, + const base::DictionaryValue& dict); + + virtual ~RemotePage(); + + scoped_refptr<AndroidDeviceManager::Device> device_; + std::string browser_id_; + std::string frontend_url_; + std::unique_ptr<base::DictionaryValue> dict_; + scoped_refptr<content::DevToolsAgentHost> agent_host_; + + DISALLOW_COPY_AND_ASSIGN(RemotePage); + }; + + using RemotePages = std::vector<scoped_refptr<RemotePage>>; + + class RemoteBrowser : public base::RefCountedThreadSafe<RemoteBrowser> { + public: + const std::string& serial() { return serial_; } + const std::string& socket() { return browser_id_; } + const std::string& display_name() { return display_name_; } + const std::string& user() { return user_; } + const std::string& version() { return version_; } + const RemotePages& pages() { return pages_; } + + bool IsChrome(); + std::string GetId(); + + using ParsedVersion = std::vector<int>; + ParsedVersion GetParsedVersion(); + + private: + friend class base::RefCountedThreadSafe<RemoteBrowser>; + friend class DevToolsDeviceDiscovery; + + RemoteBrowser(const std::string& serial, + const AndroidDeviceManager::BrowserInfo& browser_info); + + virtual ~RemoteBrowser(); + + std::string serial_; + std::string browser_id_; + std::string display_name_; + std::string user_; + AndroidDeviceManager::BrowserInfo::Type type_; + std::string version_; + RemotePages pages_; + + DISALLOW_COPY_AND_ASSIGN(RemoteBrowser); + }; + + using RemoteBrowsers = std::vector<scoped_refptr<RemoteBrowser>>; + + class RemoteDevice : public base::RefCountedThreadSafe<RemoteDevice> { + public: + std::string serial() { return serial_; } + std::string model() { return model_; } + bool is_connected() { return connected_; } + RemoteBrowsers& browsers() { return browsers_; } + gfx::Size screen_size() { return screen_size_; } + + private: + friend class base::RefCountedThreadSafe<RemoteDevice>; + friend class DevToolsDeviceDiscovery; + + RemoteDevice(const std::string& serial, + const AndroidDeviceManager::DeviceInfo& device_info); + + virtual ~RemoteDevice(); + + std::string serial_; + std::string model_; + bool connected_; + RemoteBrowsers browsers_; + gfx::Size screen_size_; + + DISALLOW_COPY_AND_ASSIGN(RemoteDevice); + }; + + using RemoteDevices = std::vector<scoped_refptr<RemoteDevice>>; + + using CompleteDevice = + std::pair<scoped_refptr<AndroidDeviceManager::Device>, + scoped_refptr<RemoteDevice>>; + using CompleteDevices = std::vector<CompleteDevice>; + using DeviceListCallback = base::Callback<void(const CompleteDevices&)>; + + DevToolsDeviceDiscovery( + AndroidDeviceManager* device_manager, + const DeviceListCallback& callback); + ~DevToolsDeviceDiscovery(); + + void SetScheduler(base::Callback<void(const base::Closure&)> scheduler); + + static scoped_refptr<content::DevToolsAgentHost> CreateBrowserAgentHost( + scoped_refptr<AndroidDeviceManager::Device> device, + scoped_refptr<RemoteBrowser> browser); + + private: + class DiscoveryRequest; + + void RequestDeviceList(); + void ReceivedDeviceList(const CompleteDevices& complete_devices); + + AndroidDeviceManager* device_manager_; + const DeviceListCallback callback_; + base::Callback<void(const base::Closure&)> task_scheduler_; + base::WeakPtrFactory<DevToolsDeviceDiscovery> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsDeviceDiscovery); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_DEVTOOLS_DEVICE_DISCOVERY_H_ diff --git a/chromium/chrome/browser/devtools/device/port_forwarding_browsertest.cc b/chromium/chrome/browser/devtools/device/port_forwarding_browsertest.cc new file mode 100644 index 00000000000..016b64b7dba --- /dev/null +++ b/chromium/chrome/browser/devtools/device/port_forwarding_browsertest.cc @@ -0,0 +1,190 @@ +// 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 "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" +#include "chrome/browser/devtools/device/tcp_device_provider.h" +#include "chrome/browser/devtools/remote_debugging_server.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_switches.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_utils.h" +#include "net/test/embedded_test_server/embedded_test_server.h" + +namespace { +const char kPortForwardingTestPage[] = "/devtools/port_forwarding/main.html"; + +const int kDefaultDebuggingPort = 9223; +const int kAlternativeDebuggingPort = 9224; + +} + +class PortForwardingTest: public InProcessBrowserTest { + virtual int GetRemoteDebuggingPort() { + return kDefaultDebuggingPort; + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + InProcessBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitchASCII(switches::kRemoteDebuggingPort, + base::IntToString(GetRemoteDebuggingPort())); + } + + protected: + class Listener : public DevToolsAndroidBridge::PortForwardingListener { + public: + explicit Listener(Profile* profile) + : profile_(profile), + skip_empty_devices_(true) { + DevToolsAndroidBridge::Factory::GetForProfile(profile_)-> + AddPortForwardingListener(this); + } + + ~Listener() override { + DevToolsAndroidBridge::Factory::GetForProfile(profile_)-> + RemovePortForwardingListener(this); + } + + void PortStatusChanged(const ForwardingStatus& status) override { + if (status.empty() && skip_empty_devices_) + return; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); + } + + void set_skip_empty_devices(bool skip_empty_devices) { + skip_empty_devices_ = skip_empty_devices; + } + + private: + Profile* profile_; + bool skip_empty_devices_; + }; +}; + +// Flaky on all platforms. https://crbug.com/477696 +IN_PROC_BROWSER_TEST_F(PortForwardingTest, + DISABLED_LoadPageWithStyleAnsScript) { + Profile* profile = browser()->profile(); + + AndroidDeviceManager::DeviceProviders device_providers; + + device_providers.push_back( + TCPDeviceProvider::CreateForLocalhost(kDefaultDebuggingPort)); + DevToolsAndroidBridge::Factory::GetForProfile(profile)-> + set_device_providers_for_test(device_providers); + + ASSERT_TRUE(embedded_test_server()->Start()); + GURL original_url = embedded_test_server()->GetURL(kPortForwardingTestPage); + + std::string forwarding_port("8000"); + GURL forwarding_url(original_url.scheme() + "://" + + original_url.host() + ":" + forwarding_port + original_url.path()); + + PrefService* prefs = profile->GetPrefs(); + prefs->SetBoolean(prefs::kDevToolsPortForwardingEnabled, true); + + base::DictionaryValue config; + config.SetString( + forwarding_port, original_url.host() + ":" + original_url.port()); + prefs->Set(prefs::kDevToolsPortForwardingConfig, config); + + Listener wait_for_port_forwarding(profile); + content::RunMessageLoop(); + + RemoteDebuggingServer::EnableTetheringForDebug(); + + ui_test_utils::NavigateToURL(browser(), forwarding_url); + + content::RenderViewHost* rvh = browser()->tab_strip_model()-> + GetWebContentsAt(0)->GetRenderViewHost(); + + std::string result; + ASSERT_TRUE( + content::ExecuteScriptAndExtractString( + rvh, + "window.domAutomationController.send(document.title)", + &result)); + ASSERT_EQ("Port forwarding test", result) << "Document has not loaded."; + + ASSERT_TRUE( + content::ExecuteScriptAndExtractString( + rvh, + "window.domAutomationController.send(getBodyTextContent())", + &result)); + ASSERT_EQ("content", result) << "Javascript has not loaded."; + + ASSERT_TRUE( + content::ExecuteScriptAndExtractString( + rvh, + "window.domAutomationController.send(getBodyMarginLeft())", + &result)); + ASSERT_EQ("100px", result) << "CSS has not loaded."; + + // Test that disabling port forwarding is handled normally. + wait_for_port_forwarding.set_skip_empty_devices(false); + prefs->SetBoolean(prefs::kDevToolsPortForwardingEnabled, false); + content::RunMessageLoop(); +} + +class PortForwardingDisconnectTest : public PortForwardingTest { + int GetRemoteDebuggingPort() override { + return kAlternativeDebuggingPort; + } +}; + +IN_PROC_BROWSER_TEST_F(PortForwardingDisconnectTest, DisconnectOnRelease) { + Profile* profile = browser()->profile(); + + AndroidDeviceManager::DeviceProviders device_providers; + + scoped_refptr<TCPDeviceProvider> self_provider( + TCPDeviceProvider::CreateForLocalhost(kAlternativeDebuggingPort)); + device_providers.push_back(self_provider); + + DevToolsAndroidBridge::Factory::GetForProfile(profile)-> + set_device_providers_for_test(device_providers); + + ASSERT_TRUE(embedded_test_server()->Start()); + GURL original_url = embedded_test_server()->GetURL(kPortForwardingTestPage); + + std::string forwarding_port("8000"); + GURL forwarding_url(original_url.scheme() + "://" + + original_url.host() + ":" + forwarding_port + original_url.path()); + + PrefService* prefs = profile->GetPrefs(); + prefs->SetBoolean(prefs::kDevToolsPortForwardingEnabled, true); + + base::DictionaryValue config; + config.SetString( + forwarding_port, original_url.host() + ":" + original_url.port()); + prefs->Set(prefs::kDevToolsPortForwardingConfig, config); + + std::unique_ptr<Listener> wait_for_port_forwarding(new Listener(profile)); + content::RunMessageLoop(); + + base::RunLoop run_loop; + + self_provider->set_release_callback_for_test( + base::Bind(base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask), + base::ThreadTaskRunnerHandle::Get(), FROM_HERE, + run_loop.QuitWhenIdleClosure())); + wait_for_port_forwarding.reset(); + + content::RunThisRunLoop(&run_loop); +} diff --git a/chromium/chrome/browser/devtools/device/port_forwarding_controller.cc b/chromium/chrome/browser/devtools/device/port_forwarding_controller.cc new file mode 100644 index 00000000000..35587ac62e3 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/port_forwarding_controller.cc @@ -0,0 +1,500 @@ +// 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/device/port_forwarding_controller.h" + +#include <map> +#include <utility> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/browser/devtools/devtools_protocol.h" +#include "chrome/browser/devtools/devtools_protocol_constants.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/pref_names.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/address_list.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/dns/host_resolver.h" +#include "net/log/net_log_source.h" +#include "net/log/net_log_with_source.h" +#include "net/socket/tcp_client_socket.h" +#include "third_party/WebKit/public/public_features.h" + +using content::BrowserThread; + +namespace { + +const int kBufferSize = 16 * 1024; + +enum { + kStatusError = -3, + kStatusDisconnecting = -2, + kStatusConnecting = -1, + kStatusOK = 0, +}; + +namespace tethering = ::chrome::devtools::Tethering; + +static const char kDevToolsRemoteBrowserTarget[] = "/devtools/browser"; + +class SocketTunnel : public base::NonThreadSafe { + public: + static void StartTunnel(const std::string& host, + int port, + int result, + std::unique_ptr<net::StreamSocket> socket) { + if (result == net::OK) + new SocketTunnel(std::move(socket), host, port); + } + + private: + SocketTunnel(std::unique_ptr<net::StreamSocket> socket, + const std::string& host, + int port) + : remote_socket_(std::move(socket)), + pending_writes_(0), + pending_destruction_(false) { + host_resolver_ = net::HostResolver::CreateDefaultResolver(nullptr); + net::HostResolver::RequestInfo request_info(net::HostPortPair(host, port)); + int result = host_resolver_->Resolve( + request_info, net::DEFAULT_PRIORITY, &address_list_, + base::Bind(&SocketTunnel::OnResolved, base::Unretained(this)), + &request_, net::NetLogWithSource()); + if (result != net::ERR_IO_PENDING) + OnResolved(result); + } + + void OnResolved(int result) { + if (result < 0) { + SelfDestruct(); + return; + } + + host_socket_.reset(new net::TCPClientSocket(address_list_, nullptr, nullptr, + net::NetLogSource())); + result = host_socket_->Connect(base::Bind(&SocketTunnel::OnConnected, + base::Unretained(this))); + if (result != net::ERR_IO_PENDING) + OnConnected(result); + } + + void OnConnected(int result) { + if (result < 0) { + SelfDestruct(); + return; + } + + ++pending_writes_; // avoid SelfDestruct in first Pump + Pump(host_socket_.get(), remote_socket_.get()); + --pending_writes_; + if (pending_destruction_) { + SelfDestruct(); + } else { + Pump(remote_socket_.get(), host_socket_.get()); + } + } + + void Pump(net::StreamSocket* from, net::StreamSocket* to) { + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kBufferSize); + int result = from->Read( + buffer.get(), + kBufferSize, + base::Bind( + &SocketTunnel::OnRead, base::Unretained(this), from, to, buffer)); + if (result != net::ERR_IO_PENDING) + OnRead(from, to, buffer, result); + } + + void OnRead(net::StreamSocket* from, + net::StreamSocket* to, + scoped_refptr<net::IOBuffer> buffer, + int result) { + if (result <= 0) { + SelfDestruct(); + return; + } + + int total = result; + scoped_refptr<net::DrainableIOBuffer> drainable = + new net::DrainableIOBuffer(buffer.get(), total); + + ++pending_writes_; + result = to->Write(drainable.get(), + total, + base::Bind(&SocketTunnel::OnWritten, + base::Unretained(this), + drainable, + from, + to)); + if (result != net::ERR_IO_PENDING) + OnWritten(drainable, from, to, result); + } + + void OnWritten(scoped_refptr<net::DrainableIOBuffer> drainable, + net::StreamSocket* from, + net::StreamSocket* to, + int result) { + --pending_writes_; + if (result < 0) { + SelfDestruct(); + return; + } + + drainable->DidConsume(result); + if (drainable->BytesRemaining() > 0) { + ++pending_writes_; + result = to->Write(drainable.get(), + drainable->BytesRemaining(), + base::Bind(&SocketTunnel::OnWritten, + base::Unretained(this), + drainable, + from, + to)); + if (result != net::ERR_IO_PENDING) + OnWritten(drainable, from, to, result); + return; + } + + if (pending_destruction_) { + SelfDestruct(); + return; + } + Pump(from, to); + } + + void SelfDestruct() { + if (pending_writes_ > 0) { + pending_destruction_ = true; + return; + } + delete this; + } + + std::unique_ptr<net::StreamSocket> remote_socket_; + std::unique_ptr<net::StreamSocket> host_socket_; + std::unique_ptr<net::HostResolver> host_resolver_; + std::unique_ptr<net::HostResolver::Request> request_; + net::AddressList address_list_; + int pending_writes_; + bool pending_destruction_; +}; + +} // namespace + +class PortForwardingController::Connection + : public AndroidDeviceManager::AndroidWebSocket::Delegate { + public: + Connection(Registry* registry, + scoped_refptr<AndroidDeviceManager::Device> device, + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, + const ForwardingMap& forwarding_map); + ~Connection() override; + + const PortStatusMap& GetPortStatusMap(); + + void UpdateForwardingMap(const ForwardingMap& new_forwarding_map); + + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser() { + return browser_; + } + + private: + friend struct content::BrowserThread::DeleteOnThread< + content::BrowserThread::UI>; + friend class base::DeleteHelper<Connection>; + + typedef std::map<int, std::string> ForwardingMap; + typedef base::Callback<void(PortStatus)> CommandCallback; + typedef std::map<int, CommandCallback> CommandCallbackMap; + + void SerializeChanges(const std::string& method, + const ForwardingMap& old_map, + const ForwardingMap& new_map); + + void SendCommand(const std::string& method, int port); + bool ProcessResponse(const std::string& json); + + void ProcessBindResponse(int port, PortStatus status); + void ProcessUnbindResponse(int port, PortStatus status); + + // DevToolsAndroidBridge::AndroidWebSocket::Delegate implementation: + void OnSocketOpened() override; + void OnFrameRead(const std::string& message) override; + void OnSocketClosed() override; + + PortForwardingController::Registry* registry_; + scoped_refptr<AndroidDeviceManager::Device> device_; + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser_; + std::unique_ptr<AndroidDeviceManager::AndroidWebSocket> web_socket_; + int command_id_; + bool connected_; + ForwardingMap forwarding_map_; + CommandCallbackMap pending_responses_; + PortStatusMap port_status_; + + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +PortForwardingController::Connection::Connection( + Registry* registry, + scoped_refptr<AndroidDeviceManager::Device> device, + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, + const ForwardingMap& forwarding_map) + : registry_(registry), + device_(device), + browser_(browser), + command_id_(0), + connected_(false), + forwarding_map_(forwarding_map) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + (*registry_)[device_->serial()] = this; + web_socket_.reset( + device_->CreateWebSocket(browser->socket(), + kDevToolsRemoteBrowserTarget, this)); +} + +PortForwardingController::Connection::~Connection() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(registry_->find(device_->serial()) != registry_->end()); + registry_->erase(device_->serial()); +} + +void PortForwardingController::Connection::UpdateForwardingMap( + const ForwardingMap& new_forwarding_map) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (connected_) { + SerializeChanges(tethering::unbind::kName, + new_forwarding_map, forwarding_map_); + SerializeChanges(tethering::bind::kName, + forwarding_map_, new_forwarding_map); + } + forwarding_map_ = new_forwarding_map; +} + +void PortForwardingController::Connection::SerializeChanges( + const std::string& method, + const ForwardingMap& old_map, + const ForwardingMap& new_map) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + for (ForwardingMap::const_iterator new_it(new_map.begin()); + new_it != new_map.end(); ++new_it) { + int port = new_it->first; + const std::string& location = new_it->second; + ForwardingMap::const_iterator old_it = old_map.find(port); + if (old_it != old_map.end() && old_it->second == location) + continue; // The port points to the same location in both configs, skip. + + SendCommand(method, port); + } +} + +void PortForwardingController::Connection::SendCommand( + const std::string& method, int port) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue); + if (method == tethering::bind::kName) { + params->SetInteger(tethering::bind::kParamPort, port); + } else { + DCHECK_EQ(tethering::unbind::kName, method); + params->SetInteger(tethering::unbind::kParamPort, port); + } + int id = ++command_id_; + + if (method == tethering::bind::kName) { + pending_responses_[id] = + base::Bind(&Connection::ProcessBindResponse, + base::Unretained(this), port); +#if BUILDFLAG(DEBUG_DEVTOOLS) + port_status_[port] = kStatusConnecting; +#endif // BUILDFLAG(DEBUG_DEVTOOLS) + } else { + PortStatusMap::iterator it = port_status_.find(port); + if (it != port_status_.end() && it->second == kStatusError) { + // The bind command failed on this port, do not attempt unbind. + port_status_.erase(it); + return; + } + + pending_responses_[id] = + base::Bind(&Connection::ProcessUnbindResponse, + base::Unretained(this), port); +#if BUILDFLAG(DEBUG_DEVTOOLS) + port_status_[port] = kStatusDisconnecting; +#endif // BUILDFLAG(DEBUG_DEVTOOLS) + } + + web_socket_->SendFrame( + DevToolsProtocol::SerializeCommand(id, method, std::move(params))); +} + +bool PortForwardingController::Connection::ProcessResponse( + const std::string& message) { + int id = 0; + int error_code = 0; + if (!DevToolsProtocol::ParseResponse(message, &id, &error_code)) + return false; + + CommandCallbackMap::iterator it = pending_responses_.find(id); + if (it == pending_responses_.end()) + return false; + + it->second.Run(error_code ? kStatusError : kStatusOK); + pending_responses_.erase(it); + return true; +} + +void PortForwardingController::Connection::ProcessBindResponse( + int port, PortStatus status) { + port_status_[port] = status; +} + +void PortForwardingController::Connection::ProcessUnbindResponse( + int port, PortStatus status) { + PortStatusMap::iterator it = port_status_.find(port); + if (it == port_status_.end()) + return; + if (status == kStatusError) + it->second = status; + else + port_status_.erase(it); +} + +const PortForwardingController::PortStatusMap& +PortForwardingController::Connection::GetPortStatusMap() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + return port_status_; +} + +void PortForwardingController::Connection::OnSocketOpened() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + connected_ = true; + SerializeChanges(tethering::bind::kName, ForwardingMap(), forwarding_map_); +} + +void PortForwardingController::Connection::OnSocketClosed() { + delete this; +} + +void PortForwardingController::Connection::OnFrameRead( + const std::string& message) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (ProcessResponse(message)) + return; + + std::string method; + std::unique_ptr<base::DictionaryValue> params; + if (!DevToolsProtocol::ParseNotification(message, &method, ¶ms)) + return; + + if (method != tethering::accepted::kName || !params) + return; + + int port; + std::string connection_id; + if (!params->GetInteger(tethering::accepted::kParamPort, &port) || + !params->GetString(tethering::accepted::kParamConnectionId, + &connection_id)) + return; + + std::map<int, std::string>::iterator it = forwarding_map_.find(port); + if (it == forwarding_map_.end()) + return; + + std::string location = it->second; + std::vector<std::string> tokens = base::SplitString( + location, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + int destination_port = 0; + if (tokens.size() != 2 || !base::StringToInt(tokens[1], &destination_port)) + return; + std::string destination_host = tokens[0]; + + device_->OpenSocket( + connection_id.c_str(), + base::Bind(&SocketTunnel::StartTunnel, + destination_host, + destination_port)); +} + +PortForwardingController::PortForwardingController(Profile* profile) + : pref_service_(profile->GetPrefs()) { + pref_change_registrar_.Init(pref_service_); + base::Closure callback = base::Bind( + &PortForwardingController::OnPrefsChange, base::Unretained(this)); + pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled, callback); + pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig, callback); + OnPrefsChange(); +} + +PortForwardingController::~PortForwardingController() {} + +PortForwardingController::ForwardingStatus +PortForwardingController::DeviceListChanged( + const DevToolsAndroidBridge::CompleteDevices& complete_devices) { + ForwardingStatus status; + if (forwarding_map_.empty()) + return status; + + for (const auto& pair : complete_devices) { + scoped_refptr<AndroidDeviceManager::Device> device(pair.first); + scoped_refptr<DevToolsAndroidBridge::RemoteDevice> remote_device( + pair.second); + if (!remote_device->is_connected()) + continue; + Registry::iterator rit = registry_.find(remote_device->serial()); + if (rit == registry_.end()) { + if (remote_device->browsers().size() > 0) { + new Connection(®istry_, device, remote_device->browsers()[0], + forwarding_map_); + } + } else { + status.push_back(std::make_pair(rit->second->browser(), + rit->second->GetPortStatusMap())); + } + } + return status; +} + +void PortForwardingController::CloseAllConnections() { + Registry copy(registry_); + for (auto& entry : copy) + delete entry.second; +} + +void PortForwardingController::OnPrefsChange() { + forwarding_map_.clear(); + + if (pref_service_->GetBoolean(prefs::kDevToolsPortForwardingEnabled)) { + const base::DictionaryValue* dict = + pref_service_->GetDictionary(prefs::kDevToolsPortForwardingConfig); + for (base::DictionaryValue::Iterator it(*dict); + !it.IsAtEnd(); it.Advance()) { + int port_num; + std::string location; + if (base::StringToInt(it.key(), &port_num) && + dict->GetString(it.key(), &location)) + forwarding_map_[port_num] = location; + } + } + + if (!forwarding_map_.empty()) + UpdateConnections(); + else + CloseAllConnections(); +} + +void PortForwardingController::UpdateConnections() { + for (Registry::iterator it = registry_.begin(); it != registry_.end(); ++it) + it->second->UpdateForwardingMap(forwarding_map_); +} diff --git a/chromium/chrome/browser/devtools/device/port_forwarding_controller.h b/chromium/chrome/browser/devtools/device/port_forwarding_controller.h new file mode 100644 index 00000000000..6d9e2d59b9d --- /dev/null +++ b/chromium/chrome/browser/devtools/device/port_forwarding_controller.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_PORT_FORWARDING_CONTROLLER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_PORT_FORWARDING_CONTROLLER_H_ + +#include <map> +#include <string> + +#include "base/macros.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/prefs/pref_change_registrar.h" + +class PrefService; +class Profile; + +class PortForwardingController { + public: + typedef DevToolsAndroidBridge::PortStatus PortStatus; + typedef DevToolsAndroidBridge::PortStatusMap PortStatusMap; + typedef DevToolsAndroidBridge::BrowserStatus BrowserStatus; + typedef DevToolsAndroidBridge::ForwardingStatus ForwardingStatus; + + explicit PortForwardingController(Profile* profile); + + virtual ~PortForwardingController(); + + ForwardingStatus DeviceListChanged( + const DevToolsAndroidBridge::CompleteDevices& complete_devices); + void CloseAllConnections(); + + private: + class Connection; + typedef std::map<std::string, Connection*> Registry; + + void OnPrefsChange(); + + void UpdateConnections(); + + PrefService* pref_service_; + PrefChangeRegistrar pref_change_registrar_; + Registry registry_; + + typedef std::map<int, std::string> ForwardingMap; + ForwardingMap forwarding_map_; + + DISALLOW_COPY_AND_ASSIGN(PortForwardingController); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_PORT_FORWARDING_CONTROLLER_H_ diff --git a/chromium/chrome/browser/devtools/device/tcp_device_provider.cc b/chromium/chrome/browser/devtools/device/tcp_device_provider.cc new file mode 100644 index 00000000000..29d3868e3e8 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/tcp_device_provider.cc @@ -0,0 +1,140 @@ +// 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/device/tcp_device_provider.h" + +#include <utility> + +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/devtools/device/adb/adb_client_socket.h" +#include "net/base/net_errors.h" +#include "net/dns/host_resolver.h" +#include "net/log/net_log_source.h" +#include "net/log/net_log_with_source.h" +#include "net/socket/tcp_client_socket.h" + +namespace { + +const char kDeviceModel[] = "Remote Target"; +const char kBrowserName[] = "Target"; + +static void RunSocketCallback( + const AndroidDeviceManager::SocketCallback& callback, + std::unique_ptr<net::StreamSocket> socket, + int result) { + callback.Run(result, std::move(socket)); +} + +class ResolveHostAndOpenSocket final { + public: + ResolveHostAndOpenSocket(const net::HostPortPair& address, + const AdbClientSocket::SocketCallback& callback) + : callback_(callback) { + host_resolver_ = net::HostResolver::CreateDefaultResolver(nullptr); + net::HostResolver::RequestInfo request_info(address); + int result = host_resolver_->Resolve( + request_info, net::DEFAULT_PRIORITY, &address_list_, + base::Bind(&ResolveHostAndOpenSocket::OnResolved, + base::Unretained(this)), + &request_, net::NetLogWithSource()); + if (result != net::ERR_IO_PENDING) + OnResolved(result); + } + + private: + void OnResolved(int result) { + if (result < 0) { + RunSocketCallback(callback_, nullptr, result); + delete this; + return; + } + std::unique_ptr<net::StreamSocket> socket(new net::TCPClientSocket( + address_list_, NULL, NULL, net::NetLogSource())); + net::StreamSocket* socket_ptr = socket.get(); + net::CompletionCallback on_connect = + base::Bind(&RunSocketCallback, callback_, base::Passed(&socket)); + result = socket_ptr->Connect(on_connect); + if (result != net::ERR_IO_PENDING) + on_connect.Run(result); + delete this; + } + + std::unique_ptr<net::HostResolver> host_resolver_; + std::unique_ptr<net::HostResolver::Request> request_; + net::AddressList address_list_; + AdbClientSocket::SocketCallback callback_; +}; + +} // namespace + +scoped_refptr<TCPDeviceProvider> TCPDeviceProvider::CreateForLocalhost( + uint16_t port) { + TCPDeviceProvider::HostPortSet targets; + targets.insert(net::HostPortPair("127.0.0.1", port)); + return new TCPDeviceProvider(targets); +} + +TCPDeviceProvider::TCPDeviceProvider(const HostPortSet& targets) + : targets_(targets) { +} + +void TCPDeviceProvider::QueryDevices(const SerialsCallback& callback) { + std::vector<std::string> result; + for (const net::HostPortPair& target : targets_) { + const std::string& host = target.host(); + if (base::ContainsValue(result, host)) + continue; + result.push_back(host); + } + callback.Run(result); +} + +void TCPDeviceProvider::QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) { + AndroidDeviceManager::DeviceInfo device_info; + device_info.model = kDeviceModel; + device_info.connected = true; + + for (const net::HostPortPair& target : targets_) { + if (serial != target.host()) + continue; + AndroidDeviceManager::BrowserInfo browser_info; + browser_info.socket_name = base::UintToString(target.port()); + browser_info.display_name = kBrowserName; + browser_info.type = AndroidDeviceManager::BrowserInfo::kTypeChrome; + + device_info.browser_info.push_back(browser_info); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(callback, device_info)); +} + +void TCPDeviceProvider::OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) { + // Use plain socket for remote debugging and port forwarding on Desktop + // (debugging purposes). + int port; + base::StringToInt(socket_name, &port); + net::HostPortPair host_port(serial, port); + new ResolveHostAndOpenSocket(host_port, callback); +} + +void TCPDeviceProvider::ReleaseDevice(const std::string& serial) { + if (!release_callback_.is_null()) + release_callback_.Run(); +} + +void TCPDeviceProvider::set_release_callback_for_test( + const base::Closure& callback) { + release_callback_ = callback; +} + +TCPDeviceProvider::~TCPDeviceProvider() { +} diff --git a/chromium/chrome/browser/devtools/device/tcp_device_provider.h b/chromium/chrome/browser/devtools/device/tcp_device_provider.h new file mode 100644 index 00000000000..25ce2a81cfd --- /dev/null +++ b/chromium/chrome/browser/devtools/device/tcp_device_provider.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_TCP_DEVICE_PROVIDER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_TCP_DEVICE_PROVIDER_H_ + +#include <stdint.h> + +#include <set> + +#include "chrome/browser/devtools/device/android_device_manager.h" +#include "net/base/host_port_pair.h" + +// Instantiate this class only in a test and/or when the DEBUG_DEVTOOLS +// BUILDFLAG is set. +class TCPDeviceProvider : public AndroidDeviceManager::DeviceProvider { + public: + static scoped_refptr<TCPDeviceProvider> CreateForLocalhost(uint16_t port); + + using HostPortSet = std::set<net::HostPortPair>; + explicit TCPDeviceProvider(const HostPortSet& targets); + + void QueryDevices(const SerialsCallback& callback) override; + + void QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) override; + + void OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) override; + + void ReleaseDevice(const std::string& serial) override; + + void set_release_callback_for_test(const base::Closure& callback); + + HostPortSet get_targets_for_test() { return targets_; } + + private: + ~TCPDeviceProvider() override; + + HostPortSet targets_; + base::Closure release_callback_; +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_TCP_DEVICE_PROVIDER_H_ diff --git a/chromium/chrome/browser/devtools/device/usb/android_rsa.cc b/chromium/chrome/browser/devtools/device/usb/android_rsa.cc new file mode 100644 index 00000000000..e9a26b14602 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/android_rsa.cc @@ -0,0 +1,280 @@ +// 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/device/usb/android_rsa.h" + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#include <limits> +#include <memory> + +#include "base/base64.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/pref_names.h" +#include "components/sync_preferences/pref_service_syncable.h" +#include "crypto/rsa_private_key.h" +#include "crypto/signature_creator.h" +#include "net/cert/asn1_util.h" + +namespace { + +const size_t kRSANumWords = 64; +const size_t kBigIntSize = 1024; + +static const char kDummyRSAPublicKey[] = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6OSJ64q+ZLg7VV2ojEPh5TRbYjwbT" + "TifSPeFIV45CHnbTWYiiIn41wrozpYizNsMWZUBjdah1N78WVhbyDrnr0bDgFp+gXjfVppa3I" + "gjiohEcemK3omXi3GDMK8ERhriLUKfQS842SXtQ8I+KoZtpCkGM//0h7+P+Rhm0WwdipIRMhR" + "8haNAeyDiiCvqJcvevv2T52vqKtS3aWz+GjaTJJLVWydEpz9WdvWeLfFVhe2ZnqwwZNa30Qoj" + "fsnvjaMwK2MU7uYfRBPuvLyK5QESWBpArNDd6ULl8Y+NU6kwNOVDc87OASCVEM1gw2IMi2mo2" + "WO5ywp0UWRiGZCkK+wOFQIDAQAB"; + +typedef struct RSAPublicKey { + int len; // Length of n[] in number of uint32_t + uint32_t n0inv; // -1 / n[0] mod 2^32 + uint32_t n[kRSANumWords]; // modulus as little endian array + uint32_t rr[kRSANumWords]; // R^2 as little endian array + int exponent; // 3 or 65537 +} RSAPublicKey; + +// http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm +// a * x + b * y = gcd(a, b) = d +void ExtendedEuclid(uint64_t a, + uint64_t b, + uint64_t* x, + uint64_t* y, + uint64_t* d) { + uint64_t x1 = 0, x2 = 1, y1 = 1, y2 = 0; + + while (b > 0) { + uint64_t q = a / b; + uint64_t r = a % b; + *x = x2 - q * x1; + *y = y2 - q * y1; + a = b; + b = r; + x2 = x1; + x1 = *x; + y2 = y1; + y1 = *y; + } + + *d = a; + *x = x2; + *y = y2; +} + +uint32_t ModInverse(uint64_t a, uint64_t m) { + uint64_t d, x, y; + ExtendedEuclid(a, m, &x, &y, &d); + if (d == 1) + return static_cast<uint32_t>(x); + return 0; +} + +uint32_t* BnNew() { + uint32_t* result = new uint32_t[kBigIntSize]; + memset(result, 0, kBigIntSize * sizeof(uint32_t)); + return result; +} + +void BnFree(uint32_t* a) { + delete[] a; +} + +uint32_t* BnCopy(uint32_t* a) { + uint32_t* result = new uint32_t[kBigIntSize]; + memcpy(result, a, kBigIntSize * sizeof(uint32_t)); + return result; +} + +uint32_t* BnMul(uint32_t* a, uint32_t b) { + uint32_t* result = BnNew(); + uint64_t carry_over = 0; + for (size_t i = 0; i < kBigIntSize; ++i) { + carry_over += static_cast<uint64_t>(a[i]) * b; + result[i] = carry_over & std::numeric_limits<uint32_t>::max(); + carry_over >>= 32; + } + return result; +} + +void BnSub(uint32_t* a, uint32_t* b) { + int carry_over = 0; + for (size_t i = 0; i < kBigIntSize; ++i) { + int64_t sub = static_cast<int64_t>(a[i]) - b[i] - carry_over; + carry_over = 0; + if (sub < 0) { + carry_over = 1; + sub += 0x100000000LL; + } + a[i] = static_cast<uint32_t>(sub); + } +} + +void BnLeftShift(uint32_t* a, int offset) { + for (int i = kBigIntSize - offset - 1; i >= 0; --i) + a[i + offset] = a[i]; + for (int i = 0; i < offset; ++i) + a[i] = 0; +} + +int BnCompare(uint32_t* a, uint32_t* b) { + for (int i = kBigIntSize - 1; i >= 0; --i) { + if (a[i] > b[i]) + return 1; + if (a[i] < b[i]) + return -1; + } + return 0; +} + +uint64_t BnGuess(uint32_t* a, uint32_t* b, uint64_t from, uint64_t to) { + if (from + 1 >= to) + return from; + + uint64_t guess = (from + to) / 2; + uint32_t* t = BnMul(b, static_cast<uint32_t>(guess)); + int result = BnCompare(a, t); + BnFree(t); + if (result > 0) + return BnGuess(a, b, guess, to); + if (result < 0) + return BnGuess(a, b, from, guess); + return guess; +} + +void BnDiv(uint32_t* a, uint32_t* b, uint32_t** pq, uint32_t** pr) { + if (BnCompare(a, b) < 0) { + if (pq) + *pq = BnNew(); + if (pr) + *pr = BnCopy(a); + return; + } + + int oa = kBigIntSize - 1; + int ob = kBigIntSize - 1; + for (; oa > 0 && !a[oa]; --oa) {} + for (; ob > 0 && !b[ob]; --ob) {} + uint32_t* q = BnNew(); + uint32_t* ca = BnCopy(a); + + int digit = a[oa] < b[ob] ? oa - ob - 1 : oa - ob; + + for (; digit >= 0; --digit) { + uint32_t* shifted_b = BnCopy(b); + BnLeftShift(shifted_b, digit); + uint32_t value = static_cast<uint32_t>(BnGuess( + ca, shifted_b, 0, + static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1)); + q[digit] = value; + uint32_t* t = BnMul(shifted_b, value); + BnSub(ca, t); + BnFree(t); + BnFree(shifted_b); + } + + if (pq) + *pq = q; + else + BnFree(q); + if (pr) + *pr = ca; + else + BnFree(ca); +} + +} // namespace + +std::unique_ptr<crypto::RSAPrivateKey> AndroidRSAPrivateKey(Profile* profile) { + std::string encoded_key = + profile->GetPrefs()->GetString(prefs::kDevToolsAdbKey); + std::string decoded_key; + std::unique_ptr<crypto::RSAPrivateKey> key; + if (!encoded_key.empty() && base::Base64Decode(encoded_key, &decoded_key)) { + std::vector<uint8_t> key_info(decoded_key.begin(), decoded_key.end()); + key = crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_info); + } + if (!key) { + key = crypto::RSAPrivateKey::Create(2048); + std::vector<uint8_t> key_info; + if (!key || !key->ExportPrivateKey(&key_info)) + return nullptr; + + std::string key_string(key_info.begin(), key_info.end()); + base::Base64Encode(key_string, &encoded_key); + profile->GetPrefs()->SetString(prefs::kDevToolsAdbKey, + encoded_key); + } + return key; +} + +std::string AndroidRSAPublicKey(crypto::RSAPrivateKey* key) { + std::vector<uint8_t> public_key; + if (!key) + return kDummyRSAPublicKey; + + key->ExportPublicKey(&public_key); + std::string asn1(public_key.begin(), public_key.end()); + + base::StringPiece pk; + if (!net::asn1::ExtractSubjectPublicKeyFromSPKI(asn1, &pk)) + return kDummyRSAPublicKey; + + // Skip 10 byte asn1 prefix to the modulus. + std::vector<uint8_t> pk_data(pk.data() + 10, pk.data() + pk.length()); + uint32_t* n = BnNew(); + for (size_t i = 0; i < kRSANumWords; ++i) { + uint32_t t = pk_data[4 * i]; + t = t << 8; + t += pk_data[4 * i + 1]; + t = t << 8; + t += pk_data[4 * i + 2]; + t = t << 8; + t += pk_data[4 * i + 3]; + n[kRSANumWords - i - 1] = t; + } + uint64_t n0 = n[0]; + + RSAPublicKey pkey; + pkey.len = kRSANumWords; + pkey.exponent = 65537; // Fixed public exponent + pkey.n0inv = 0 - ModInverse(n0, 0x100000000LL); + if (pkey.n0inv == 0) + return kDummyRSAPublicKey; + + uint32_t* r = BnNew(); + r[kRSANumWords * 2] = 1; + + uint32_t* rr; + BnDiv(r, n, NULL, &rr); + + for (size_t i = 0; i < kRSANumWords; ++i) { + pkey.n[i] = n[i]; + pkey.rr[i] = rr[i]; + } + + BnFree(n); + BnFree(r); + BnFree(rr); + + std::string output; + std::string input(reinterpret_cast<char*>(&pkey), sizeof(pkey)); + base::Base64Encode(input, &output); + return output; +} + +std::string AndroidRSASign(crypto::RSAPrivateKey* key, + const std::string& body) { + std::vector<uint8_t> digest(body.begin(), body.end()); + std::vector<uint8_t> result; + if (!crypto::SignatureCreator::Sign(key, crypto::SignatureCreator::SHA1, + digest.data(), digest.size(), &result)) { + return std::string(); + } + return std::string(result.begin(), result.end()); +} diff --git a/chromium/chrome/browser/devtools/device/usb/android_rsa.h b/chromium/chrome/browser/devtools/device/usb/android_rsa.h new file mode 100644 index 00000000000..4942589b6ce --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/android_rsa.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_RSA_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_RSA_H_ + +#include <memory> +#include <string> + +namespace crypto { +class RSAPrivateKey; +} + +class Profile; + +std::unique_ptr<crypto::RSAPrivateKey> AndroidRSAPrivateKey(Profile* profile); + +std::string AndroidRSAPublicKey(crypto::RSAPrivateKey* key); + +std::string AndroidRSASign(crypto::RSAPrivateKey* key, + const std::string& body); + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_RSA_H_ diff --git a/chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc b/chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc new file mode 100644 index 00000000000..dec93dfb8bc --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc @@ -0,0 +1,790 @@ +// 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 <stddef.h> +#include <stdint.h> + +#include <algorithm> +#include <unordered_map> +#include <utility> + +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/devtools/device/adb/mock_adb_server.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" +#include "chrome/browser/devtools/device/usb/android_usb_device.h" +#include "chrome/browser/devtools/device/usb/usb_device_provider.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_utils.h" +#include "device/base/device_client.h" +#include "device/usb/mock_usb_service.h" +#include "device/usb/usb_descriptors.h" +#include "device/usb/usb_device.h" +#include "device/usb/usb_device_handle.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; +using device::DeviceClient; +using device::MockUsbService; +using device::UsbConfigDescriptor; +using device::UsbDevice; +using device::UsbDeviceHandle; +using device::UsbEndpointDescriptor; +using device::UsbInterfaceDescriptor; +using device::UsbService; +using device::UsbSynchronizationType; +using device::UsbTransferDirection; +using device::UsbTransferStatus; +using device::UsbTransferType; +using device::UsbUsageType; + +namespace { + +struct NoConfigTraits { + static const int kClass = 0xff; + static const int kSubclass = 0x42; + static const int kProtocol = 0x1; + static const bool kBreaks = false; + static const bool kConfigured = false; +}; + +struct AndroidTraits { + static const int kClass = 0xff; + static const int kSubclass = 0x42; + static const int kProtocol = 0x1; + static const bool kBreaks = false; + static const bool kConfigured = true; +}; + +struct NonAndroidTraits { + static const int kClass = 0xf0; + static const int kSubclass = 0x42; + static const int kProtocol = 0x2; + static const bool kBreaks = false; + static const bool kConfigured = true; +}; + +struct BreakingAndroidTraits { + static const int kClass = 0xff; + static const int kSubclass = 0x42; + static const int kProtocol = 0x1; + static const bool kBreaks = true; + static const bool kConfigured = true; +}; + +const uint32_t kMaxPayload = 4096; +const uint32_t kVersion = 0x01000000; + +const char kDeviceManufacturer[] = "Test Manufacturer"; +const char kDeviceModel[] = "Nexus 6"; +const char kDeviceSerial[] = "01498B321301A00A"; + +template <class T> +class MockUsbDevice; + +class MockLocalSocket : public MockAndroidConnection::Delegate { + public: + using Callback = base::Callback<void(int command, + const std::string& message)>; + + MockLocalSocket(const Callback& callback, + const std::string& serial, + const std::string& command) + : callback_(callback), + connection_(new MockAndroidConnection(this, serial, command)) { + } + + void Receive(const std::string& data) { + connection_->Receive(data); + } + + private: + void SendSuccess(const std::string& message) override { + if (!message.empty()) + callback_.Run(AdbMessage::kCommandWRTE, message); + } + + void SendRaw(const std::string& message) override { + callback_.Run(AdbMessage::kCommandWRTE, message); + } + + void Close() override { + callback_.Run(AdbMessage::kCommandCLSE, std::string()); + } + + Callback callback_; + std::unique_ptr<MockAndroidConnection> connection_; +}; + +template <class T> +class MockUsbDeviceHandle : public UsbDeviceHandle { + public: + explicit MockUsbDeviceHandle(MockUsbDevice<T>* device) + : device_(device), + remaining_body_length_(0), + last_local_socket_(0), + broken_(false) { + } + + scoped_refptr<UsbDevice> GetDevice() const override { + return device_; + } + + void Close() override { + device_->set_open(false); + device_ = nullptr; + } + + void SetConfiguration(int configuration_value, + const ResultCallback& callback) override { + NOTIMPLEMENTED(); + } + + void ClaimInterface(int interface_number, + const ResultCallback& callback) override { + bool success = false; + if (device_->claimed_interfaces_.find(interface_number) == + device_->claimed_interfaces_.end()) { + device_->claimed_interfaces_.insert(interface_number); + success = true; + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(callback, success)); + } + + void ReleaseInterface(int interface_number, + const ResultCallback& callback) override { + bool success = false; + if (device_->claimed_interfaces_.find(interface_number) == + device_->claimed_interfaces_.end()) + success = false; + + device_->claimed_interfaces_.erase(interface_number); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(callback, success)); + } + + void SetInterfaceAlternateSetting(int interface_number, + int alternate_setting, + const ResultCallback& callback) override { + NOTIMPLEMENTED(); + } + + void ResetDevice(const ResultCallback& callback) override { + NOTIMPLEMENTED(); + } + + void ClearHalt(uint8_t endpoint, const ResultCallback& callback) override { + NOTIMPLEMENTED(); + } + + // Async IO. Can be called on any thread. + void ControlTransfer(UsbTransferDirection direction, + device::UsbControlTransferType request_type, + device::UsbControlTransferRecipient recipient, + uint8_t request, + uint16_t value, + uint16_t index, + scoped_refptr<net::IOBuffer> buffer, + size_t length, + unsigned int timeout, + const TransferCallback& callback) override {} + + void GenericTransfer(UsbTransferDirection direction, + uint8_t endpoint, + scoped_refptr<net::IOBuffer> buffer, + size_t length, + unsigned int timeout, + const TransferCallback& callback) override { + if (direction == device::UsbTransferDirection::OUTBOUND) { + if (remaining_body_length_ == 0) { + std::vector<uint32_t> header(6); + memcpy(&header[0], buffer->data(), length); + current_message_.reset( + new AdbMessage(header[0], header[1], header[2], std::string())); + remaining_body_length_ = header[3]; + uint32_t magic = header[5]; + if ((current_message_->command ^ 0xffffffff) != magic) { + DCHECK(false) << "Header checksum error"; + return; + } + } else { + DCHECK(current_message_.get()); + current_message_->body += std::string(buffer->data(), length); + remaining_body_length_ -= length; + } + + if (remaining_body_length_ == 0) { + ProcessIncoming(); + } + + device::UsbTransferStatus status = broken_ + ? UsbTransferStatus::TRANSFER_ERROR + : UsbTransferStatus::COMPLETED; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(callback, status, nullptr, 0)); + ProcessQueries(); + } else if (direction == device::UsbTransferDirection::INBOUND) { + queries_.push(Query(callback, buffer, length)); + ProcessQueries(); + } + } + + const UsbInterfaceDescriptor* FindInterfaceByEndpoint( + uint8_t endpoint_address) { + NOTIMPLEMENTED(); + return nullptr; + } + + template <class D> + void append(D data) { + std::copy(reinterpret_cast<char*>(&data), + (reinterpret_cast<char*>(&data)) + sizeof(D), + std::back_inserter(output_buffer_)); + } + + // Copied from AndroidUsbDevice::Checksum + uint32_t Checksum(const std::string& data) { + unsigned char* x = (unsigned char*)data.data(); + int count = data.length(); + uint32_t sum = 0; + while (count-- > 0) + sum += *x++; + return sum; + } + + void ProcessIncoming() { + DCHECK(current_message_.get()); + switch (current_message_->command) { + case AdbMessage::kCommandCNXN: { + WriteResponse(kVersion, + kMaxPayload, + AdbMessage::kCommandCNXN, + "device::ro.product.name=SampleProduct;ro.product.model=" + "SampleModel;ro.product.device=SampleDevice;"); + break; + } + case AdbMessage::kCommandCLSE: { + WriteResponse(0, + current_message_->arg0, + AdbMessage::kCommandCLSE, + std::string()); + local_sockets_.erase(current_message_->arg0); + break; + } + case AdbMessage::kCommandWRTE: { + if (T::kBreaks) { + broken_ = true; + return; + } + auto it = local_sockets_.find(current_message_->arg0); + if (it == local_sockets_.end()) + return; + + DCHECK(current_message_->arg1 != 0); + WriteResponse(current_message_->arg1, + current_message_->arg0, + AdbMessage::kCommandOKAY, + std::string()); + it->second->Receive(current_message_->body); + break; + } + case AdbMessage::kCommandOPEN: { + DCHECK(current_message_->arg1 == 0); + DCHECK(current_message_->arg0 != 0); + std::string response; + WriteResponse(++last_local_socket_, + current_message_->arg0, + AdbMessage::kCommandOKAY, + std::string()); + local_sockets_[current_message_->arg0] = + base::MakeUnique<MockLocalSocket>( + base::Bind(&MockUsbDeviceHandle::WriteResponse, + base::Unretained(this), last_local_socket_, + current_message_->arg0), + kDeviceSerial, current_message_->body.substr( + 0, current_message_->body.size() - 1)); + return; + } + default: { + return; + } + } + ProcessQueries(); + } + + void WriteResponse(int arg0, int arg1, int command, const std::string& body) { + append(command); + append(arg0); + append(arg1); + bool add_zero = !body.empty() && (command != AdbMessage::kCommandWRTE); + append(static_cast<uint32_t>(body.size() + (add_zero ? 1 : 0))); + append(Checksum(body)); + append(command ^ 0xffffffff); + std::copy(body.begin(), body.end(), std::back_inserter(output_buffer_)); + if (add_zero) { + output_buffer_.push_back(0); + } + ProcessQueries(); + } + + void ProcessQueries() { + if (queries_.empty()) + return; + Query query = queries_.front(); + if (broken_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(query.callback, UsbTransferStatus::TRANSFER_ERROR, + nullptr, 0)); + } + + if (query.size > output_buffer_.size()) + return; + + queries_.pop(); + std::copy(output_buffer_.begin(), + output_buffer_.begin() + query.size, + query.buffer->data()); + output_buffer_.erase(output_buffer_.begin(), + output_buffer_.begin() + query.size); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(query.callback, UsbTransferStatus::COMPLETED, + query.buffer, query.size)); + } + + void IsochronousTransferIn( + uint8_t endpoint_number, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) override {} + + void IsochronousTransferOut( + uint8_t endpoint_number, + scoped_refptr<net::IOBuffer> buffer, + const std::vector<uint32_t>& packet_lengths, + unsigned int timeout, + const IsochronousTransferCallback& callback) override {} + + protected: + virtual ~MockUsbDeviceHandle() {} + + struct Query { + TransferCallback callback; + scoped_refptr<net::IOBuffer> buffer; + size_t size; + + Query(TransferCallback callback, + scoped_refptr<net::IOBuffer> buffer, + int size) + : callback(callback), buffer(buffer), size(size) {} + }; + + scoped_refptr<MockUsbDevice<T> > device_; + uint32_t remaining_body_length_; + std::unique_ptr<AdbMessage> current_message_; + std::vector<char> output_buffer_; + std::queue<Query> queries_; + std::unordered_map<int, std::unique_ptr<MockLocalSocket>> local_sockets_; + int last_local_socket_; + bool broken_; +}; + +template <class T> +class MockUsbDevice : public UsbDevice { + public: + MockUsbDevice() + : UsbDevice(0x0200, // usb_version + 0, // device_class + 0, // device_subclass + 0, // device_protocol + 0, // vendor_id + 0, // product_id + 0x0100, // device_version + base::UTF8ToUTF16(kDeviceManufacturer), + base::UTF8ToUTF16(kDeviceModel), + base::UTF8ToUTF16(kDeviceSerial)) { + UsbConfigDescriptor config_desc(1, false, false, 0); + UsbInterfaceDescriptor interface_desc(0, 0, T::kClass, T::kSubclass, + T::kProtocol); + interface_desc.endpoints.emplace_back(0x81, 0x02, 512, 0); + interface_desc.endpoints.emplace_back(0x01, 0x02, 512, 0); + config_desc.interfaces.push_back(interface_desc); + descriptor_.configurations.push_back(config_desc); + if (T::kConfigured) + ActiveConfigurationChanged(1); + } + + void Open(const OpenCallback& callback) override { + // While most operating systems allow multiple applications to open a + // device simultaneously so that they may claim separate interfaces DevTools + // will always be trying to claim the same interface and so multiple + // connections are more likely to cause problems. https://crbug.com/725320 + EXPECT_FALSE(open_); + open_ = true; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(callback, + make_scoped_refptr(new MockUsbDeviceHandle<T>(this)))); + } + + void set_open(bool open) { open_ = open; } + + bool open_ = false; + std::set<int> claimed_interfaces_; + + protected: + virtual ~MockUsbDevice() {} +}; + +class MockUsbServiceForCheckingTraits : public MockUsbService { + public: + MockUsbServiceForCheckingTraits() : step_(0) {} + + void GetDevices(const GetDevicesCallback& callback) override { + std::vector<scoped_refptr<UsbDevice>> devices; + // This switch should be kept in sync with + // AndroidUsbBrowserTest::DeviceCountChanged. + switch (step_) { + case 0: + // No devices. + break; + case 1: + // Android device. + devices.push_back(new MockUsbDevice<AndroidTraits>()); + break; + case 2: + // Android and non-android device. + devices.push_back(new MockUsbDevice<AndroidTraits>()); + devices.push_back(new MockUsbDevice<NonAndroidTraits>()); + break; + case 3: + // Non-android device. + devices.push_back(new MockUsbDevice<NonAndroidTraits>()); + break; + } + step_++; + callback.Run(devices); + } + + private: + int step_; +}; + +class TestDeviceClient : public DeviceClient { + public: + explicit TestDeviceClient(std::unique_ptr<UsbService> service) + : DeviceClient(), usb_service_(std::move(service)) {} + ~TestDeviceClient() override {} + + private: + UsbService* GetUsbService() override { return usb_service_.get(); } + + std::unique_ptr<UsbService> usb_service_; +}; + +class DevToolsAndroidBridgeWarmUp + : public DevToolsAndroidBridge::DeviceCountListener { + public: + DevToolsAndroidBridgeWarmUp(base::Closure closure, + DevToolsAndroidBridge* adb_bridge) + : closure_(closure), adb_bridge_(adb_bridge) {} + + void DeviceCountChanged(int count) override { + adb_bridge_->RemoveDeviceCountListener(this); + closure_.Run(); + } + + base::Closure closure_; + DevToolsAndroidBridge* adb_bridge_; +}; + +class AndroidUsbDiscoveryTest : public InProcessBrowserTest { + protected: + AndroidUsbDiscoveryTest() + : scheduler_invoked_(0) { + } + + void SetUpOnMainThread() override { + device_client_.reset(new TestDeviceClient(CreateMockService())); + adb_bridge_ = + DevToolsAndroidBridge::Factory::GetForProfile(browser()->profile()); + DCHECK(adb_bridge_); + adb_bridge_->set_task_scheduler_for_test(base::Bind( + &AndroidUsbDiscoveryTest::ScheduleDeviceCountRequest, + base::Unretained(this))); + + scoped_refptr<UsbDeviceProvider> provider = + new UsbDeviceProvider(browser()->profile()); + + AndroidDeviceManager::DeviceProviders providers; + providers.push_back(provider); + adb_bridge_->set_device_providers_for_test(providers); + runner_ = new content::MessageLoopRunner; + } + + void ScheduleDeviceCountRequest(const base::Closure& request) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + scheduler_invoked_++; + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, request); + } + + virtual std::unique_ptr<MockUsbService> CreateMockService() { + std::unique_ptr<MockUsbService> service(new MockUsbService()); + service->AddDevice(new MockUsbDevice<AndroidTraits>()); + return service; + } + + scoped_refptr<content::MessageLoopRunner> runner_; + std::unique_ptr<TestDeviceClient> device_client_; + DevToolsAndroidBridge* adb_bridge_; + int scheduler_invoked_; +}; + +class AndroidUsbCountTest : public AndroidUsbDiscoveryTest { + protected: + void SetUpOnMainThread() override { + AndroidUsbDiscoveryTest::SetUpOnMainThread(); + DevToolsAndroidBridgeWarmUp warmup(runner_->QuitClosure(), adb_bridge_); + adb_bridge_->AddDeviceCountListener(&warmup); + runner_->Run(); + runner_ = new content::MessageLoopRunner; + } +}; + +class AndroidUsbTraitsTest : public AndroidUsbDiscoveryTest { + protected: + std::unique_ptr<MockUsbService> CreateMockService() override { + return base::MakeUnique<MockUsbServiceForCheckingTraits>(); + } +}; + +class AndroidBreakingUsbTest : public AndroidUsbDiscoveryTest { + protected: + std::unique_ptr<MockUsbService> CreateMockService() override { + std::unique_ptr<MockUsbService> service(new MockUsbService()); + service->AddDevice(new MockUsbDevice<BreakingAndroidTraits>()); + return service; + } +}; + +class AndroidNoConfigUsbTest : public AndroidUsbDiscoveryTest { + protected: + std::unique_ptr<MockUsbService> CreateMockService() override { + std::unique_ptr<MockUsbService> service(new MockUsbService()); + service->AddDevice(new MockUsbDevice<AndroidTraits>()); + service->AddDevice(new MockUsbDevice<NoConfigTraits>()); + return service; + } +}; + +class MockListListener : public DevToolsAndroidBridge::DeviceListListener { + public: + MockListListener(DevToolsAndroidBridge* adb_bridge, + const base::Closure& callback) + : adb_bridge_(adb_bridge), + callback_(callback) { + } + + void DeviceListChanged( + const DevToolsAndroidBridge::RemoteDevices& devices) override { + if (devices.size() > 0) { + for (const auto& device : devices) { + if (device->is_connected()) { + ASSERT_EQ(kDeviceModel, device->model()); + ASSERT_EQ(kDeviceSerial, device->serial()); + adb_bridge_->RemoveDeviceListListener(this); + callback_.Run(); + break; + } + } + } + } + + DevToolsAndroidBridge* adb_bridge_; + base::Closure callback_; +}; + +class MockCountListener : public DevToolsAndroidBridge::DeviceCountListener { + public: + explicit MockCountListener(DevToolsAndroidBridge* adb_bridge) + : adb_bridge_(adb_bridge), invoked_(0) {} + + void DeviceCountChanged(int count) override { + ++invoked_; + adb_bridge_->RemoveDeviceCountListener(this); + Shutdown(); + } + + void Shutdown() { base::MessageLoop::current()->QuitWhenIdle(); } + + DevToolsAndroidBridge* adb_bridge_; + int invoked_; +}; + +class MockCountListenerWithReAdd : public MockCountListener { + public: + explicit MockCountListenerWithReAdd( + DevToolsAndroidBridge* adb_bridge) + : MockCountListener(adb_bridge), + readd_count_(2) { + } + + void DeviceCountChanged(int count) override { + ++invoked_; + adb_bridge_->RemoveDeviceCountListener(this); + if (readd_count_ > 0) { + readd_count_--; + adb_bridge_->AddDeviceCountListener(this); + adb_bridge_->RemoveDeviceCountListener(this); + adb_bridge_->AddDeviceCountListener(this); + } else { + Shutdown(); + } + } + + int readd_count_; +}; + +class MockCountListenerWithReAddWhileQueued : public MockCountListener { + public: + MockCountListenerWithReAddWhileQueued( + DevToolsAndroidBridge* adb_bridge) + : MockCountListener(adb_bridge), + readded_(false) { + } + + void DeviceCountChanged(int count) override { + ++invoked_; + if (!readded_) { + readded_ = true; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&MockCountListenerWithReAddWhileQueued::ReAdd, + base::Unretained(this))); + } else { + adb_bridge_->RemoveDeviceCountListener(this); + Shutdown(); + } + } + + void ReAdd() { + adb_bridge_->RemoveDeviceCountListener(this); + adb_bridge_->AddDeviceCountListener(this); + } + + bool readded_; +}; + +class MockCountListenerForCheckingTraits : public MockCountListener { + public: + MockCountListenerForCheckingTraits( + DevToolsAndroidBridge* adb_bridge) + : MockCountListener(adb_bridge), + step_(0) { + } + void DeviceCountChanged(int count) override { + switch (step_) { + case 0: + // Check for 0 devices when no devices present. + EXPECT_EQ(0, count); + break; + case 1: + // Check for 1 device when only android device present. + EXPECT_EQ(1, count); + break; + case 2: + // Check for 1 device when android and non-android devices present. + EXPECT_EQ(1, count); + break; + case 3: + // Check for 0 devices when only non-android devices present. + EXPECT_EQ(0, count); + adb_bridge_->RemoveDeviceCountListener(this); + Shutdown(); + break; + default: + EXPECT_TRUE(false) << "Unknown step " << step_; + } + step_++; + } + + int step_; +}; + +} // namespace + +IN_PROC_BROWSER_TEST_F(AndroidUsbDiscoveryTest, TestDeviceDiscovery) { + MockListListener listener(adb_bridge_, runner_->QuitClosure()); + adb_bridge_->AddDeviceListListener(&listener); + runner_->Run(); +} + +IN_PROC_BROWSER_TEST_F(AndroidBreakingUsbTest, TestDeviceBreaking) { + MockListListener listener(adb_bridge_, runner_->QuitClosure()); + adb_bridge_->AddDeviceListListener(&listener); + runner_->Run(); +} + +IN_PROC_BROWSER_TEST_F(AndroidNoConfigUsbTest, TestDeviceNoConfig) { + MockListListener listener(adb_bridge_, runner_->QuitClosure()); + adb_bridge_->AddDeviceListListener(&listener); + runner_->Run(); +} + +IN_PROC_BROWSER_TEST_F(AndroidUsbCountTest, + TestNoMultipleCallsRemoveInCallback) { + MockCountListener listener(adb_bridge_); + adb_bridge_->AddDeviceCountListener(&listener); + runner_->Run(); + EXPECT_EQ(1, listener.invoked_); + EXPECT_EQ(listener.invoked_ - 1, scheduler_invoked_); + EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); +} + +IN_PROC_BROWSER_TEST_F(AndroidUsbCountTest, + TestNoMultipleCallsRemoveAddInCallback) { + MockCountListenerWithReAdd listener(adb_bridge_); + adb_bridge_->AddDeviceCountListener(&listener); + runner_->Run(); + EXPECT_EQ(3, listener.invoked_); + EXPECT_EQ(listener.invoked_ - 1, scheduler_invoked_); + EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); +} + +IN_PROC_BROWSER_TEST_F(AndroidUsbCountTest, + TestNoMultipleCallsRemoveAddOnStart) { + MockCountListener listener(adb_bridge_); + adb_bridge_->AddDeviceCountListener(&listener); + adb_bridge_->RemoveDeviceCountListener(&listener); + adb_bridge_->AddDeviceCountListener(&listener); + runner_->Run(); + EXPECT_EQ(1, listener.invoked_); + EXPECT_EQ(listener.invoked_ - 1, scheduler_invoked_); + EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); +} + +IN_PROC_BROWSER_TEST_F(AndroidUsbCountTest, + TestNoMultipleCallsRemoveAddWhileQueued) { + MockCountListenerWithReAddWhileQueued listener(adb_bridge_); + adb_bridge_->AddDeviceCountListener(&listener); + runner_->Run(); + EXPECT_EQ(2, listener.invoked_); + EXPECT_EQ(listener.invoked_ - 1, scheduler_invoked_); + EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); +} + +IN_PROC_BROWSER_TEST_F(AndroidUsbTraitsTest, TestDeviceCounting) { + MockCountListenerForCheckingTraits listener(adb_bridge_); + adb_bridge_->AddDeviceCountListener(&listener); + runner_->Run(); +} diff --git a/chromium/chrome/browser/devtools/device/usb/android_usb_device.cc b/chromium/chrome/browser/devtools/device/usb/android_usb_device.cc new file mode 100644 index 00000000000..c8acbd19d80 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/android_usb_device.cc @@ -0,0 +1,692 @@ +// 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/device/usb/android_usb_device.h" + +#include <set> +#include <utility> + +#include "base/barrier_closure.h" +#include "base/base64.h" +#include "base/lazy_instance.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "chrome/browser/devtools/device/usb/android_rsa.h" +#include "chrome/browser/devtools/device/usb/android_usb_socket.h" +#include "content/public/browser/browser_thread.h" +#include "crypto/rsa_private_key.h" +#include "device/base/device_client.h" +#include "device/usb/usb_descriptors.h" +#include "device/usb/usb_device.h" +#include "device/usb/usb_service.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/socket/stream_socket.h" + +using device::UsbConfigDescriptor; +using device::UsbDevice; +using device::UsbDeviceHandle; +using device::UsbInterfaceDescriptor; +using device::UsbEndpointDescriptor; +using device::UsbService; +using device::UsbTransferDirection; +using device::UsbTransferStatus; +using device::UsbTransferType; + +namespace { + +const size_t kHeaderSize = 24; + +const int kAdbClass = 0xff; +const int kAdbSubclass = 0x42; +const int kAdbProtocol = 0x1; + +const int kUsbTimeout = 0; + +const uint32_t kMaxPayload = 4096; +const uint32_t kVersion = 0x01000000; + +static const char kHostConnectMessage[] = "host::"; + +using content::BrowserThread; + +typedef std::vector<scoped_refptr<UsbDevice> > UsbDevices; +typedef std::set<scoped_refptr<UsbDevice> > UsbDeviceSet; + +// Stores android wrappers around claimed usb devices on caller thread. +base::LazyInstance<std::vector<AndroidUsbDevice*>>::Leaky g_devices = + LAZY_INSTANCE_INITIALIZER; + +// Stores the GUIDs of devices that are currently opened so that they are not +// re-probed. +base::LazyInstance<std::vector<std::string>>::Leaky g_open_devices = + LAZY_INSTANCE_INITIALIZER; + +bool IsAndroidInterface(const UsbInterfaceDescriptor& interface) { + if (interface.alternate_setting != 0 || + interface.interface_class != kAdbClass || + interface.interface_subclass != kAdbSubclass || + interface.interface_protocol != kAdbProtocol || + interface.endpoints.size() != 2) { + return false; + } + return true; +} + +void CountAndroidDevices(const base::Callback<void(int)>& callback, + const UsbDevices& devices) { + int device_count = 0; + for (const scoped_refptr<UsbDevice>& device : devices) { + const UsbConfigDescriptor* config = device->active_configuration(); + if (config) { + for (const UsbInterfaceDescriptor& iface : config->interfaces) { + if (IsAndroidInterface(iface)) { + ++device_count; + } + } + } + } + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::BindOnce(callback, device_count)); +} + +uint32_t Checksum(const std::string& data) { + unsigned char* x = (unsigned char*)data.data(); + int count = data.length(); + uint32_t sum = 0; + while (count-- > 0) + sum += *x++; + return sum; +} + +void DumpMessage(bool outgoing, const char* data, size_t length) { +#if 0 + std::string result = ""; + if (length == kHeaderSize) { + for (size_t i = 0; i < 24; ++i) { + result += base::StringPrintf("%02x", + data[i] > 0 ? data[i] : (data[i] + 0x100) & 0xFF); + if ((i + 1) % 4 == 0) + result += " "; + } + for (size_t i = 0; i < 24; ++i) { + if (data[i] >= 0x20 && data[i] <= 0x7E) + result += data[i]; + else + result += "."; + } + } else { + result = base::StringPrintf("%d: ", (int)length); + for (size_t i = 0; i < length; ++i) { + if (data[i] >= 0x20 && data[i] <= 0x7E) + result += data[i]; + else + result += "."; + } + } + LOG(ERROR) << (outgoing ? "[out] " : "[ in] ") << result; +#endif // 0 +} + +void CloseDevice(scoped_refptr<UsbDeviceHandle> usb_device, + bool release_successful) { + base::Erase(g_open_devices.Get(), usb_device->GetDevice()->guid()); + usb_device->Close(); +} + +void ReleaseInterface(scoped_refptr<UsbDeviceHandle> usb_device, + int interface_id) { + usb_device->ReleaseInterface(interface_id, + base::Bind(&CloseDevice, usb_device)); +} + +void RespondOnCallerThread(const AndroidUsbDevicesCallback& callback, + AndroidUsbDevices* new_devices) { + std::unique_ptr<AndroidUsbDevices> devices(new_devices); + + // Add raw pointers to the newly claimed devices. + for (const scoped_refptr<AndroidUsbDevice>& device : *devices) { + g_devices.Get().push_back(device.get()); + } + + // Return all claimed devices. + AndroidUsbDevices result(g_devices.Get().begin(), g_devices.Get().end()); + callback.Run(result); +} + +void RespondOnUIThread( + const AndroidUsbDevicesCallback& callback, + AndroidUsbDevices* devices, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + caller_task_runner->PostTask( + FROM_HERE, base::BindOnce(&RespondOnCallerThread, callback, devices)); +} + +void CreateDeviceOnInterfaceClaimed(AndroidUsbDevices* devices, + crypto::RSAPrivateKey* rsa_key, + scoped_refptr<UsbDeviceHandle> usb_handle, + int inbound_address, + int outbound_address, + int zero_mask, + int interface_number, + const base::Closure& barrier, + bool success) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (success) { + devices->push_back(new AndroidUsbDevice( + rsa_key, usb_handle, + base::UTF16ToASCII(usb_handle->GetDevice()->serial_number()), + inbound_address, outbound_address, zero_mask, interface_number)); + } else { + usb_handle->Close(); + } + barrier.Run(); +} + +void OnDeviceOpened(AndroidUsbDevices* devices, + const std::string& device_guid, + crypto::RSAPrivateKey* rsa_key, + int inbound_address, + int outbound_address, + int zero_mask, + int interface_number, + const base::Closure& barrier, + scoped_refptr<UsbDeviceHandle> usb_handle) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (usb_handle.get()) { + usb_handle->ClaimInterface( + interface_number, + base::Bind(&CreateDeviceOnInterfaceClaimed, devices, rsa_key, + usb_handle, inbound_address, outbound_address, zero_mask, + interface_number, barrier)); + } else { + base::Erase(g_open_devices.Get(), device_guid); + barrier.Run(); + } +} + +void OpenAndroidDevice(AndroidUsbDevices* devices, + crypto::RSAPrivateKey* rsa_key, + const base::Closure& barrier, + scoped_refptr<UsbDevice> device, + int interface_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (device->serial_number().empty()) { + barrier.Run(); + return; + } + + const UsbConfigDescriptor* config = device->active_configuration(); + if (!config) { + barrier.Run(); + return; + } + + const UsbInterfaceDescriptor& interface = config->interfaces[interface_id]; + int inbound_address = 0; + int outbound_address = 0; + int zero_mask = 0; + + for (const UsbEndpointDescriptor& endpoint : interface.endpoints) { + if (endpoint.transfer_type != UsbTransferType::BULK) + continue; + if (endpoint.direction == UsbTransferDirection::INBOUND) + inbound_address = endpoint.address; + else + outbound_address = endpoint.address; + zero_mask = endpoint.maximum_packet_size - 1; + } + + if (inbound_address == 0 || outbound_address == 0) { + barrier.Run(); + return; + } + + if (base::ContainsValue(g_open_devices.Get(), device->guid())) { + // |device| is already open, do not make parallel attempts to connect to it. + barrier.Run(); + return; + } + + g_open_devices.Get().push_back(device->guid()); + device->Open(base::Bind(&OnDeviceOpened, devices, device->guid(), rsa_key, + inbound_address, outbound_address, zero_mask, + interface.interface_number, barrier)); +} + +void OpenAndroidDevices( + crypto::RSAPrivateKey* rsa_key, + const AndroidUsbDevicesCallback& callback, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + const UsbDevices& usb_devices) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + // Add new devices. + AndroidUsbDevices* devices = new AndroidUsbDevices(); + base::Closure barrier = base::BarrierClosure( + usb_devices.size(), + base::Bind(&RespondOnUIThread, callback, devices, caller_task_runner)); + + for (const scoped_refptr<UsbDevice>& device : usb_devices) { + const UsbConfigDescriptor* config = device->active_configuration(); + if (!config) { + barrier.Run(); + continue; + } + bool has_android_interface = false; + for (size_t j = 0; j < config->interfaces.size(); ++j) { + if (!IsAndroidInterface(config->interfaces[j])) { + continue; + } + + OpenAndroidDevice(devices, rsa_key, barrier, device, j); + has_android_interface = true; + break; + } + if (!has_android_interface) { + barrier.Run(); + } + } +} + +void EnumerateOnUIThread( + crypto::RSAPrivateKey* rsa_key, + const AndroidUsbDevicesCallback& callback, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + UsbService* service = device::DeviceClient::Get()->GetUsbService(); + if (service == NULL) { + caller_task_runner->PostTask(FROM_HERE, + base::BindOnce(callback, AndroidUsbDevices())); + } else { + service->GetDevices( + base::Bind(&OpenAndroidDevices, rsa_key, callback, caller_task_runner)); + } +} + +} // namespace + +AdbMessage::AdbMessage(uint32_t command, + uint32_t arg0, + uint32_t arg1, + const std::string& body) + : command(command), arg0(arg0), arg1(arg1), body(body) {} + +AdbMessage::~AdbMessage() { +} + +// static +void AndroidUsbDevice::CountDevices(const base::Callback<void(int)>& callback) { + UsbService* service = device::DeviceClient::Get()->GetUsbService(); + if (service != NULL) { + service->GetDevices(base::Bind(&CountAndroidDevices, callback)); + } else { + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + base::BindOnce(callback, 0)); + } +} + +// static +void AndroidUsbDevice::Enumerate(crypto::RSAPrivateKey* rsa_key, + const AndroidUsbDevicesCallback& callback) { + // Collect devices with closed handles. + for (AndroidUsbDevice* device : g_devices.Get()) { + if (device->usb_handle_.get()) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::BindOnce(&AndroidUsbDevice::TerminateIfReleased, device, + device->usb_handle_)); + } + } + + // Then look for the new devices. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::BindOnce(&EnumerateOnUIThread, rsa_key, callback, + base::ThreadTaskRunnerHandle::Get())); +} + +AndroidUsbDevice::AndroidUsbDevice(crypto::RSAPrivateKey* rsa_key, + scoped_refptr<UsbDeviceHandle> usb_device, + const std::string& serial, + int inbound_address, + int outbound_address, + int zero_mask, + int interface_id) + : rsa_key_(rsa_key->Copy()), + usb_handle_(usb_device), + serial_(serial), + inbound_address_(inbound_address), + outbound_address_(outbound_address), + zero_mask_(zero_mask), + interface_id_(interface_id), + is_connected_(false), + signature_sent_(false), + last_socket_id_(256), + weak_factory_(this) { +} + +void AndroidUsbDevice::InitOnCallerThread() { + if (task_runner_) + return; + task_runner_ = base::ThreadTaskRunnerHandle::Get(); + Queue(base::MakeUnique<AdbMessage>(AdbMessage::kCommandCNXN, kVersion, + kMaxPayload, kHostConnectMessage)); + ReadHeader(); +} + +net::StreamSocket* AndroidUsbDevice::CreateSocket(const std::string& command) { + if (!usb_handle_.get()) + return NULL; + + uint32_t socket_id = ++last_socket_id_; + sockets_[socket_id] = new AndroidUsbSocket(this, socket_id, command, + base::Bind(&AndroidUsbDevice::SocketDeleted, this, socket_id)); + return sockets_[socket_id]; +} + +void AndroidUsbDevice::Send(uint32_t command, + uint32_t arg0, + uint32_t arg1, + const std::string& body) { + auto message = base::MakeUnique<AdbMessage>(command, arg0, arg1, body); + // Delay open request if not yet connected. + if (!is_connected_) { + pending_messages_.push_back(std::move(message)); + return; + } + Queue(std::move(message)); +} + +AndroidUsbDevice::~AndroidUsbDevice() { + DCHECK(task_runner_->BelongsToCurrentThread()); + Terminate(); +} + +void AndroidUsbDevice::Queue(std::unique_ptr<AdbMessage> message) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + // Queue header. + std::vector<uint32_t> header; + header.push_back(message->command); + header.push_back(message->arg0); + header.push_back(message->arg1); + bool append_zero = true; + if (message->body.empty()) + append_zero = false; + if (message->command == AdbMessage::kCommandAUTH && + message->arg0 == AdbMessage::kAuthSignature) + append_zero = false; + if (message->command == AdbMessage::kCommandWRTE) + append_zero = false; + + size_t body_length = message->body.length() + (append_zero ? 1 : 0); + header.push_back(body_length); + header.push_back(Checksum(message->body)); + header.push_back(message->command ^ 0xffffffff); + scoped_refptr<net::IOBufferWithSize> header_buffer = + new net::IOBufferWithSize(kHeaderSize); + memcpy(header_buffer.get()->data(), &header[0], kHeaderSize); + outgoing_queue_.push(header_buffer); + + // Queue body. + if (!message->body.empty()) { + scoped_refptr<net::IOBufferWithSize> body_buffer = + new net::IOBufferWithSize(body_length); + memcpy(body_buffer->data(), message->body.data(), message->body.length()); + if (append_zero) + body_buffer->data()[body_length - 1] = 0; + outgoing_queue_.push(body_buffer); + if (zero_mask_ && (body_length & zero_mask_) == 0) { + // Send a zero length packet. + outgoing_queue_.push(new net::IOBufferWithSize(0)); + } + } + ProcessOutgoing(); +} + +void AndroidUsbDevice::ProcessOutgoing() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (outgoing_queue_.empty() || !usb_handle_.get()) + return; + + BulkMessage message = outgoing_queue_.front(); + outgoing_queue_.pop(); + DumpMessage(true, message->data(), message->size()); + + usb_handle_->GenericTransfer( + UsbTransferDirection::OUTBOUND, outbound_address_, message, + message->size(), kUsbTimeout, + base::Bind(&AndroidUsbDevice::OutgoingMessageSent, + weak_factory_.GetWeakPtr())); +} + +void AndroidUsbDevice::OutgoingMessageSent(UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t result) { + if (status != UsbTransferStatus::COMPLETED) + return; + + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&AndroidUsbDevice::ProcessOutgoing, this)); +} + +void AndroidUsbDevice::ReadHeader() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (!usb_handle_.get()) { + return; + } + + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kHeaderSize); + usb_handle_->GenericTransfer( + UsbTransferDirection::INBOUND, inbound_address_, buffer, kHeaderSize, + kUsbTimeout, + base::Bind(&AndroidUsbDevice::ParseHeader, weak_factory_.GetWeakPtr())); +} + +void AndroidUsbDevice::ParseHeader(UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t result) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (status == UsbTransferStatus::TIMEOUT) { + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&AndroidUsbDevice::ReadHeader, this)); + return; + } + + if (status != UsbTransferStatus::COMPLETED || result != kHeaderSize) { + TransferError(status); + return; + } + + DumpMessage(false, buffer->data(), result); + std::vector<uint32_t> header(6); + memcpy(&header[0], buffer->data(), result); + std::unique_ptr<AdbMessage> message( + new AdbMessage(header[0], header[1], header[2], "")); + uint32_t data_length = header[3]; + uint32_t data_check = header[4]; + uint32_t magic = header[5]; + if ((message->command ^ 0xffffffff) != magic) { + TransferError(UsbTransferStatus::TRANSFER_ERROR); + return; + } + + if (data_length == 0) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&AndroidUsbDevice::HandleIncoming, this, + base::Passed(&message))); + } else { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&AndroidUsbDevice::ReadBody, this, + base::Passed(&message), data_length, data_check)); + } +} + +void AndroidUsbDevice::ReadBody(std::unique_ptr<AdbMessage> message, + uint32_t data_length, + uint32_t data_check) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (!usb_handle_.get()) { + return; + } + + scoped_refptr<net::IOBuffer> buffer = + new net::IOBuffer(static_cast<size_t>(data_length)); + usb_handle_->GenericTransfer( + UsbTransferDirection::INBOUND, inbound_address_, buffer, data_length, + kUsbTimeout, + base::Bind(&AndroidUsbDevice::ParseBody, weak_factory_.GetWeakPtr(), + base::Passed(&message), data_length, data_check)); +} + +void AndroidUsbDevice::ParseBody(std::unique_ptr<AdbMessage> message, + uint32_t data_length, + uint32_t data_check, + UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t result) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (status == UsbTransferStatus::TIMEOUT) { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&AndroidUsbDevice::ReadBody, this, + base::Passed(&message), data_length, data_check)); + return; + } + + if (status != UsbTransferStatus::COMPLETED || + static_cast<uint32_t>(result) != data_length) { + TransferError(status); + return; + } + + DumpMessage(false, buffer->data(), data_length); + message->body = std::string(buffer->data(), result); + if (Checksum(message->body) != data_check) { + TransferError(UsbTransferStatus::TRANSFER_ERROR); + return; + } + + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&AndroidUsbDevice::HandleIncoming, this, + base::Passed(&message))); +} + +void AndroidUsbDevice::HandleIncoming(std::unique_ptr<AdbMessage> message) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + switch (message->command) { + case AdbMessage::kCommandAUTH: + { + DCHECK_EQ(message->arg0, static_cast<uint32_t>(AdbMessage::kAuthToken)); + if (signature_sent_) { + Queue(base::MakeUnique<AdbMessage>( + AdbMessage::kCommandAUTH, AdbMessage::kAuthRSAPublicKey, 0, + AndroidRSAPublicKey(rsa_key_.get()))); + } else { + signature_sent_ = true; + std::string signature = AndroidRSASign(rsa_key_.get(), message->body); + if (!signature.empty()) { + Queue(base::MakeUnique<AdbMessage>(AdbMessage::kCommandAUTH, + AdbMessage::kAuthSignature, 0, + signature)); + } else { + Queue(base::MakeUnique<AdbMessage>( + AdbMessage::kCommandAUTH, AdbMessage::kAuthRSAPublicKey, 0, + AndroidRSAPublicKey(rsa_key_.get()))); + } + } + } + break; + case AdbMessage::kCommandCNXN: + { + is_connected_ = true; + PendingMessages pending; + pending.swap(pending_messages_); + for (auto& msg : pending) + Queue(std::move(msg)); + } + break; + case AdbMessage::kCommandOKAY: + case AdbMessage::kCommandWRTE: + case AdbMessage::kCommandCLSE: + { + AndroidUsbSockets::iterator it = sockets_.find(message->arg1); + if (it != sockets_.end()) + it->second->HandleIncoming(std::move(message)); + } + break; + default: + break; + } + ReadHeader(); +} + +void AndroidUsbDevice::TransferError(UsbTransferStatus status) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + Terminate(); +} + +void AndroidUsbDevice::TerminateIfReleased( + scoped_refptr<UsbDeviceHandle> usb_handle) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (usb_handle->GetDevice().get()) { + return; + } + + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&AndroidUsbDevice::Terminate, this)); +} + +void AndroidUsbDevice::Terminate() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + std::vector<AndroidUsbDevice*>::iterator it = + std::find(g_devices.Get().begin(), g_devices.Get().end(), this); + if (it != g_devices.Get().end()) + g_devices.Get().erase(it); + + if (!usb_handle_.get()) + return; + + // Make sure we zero-out handle so that closing connections did not open + // new connections. + scoped_refptr<UsbDeviceHandle> usb_handle = usb_handle_; + usb_handle_ = NULL; + + // Iterate over copy. + AndroidUsbSockets sockets(sockets_); + for (AndroidUsbSockets::iterator it = sockets.begin(); + it != sockets.end(); ++it) { + it->second->Terminated(true); + } + DCHECK(sockets_.empty()); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::BindOnce(&ReleaseInterface, usb_handle, interface_id_)); +} + +void AndroidUsbDevice::SocketDeleted(uint32_t socket_id) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + sockets_.erase(socket_id); +} diff --git a/chromium/chrome/browser/devtools/device/usb/android_usb_device.h b/chromium/chrome/browser/devtools/device/usb/android_usb_device.h new file mode 100644 index 00000000000..23b21b3e210 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/android_usb_device.h @@ -0,0 +1,171 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_USB_DEVICE_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_USB_DEVICE_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <memory> +#include <queue> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "device/usb/usb_device_handle.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace crypto { +class RSAPrivateKey; +} + +namespace net { +class IOBuffer; +class IOBufferWithSize; +class StreamSocket; +} + +class AndroidUsbSocket; + +class AdbMessage { + public: + enum Command { + kCommandSYNC = 0x434e5953, + kCommandCNXN = 0x4e584e43, + kCommandOPEN = 0x4e45504f, + kCommandOKAY = 0x59414b4f, + kCommandCLSE = 0x45534c43, + kCommandWRTE = 0x45545257, + kCommandAUTH = 0x48545541 + }; + + enum Auth { + kAuthToken = 1, + kAuthSignature = 2, + kAuthRSAPublicKey = 3 + }; + + AdbMessage(uint32_t command, + uint32_t arg0, + uint32_t arg1, + const std::string& body); + ~AdbMessage(); + + uint32_t command; + uint32_t arg0; + uint32_t arg1; + std::string body; + + private: + DISALLOW_COPY_AND_ASSIGN(AdbMessage); +}; + +class AndroidUsbDevice; +typedef std::vector<scoped_refptr<AndroidUsbDevice> > AndroidUsbDevices; +typedef base::Callback<void(const AndroidUsbDevices&)> + AndroidUsbDevicesCallback; + +class AndroidUsbDevice : public base::RefCountedThreadSafe<AndroidUsbDevice> { + public: + static void CountDevices(const base::Callback<void(int)>& callback); + static void Enumerate(crypto::RSAPrivateKey* rsa_key, + const AndroidUsbDevicesCallback& callback); + + AndroidUsbDevice(crypto::RSAPrivateKey* rsa_key, + scoped_refptr<device::UsbDeviceHandle> device, + const std::string& serial, + int inbound_address, + int outbound_address, + int zero_mask, + int interface_id); + + void InitOnCallerThread(); + + net::StreamSocket* CreateSocket(const std::string& command); + + void Send(uint32_t command, + uint32_t arg0, + uint32_t arg1, + const std::string& body); + + scoped_refptr<device::UsbDeviceHandle> usb_device() { return usb_handle_; } + + std::string serial() { return serial_; } + + bool is_connected() { return is_connected_; } + + private: + friend class base::RefCountedThreadSafe<AndroidUsbDevice>; + virtual ~AndroidUsbDevice(); + + void Queue(std::unique_ptr<AdbMessage> message); + void ProcessOutgoing(); + void OutgoingMessageSent(device::UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t result); + + void ReadHeader(); + void ParseHeader(device::UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t result); + + void ReadBody(std::unique_ptr<AdbMessage> message, + uint32_t data_length, + uint32_t data_check); + void ParseBody(std::unique_ptr<AdbMessage> message, + uint32_t data_length, + uint32_t data_check, + device::UsbTransferStatus status, + scoped_refptr<net::IOBuffer> buffer, + size_t result); + + void HandleIncoming(std::unique_ptr<AdbMessage> message); + + void TransferError(device::UsbTransferStatus status); + + void TerminateIfReleased(scoped_refptr<device::UsbDeviceHandle> usb_handle); + void Terminate(); + + void SocketDeleted(uint32_t socket_id); + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + std::unique_ptr<crypto::RSAPrivateKey> rsa_key_; + + // Device info + scoped_refptr<device::UsbDeviceHandle> usb_handle_; + std::string serial_; + int inbound_address_; + int outbound_address_; + int zero_mask_; + int interface_id_; + + bool is_connected_; + bool signature_sent_; + + // Created sockets info + uint32_t last_socket_id_; + typedef std::map<uint32_t, AndroidUsbSocket*> AndroidUsbSockets; + AndroidUsbSockets sockets_; + + // Outgoing bulk queue + typedef scoped_refptr<net::IOBufferWithSize> BulkMessage; + std::queue<BulkMessage> outgoing_queue_; + + // Outgoing messages pending connect + using PendingMessages = std::vector<std::unique_ptr<AdbMessage>>; + PendingMessages pending_messages_; + + base::WeakPtrFactory<AndroidUsbDevice> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(AndroidUsbDevice); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_USB_DEVICE_H_ diff --git a/chromium/chrome/browser/devtools/device/usb/android_usb_socket.cc b/chromium/chrome/browser/devtools/device/usb/android_usb_socket.cc new file mode 100644 index 00000000000..8736c76f45b --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/android_usb_socket.cc @@ -0,0 +1,262 @@ +// 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/device/usb/android_usb_socket.h" + +#include <stddef.h> + +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_address.h" +#include "net/base/net_errors.h" + +namespace { + +const int kMaxPayload = 4096; + +} // namespace + +AndroidUsbSocket::AndroidUsbSocket(scoped_refptr<AndroidUsbDevice> device, + uint32_t socket_id, + const std::string& command, + base::Closure delete_callback) + : device_(device), + command_(command), + local_id_(socket_id), + remote_id_(0), + is_connected_(false), + delete_callback_(delete_callback), + weak_factory_(this) {} + +AndroidUsbSocket::~AndroidUsbSocket() { + DCHECK(CalledOnValidThread()); + if (is_connected_) + Disconnect(); + if (!delete_callback_.is_null()) + delete_callback_.Run(); +} + +void AndroidUsbSocket::HandleIncoming(std::unique_ptr<AdbMessage> message) { + if (!device_.get()) + return; + + CHECK_EQ(message->arg1, local_id_); + switch (message->command) { + case AdbMessage::kCommandOKAY: + if (!is_connected_) { + remote_id_ = message->arg0; + is_connected_ = true; + if (!connect_callback_.is_null()) + base::ResetAndReturn(&connect_callback_).Run(net::OK); + // "this" can be deleted. + } else { + RespondToWriter(write_length_); + // "this" can be deleted. + } + break; + case AdbMessage::kCommandWRTE: + device_->Send(AdbMessage::kCommandOKAY, local_id_, message->arg0, ""); + read_buffer_ += message->body; + // Allow WRTE over new connection even though OKAY ack was not received. + if (!is_connected_) { + remote_id_ = message->arg0; + is_connected_ = true; + if (!connect_callback_.is_null()) + base::ResetAndReturn(&connect_callback_).Run(net::OK); + // "this" can be deleted. + } else { + RespondToReader(false); + // "this" can be deleted. + } + break; + case AdbMessage::kCommandCLSE: + if (is_connected_) + device_->Send(AdbMessage::kCommandCLSE, local_id_, 0, ""); + Terminated(true); + // "this" can be deleted. + break; + default: + break; + } +} + +void AndroidUsbSocket::Terminated(bool closed_by_device) { + is_connected_ = false; + + // Break the socket -> device connection, release the device. + device_ = nullptr; + base::ResetAndReturn(&delete_callback_).Run(); + + if (!closed_by_device) + return; + + // Respond to pending callbacks. + if (!connect_callback_.is_null()) { + base::ResetAndReturn(&connect_callback_).Run(net::ERR_FAILED); + // "this" can be deleted. + return; + } + base::WeakPtr<AndroidUsbSocket> weak_this = weak_factory_.GetWeakPtr(); + RespondToReader(true); + // "this" can be deleted. + if (weak_this) { + RespondToWriter(net::ERR_FAILED); + // "this" can be deleted. + } +} + +int AndroidUsbSocket::Read(net::IOBuffer* buffer, + int length, + const net::CompletionCallback& callback) { + DCHECK(!callback.is_null()); + if (!is_connected_) + return device_.get() ? net::ERR_SOCKET_NOT_CONNECTED : 0; + + DCHECK(read_callback_.is_null()); + if (read_buffer_.empty()) { + read_callback_ = callback; + read_io_buffer_ = buffer; + read_length_ = length; + return net::ERR_IO_PENDING; + } + + size_t bytes_to_copy = static_cast<size_t>(length) > read_buffer_.length() ? + read_buffer_.length() : static_cast<size_t>(length); + memcpy(buffer->data(), read_buffer_.data(), bytes_to_copy); + if (read_buffer_.length() > bytes_to_copy) + read_buffer_ = read_buffer_.substr(bytes_to_copy); + else + read_buffer_ = std::string(); + return bytes_to_copy; +} + +int AndroidUsbSocket::Write(net::IOBuffer* buffer, + int length, + const net::CompletionCallback& callback) { + DCHECK(!callback.is_null()); + if (!is_connected_) + return net::ERR_SOCKET_NOT_CONNECTED; + + if (length > kMaxPayload) + length = kMaxPayload; + + DCHECK(write_callback_.is_null()); + write_callback_ = callback; + write_length_ = length; + device_->Send(AdbMessage::kCommandWRTE, local_id_, remote_id_, + std::string(buffer->data(), length)); + return net::ERR_IO_PENDING; +} + +int AndroidUsbSocket::SetReceiveBufferSize(int32_t size) { + NOTIMPLEMENTED(); + return net::ERR_NOT_IMPLEMENTED; +} + +int AndroidUsbSocket::SetSendBufferSize(int32_t size) { + NOTIMPLEMENTED(); + return net::ERR_NOT_IMPLEMENTED; +} + +int AndroidUsbSocket::Connect(const net::CompletionCallback& callback) { + DCHECK(CalledOnValidThread()); + DCHECK(!callback.is_null()); + if (!device_.get()) + return net::ERR_FAILED; + + DCHECK(!is_connected_); + DCHECK(connect_callback_.is_null()); + connect_callback_ = callback; + device_->Send(AdbMessage::kCommandOPEN, local_id_, 0, command_); + return net::ERR_IO_PENDING; +} + +void AndroidUsbSocket::Disconnect() { + if (!device_.get()) + return; + device_->Send(AdbMessage::kCommandCLSE, local_id_, remote_id_, ""); + Terminated(false); +} + +bool AndroidUsbSocket::IsConnected() const { + DCHECK(CalledOnValidThread()); + return is_connected_; +} + +bool AndroidUsbSocket::IsConnectedAndIdle() const { + NOTIMPLEMENTED(); + return false; +} + +int AndroidUsbSocket::GetPeerAddress(net::IPEndPoint* address) const { + *address = net::IPEndPoint(net::IPAddress(0, 0, 0, 0), 0); + return net::OK; +} + +int AndroidUsbSocket::GetLocalAddress(net::IPEndPoint* address) const { + NOTIMPLEMENTED(); + return net::ERR_NOT_IMPLEMENTED; +} + +const net::NetLogWithSource& AndroidUsbSocket::NetLog() const { + return net_log_; +} + +void AndroidUsbSocket::SetSubresourceSpeculation() { + NOTIMPLEMENTED(); +} + +void AndroidUsbSocket::SetOmniboxSpeculation() { + NOTIMPLEMENTED(); +} + +bool AndroidUsbSocket::WasEverUsed() const { + NOTIMPLEMENTED(); + return true; +} + +bool AndroidUsbSocket::WasAlpnNegotiated() const { + NOTIMPLEMENTED(); + return true; +} + +net::NextProto AndroidUsbSocket::GetNegotiatedProtocol() const { + NOTIMPLEMENTED(); + return net::kProtoUnknown; +} + +bool AndroidUsbSocket::GetSSLInfo(net::SSLInfo* ssl_info) { + return false; +} + +void AndroidUsbSocket::GetConnectionAttempts( + net::ConnectionAttempts* out) const { + out->clear(); +} + +int64_t AndroidUsbSocket::GetTotalReceivedBytes() const { + NOTIMPLEMENTED(); + return 0; +} + +void AndroidUsbSocket::RespondToReader(bool disconnect) { + if (read_callback_.is_null() || (read_buffer_.empty() && !disconnect)) + return; + size_t bytes_to_copy = + static_cast<size_t>(read_length_) > read_buffer_.length() ? + read_buffer_.length() : static_cast<size_t>(read_length_); + memcpy(read_io_buffer_->data(), read_buffer_.data(), bytes_to_copy); + if (read_buffer_.length() > bytes_to_copy) + read_buffer_ = read_buffer_.substr(bytes_to_copy); + else + read_buffer_ = std::string(); + base::ResetAndReturn(&read_callback_).Run(bytes_to_copy); +} + +void AndroidUsbSocket::RespondToWriter(int result) { + if (!write_callback_.is_null()) + base::ResetAndReturn(&write_callback_).Run(result); +} diff --git a/chromium/chrome/browser/devtools/device/usb/android_usb_socket.h b/chromium/chrome/browser/devtools/device/usb/android_usb_socket.h new file mode 100644 index 00000000000..8d3811fa415 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/android_usb_socket.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_USB_SOCKET_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_USB_SOCKET_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/browser/devtools/device/usb/android_usb_device.h" +#include "net/base/ip_endpoint.h" +#include "net/log/net_log_with_source.h" +#include "net/socket/stream_socket.h" + +class AndroidUsbSocket : public net::StreamSocket, + public base::NonThreadSafe { + public: + AndroidUsbSocket(scoped_refptr<AndroidUsbDevice> device, + uint32_t socket_id, + const std::string& command, + base::Closure delete_callback); + ~AndroidUsbSocket() override; + + void HandleIncoming(std::unique_ptr<AdbMessage> message); + + void Terminated(bool closed_by_device); + + // net::StreamSocket implementation. + int Read(net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback) override; + int Write(net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback) override; + int SetReceiveBufferSize(int32_t size) override; + int SetSendBufferSize(int32_t size) override; + int Connect(const net::CompletionCallback& callback) override; + void Disconnect() override; + bool IsConnected() const override; + bool IsConnectedAndIdle() const override; + int GetPeerAddress(net::IPEndPoint* address) const override; + int GetLocalAddress(net::IPEndPoint* address) const override; + const net::NetLogWithSource& NetLog() const override; + void SetSubresourceSpeculation() override; + void SetOmniboxSpeculation() override; + bool WasEverUsed() const override; + bool WasAlpnNegotiated() const override; + net::NextProto GetNegotiatedProtocol() const override; + bool GetSSLInfo(net::SSLInfo* ssl_info) override; + void GetConnectionAttempts(net::ConnectionAttempts* out) const override; + void ClearConnectionAttempts() override {} + void AddConnectionAttempts(const net::ConnectionAttempts& attempts) override { + } + int64_t GetTotalReceivedBytes() const override; + + private: + void RespondToReader(bool disconnect); + void RespondToWriter(int result); + + scoped_refptr<AndroidUsbDevice> device_; + std::string command_; + uint32_t local_id_; + uint32_t remote_id_; + net::NetLogWithSource net_log_; + bool is_connected_; + std::string read_buffer_; + scoped_refptr<net::IOBuffer> read_io_buffer_; + int read_length_; + int write_length_; + net::CompletionCallback connect_callback_; + net::CompletionCallback read_callback_; + net::CompletionCallback write_callback_; + base::Closure delete_callback_; + base::WeakPtrFactory<AndroidUsbSocket> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(AndroidUsbSocket); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_USB_ANDROID_USB_SOCKET_H_ diff --git a/chromium/chrome/browser/devtools/device/usb/usb_device_provider.cc b/chromium/chrome/browser/devtools/device/usb/usb_device_provider.cc new file mode 100644 index 00000000000..2dfdf9f25f3 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/usb_device_provider.cc @@ -0,0 +1,154 @@ +// 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/device/usb/usb_device_provider.h" + +#include <utility> + +#include "base/memory/ptr_util.h" +#include "base/strings/stringprintf.h" +#include "chrome/browser/devtools/device/usb/android_rsa.h" +#include "chrome/browser/devtools/device/usb/android_usb_device.h" +#include "crypto/rsa_private_key.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/socket/stream_socket.h" + +namespace { + +const char kLocalAbstractCommand[] = "localabstract:%s"; + +const int kBufferSize = 16 * 1024; + +void OnOpenSocket(const UsbDeviceProvider::SocketCallback& callback, + net::StreamSocket* socket_raw, + int result) { + std::unique_ptr<net::StreamSocket> socket(socket_raw); + if (result != net::OK) + socket.reset(); + callback.Run(result, std::move(socket)); +} + +void OnRead(net::StreamSocket* socket, + scoped_refptr<net::IOBuffer> buffer, + const std::string& data, + const UsbDeviceProvider::CommandCallback& callback, + int result) { + if (result <= 0) { + callback.Run(result, result == 0 ? data : std::string()); + delete socket; + return; + } + + std::string new_data = data + std::string(buffer->data(), result); + result = + socket->Read(buffer.get(), + kBufferSize, + base::Bind(&OnRead, socket, buffer, new_data, callback)); + if (result != net::ERR_IO_PENDING) + OnRead(socket, buffer, new_data, callback, result); +} + +void OpenedForCommand(const UsbDeviceProvider::CommandCallback& callback, + net::StreamSocket* socket, + int result) { + if (result != net::OK) { + callback.Run(result, std::string()); + return; + } + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kBufferSize); + result = socket->Read( + buffer.get(), + kBufferSize, + base::Bind(&OnRead, socket, buffer, std::string(), callback)); + if (result != net::ERR_IO_PENDING) + OnRead(socket, buffer, std::string(), callback, result); +} + +void RunCommand(scoped_refptr<AndroidUsbDevice> device, + const std::string& command, + const UsbDeviceProvider::CommandCallback& callback) { + net::StreamSocket* socket = device->CreateSocket(command); + if (!socket) { + callback.Run(net::ERR_CONNECTION_FAILED, std::string()); + return; + } + int result = socket->Connect( + base::Bind(&OpenedForCommand, callback, socket)); + if (result != net::ERR_IO_PENDING) + callback.Run(result, std::string()); +} + +} // namespace + +// static +void UsbDeviceProvider::CountDevices( + const base::Callback<void(int)>& callback) { + AndroidUsbDevice::CountDevices(callback); +} + +UsbDeviceProvider::UsbDeviceProvider(Profile* profile){ + rsa_key_ = AndroidRSAPrivateKey(profile); +} + +void UsbDeviceProvider::QueryDevices(const SerialsCallback& callback) { + AndroidUsbDevice::Enumerate( + rsa_key_.get(), + base::Bind(&UsbDeviceProvider::EnumeratedDevices, this, callback)); +} + +void UsbDeviceProvider::QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) { + UsbDeviceMap::iterator it = device_map_.find(serial); + if (it == device_map_.end() || !it->second->is_connected()) { + AndroidDeviceManager::DeviceInfo offline_info; + callback.Run(offline_info); + return; + } + AndroidDeviceManager::QueryDeviceInfo(base::Bind(&RunCommand, it->second), + callback); +} + +void UsbDeviceProvider::OpenSocket(const std::string& serial, + const std::string& name, + const SocketCallback& callback) { + UsbDeviceMap::iterator it = device_map_.find(serial); + if (it == device_map_.end()) { + callback.Run(net::ERR_CONNECTION_FAILED, + base::WrapUnique<net::StreamSocket>(NULL)); + return; + } + std::string socket_name = + base::StringPrintf(kLocalAbstractCommand, name.c_str()); + net::StreamSocket* socket = it->second->CreateSocket(socket_name); + if (!socket) { + callback.Run(net::ERR_CONNECTION_FAILED, + base::WrapUnique<net::StreamSocket>(NULL)); + return; + } + int result = socket->Connect(base::Bind(&OnOpenSocket, callback, socket)); + if (result != net::ERR_IO_PENDING) + callback.Run(result, base::WrapUnique<net::StreamSocket>(NULL)); +} + +void UsbDeviceProvider::ReleaseDevice(const std::string& serial) { + device_map_.erase(serial); +} + +UsbDeviceProvider::~UsbDeviceProvider() { +} + +void UsbDeviceProvider::EnumeratedDevices(const SerialsCallback& callback, + const AndroidUsbDevices& devices) { + std::vector<std::string> result; + device_map_.clear(); + for (AndroidUsbDevices::const_iterator it = devices.begin(); + it != devices.end(); ++it) { + result.push_back((*it)->serial()); + device_map_[(*it)->serial()] = *it; + (*it)->InitOnCallerThread(); + } + callback.Run(result); +} + diff --git a/chromium/chrome/browser/devtools/device/usb/usb_device_provider.h b/chromium/chrome/browser/devtools/device/usb/usb_device_provider.h new file mode 100644 index 00000000000..02d06843f77 --- /dev/null +++ b/chromium/chrome/browser/devtools/device/usb/usb_device_provider.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVICE_USB_USB_DEVICE_PROVIDER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVICE_USB_USB_DEVICE_PROVIDER_H_ + +#include "chrome/browser/devtools/device/android_device_manager.h" + +namespace crypto { +class RSAPrivateKey; +} + +class AndroidUsbDevice; + +class UsbDeviceProvider : public AndroidDeviceManager::DeviceProvider { + public: + static void CountDevices(const base::Callback<void(int)>& callback); + + explicit UsbDeviceProvider(Profile* profile); + + void QueryDevices(const SerialsCallback& callback) override; + + void QueryDeviceInfo(const std::string& serial, + const DeviceInfoCallback& callback) override; + + void OpenSocket(const std::string& serial, + const std::string& socket_name, + const SocketCallback& callback) override; + + void ReleaseDevice(const std::string& serial) override; + + private: + ~UsbDeviceProvider() override; + + void EnumeratedDevices( + const SerialsCallback& callback, + const std::vector<scoped_refptr<AndroidUsbDevice> >& devices); + + typedef std::map<std::string, scoped_refptr<AndroidUsbDevice> > UsbDeviceMap; + + std::unique_ptr<crypto::RSAPrivateKey> rsa_key_; + UsbDeviceMap device_map_; +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVICE_USB_USB_DEVICE_PROVIDER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_auto_opener.cc b/chromium/chrome/browser/devtools/devtools_auto_opener.cc new file mode 100644 index 00000000000..834030e387d --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_auto_opener.cc @@ -0,0 +1,24 @@ +// Copyright 2016 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/devtools_auto_opener.h" + +#include "base/command_line.h" +#include "chrome/browser/devtools/devtools_window.h" + +DevToolsAutoOpener::DevToolsAutoOpener() + : browser_tab_strip_tracker_(this, nullptr, nullptr) { + browser_tab_strip_tracker_.Init(); +} + +DevToolsAutoOpener::~DevToolsAutoOpener() { +} + +void DevToolsAutoOpener::TabInsertedAt(TabStripModel* tab_strip_model, + content::WebContents* contents, + int index, + bool foreground) { + if (!DevToolsWindow::IsDevToolsWindow(contents)) + DevToolsWindow::OpenDevToolsWindow(contents); +} diff --git a/chromium/chrome/browser/devtools/devtools_auto_opener.h b/chromium/chrome/browser/devtools/devtools_auto_opener.h new file mode 100644 index 00000000000..7efe2ea7b79 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_auto_opener.h @@ -0,0 +1,28 @@ +// Copyright 2016 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_AUTO_OPENER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_AUTO_OPENER_H_ + +#include "chrome/browser/ui/browser_tab_strip_tracker.h" +#include "chrome/browser/ui/tabs/tab_strip_model_observer.h" + +class DevToolsAutoOpener : public TabStripModelObserver { + public: + DevToolsAutoOpener(); + ~DevToolsAutoOpener() override; + + private: + // TabStripModelObserver overrides. + void TabInsertedAt(TabStripModel* tab_strip_model, + content::WebContents* contents, + int index, + bool foreground) override; + + BrowserTabStripTracker browser_tab_strip_tracker_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsAutoOpener); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_AUTO_OPENER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.cc b/chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.cc new file mode 100644 index 00000000000..b11cd81456c --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.cc @@ -0,0 +1,53 @@ +// 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/devtools_contents_resizing_strategy.h" + +#include <algorithm> + +DevToolsContentsResizingStrategy::DevToolsContentsResizingStrategy() + : hide_inspected_contents_(false) { +} + +DevToolsContentsResizingStrategy::DevToolsContentsResizingStrategy( + const gfx::Rect& bounds) + : bounds_(bounds), + hide_inspected_contents_(bounds_.IsEmpty() && !bounds_.x() && + !bounds_.y()) { +} + + +void DevToolsContentsResizingStrategy::CopyFrom( + const DevToolsContentsResizingStrategy& strategy) { + bounds_ = strategy.bounds(); + hide_inspected_contents_ = strategy.hide_inspected_contents(); +} + +bool DevToolsContentsResizingStrategy::Equals( + const DevToolsContentsResizingStrategy& strategy) { + return bounds_ == strategy.bounds() && + hide_inspected_contents_ == strategy.hide_inspected_contents(); +} + +void ApplyDevToolsContentsResizingStrategy( + const DevToolsContentsResizingStrategy& strategy, + const gfx::Size& container_size, + gfx::Rect* new_devtools_bounds, + gfx::Rect* new_contents_bounds) { + new_devtools_bounds->SetRect( + 0, 0, container_size.width(), container_size.height()); + + const gfx::Rect& bounds = strategy.bounds(); + if (bounds.size().IsEmpty() && !strategy.hide_inspected_contents()) { + new_contents_bounds->SetRect( + 0, 0, container_size.width(), container_size.height()); + return; + } + + int left = std::min(bounds.x(), container_size.width()); + int top = std::min(bounds.y(), container_size.height()); + int width = std::min(bounds.width(), container_size.width() - left); + int height = std::min(bounds.height(), container_size.height() - top); + new_contents_bounds->SetRect(left, top, width, height); +} diff --git a/chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.h b/chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.h new file mode 100644 index 00000000000..9a8d22cc64e --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_CONTENTS_RESIZING_STRATEGY_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_CONTENTS_RESIZING_STRATEGY_H_ + +#include "base/macros.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +// This class knows how to resize both DevTools and inspected WebContents +// inside a browser window hierarchy. +class DevToolsContentsResizingStrategy { + public: + DevToolsContentsResizingStrategy(); + explicit DevToolsContentsResizingStrategy( + const gfx::Rect& bounds); + + void CopyFrom(const DevToolsContentsResizingStrategy& strategy); + bool Equals(const DevToolsContentsResizingStrategy& strategy); + + const gfx::Rect& bounds() const { return bounds_; } + bool hide_inspected_contents() const { return hide_inspected_contents_; } + + private: + // Contents bounds. When non-empty, used instead of insets. + gfx::Rect bounds_; + + // Determines whether inspected contents is visible. + bool hide_inspected_contents_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsContentsResizingStrategy); +}; + +// Applies contents resizing strategy, producing bounds for devtools and +// page contents views. Generally, page contents view is placed atop of devtools +// inside a common parent view, which size should be passed in |container_size|. +// When unknown, providing empty rect as previous devtools and contents bounds +// is allowed. +void ApplyDevToolsContentsResizingStrategy( + const DevToolsContentsResizingStrategy& strategy, + const gfx::Size& container_size, + gfx::Rect* new_devtools_bounds, + gfx::Rect* new_contents_bounds); + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_CONTENTS_RESIZING_STRATEGY_H_ diff --git a/chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc b/chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc new file mode 100644 index 00000000000..cb88e6262c1 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc @@ -0,0 +1,221 @@ +// Copyright 2013 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/devtools_embedder_message_dispatcher.h" + +#include "base/bind.h" +#include "base/values.h" + +namespace { + +using DispatchCallback = DevToolsEmbedderMessageDispatcher::DispatchCallback; + +bool GetValue(const base::Value& value, std::string* result) { + return value.GetAsString(result); +} + +bool GetValue(const base::Value& value, int* result) { + return value.GetAsInteger(result); +} + +bool GetValue(const base::Value& value, bool* result) { + return value.GetAsBoolean(result); +} + +bool GetValue(const base::Value& value, gfx::Rect* rect) { + const base::DictionaryValue* dict; + if (!value.GetAsDictionary(&dict)) + return false; + int x = 0; + int y = 0; + int width = 0; + int height = 0; + if (!dict->GetInteger("x", &x) || + !dict->GetInteger("y", &y) || + !dict->GetInteger("width", &width) || + !dict->GetInteger("height", &height)) + return false; + rect->SetRect(x, y, width, height); + return true; +} + +template <typename T> +struct StorageTraits { + using StorageType = T; +}; + +template <typename T> +struct StorageTraits<const T&> { + using StorageType = T; +}; + +template <typename... Ts> +struct ParamTuple { + bool Parse(const base::ListValue& list, + const base::ListValue::const_iterator& it) { + return it == list.end(); + } + + template <typename H, typename... As> + void Apply(const H& handler, As... args) { + handler.Run(args...); + } +}; + +template <typename T, typename... Ts> +struct ParamTuple<T, Ts...> { + bool Parse(const base::ListValue& list, + const base::ListValue::const_iterator& it) { + return it != list.end() && GetValue(*it, &head) && tail.Parse(list, it + 1); + } + + template <typename H, typename... As> + void Apply(const H& handler, As... args) { + tail.template Apply<H, As..., T>(handler, args..., head); + } + + typename StorageTraits<T>::StorageType head; + ParamTuple<Ts...> tail; +}; + +template<typename... As> +bool ParseAndHandle(const base::Callback<void(As...)>& handler, + const DispatchCallback& callback, + const base::ListValue& list) { + ParamTuple<As...> tuple; + if (!tuple.Parse(list, list.begin())) + return false; + tuple.Apply(handler); + return true; +} + +template<typename... As> +bool ParseAndHandleWithCallback( + const base::Callback<void(const DispatchCallback&, As...)>& handler, + const DispatchCallback& callback, + const base::ListValue& list) { + ParamTuple<As...> tuple; + if (!tuple.Parse(list, list.begin())) + return false; + tuple.Apply(handler, callback); + return true; +} + +} // namespace + +/** + * Dispatcher for messages sent from the frontend running in an + * isolated renderer (chrome-devtools:// or chrome://inspect) to the embedder + * in the browser. + * + * The messages are sent via InspectorFrontendHost.sendMessageToEmbedder or + * chrome.send method accordingly. + */ +class DispatcherImpl : public DevToolsEmbedderMessageDispatcher { + public: + ~DispatcherImpl() override {} + + bool Dispatch(const DispatchCallback& callback, + const std::string& method, + const base::ListValue* params) override { + HandlerMap::iterator it = handlers_.find(method); + return it != handlers_.end() && it->second.Run(callback, *params); + } + + template<typename... As> + void RegisterHandler(const std::string& method, + void (Delegate::*handler)(As...), + Delegate* delegate) { + handlers_[method] = base::Bind(&ParseAndHandle<As...>, + base::Bind(handler, + base::Unretained(delegate))); + } + + template<typename... As> + void RegisterHandlerWithCallback( + const std::string& method, + void (Delegate::*handler)(const DispatchCallback&, As...), + Delegate* delegate) { + handlers_[method] = base::Bind(&ParseAndHandleWithCallback<As...>, + base::Bind(handler, + base::Unretained(delegate))); + } + + + private: + using Handler = base::Callback<bool(const DispatchCallback&, + const base::ListValue&)>; + using HandlerMap = std::map<std::string, Handler>; + HandlerMap handlers_; +}; + +// static +DevToolsEmbedderMessageDispatcher* +DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend( + Delegate* delegate) { + DispatcherImpl* d = new DispatcherImpl(); + + d->RegisterHandler("bringToFront", &Delegate::ActivateWindow, delegate); + d->RegisterHandler("closeWindow", &Delegate::CloseWindow, delegate); + d->RegisterHandler("loadCompleted", &Delegate::LoadCompleted, delegate); + d->RegisterHandler("setInspectedPageBounds", + &Delegate::SetInspectedPageBounds, delegate); + d->RegisterHandler("inspectElementCompleted", + &Delegate::InspectElementCompleted, delegate); + d->RegisterHandler("inspectedURLChanged", + &Delegate::InspectedURLChanged, delegate); + d->RegisterHandlerWithCallback("setIsDocked", + &Delegate::SetIsDocked, delegate); + d->RegisterHandler("openInNewTab", &Delegate::OpenInNewTab, delegate); + d->RegisterHandler("save", &Delegate::SaveToFile, delegate); + d->RegisterHandler("append", &Delegate::AppendToFile, delegate); + d->RegisterHandler("requestFileSystems", + &Delegate::RequestFileSystems, delegate); + d->RegisterHandler("addFileSystem", &Delegate::AddFileSystem, delegate); + d->RegisterHandler("removeFileSystem", &Delegate::RemoveFileSystem, delegate); + d->RegisterHandler("upgradeDraggedFileSystemPermissions", + &Delegate::UpgradeDraggedFileSystemPermissions, delegate); + d->RegisterHandler("indexPath", &Delegate::IndexPath, delegate); + d->RegisterHandlerWithCallback("loadNetworkResource", + &Delegate::LoadNetworkResource, delegate); + d->RegisterHandler("stopIndexing", &Delegate::StopIndexing, delegate); + d->RegisterHandler("searchInPath", &Delegate::SearchInPath, delegate); + d->RegisterHandler("setWhitelistedShortcuts", + &Delegate::SetWhitelistedShortcuts, delegate); + d->RegisterHandler("setEyeDropperActive", &Delegate::SetEyeDropperActive, + delegate); + d->RegisterHandler("showCertificateViewer", + &Delegate::ShowCertificateViewer, delegate); + d->RegisterHandler("zoomIn", &Delegate::ZoomIn, delegate); + d->RegisterHandler("zoomOut", &Delegate::ZoomOut, delegate); + d->RegisterHandler("resetZoom", &Delegate::ResetZoom, delegate); + d->RegisterHandler("setDevicesDiscoveryConfig", + &Delegate::SetDevicesDiscoveryConfig, delegate); + d->RegisterHandler("setDevicesUpdatesEnabled", + &Delegate::SetDevicesUpdatesEnabled, delegate); + d->RegisterHandler("performActionOnRemotePage", + &Delegate::PerformActionOnRemotePage, delegate); + d->RegisterHandler("openRemotePage", &Delegate::OpenRemotePage, delegate); + d->RegisterHandler("openNodeFrontend", &Delegate::OpenNodeFrontend, delegate); + d->RegisterHandler("dispatchProtocolMessage", + &Delegate::DispatchProtocolMessageFromDevToolsFrontend, + delegate); + d->RegisterHandler("recordEnumeratedHistogram", + &Delegate::RecordEnumeratedHistogram, delegate); + d->RegisterHandlerWithCallback("sendJsonRequest", + &Delegate::SendJsonRequest, delegate); + d->RegisterHandlerWithCallback("getPreferences", + &Delegate::GetPreferences, delegate); + d->RegisterHandler("setPreference", + &Delegate::SetPreference, delegate); + d->RegisterHandler("removePreference", + &Delegate::RemovePreference, delegate); + d->RegisterHandler("clearPreferences", + &Delegate::ClearPreferences, delegate); + d->RegisterHandlerWithCallback("reattach", + &Delegate::Reattach, delegate); + d->RegisterHandler("readyForTest", + &Delegate::ReadyForTest, delegate); + return d; +} diff --git a/chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.h b/chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.h new file mode 100644 index 00000000000..db6a8f10178 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.h @@ -0,0 +1,110 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_EMBEDDER_MESSAGE_DISPATCHER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_EMBEDDER_MESSAGE_DISPATCHER_H_ + +#include <map> +#include <string> + +#include "base/callback.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace base { +class ListValue; +class Value; +} + +/** + * Dispatcher for messages sent from the DevTools frontend running in an + * isolated renderer (on chrome-devtools://) to the embedder in the browser. + * + * The messages are sent via InspectorFrontendHost.sendMessageToEmbedder method. + */ +class DevToolsEmbedderMessageDispatcher { + public: + class Delegate { + public: + using DispatchCallback = base::Callback<void(const base::Value*)>; + + virtual ~Delegate() {} + + virtual void ActivateWindow() = 0; + virtual void CloseWindow() = 0; + virtual void LoadCompleted() = 0; + virtual void SetInspectedPageBounds(const gfx::Rect& rect) = 0; + virtual void InspectElementCompleted() = 0; + virtual void InspectedURLChanged(const std::string& url) = 0; + virtual void SetIsDocked(const DispatchCallback& callback, + bool is_docked) = 0; + virtual void OpenInNewTab(const std::string& url) = 0; + virtual void SaveToFile(const std::string& url, + const std::string& content, + bool save_as) = 0; + virtual void AppendToFile(const std::string& url, + const std::string& content) = 0; + virtual void RequestFileSystems() = 0; + virtual void AddFileSystem(const std::string& file_system_path) = 0; + virtual void RemoveFileSystem(const std::string& file_system_path) = 0; + virtual void UpgradeDraggedFileSystemPermissions( + const std::string& file_system_url) = 0; + virtual void IndexPath(int index_request_id, + const std::string& file_system_path) = 0; + virtual void StopIndexing(int index_request_id) = 0; + virtual void LoadNetworkResource(const DispatchCallback& callback, + const std::string& url, + const std::string& headers, + int stream_id) = 0; + virtual void SearchInPath(int search_request_id, + const std::string& file_system_path, + const std::string& query) = 0; + virtual void SetWhitelistedShortcuts(const std::string& message) = 0; + virtual void SetEyeDropperActive(bool active) = 0; + virtual void ShowCertificateViewer(const std::string& cert_chain) = 0; + virtual void ZoomIn() = 0; + virtual void ZoomOut() = 0; + virtual void ResetZoom() = 0; + virtual void SetDevicesUpdatesEnabled(bool enabled) = 0; + virtual void SetDevicesDiscoveryConfig( + bool discover_usb_devices, + bool port_forwarding_enabled, + const std::string& port_forwarding_config, + bool network_discovery_enabled, + const std::string& network_discovery_config) = 0; + virtual void PerformActionOnRemotePage(const std::string& page_id, + const std::string& action) = 0; + virtual void OpenRemotePage(const std::string& browser_id, + const std::string& url) = 0; + virtual void OpenNodeFrontend() = 0; + virtual void GetPreferences(const DispatchCallback& callback) = 0; + virtual void SetPreference(const std::string& name, + const std::string& value) = 0; + virtual void RemovePreference(const std::string& name) = 0; + virtual void ClearPreferences() = 0; + virtual void DispatchProtocolMessageFromDevToolsFrontend( + const std::string& message) = 0; + virtual void RecordEnumeratedHistogram(const std::string& name, + int sample, + int boundary_value) = 0; + virtual void SendJsonRequest(const DispatchCallback& callback, + const std::string& browser_id, + const std::string& url) = 0; + virtual void Reattach(const DispatchCallback& callback) = 0; + virtual void ReadyForTest() = 0; + }; + + using DispatchCallback = Delegate::DispatchCallback; + + virtual ~DevToolsEmbedderMessageDispatcher() {} + virtual bool Dispatch(const DispatchCallback& callback, + const std::string& method, + const base::ListValue* params) = 0; + + static DevToolsEmbedderMessageDispatcher* CreateForDevToolsFrontend( + Delegate* delegate); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_EMBEDDER_MESSAGE_DISPATCHER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_eye_dropper.cc b/chromium/chrome/browser/devtools/devtools_eye_dropper.cc new file mode 100644 index 00000000000..bb38173b238 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_eye_dropper.cc @@ -0,0 +1,249 @@ +// 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/devtools_eye_dropper.h" + +#include "base/bind.h" +#include "build/build_config.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/cursor_info.h" +#include "content/public/common/screen_info.h" +#include "third_party/WebKit/public/platform/WebInputEvent.h" +#include "third_party/WebKit/public/platform/WebMouseEvent.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPath.h" +#include "ui/gfx/geometry/size_conversions.h" + +DevToolsEyeDropper::DevToolsEyeDropper(content::WebContents* web_contents, + EyeDropperCallback callback) + : content::WebContentsObserver(web_contents), + callback_(callback), + last_cursor_x_(-1), + last_cursor_y_(-1), + host_(nullptr), + weak_factory_(this) { + mouse_event_callback_ = + base::Bind(&DevToolsEyeDropper::HandleMouseEvent, base::Unretained(this)); + content::RenderViewHost* rvh = web_contents->GetRenderViewHost(); + if (rvh) { + AttachToHost(rvh->GetWidget()); + UpdateFrame(); + } +} + +DevToolsEyeDropper::~DevToolsEyeDropper() { + DetachFromHost(); +} + +void DevToolsEyeDropper::AttachToHost(content::RenderWidgetHost* host) { + host_ = host; + host_->AddMouseEventCallback(mouse_event_callback_); +} + +void DevToolsEyeDropper::DetachFromHost() { + if (!host_) + return; + host_->RemoveMouseEventCallback(mouse_event_callback_); + content::CursorInfo cursor_info; + cursor_info.type = blink::WebCursorInfo::kTypePointer; + host_->SetCursor(cursor_info); + host_ = nullptr; +} + +void DevToolsEyeDropper::RenderViewCreated(content::RenderViewHost* host) { + if (!host_) { + AttachToHost(host->GetWidget()); + UpdateFrame(); + } +} + +void DevToolsEyeDropper::RenderViewDeleted(content::RenderViewHost* host) { + if (host->GetWidget() == host_) { + DetachFromHost(); + ResetFrame(); + } +} + +void DevToolsEyeDropper::RenderViewHostChanged( + content::RenderViewHost* old_host, + content::RenderViewHost* new_host) { + if ((old_host && old_host->GetWidget() == host_) || (!old_host && !host_)) { + DetachFromHost(); + AttachToHost(new_host->GetWidget()); + UpdateFrame(); + } +} + +void DevToolsEyeDropper::DidReceiveCompositorFrame() { + UpdateFrame(); +} + +void DevToolsEyeDropper::UpdateFrame() { + if (!host_ || !host_->GetView()) + return; + + // TODO(miu): This is the wrong size. It's the size of the view on-screen, and + // not the rendering size of the view. The latter is what is wanted here, so + // that the resulting bitmap's pixel coordinates line-up with the + // blink::WebMouseEvent coordinates. http://crbug.com/73362 + gfx::Size should_be_rendering_size = host_->GetView()->GetViewBounds().size(); + host_->GetView()->CopyFromSurface( + gfx::Rect(), should_be_rendering_size, + base::Bind(&DevToolsEyeDropper::FrameUpdated, weak_factory_.GetWeakPtr()), + kN32_SkColorType); +} + +void DevToolsEyeDropper::ResetFrame() { + frame_.reset(); + last_cursor_x_ = -1; + last_cursor_y_ = -1; +} + +void DevToolsEyeDropper::FrameUpdated(const SkBitmap& bitmap, + content::ReadbackResponse response) { + if (response == content::READBACK_SUCCESS) { + frame_ = bitmap; + UpdateCursor(); + } +} + +bool DevToolsEyeDropper::HandleMouseEvent(const blink::WebMouseEvent& event) { + last_cursor_x_ = event.PositionInWidget().x; + last_cursor_y_ = event.PositionInWidget().y; + if (frame_.drawsNothing()) + return true; + + if (event.button == blink::WebMouseEvent::Button::kLeft && + (event.GetType() == blink::WebInputEvent::kMouseDown || + event.GetType() == blink::WebInputEvent::kMouseMove)) { + if (last_cursor_x_ < 0 || last_cursor_x_ >= frame_.width() || + last_cursor_y_ < 0 || last_cursor_y_ >= frame_.height()) { + return true; + } + + SkColor sk_color = frame_.getColor(last_cursor_x_, last_cursor_y_); + callback_.Run(SkColorGetR(sk_color), SkColorGetG(sk_color), + SkColorGetB(sk_color), SkColorGetA(sk_color)); + } + UpdateCursor(); + return true; +} + +void DevToolsEyeDropper::UpdateCursor() { + if (!host_ || frame_.drawsNothing()) + return; + + if (last_cursor_x_ < 0 || last_cursor_x_ >= frame_.width() || + last_cursor_y_ < 0 || last_cursor_y_ >= frame_.height()) { + return; + } + +// Due to platform limitations, we are using two different cursors +// depending on the platform. Mac and Win have large cursors with two circles +// for original spot and its magnified projection; Linux gets smaller (64 px) +// magnified projection only with centered hotspot. +// Mac Retina requires cursor to be > 120px in order to render smoothly. + +#if defined(OS_LINUX) + const float kCursorSize = 63; + const float kDiameter = 63; + const float kHotspotOffset = 32; + const float kHotspotRadius = 0; + const float kPixelSize = 9; +#else + const float kCursorSize = 150; + const float kDiameter = 110; + const float kHotspotOffset = 25; + const float kHotspotRadius = 5; + const float kPixelSize = 10; +#endif + + content::ScreenInfo screen_info; + host_->GetScreenInfo(&screen_info); + double device_scale_factor = screen_info.device_scale_factor; + + SkBitmap result; + result.allocN32Pixels(kCursorSize * device_scale_factor, + kCursorSize * device_scale_factor); + result.eraseARGB(0, 0, 0, 0); + + SkCanvas canvas(result); + canvas.scale(device_scale_factor, device_scale_factor); + canvas.translate(0.5f, 0.5f); + + SkPaint paint; + + // Paint original spot with cross. + if (kHotspotRadius > 0) { + paint.setStrokeWidth(1); + paint.setAntiAlias(false); + paint.setColor(SK_ColorDKGRAY); + paint.setStyle(SkPaint::kStroke_Style); + + canvas.drawLine(kHotspotOffset, kHotspotOffset - 2 * kHotspotRadius, + kHotspotOffset, kHotspotOffset - kHotspotRadius, paint); + canvas.drawLine(kHotspotOffset, kHotspotOffset + kHotspotRadius, + kHotspotOffset, kHotspotOffset + 2 * kHotspotRadius, paint); + canvas.drawLine(kHotspotOffset - 2 * kHotspotRadius, kHotspotOffset, + kHotspotOffset - kHotspotRadius, kHotspotOffset, paint); + canvas.drawLine(kHotspotOffset + kHotspotRadius, kHotspotOffset, + kHotspotOffset + 2 * kHotspotRadius, kHotspotOffset, paint); + + paint.setStrokeWidth(2); + paint.setAntiAlias(true); + canvas.drawCircle(kHotspotOffset, kHotspotOffset, kHotspotRadius, paint); + } + + // Clip circle for magnified projection. + float padding = (kCursorSize - kDiameter) / 2; + SkPath clip_path; + clip_path.addOval(SkRect::MakeXYWH(padding, padding, kDiameter, kDiameter)); + clip_path.close(); + canvas.clipPath(clip_path, SkClipOp::kIntersect, true); + + // Project pixels. + int pixel_count = kDiameter / kPixelSize; + SkRect src_rect = SkRect::MakeXYWH(last_cursor_x_ - pixel_count / 2, + last_cursor_y_ - pixel_count / 2, + pixel_count, pixel_count); + SkRect dst_rect = SkRect::MakeXYWH(padding, padding, kDiameter, kDiameter); + canvas.drawBitmapRect(frame_, src_rect, dst_rect, NULL); + + // Paint grid. + paint.setStrokeWidth(1); + paint.setAntiAlias(false); + paint.setColor(SK_ColorGRAY); + for (int i = 0; i < pixel_count; ++i) { + canvas.drawLine(padding + i * kPixelSize, padding, padding + i * kPixelSize, + kCursorSize - padding, paint); + canvas.drawLine(padding, padding + i * kPixelSize, kCursorSize - padding, + padding + i * kPixelSize, paint); + } + + // Paint central pixel in red. + SkRect pixel = + SkRect::MakeXYWH((kCursorSize - kPixelSize) / 2, + (kCursorSize - kPixelSize) / 2, kPixelSize, kPixelSize); + paint.setColor(SK_ColorRED); + paint.setStyle(SkPaint::kStroke_Style); + canvas.drawRect(pixel, paint); + + // Paint outline. + paint.setStrokeWidth(2); + paint.setColor(SK_ColorDKGRAY); + paint.setAntiAlias(true); + canvas.drawCircle(kCursorSize / 2, kCursorSize / 2, kDiameter / 2, paint); + + content::CursorInfo cursor_info; + cursor_info.type = blink::WebCursorInfo::kTypeCustom; + cursor_info.image_scale_factor = device_scale_factor; + cursor_info.custom_image = result; + cursor_info.hotspot = gfx::Point(kHotspotOffset * device_scale_factor, + kHotspotOffset * device_scale_factor); + host_->SetCursor(cursor_info); +} diff --git a/chromium/chrome/browser/devtools/devtools_eye_dropper.h b/chromium/chrome/browser/devtools/devtools_eye_dropper.h new file mode 100644 index 00000000000..8d92d53c4e0 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_eye_dropper.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_EYE_DROPPER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_EYE_DROPPER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "content/public/browser/readback_types.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/browser/web_contents_observer.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace blink { +class WebMouseEvent; +} + +class DevToolsEyeDropper : public content::WebContentsObserver { + public: + typedef base::Callback<void(int, int, int, int)> EyeDropperCallback; + + DevToolsEyeDropper(content::WebContents* web_contents, + EyeDropperCallback callback); + ~DevToolsEyeDropper() override; + + private: + void AttachToHost(content::RenderWidgetHost* host); + void DetachFromHost(); + + // content::WebContentsObserver. + void DidReceiveCompositorFrame() override; + void RenderViewCreated(content::RenderViewHost* host) override; + void RenderViewDeleted(content::RenderViewHost* host) override; + void RenderViewHostChanged(content::RenderViewHost* old_host, + content::RenderViewHost* new_host) override; + + void UpdateFrame(); + void ResetFrame(); + void FrameUpdated(const SkBitmap&, content::ReadbackResponse); + bool HandleMouseEvent(const blink::WebMouseEvent& event); + void UpdateCursor(); + + EyeDropperCallback callback_; + SkBitmap frame_; + int last_cursor_x_; + int last_cursor_y_; + content::RenderWidgetHost::MouseEventCallback mouse_event_callback_; + content::RenderWidgetHost* host_; + base::WeakPtrFactory<DevToolsEyeDropper> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsEyeDropper); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_EYE_DROPPER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_file_helper.cc b/chromium/chrome/browser/devtools/devtools_file_helper.cc new file mode 100644 index 00000000000..28599b7b178 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_file_helper.cc @@ -0,0 +1,465 @@ +// Copyright (c) 2012 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/devtools_file_helper.h" + +#include <set> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_util.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/md5.h" +#include "base/memory/ptr_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/value_conversions.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/devtools/devtools_file_watcher.h" +#include "chrome/browser/download/download_prefs.h" +#include "chrome/browser/platform_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/chrome_select_file_policy.h" +#include "chrome/common/pref_names.h" +#include "chrome/grit/generated_resources.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_client.h" +#include "content/public/common/url_constants.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/isolated_context.h" +#include "storage/common/fileapi/file_system_util.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/shell_dialogs/select_file_dialog.h" + +using base::Bind; +using base::Callback; +using content::BrowserContext; +using content::BrowserThread; +using content::DownloadManager; +using content::RenderViewHost; +using content::WebContents; +using std::set; + +namespace { + +static const char kRootName[] = "<root>"; + +base::LazyInstance<base::FilePath>::Leaky + g_last_save_path = LAZY_INSTANCE_INITIALIZER; + +typedef Callback<void(const base::FilePath&)> SelectedCallback; +typedef Callback<void(void)> CanceledCallback; + +class SelectFileDialog : public ui::SelectFileDialog::Listener, + public base::RefCounted<SelectFileDialog> { + public: + SelectFileDialog(const SelectedCallback& selected_callback, + const CanceledCallback& canceled_callback, + WebContents* web_contents) + : selected_callback_(selected_callback), + canceled_callback_(canceled_callback), + web_contents_(web_contents) { + select_file_dialog_ = ui::SelectFileDialog::Create( + this, new ChromeSelectFilePolicy(web_contents)); + } + + void Show(ui::SelectFileDialog::Type type, + const base::FilePath& default_path) { + AddRef(); // Balanced in the three listener outcomes. + select_file_dialog_->SelectFile( + type, + base::string16(), + default_path, + NULL, + 0, + base::FilePath::StringType(), + platform_util::GetTopLevel(web_contents_->GetNativeView()), + NULL); + } + + // ui::SelectFileDialog::Listener implementation. + void FileSelected(const base::FilePath& path, + int index, + void* params) override { + selected_callback_.Run(path); + Release(); // Balanced in ::Show. + } + + void MultiFilesSelected(const std::vector<base::FilePath>& files, + void* params) override { + Release(); // Balanced in ::Show. + NOTREACHED() << "Should not be able to select multiple files"; + } + + void FileSelectionCanceled(void* params) override { + if (!canceled_callback_.is_null()) + canceled_callback_.Run(); + Release(); // Balanced in ::Show. + } + + private: + friend class base::RefCounted<SelectFileDialog>; + ~SelectFileDialog() override {} + + scoped_refptr<ui::SelectFileDialog> select_file_dialog_; + SelectedCallback selected_callback_; + CanceledCallback canceled_callback_; + WebContents* web_contents_; + + DISALLOW_COPY_AND_ASSIGN(SelectFileDialog); +}; + +void WriteToFile(const base::FilePath& path, const std::string& content) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + DCHECK(!path.empty()); + + base::WriteFile(path, content.c_str(), content.length()); +} + +void AppendToFile(const base::FilePath& path, const std::string& content) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + DCHECK(!path.empty()); + + base::AppendToFile(path, content.c_str(), content.size()); +} + +storage::IsolatedContext* isolated_context() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + storage::IsolatedContext* isolated_context = + storage::IsolatedContext::GetInstance(); + DCHECK(isolated_context); + return isolated_context; +} + +std::string RegisterFileSystem(WebContents* web_contents, + const base::FilePath& path) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + CHECK(web_contents->GetURL().SchemeIs(content::kChromeDevToolsScheme)); + std::string root_name(kRootName); + std::string file_system_id = isolated_context()->RegisterFileSystemForPath( + storage::kFileSystemTypeNativeLocal, std::string(), path, &root_name); + + content::ChildProcessSecurityPolicy* policy = + content::ChildProcessSecurityPolicy::GetInstance(); + RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); + int renderer_id = render_view_host->GetProcess()->GetID(); + policy->GrantReadFileSystem(renderer_id, file_system_id); + policy->GrantWriteFileSystem(renderer_id, file_system_id); + policy->GrantCreateFileForFileSystem(renderer_id, file_system_id); + policy->GrantDeleteFromFileSystem(renderer_id, file_system_id); + + // We only need file level access for reading FileEntries. Saving FileEntries + // just needs the file system to have read/write access, which is granted + // above if required. + if (!policy->CanReadFile(renderer_id, path)) + policy->GrantReadFile(renderer_id, path); + return file_system_id; +} + +DevToolsFileHelper::FileSystem CreateFileSystemStruct( + WebContents* web_contents, + const std::string& file_system_id, + const std::string& file_system_path) { + const GURL origin = web_contents->GetURL().GetOrigin(); + std::string file_system_name = + storage::GetIsolatedFileSystemName(origin, file_system_id); + std::string root_url = storage::GetIsolatedFileSystemRootURIString( + origin, file_system_id, kRootName); + return DevToolsFileHelper::FileSystem(file_system_name, + root_url, + file_system_path); +} + +set<std::string> GetAddedFileSystemPaths(Profile* profile) { + const base::DictionaryValue* file_systems_paths_value = + profile->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths); + set<std::string> result; + for (base::DictionaryValue::Iterator it(*file_systems_paths_value); + !it.IsAtEnd(); it.Advance()) { + result.insert(it.key()); + } + return result; +} + +} // namespace + +DevToolsFileHelper::FileSystem::FileSystem() { +} + +DevToolsFileHelper::FileSystem::FileSystem(const std::string& file_system_name, + const std::string& root_url, + const std::string& file_system_path) + : file_system_name(file_system_name), + root_url(root_url), + file_system_path(file_system_path) { +} + +DevToolsFileHelper::DevToolsFileHelper(WebContents* web_contents, + Profile* profile, + Delegate* delegate) + : web_contents_(web_contents), + profile_(profile), + delegate_(delegate), + weak_factory_(this) { + pref_change_registrar_.Init(profile_->GetPrefs()); + pref_change_registrar_.Add(prefs::kDevToolsFileSystemPaths, + base::Bind(&DevToolsFileHelper::FileSystemPathsSettingChanged, + base::Unretained(this))); + file_watcher_.reset(new DevToolsFileWatcher( + base::Bind(&DevToolsFileHelper::FilePathsChanged, + weak_factory_.GetWeakPtr()))); +} + +DevToolsFileHelper::~DevToolsFileHelper() { + BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, + file_watcher_.release()); +} + +void DevToolsFileHelper::Save(const std::string& url, + const std::string& content, + bool save_as, + const SaveCallback& saveCallback, + const SaveCallback& cancelCallback) { + PathsMap::iterator it = saved_files_.find(url); + if (it != saved_files_.end() && !save_as) { + SaveAsFileSelected(url, content, saveCallback, it->second); + return; + } + + const base::DictionaryValue* file_map = + profile_->GetPrefs()->GetDictionary(prefs::kDevToolsEditedFiles); + base::FilePath initial_path; + + const base::Value* path_value; + if (file_map->Get(base::MD5String(url), &path_value)) + base::GetValueAsFilePath(*path_value, &initial_path); + + if (initial_path.empty()) { + GURL gurl(url); + std::string suggested_file_name = gurl.is_valid() ? + gurl.ExtractFileName() : url; + + if (suggested_file_name.length() > 64) + suggested_file_name = suggested_file_name.substr(0, 64); + + if (!g_last_save_path.Pointer()->empty()) { + initial_path = g_last_save_path.Pointer()->DirName().AppendASCII( + suggested_file_name); + } else { + base::FilePath download_path = DownloadPrefs::FromDownloadManager( + BrowserContext::GetDownloadManager(profile_))->DownloadPath(); + initial_path = download_path.AppendASCII(suggested_file_name); + } + } + + scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog( + Bind(&DevToolsFileHelper::SaveAsFileSelected, + weak_factory_.GetWeakPtr(), + url, + content, + saveCallback), + cancelCallback, + web_contents_); + select_file_dialog->Show(ui::SelectFileDialog::SELECT_SAVEAS_FILE, + initial_path); +} + +void DevToolsFileHelper::Append(const std::string& url, + const std::string& content, + const AppendCallback& callback) { + PathsMap::iterator it = saved_files_.find(url); + if (it == saved_files_.end()) + return; + callback.Run(); + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + BindOnce(&AppendToFile, it->second, content)); +} + +void DevToolsFileHelper::SaveAsFileSelected(const std::string& url, + const std::string& content, + const SaveCallback& callback, + const base::FilePath& path) { + *g_last_save_path.Pointer() = path; + saved_files_[url] = path; + + DictionaryPrefUpdate update(profile_->GetPrefs(), + prefs::kDevToolsEditedFiles); + base::DictionaryValue* files_map = update.Get(); + files_map->SetWithoutPathExpansion(base::MD5String(url), + base::CreateFilePathValue(path)); + callback.Run(); + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + BindOnce(&WriteToFile, path, content)); +} + +void DevToolsFileHelper::AddFileSystem( + const std::string& file_system_path, + const ShowInfoBarCallback& show_info_bar_callback) { + if (file_system_path.empty()) { + scoped_refptr<SelectFileDialog> select_file_dialog = new SelectFileDialog( + Bind(&DevToolsFileHelper::InnerAddFileSystem, + weak_factory_.GetWeakPtr(), show_info_bar_callback), + base::Closure(), + web_contents_); + select_file_dialog->Show(ui::SelectFileDialog::SELECT_FOLDER, + base::FilePath()); + } else { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&DevToolsFileHelper::CheckProjectFileExistsAndAddFileSystem, + weak_factory_.GetWeakPtr(), show_info_bar_callback, + base::FilePath::FromUTF8Unsafe(file_system_path))); + } +} + +void DevToolsFileHelper::CheckProjectFileExistsAndAddFileSystem( + const ShowInfoBarCallback& show_info_bar_callback, + const base::FilePath& path) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + if (base::PathExists(path.Append(FILE_PATH_LITERAL(".devtools")))) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + BindOnce(&DevToolsFileHelper::InnerAddFileSystem, + weak_factory_.GetWeakPtr(), show_info_bar_callback, path)); + } +} + +void DevToolsFileHelper::UpgradeDraggedFileSystemPermissions( + const std::string& file_system_url, + const ShowInfoBarCallback& show_info_bar_callback) { + storage::FileSystemURL root_url = + isolated_context()->CrackURL(GURL(file_system_url)); + if (!root_url.is_valid() || !root_url.path().empty()) + return; + + std::vector<storage::MountPoints::MountPointInfo> mount_points; + isolated_context()->GetDraggedFileInfo(root_url.filesystem_id(), + &mount_points); + + std::vector<storage::MountPoints::MountPointInfo>::const_iterator it = + mount_points.begin(); + for (; it != mount_points.end(); ++it) + InnerAddFileSystem(show_info_bar_callback, it->path); +} + +void DevToolsFileHelper::InnerAddFileSystem( + const ShowInfoBarCallback& show_info_bar_callback, + const base::FilePath& path) { + std::string file_system_path = path.AsUTF8Unsafe(); + + const base::DictionaryValue* file_systems_paths_value = + profile_->GetPrefs()->GetDictionary(prefs::kDevToolsFileSystemPaths); + if (file_systems_paths_value->HasKey(file_system_path)) + return; + + std::string path_display_name = path.AsEndingWithSeparator().AsUTF8Unsafe(); + base::string16 message = l10n_util::GetStringFUTF16( + IDS_DEV_TOOLS_CONFIRM_ADD_FILE_SYSTEM_MESSAGE, + base::UTF8ToUTF16(path_display_name)); + show_info_bar_callback.Run( + message, + Bind(&DevToolsFileHelper::AddUserConfirmedFileSystem, + weak_factory_.GetWeakPtr(), path)); +} + +void DevToolsFileHelper::AddUserConfirmedFileSystem( + const base::FilePath& path, + bool allowed) { + if (!allowed) + return; + + std::string file_system_id = RegisterFileSystem(web_contents_, path); + std::string file_system_path = path.AsUTF8Unsafe(); + + DictionaryPrefUpdate update(profile_->GetPrefs(), + prefs::kDevToolsFileSystemPaths); + base::DictionaryValue* file_systems_paths_value = update.Get(); + file_systems_paths_value->SetWithoutPathExpansion( + file_system_path, base::MakeUnique<base::Value>()); +} + +std::vector<DevToolsFileHelper::FileSystem> +DevToolsFileHelper::GetFileSystems() { + file_system_paths_ = GetAddedFileSystemPaths(profile_); + std::vector<FileSystem> file_systems; + for (auto file_system_path : file_system_paths_) { + base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); + std::string file_system_id = RegisterFileSystem(web_contents_, path); + FileSystem filesystem = CreateFileSystemStruct(web_contents_, + file_system_id, + file_system_path); + file_systems.push_back(filesystem); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&DevToolsFileWatcher::AddWatch, + base::Unretained(file_watcher_.get()), path)); + } + return file_systems; +} + +void DevToolsFileHelper::RemoveFileSystem(const std::string& file_system_path) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); + isolated_context()->RevokeFileSystemByPath(path); + + DictionaryPrefUpdate update(profile_->GetPrefs(), + prefs::kDevToolsFileSystemPaths); + base::DictionaryValue* file_systems_paths_value = update.Get(); + file_systems_paths_value->RemoveWithoutPathExpansion(file_system_path, NULL); +} + +bool DevToolsFileHelper::IsFileSystemAdded( + const std::string& file_system_path) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + set<std::string> file_system_paths = GetAddedFileSystemPaths(profile_); + return file_system_paths.find(file_system_path) != file_system_paths.end(); +} + +void DevToolsFileHelper::FileSystemPathsSettingChanged() { + std::set<std::string> remaining; + remaining.swap(file_system_paths_); + + for (auto file_system_path : GetAddedFileSystemPaths(profile_)) { + if (remaining.find(file_system_path) == remaining.end()) { + base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); + std::string file_system_id = RegisterFileSystem(web_contents_, path); + FileSystem filesystem = CreateFileSystemStruct(web_contents_, + file_system_id, + file_system_path); + delegate_->FileSystemAdded(filesystem); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&DevToolsFileWatcher::AddWatch, + base::Unretained(file_watcher_.get()), path)); + } else { + remaining.erase(file_system_path); + } + file_system_paths_.insert(file_system_path); + } + + for (auto file_system_path : remaining) { + delegate_->FileSystemRemoved(file_system_path); + base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&DevToolsFileWatcher::RemoveWatch, + base::Unretained(file_watcher_.get()), path)); + } +} + +void DevToolsFileHelper::FilePathsChanged( + const std::vector<std::string>& changed_paths, + const std::vector<std::string>& added_paths, + const std::vector<std::string>& removed_paths) { + delegate_->FilePathsChanged(changed_paths, added_paths, removed_paths); +} diff --git a/chromium/chrome/browser/devtools/devtools_file_helper.h b/chromium/chrome/browser/devtools/devtools_file_helper.h new file mode 100644 index 00000000000..e613d02595d --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_file_helper.h @@ -0,0 +1,154 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_HELPER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_HELPER_H_ + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "components/prefs/pref_change_registrar.h" + +class DevToolsFileWatcher; +class Profile; + +namespace base { +class FilePath; +} + +namespace content { +class WebContents; +} + +class DevToolsFileHelper { + public: + struct FileSystem { + FileSystem(); + FileSystem(const std::string& file_system_name, + const std::string& root_url, + const std::string& file_system_path); + + std::string file_system_name; + std::string root_url; + std::string file_system_path; + }; + + class Delegate { + public: + virtual ~Delegate() {} + virtual void FileSystemAdded(const FileSystem& file_system) = 0; + virtual void FileSystemRemoved(const std::string& file_system_path) = 0; + virtual void FilePathsChanged( + const std::vector<std::string>& changed_paths, + const std::vector<std::string>& added_paths, + const std::vector<std::string>& removed_paths) = 0; + }; + + DevToolsFileHelper(content::WebContents* web_contents, Profile* profile, + Delegate* delegate); + ~DevToolsFileHelper(); + + typedef base::Callback<void(void)> SaveCallback; + typedef base::Callback<void(void)> AppendCallback; + typedef base::Callback<void(const base::string16&, + const base::Callback<void(bool)>&)> + ShowInfoBarCallback; + + // Saves |content| to the file and associates its path with given |url|. + // If client is calling this method with given |url| for the first time + // or |save_as| is true, confirmation dialog is shown to the user. + void Save(const std::string& url, + const std::string& content, + bool save_as, + const SaveCallback& saveCallback, + const SaveCallback& cancelCallback); + + // Append |content| to the file that has been associated with given |url|. + // The |url| can be associated with a file via calling Save method. + // If the Save method has not been called for this |url|, then + // Append method does nothing. + void Append(const std::string& url, + const std::string& content, + const AppendCallback& callback); + + // 1. If empty |file_system_path| is passed, shows select folder dialog. + // If user cancels folder selection, passes empty FileSystem struct to + // |callback|. + // If non-empty |file_system_path| is passed, verifies that corresponding + // folder contains the ".devtools" project file, if not, passes empty + // FileSystem struct to |callback|. + // 2. Shows infobar by means of |show_info_bar_callback| to let the user + // decide whether to grant security permissions or not. + // If user allows adding file system in infobar, grants renderer + // read/write permissions, registers isolated file system for it and + // passes FileSystem struct to |callback|. Saves file system path to prefs. + // If user denies adding file system in infobar, passes error string to + // |callback|. + void AddFileSystem(const std::string& file_system_path, + const ShowInfoBarCallback& show_info_bar_callback); + + // Upgrades dragged file system permissions to a read-write access. + // Shows infobar by means of |show_info_bar_callback| to let the user decide + // whether to grant security permissions or not. + // If user allows adding file system in infobar, grants renderer read/write + // permissions, registers isolated file system for it and passes FileSystem + // struct to |callback|. Saves file system path to prefs. + // If user denies adding file system in infobar, passes error string to + // |callback|. + void UpgradeDraggedFileSystemPermissions( + const std::string& file_system_url, + const ShowInfoBarCallback& show_info_bar_callback); + + // Loads file system paths from prefs, grants permissions and registers + // isolated file system for those of them that contain magic file and passes + // FileSystem structs for registered file systems to |callback|. + std::vector<FileSystem> GetFileSystems(); + + // Removes isolated file system for given |file_system_path|. + void RemoveFileSystem(const std::string& file_system_path); + + // Returns whether access to the folder on given |file_system_path| was + // granted. + bool IsFileSystemAdded(const std::string& file_system_path); + + private: + void SaveAsFileSelected(const std::string& url, + const std::string& content, + const SaveCallback& callback, + const base::FilePath& path); + void InnerAddFileSystem( + const ShowInfoBarCallback& show_info_bar_callback, + const base::FilePath& path); + void CheckProjectFileExistsAndAddFileSystem( + const ShowInfoBarCallback& show_info_bar_callback, + const base::FilePath& path); + void AddUserConfirmedFileSystem( + const base::FilePath& path, + bool allowed); + void FileSystemPathsSettingChanged(); + void FilePathsChanged(const std::vector<std::string>& changed_paths, + const std::vector<std::string>& added_paths, + const std::vector<std::string>& removed_paths); + + content::WebContents* web_contents_; + Profile* profile_; + DevToolsFileHelper::Delegate* delegate_; + typedef std::map<std::string, base::FilePath> PathsMap; + PathsMap saved_files_; + PrefChangeRegistrar pref_change_registrar_; + std::set<std::string> file_system_paths_; + std::unique_ptr<DevToolsFileWatcher> file_watcher_; + base::WeakPtrFactory<DevToolsFileHelper> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(DevToolsFileHelper); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_HELPER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_file_system_indexer.cc b/chromium/chrome/browser/devtools/devtools_file_system_indexer.cc new file mode 100644 index 00000000000..b538fe1a205 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_file_system_indexer.cc @@ -0,0 +1,459 @@ +// Copyright 2013 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/devtools_file_system_indexer.h" + +#include <stddef.h> + +#include <iterator> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/files/file_util_proxy.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/browser_thread.h" + +using base::Bind; +using base::Callback; +using base::FileEnumerator; +using base::FilePath; +using base::Time; +using base::TimeDelta; +using base::TimeTicks; +using content::BrowserThread; +using std::map; +using std::set; +using std::string; +using std::vector; + +namespace { + +typedef int32_t Trigram; +typedef char TrigramChar; +typedef uint16_t FileId; + +const int kMinTimeoutBetweenWorkedNitification = 200; +// Trigram characters include all ASCII printable characters (32-126) except for +// the capital letters, because the index is case insensitive. +const size_t kTrigramCharacterCount = 126 - 'Z' - 1 + 'A' - ' ' + 1; +const size_t kTrigramCount = + kTrigramCharacterCount * kTrigramCharacterCount * kTrigramCharacterCount; +const int kMaxReadLength = 10 * 1024; +const TrigramChar kUndefinedTrigramChar = -1; +const TrigramChar kBinaryTrigramChar = -2; +const Trigram kUndefinedTrigram = -1; + +class Index { + public: + Index(); + // Index is only instantiated as a leak LazyInstance, so the destructor is + // never called. + ~Index() = delete; + + Time LastModifiedTimeForFile(const FilePath& file_path); + void SetTrigramsForFile(const FilePath& file_path, + const vector<Trigram>& index, + const Time& time); + vector<FilePath> Search(string query); + void NormalizeVectors(); + + private: + FileId GetFileId(const FilePath& file_path); + + typedef map<FilePath, FileId> FileIdsMap; + FileIdsMap file_ids_; + FileId last_file_id_; + // The index in this vector is the trigram id. + vector<vector<FileId> > index_; + typedef map<FilePath, Time> IndexedFilesMap; + IndexedFilesMap index_times_; + vector<bool> is_normalized_; + + DISALLOW_COPY_AND_ASSIGN(Index); +}; + +base::LazyInstance<Index>::Leaky g_trigram_index = LAZY_INSTANCE_INITIALIZER; + +TrigramChar TrigramCharForChar(char c) { + static TrigramChar* trigram_chars = nullptr; + if (!trigram_chars) { + trigram_chars = new TrigramChar[256]; + for (size_t i = 0; i < 256; ++i) { + if (i > 127) { + trigram_chars[i] = kUndefinedTrigramChar; + continue; + } + char ch = static_cast<char>(i); + if (ch == '\t') + ch = ' '; + if (base::IsAsciiUpper(ch)) + ch = ch - 'A' + 'a'; + + bool is_binary_char = ch < 9 || (ch >= 14 && ch < 32) || ch == 127; + if (is_binary_char) { + trigram_chars[i] = kBinaryTrigramChar; + continue; + } + + if (ch < ' ') { + trigram_chars[i] = kUndefinedTrigramChar; + continue; + } + + if (ch >= 'Z') + ch = ch - 'Z' - 1 + 'A'; + ch -= ' '; + char signed_trigram_count = static_cast<char>(kTrigramCharacterCount); + CHECK(ch >= 0 && ch < signed_trigram_count); + trigram_chars[i] = ch; + } + } + unsigned char uc = static_cast<unsigned char>(c); + return trigram_chars[uc]; +} + +Trigram TrigramAtIndex(const vector<TrigramChar>& trigram_chars, size_t index) { + static int kTrigramCharacterCountSquared = + kTrigramCharacterCount * kTrigramCharacterCount; + if (trigram_chars[index] == kUndefinedTrigramChar || + trigram_chars[index + 1] == kUndefinedTrigramChar || + trigram_chars[index + 2] == kUndefinedTrigramChar) + return kUndefinedTrigram; + Trigram trigram = kTrigramCharacterCountSquared * trigram_chars[index] + + kTrigramCharacterCount * trigram_chars[index + 1] + + trigram_chars[index + 2]; + return trigram; +} + +Index::Index() : last_file_id_(0) { + index_.resize(kTrigramCount); + is_normalized_.resize(kTrigramCount); + std::fill(is_normalized_.begin(), is_normalized_.end(), true); +} + +Time Index::LastModifiedTimeForFile(const FilePath& file_path) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + Time last_modified_time; + if (index_times_.find(file_path) != index_times_.end()) + last_modified_time = index_times_[file_path]; + return last_modified_time; +} + +void Index::SetTrigramsForFile(const FilePath& file_path, + const vector<Trigram>& index, + const Time& time) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + FileId file_id = GetFileId(file_path); + vector<Trigram>::const_iterator it = index.begin(); + for (; it != index.end(); ++it) { + Trigram trigram = *it; + index_[trigram].push_back(file_id); + is_normalized_[trigram] = false; + } + index_times_[file_path] = time; +} + +vector<FilePath> Index::Search(string query) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + const char* data = query.c_str(); + vector<TrigramChar> trigram_chars; + trigram_chars.reserve(query.size()); + for (size_t i = 0; i < query.size(); ++i) { + TrigramChar trigram_char = TrigramCharForChar(data[i]); + if (trigram_char == kBinaryTrigramChar) + trigram_char = kUndefinedTrigramChar; + trigram_chars.push_back(trigram_char); + } + vector<Trigram> trigrams; + for (size_t i = 0; i + 2 < query.size(); ++i) { + Trigram trigram = TrigramAtIndex(trigram_chars, i); + if (trigram != kUndefinedTrigram) + trigrams.push_back(trigram); + } + set<FileId> file_ids; + bool first = true; + vector<Trigram>::const_iterator it = trigrams.begin(); + for (; it != trigrams.end(); ++it) { + Trigram trigram = *it; + if (first) { + std::copy(index_[trigram].begin(), + index_[trigram].end(), + std::inserter(file_ids, file_ids.begin())); + first = false; + continue; + } + set<FileId> intersection = base::STLSetIntersection<set<FileId> >( + file_ids, index_[trigram]); + file_ids.swap(intersection); + } + vector<FilePath> result; + FileIdsMap::const_iterator ids_it = file_ids_.begin(); + for (; ids_it != file_ids_.end(); ++ids_it) { + if (trigrams.size() == 0 || + file_ids.find(ids_it->second) != file_ids.end()) { + result.push_back(ids_it->first); + } + } + return result; +} + +FileId Index::GetFileId(const FilePath& file_path) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + string file_path_str = file_path.AsUTF8Unsafe(); + if (file_ids_.find(file_path) != file_ids_.end()) + return file_ids_[file_path]; + file_ids_[file_path] = ++last_file_id_; + return last_file_id_; +} + +void Index::NormalizeVectors() { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + for (size_t i = 0; i < kTrigramCount; ++i) { + if (!is_normalized_[i]) { + std::sort(index_[i].begin(), index_[i].end()); + if (index_[i].capacity() > index_[i].size()) + vector<FileId>(index_[i]).swap(index_[i]); + is_normalized_[i] = true; + } + } +} + +typedef Callback<void(bool, const vector<bool>&)> IndexerCallback; + +} // namespace + +DevToolsFileSystemIndexer::FileSystemIndexingJob::FileSystemIndexingJob( + const FilePath& file_system_path, + const TotalWorkCallback& total_work_callback, + const WorkedCallback& worked_callback, + const DoneCallback& done_callback) + : file_system_path_(file_system_path), + total_work_callback_(total_work_callback), + worked_callback_(worked_callback), + done_callback_(done_callback), + current_file_( + BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE).get()), + files_indexed_(0), + stopped_(false) { + current_trigrams_set_.resize(kTrigramCount); + current_trigrams_.reserve(kTrigramCount); +} + +DevToolsFileSystemIndexer::FileSystemIndexingJob::~FileSystemIndexingJob() {} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::Start() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&FileSystemIndexingJob::CollectFilesToIndex, this)); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::Stop() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&FileSystemIndexingJob::StopOnFileThread, this)); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::StopOnFileThread() { + stopped_ = true; +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::CollectFilesToIndex() { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + if (stopped_) + return; + if (!file_enumerator_) { + file_enumerator_.reset( + new FileEnumerator(file_system_path_, true, FileEnumerator::FILES)); + } + FilePath file_path = file_enumerator_->Next(); + if (file_path.empty()) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + BindOnce(total_work_callback_, file_path_times_.size())); + indexing_it_ = file_path_times_.begin(); + IndexFiles(); + return; + } + Time saved_last_modified_time = + g_trigram_index.Get().LastModifiedTimeForFile(file_path); + FileEnumerator::FileInfo file_info = file_enumerator_->GetInfo(); + Time current_last_modified_time = file_info.GetLastModifiedTime(); + if (current_last_modified_time > saved_last_modified_time) { + file_path_times_[file_path] = current_last_modified_time; + } + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&FileSystemIndexingJob::CollectFilesToIndex, this)); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::IndexFiles() { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + if (stopped_) + return; + if (indexing_it_ == file_path_times_.end()) { + g_trigram_index.Get().NormalizeVectors(); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, done_callback_); + return; + } + FilePath file_path = indexing_it_->first; + current_file_.CreateOrOpen( + file_path, + base::File::FLAG_OPEN | base::File::FLAG_READ, + Bind(&FileSystemIndexingJob::StartFileIndexing, this)); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::StartFileIndexing( + base::File::Error error) { + if (!current_file_.IsValid()) { + FinishFileIndexing(false); + return; + } + current_file_offset_ = 0; + current_trigrams_.clear(); + std::fill(current_trigrams_set_.begin(), current_trigrams_set_.end(), false); + ReadFromFile(); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::ReadFromFile() { + if (stopped_) { + CloseFile(); + return; + } + current_file_.Read(current_file_offset_, kMaxReadLength, + Bind(&FileSystemIndexingJob::OnRead, this)); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::OnRead( + base::File::Error error, + const char* data, + int bytes_read) { + if (error != base::File::FILE_OK) { + FinishFileIndexing(false); + return; + } + + if (!bytes_read || bytes_read < 3) { + FinishFileIndexing(true); + return; + } + + size_t size = static_cast<size_t>(bytes_read); + vector<TrigramChar> trigram_chars; + trigram_chars.reserve(size); + for (size_t i = 0; i < size; ++i) { + TrigramChar trigram_char = TrigramCharForChar(data[i]); + if (trigram_char == kBinaryTrigramChar) { + current_trigrams_.clear(); + FinishFileIndexing(true); + return; + } + trigram_chars.push_back(trigram_char); + } + + for (size_t i = 0; i + 2 < size; ++i) { + Trigram trigram = TrigramAtIndex(trigram_chars, i); + if ((trigram != kUndefinedTrigram) && !current_trigrams_set_[trigram]) { + current_trigrams_set_[trigram] = true; + current_trigrams_.push_back(trigram); + } + } + current_file_offset_ += bytes_read - 2; + ReadFromFile(); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::FinishFileIndexing( + bool success) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + CloseFile(); + if (success) { + FilePath file_path = indexing_it_->first; + g_trigram_index.Get().SetTrigramsForFile( + file_path, current_trigrams_, file_path_times_[file_path]); + } + ReportWorked(); + ++indexing_it_; + IndexFiles(); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::CloseFile() { + if (current_file_.IsValid()) + current_file_.Close(Bind(&FileSystemIndexingJob::CloseCallback, this)); +} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::CloseCallback( + base::File::Error error) {} + +void DevToolsFileSystemIndexer::FileSystemIndexingJob::ReportWorked() { + TimeTicks current_time = TimeTicks::Now(); + bool should_send_worked_nitification = true; + if (!last_worked_notification_time_.is_null()) { + TimeDelta delta = current_time - last_worked_notification_time_; + if (delta.InMilliseconds() < kMinTimeoutBetweenWorkedNitification) + should_send_worked_nitification = false; + } + ++files_indexed_; + if (should_send_worked_nitification) { + last_worked_notification_time_ = current_time; + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + BindOnce(worked_callback_, files_indexed_)); + files_indexed_ = 0; + } +} + +DevToolsFileSystemIndexer::DevToolsFileSystemIndexer() { +} + +DevToolsFileSystemIndexer::~DevToolsFileSystemIndexer() {} + +scoped_refptr<DevToolsFileSystemIndexer::FileSystemIndexingJob> +DevToolsFileSystemIndexer::IndexPath( + const string& file_system_path, + const TotalWorkCallback& total_work_callback, + const WorkedCallback& worked_callback, + const DoneCallback& done_callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + scoped_refptr<FileSystemIndexingJob> indexing_job = + new FileSystemIndexingJob(FilePath::FromUTF8Unsafe(file_system_path), + total_work_callback, + worked_callback, + done_callback); + indexing_job->Start(); + return indexing_job; +} + +void DevToolsFileSystemIndexer::SearchInPath(const string& file_system_path, + const string& query, + const SearchCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + BindOnce(&DevToolsFileSystemIndexer::SearchInPathOnFileThread, this, + file_system_path, query, callback)); +} + +void DevToolsFileSystemIndexer::SearchInPathOnFileThread( + const string& file_system_path, + const string& query, + const SearchCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + vector<FilePath> file_paths = g_trigram_index.Get().Search(query); + vector<string> result; + FilePath path = FilePath::FromUTF8Unsafe(file_system_path); + vector<FilePath>::const_iterator it = file_paths.begin(); + for (; it != file_paths.end(); ++it) { + if (path.IsParent(*it)) + result.push_back(it->AsUTF8Unsafe()); + } + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + BindOnce(callback, result)); +} diff --git a/chromium/chrome/browser/devtools/devtools_file_system_indexer.h b/chromium/chrome/browser/devtools/devtools_file_system_indexer.h new file mode 100644 index 00000000000..c747467c740 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_file_system_indexer.h @@ -0,0 +1,108 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_SYSTEM_INDEXER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_SYSTEM_INDEXER_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/files/file_proxy.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" + +namespace base { +class FilePath; +class FileEnumerator; +class Time; +} + +class DevToolsFileSystemIndexer + : public base::RefCountedThreadSafe<DevToolsFileSystemIndexer> { + public: + + typedef base::Callback<void(int)> TotalWorkCallback; + typedef base::Callback<void(int)> WorkedCallback; + typedef base::Callback<void()> DoneCallback; + typedef base::Callback<void(const std::vector<std::string>&)> SearchCallback; + + class FileSystemIndexingJob : public base::RefCounted<FileSystemIndexingJob> { + public: + void Stop(); + + private: + friend class base::RefCounted<FileSystemIndexingJob>; + friend class DevToolsFileSystemIndexer; + FileSystemIndexingJob(const base::FilePath& file_system_path, + const TotalWorkCallback& total_work_callback, + const WorkedCallback& worked_callback, + const DoneCallback& done_callback); + virtual ~FileSystemIndexingJob(); + + void Start(); + void StopOnFileThread(); + void CollectFilesToIndex(); + void IndexFiles(); + void StartFileIndexing(base::File::Error error); + void ReadFromFile(); + void OnRead(base::File::Error error, + const char* data, + int bytes_read); + void FinishFileIndexing(bool success); + void CloseFile(); + void CloseCallback(base::File::Error error); + void ReportWorked(); + + base::FilePath file_system_path_; + TotalWorkCallback total_work_callback_; + WorkedCallback worked_callback_; + DoneCallback done_callback_; + std::unique_ptr<base::FileEnumerator> file_enumerator_; + typedef std::map<base::FilePath, base::Time> FilePathTimesMap; + FilePathTimesMap file_path_times_; + FilePathTimesMap::const_iterator indexing_it_; + base::FileProxy current_file_; + int64_t current_file_offset_; + typedef int32_t Trigram; + std::vector<Trigram> current_trigrams_; + // The index in this vector is the trigram id. + std::vector<bool> current_trigrams_set_; + base::TimeTicks last_worked_notification_time_; + int files_indexed_; + bool stopped_; + }; + + DevToolsFileSystemIndexer(); + + // Performs file system indexing for given |file_system_path| and sends + // progress callbacks. + scoped_refptr<FileSystemIndexingJob> IndexPath( + const std::string& file_system_path, + const TotalWorkCallback& total_work_callback, + const WorkedCallback& worked_callback, + const DoneCallback& done_callback); + + // Performs trigram search for given |query| in |file_system_path|. + void SearchInPath(const std::string& file_system_path, + const std::string& query, + const SearchCallback& callback); + + private: + friend class base::RefCountedThreadSafe<DevToolsFileSystemIndexer>; + + virtual ~DevToolsFileSystemIndexer(); + + void SearchInPathOnFileThread(const std::string& file_system_path, + const std::string& query, + const SearchCallback& callback); + + DISALLOW_COPY_AND_ASSIGN(DevToolsFileSystemIndexer); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_SYSTEM_INDEXER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_file_watcher.cc b/chromium/chrome/browser/devtools/devtools_file_watcher.cc new file mode 100644 index 00000000000..e97ef213464 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_file_watcher.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2015 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/devtools_file_watcher.h" + +#include <algorithm> +#include <map> +#include <memory> +#include <set> + +#include "base/bind.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_path_watcher.h" +#include "base/files/file_util.h" +#include "base/memory/ref_counted.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +static int kFirstThrottleTimeout = 10; +static int kDefaultThrottleTimeout = 200; + +// DevToolsFileWatcher::SharedFileWatcher -------------------------------------- + +class DevToolsFileWatcher::SharedFileWatcher : + public base::RefCounted<SharedFileWatcher> { + public: + SharedFileWatcher(); + + void AddListener(DevToolsFileWatcher* watcher); + void RemoveListener(DevToolsFileWatcher* watcher); + void AddWatch(const base::FilePath& path); + void RemoveWatch(const base::FilePath& path); + + private: + friend class base::RefCounted< + DevToolsFileWatcher::SharedFileWatcher>; + ~SharedFileWatcher(); + + using FilePathTimesMap = std::map<base::FilePath, base::Time>; + void GetModificationTimes(const base::FilePath& path, + FilePathTimesMap* file_path_times); + void DirectoryChanged(const base::FilePath& path, bool error); + void DispatchNotifications(); + + std::vector<DevToolsFileWatcher*> listeners_; + std::map<base::FilePath, std::unique_ptr<base::FilePathWatcher>> watchers_; + std::map<base::FilePath, FilePathTimesMap> file_path_times_; + std::set<base::FilePath> pending_paths_; + base::Time last_event_time_; + base::TimeDelta last_dispatch_cost_; +}; + +DevToolsFileWatcher::SharedFileWatcher::SharedFileWatcher() + : last_dispatch_cost_( + base::TimeDelta::FromMilliseconds(kDefaultThrottleTimeout)) { + DevToolsFileWatcher::s_shared_watcher_ = this; +} + +DevToolsFileWatcher::SharedFileWatcher::~SharedFileWatcher() { + DevToolsFileWatcher::s_shared_watcher_ = nullptr; +} + +void DevToolsFileWatcher::SharedFileWatcher::AddListener( + DevToolsFileWatcher* watcher) { + listeners_.push_back(watcher); +} + +void DevToolsFileWatcher::SharedFileWatcher::RemoveListener( + DevToolsFileWatcher* watcher) { + auto it = std::find(listeners_.begin(), listeners_.end(), watcher); + listeners_.erase(it); +} + +void DevToolsFileWatcher::SharedFileWatcher::AddWatch( + const base::FilePath& path) { + if (watchers_.find(path) != watchers_.end()) + return; + if (!base::FilePathWatcher::RecursiveWatchAvailable()) + return; + watchers_[path].reset(new base::FilePathWatcher()); + bool success = watchers_[path]->Watch( + path, true, + base::Bind(&SharedFileWatcher::DirectoryChanged, base::Unretained(this))); + if (!success) + return; + + GetModificationTimes(path, &file_path_times_[path]); +} + +void DevToolsFileWatcher::SharedFileWatcher::GetModificationTimes( + const base::FilePath& path, + FilePathTimesMap* times_map) { + base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES); + base::FilePath file_path = enumerator.Next(); + while (!file_path.empty()) { + base::FileEnumerator::FileInfo file_info = enumerator.GetInfo(); + (*times_map)[file_path] = file_info.GetLastModifiedTime(); + file_path = enumerator.Next(); + } +} + +void DevToolsFileWatcher::SharedFileWatcher::RemoveWatch( + const base::FilePath& path) { + watchers_.erase(path); +} + +void DevToolsFileWatcher::SharedFileWatcher::DirectoryChanged( + const base::FilePath& path, + bool error) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + pending_paths_.insert(path); + if (pending_paths_.size() > 1) + return; // PostDelayedTask is already pending. + + base::Time now = base::Time::Now(); + // Quickly dispatch first chunk. + base::TimeDelta shedule_for = + now - last_event_time_ > last_dispatch_cost_ ? + base::TimeDelta::FromMilliseconds(kFirstThrottleTimeout) : + last_dispatch_cost_ * 2; + + BrowserThread::PostDelayedTask( + BrowserThread::FILE, FROM_HERE, + base::BindOnce( + &DevToolsFileWatcher::SharedFileWatcher::DispatchNotifications, this), + shedule_for); + last_event_time_ = now; +} + +void DevToolsFileWatcher::SharedFileWatcher::DispatchNotifications() { + if (!pending_paths_.size()) + return; + base::Time start = base::Time::Now(); + std::vector<std::string> added_paths; + std::vector<std::string> removed_paths; + std::vector<std::string> changed_paths; + + for (const auto& path : pending_paths_) { + FilePathTimesMap& old_times = file_path_times_[path]; + FilePathTimesMap current_times; + GetModificationTimes(path, ¤t_times); + for (const auto& path_time : current_times) { + const base::FilePath& path = path_time.first; + auto old_timestamp = old_times.find(path); + if (old_timestamp == old_times.end()) + added_paths.push_back(path.AsUTF8Unsafe()); + else if (old_timestamp->second != path_time.second) + changed_paths.push_back(path.AsUTF8Unsafe()); + } + for (const auto& path_time : old_times) { + const base::FilePath& path = path_time.first; + if (current_times.find(path) == current_times.end()) + removed_paths.push_back(path.AsUTF8Unsafe()); + } + old_times.swap(current_times); + } + pending_paths_.clear(); + + for (auto* watcher : listeners_) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::BindOnce(watcher->callback_, changed_paths, + added_paths, removed_paths)); + } + last_dispatch_cost_ = base::Time::Now() - start; +} + +// static +DevToolsFileWatcher::SharedFileWatcher* +DevToolsFileWatcher::s_shared_watcher_ = nullptr; + +// DevToolsFileWatcher --------------------------------------------------------- + +DevToolsFileWatcher::DevToolsFileWatcher(const WatchCallback& callback) + : callback_(callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::BindOnce(&DevToolsFileWatcher::InitSharedWatcher, + base::Unretained(this))); +} + +DevToolsFileWatcher::~DevToolsFileWatcher() { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + shared_watcher_->RemoveListener(this); +} + +void DevToolsFileWatcher::InitSharedWatcher() { + if (!DevToolsFileWatcher::s_shared_watcher_) + new SharedFileWatcher(); + shared_watcher_ = DevToolsFileWatcher::s_shared_watcher_; + shared_watcher_->AddListener(this); +} + +void DevToolsFileWatcher::AddWatch(const base::FilePath& path) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + shared_watcher_->AddWatch(path); +} + +void DevToolsFileWatcher::RemoveWatch(const base::FilePath& path) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + shared_watcher_->RemoveWatch(path); +} diff --git a/chromium/chrome/browser/devtools/devtools_file_watcher.h b/chromium/chrome/browser/devtools/devtools_file_watcher.h new file mode 100644 index 00000000000..eb597fccfa6 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_file_watcher.h @@ -0,0 +1,40 @@ +// Copyright (c) 2015 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_WATCHER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_WATCHER_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" + +namespace base { +class FilePath; +} + +class DevToolsFileWatcher { + public: + using WatchCallback = base::Callback<void(const std::vector<std::string>&, + const std::vector<std::string>&, + const std::vector<std::string>&)>; + explicit DevToolsFileWatcher(const WatchCallback& callback); + ~DevToolsFileWatcher(); + + void AddWatch(const base::FilePath& path); + void RemoveWatch(const base::FilePath& path); + + private: + class SharedFileWatcher; + static SharedFileWatcher* s_shared_watcher_; + + void InitSharedWatcher(); + void FileChanged(const base::FilePath&, int); + + scoped_refptr<SharedFileWatcher> shared_watcher_; + WatchCallback callback_; + DISALLOW_COPY_AND_ASSIGN(DevToolsFileWatcher); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_FILE_WATCHER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_conditions.cc b/chromium/chrome/browser/devtools/devtools_network_conditions.cc new file mode 100644 index 00000000000..e80172142d8 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_conditions.cc @@ -0,0 +1,40 @@ +// 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/devtools_network_conditions.h" + +#include "url/gurl.h" + +DevToolsNetworkConditions::DevToolsNetworkConditions() + : offline_(false), + latency_(0), + download_throughput_(0), + upload_throughput_(0) { +} + +DevToolsNetworkConditions::DevToolsNetworkConditions(bool offline) + : offline_(offline), + latency_(0), + download_throughput_(0), + upload_throughput_(0) { +} + +DevToolsNetworkConditions::DevToolsNetworkConditions( + bool offline, + double latency, + double download_throughput, + double upload_throughput) + : offline_(offline), + latency_(latency), + download_throughput_(download_throughput), + upload_throughput_(upload_throughput) { +} + +DevToolsNetworkConditions::~DevToolsNetworkConditions() { +} + +bool DevToolsNetworkConditions::IsThrottling() const { + return !offline_ && ((latency_ != 0) || (download_throughput_ != 0.0) || + (upload_throughput_ != 0)); +} diff --git a/chromium/chrome/browser/devtools/devtools_network_conditions.h b/chromium/chrome/browser/devtools/devtools_network_conditions.h new file mode 100644 index 00000000000..818437f536e --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_conditions.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONDITIONS_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONDITIONS_H_ + +#include <string> +#include <vector> + +#include "base/macros.h" + +// DevToolsNetworkConditions holds information about desired network conditions. +class DevToolsNetworkConditions { + public: + DevToolsNetworkConditions(); + ~DevToolsNetworkConditions(); + + explicit DevToolsNetworkConditions(bool offline); + DevToolsNetworkConditions(bool offline, + double latency, + double download_throughput, + double upload_throughput); + + bool IsThrottling() const; + + bool offline() const { return offline_; } + double latency() const { return latency_; } + double download_throughput() const { return download_throughput_; } + double upload_throughput() const { return upload_throughput_; } + + private: + const bool offline_; + const double latency_; + const double download_throughput_; + const double upload_throughput_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkConditions); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONDITIONS_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_controller.cc b/chromium/chrome/browser/devtools/devtools_network_controller.cc new file mode 100644 index 00000000000..5c583f5e580 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_controller.cc @@ -0,0 +1,71 @@ +// 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/devtools_network_controller.h" + +#include <utility> + +#include "chrome/browser/devtools/devtools_network_conditions.h" +#include "chrome/browser/devtools/devtools_network_interceptor.h" +#include "net/http/http_request_info.h" + +DevToolsNetworkController::DevToolsNetworkController() + : appcache_interceptor_(new DevToolsNetworkInterceptor()) {} + +DevToolsNetworkController::~DevToolsNetworkController() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +DevToolsNetworkInterceptor* DevToolsNetworkController::GetInterceptor( + const std::string& client_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (interceptors_.empty() || client_id.empty()) + return nullptr; + + auto it = interceptors_.find(client_id); + if (it == interceptors_.end()) + return nullptr; + + return it->second.get(); +} + +void DevToolsNetworkController::SetNetworkState( + const std::string& client_id, + std::unique_ptr<DevToolsNetworkConditions> conditions) { + DCHECK(thread_checker_.CalledOnValidThread()); + + auto it = interceptors_.find(client_id); + if (it == interceptors_.end()) { + if (!conditions) + return; + std::unique_ptr<DevToolsNetworkInterceptor> new_interceptor( + new DevToolsNetworkInterceptor()); + new_interceptor->UpdateConditions(std::move(conditions)); + interceptors_[client_id] = std::move(new_interceptor); + } else { + if (!conditions) { + std::unique_ptr<DevToolsNetworkConditions> online_conditions( + new DevToolsNetworkConditions()); + it->second->UpdateConditions(std::move(online_conditions)); + interceptors_.erase(client_id); + } else { + it->second->UpdateConditions(std::move(conditions)); + } + } + + bool has_offline_interceptors = false; + for (const auto& interceptor : interceptors_) { + if (interceptor.second->IsOffline()) { + has_offline_interceptors = true; + break; + } + } + + bool is_appcache_offline = appcache_interceptor_->IsOffline(); + if (is_appcache_offline != has_offline_interceptors) { + std::unique_ptr<DevToolsNetworkConditions> appcache_conditions( + new DevToolsNetworkConditions(has_offline_interceptors)); + appcache_interceptor_->UpdateConditions(std::move(appcache_conditions)); + } +} diff --git a/chromium/chrome/browser/devtools/devtools_network_controller.h b/chromium/chrome/browser/devtools/devtools_network_controller.h new file mode 100644 index 00000000000..ea3f856129d --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_controller.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_H_ + +#include <memory> +#include <string> +#include <unordered_map> + +#include "base/macros.h" +#include "base/threading/thread_checker.h" + +class DevToolsNetworkConditions; +class DevToolsNetworkInterceptor; + +// DevToolsNetworkController manages interceptors identified by client id +// and their throttling conditions. +class DevToolsNetworkController { + public: + DevToolsNetworkController(); + virtual ~DevToolsNetworkController(); + + // Applies network emulation configuration. + void SetNetworkState(const std::string& client_id, + std::unique_ptr<DevToolsNetworkConditions> conditions); + + DevToolsNetworkInterceptor* GetInterceptor( + const std::string& client_id); + + private: + using InterceptorMap = + std::unordered_map<std::string, + std::unique_ptr<DevToolsNetworkInterceptor>>; + + std::unique_ptr<DevToolsNetworkInterceptor> appcache_interceptor_; + InterceptorMap interceptors_; + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkController); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_controller_handle.cc b/chromium/chrome/browser/devtools/devtools_network_controller_handle.cc new file mode 100644 index 00000000000..d83aea36499 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_controller_handle.cc @@ -0,0 +1,55 @@ +// Copyright 2015 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/devtools_network_controller_handle.h" + +#include <utility> + +#include "base/bind.h" +#include "chrome/browser/devtools/devtools_network_conditions.h" +#include "chrome/browser/devtools/devtools_network_controller.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +DevToolsNetworkControllerHandle::DevToolsNetworkControllerHandle() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); +} + +DevToolsNetworkControllerHandle::~DevToolsNetworkControllerHandle() {} + +void DevToolsNetworkControllerHandle::SetNetworkState( + const std::string& client_id, + std::unique_ptr<DevToolsNetworkConditions> conditions) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&DevToolsNetworkControllerHandle::SetNetworkStateOnIO, + base::Unretained(this), client_id, + base::Passed(&conditions))); +} + +DevToolsNetworkController* DevToolsNetworkControllerHandle::GetController() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + LazyInitialize(); + return controller_.get(); +} + +void DevToolsNetworkControllerHandle::LazyInitialize() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + if (!controller_) + controller_.reset(new DevToolsNetworkController); +} + +void DevToolsNetworkControllerHandle::SetNetworkStateOnIO( + const std::string& client_id, + std::unique_ptr<DevToolsNetworkConditions> conditions) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + LazyInitialize(); + controller_->SetNetworkState(client_id, std::move(conditions)); +} diff --git a/chromium/chrome/browser/devtools/devtools_network_controller_handle.h b/chromium/chrome/browser/devtools/devtools_network_controller_handle.h new file mode 100644 index 00000000000..e818204b289 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_controller_handle.h @@ -0,0 +1,42 @@ +// Copyright 2015 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_HANDLE_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_HANDLE_H_ + +#include <memory> +#include <string> + +#include "base/macros.h" + +class DevToolsNetworkConditions; +class DevToolsNetworkController; + +// A handle to manage an IO-thread DevToolsNetworkController on the IO thread +// while allowing SetNetworkState to be called from the UI thread. Must be +// created on the UI thread and destroyed on the IO thread. +class DevToolsNetworkControllerHandle { + public: + DevToolsNetworkControllerHandle(); + ~DevToolsNetworkControllerHandle(); + + // Called on the UI thread. + void SetNetworkState(const std::string& client_id, + std::unique_ptr<DevToolsNetworkConditions> conditions); + + // Called on the IO thread. + DevToolsNetworkController* GetController(); + + private: + void LazyInitialize(); + void SetNetworkStateOnIO( + const std::string& client_id, + std::unique_ptr<DevToolsNetworkConditions> conditions); + + std::unique_ptr<DevToolsNetworkController> controller_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkControllerHandle); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_HANDLE_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_controller_unittest.cc b/chromium/chrome/browser/devtools/devtools_network_controller_unittest.cc new file mode 100644 index 00000000000..5463d082a1e --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_controller_unittest.cc @@ -0,0 +1,329 @@ +// 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/devtools_network_controller.h" + +#include <stdint.h> + +#include <memory> +#include <string> +#include <utility> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "chrome/browser/devtools/devtools_network_conditions.h" +#include "chrome/browser/devtools/devtools_network_interceptor.h" +#include "chrome/browser/devtools/devtools_network_transaction.h" +#include "chrome/browser/devtools/devtools_network_upload_data_stream.h" +#include "net/base/chunked_upload_data_stream.h" +#include "net/http/http_transaction_test_util.h" +#include "net/log/net_log_with_source.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace test { + +using net::kSimpleGET_Transaction; +using net::MockHttpRequest; +using net::MockNetworkLayer; +using net::MockTransaction; +using net::TEST_MODE_SYNC_NET_START; + +const char kClientId[] = "42"; +const char kAnotherClientId[] = "24"; +const char kUploadData[] = "upload_data"; +int64_t kUploadIdentifier = 17; + +class TestCallback { + public: + TestCallback() : run_count_(0), value_(0) {} + void Run(int value) { + run_count_++; + value_ = value; + } + int run_count() { return run_count_; } + int value() { return value_; } + + private: + int run_count_; + int value_; +}; + +class DevToolsNetworkControllerHelper { + public: + DevToolsNetworkControllerHelper() : + completion_callback_( + base::Bind(&TestCallback::Run, base::Unretained(&callback_))), + mock_transaction_(kSimpleGET_Transaction), + buffer_(new net::IOBuffer(64)) { + mock_transaction_.test_mode = TEST_MODE_SYNC_NET_START; + mock_transaction_.url = "http://dot.com"; + mock_transaction_.request_headers = + "X-DevTools-Emulate-Network-Conditions-Client-Id: 42\r\n"; + AddMockTransaction(&mock_transaction_); + + std::unique_ptr<net::HttpTransaction> network_transaction; + network_layer_.CreateTransaction( + net::DEFAULT_PRIORITY, &network_transaction); + transaction_.reset(new DevToolsNetworkTransaction( + &controller_, std::move(network_transaction))); + } + + void SetNetworkState(bool offline, double download, double upload) { + std::unique_ptr<DevToolsNetworkConditions> conditions( + new DevToolsNetworkConditions(offline, 0, download, upload)); + controller_.SetNetworkState(kClientId, std::move(conditions)); + } + + void SetNetworkState(const std::string& id, bool offline) { + std::unique_ptr<DevToolsNetworkConditions> conditions( + new DevToolsNetworkConditions(offline)); + controller_.SetNetworkState(id, std::move(conditions)); + } + + int Start(bool with_upload) { + request_.reset(new MockHttpRequest(mock_transaction_)); + + if (with_upload) { + upload_data_stream_.reset( + new net::ChunkedUploadDataStream(kUploadIdentifier)); + upload_data_stream_->AppendData( + kUploadData, arraysize(kUploadData), true); + request_->upload_data_stream = upload_data_stream_.get(); + } + + int rv = transaction_->Start(request_.get(), completion_callback_, + net::NetLogWithSource()); + EXPECT_EQ(with_upload, !!transaction_->custom_upload_data_stream_); + return rv; + } + + int Read() { + return transaction_->Read(buffer_.get(), 64, completion_callback_); + } + + bool ShouldFail() { + if (transaction_->interceptor_) + return transaction_->interceptor_->IsOffline(); + DevToolsNetworkInterceptor* interceptor = + controller_.GetInterceptor(kClientId); + EXPECT_TRUE(!!interceptor); + return interceptor->IsOffline(); + } + + bool HasStarted() { + return !!transaction_->request_; + } + + bool HasFailed() { + return transaction_->failed_; + } + + void CancelTransaction() { + transaction_.reset(); + } + + int ReadUploadData() { + EXPECT_EQ(net::OK, transaction_->custom_upload_data_stream_->Init( + completion_callback_, net::NetLogWithSource())); + return transaction_->custom_upload_data_stream_->Read( + buffer_.get(), 64, completion_callback_); + } + + ~DevToolsNetworkControllerHelper() { + RemoveMockTransaction(&mock_transaction_); + } + + TestCallback* callback() { return &callback_; } + DevToolsNetworkController* controller() { return &controller_; } + DevToolsNetworkTransaction* transaction() { return transaction_.get(); } + + private: + base::MessageLoop message_loop_; + MockNetworkLayer network_layer_; + TestCallback callback_; + net::CompletionCallback completion_callback_; + MockTransaction mock_transaction_; + DevToolsNetworkController controller_; + std::unique_ptr<DevToolsNetworkTransaction> transaction_; + scoped_refptr<net::IOBuffer> buffer_; + std::unique_ptr<net::ChunkedUploadDataStream> upload_data_stream_; + std::unique_ptr<MockHttpRequest> request_; +}; + +TEST(DevToolsNetworkControllerTest, SingleDisableEnable) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(false, 0, 0); + helper.Start(false); + + EXPECT_FALSE(helper.ShouldFail()); + helper.SetNetworkState(true, 0, 0); + EXPECT_TRUE(helper.ShouldFail()); + helper.SetNetworkState(false, 0, 0); + EXPECT_FALSE(helper.ShouldFail()); + + base::RunLoop().RunUntilIdle(); +} + +TEST(DevToolsNetworkControllerTest, InterceptorIsolation) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(false, 0, 0); + helper.Start(false); + + EXPECT_FALSE(helper.ShouldFail()); + helper.SetNetworkState(kAnotherClientId, true); + EXPECT_FALSE(helper.ShouldFail()); + helper.SetNetworkState(true, 0, 0); + EXPECT_TRUE(helper.ShouldFail()); + + helper.SetNetworkState(kAnotherClientId, false); + helper.SetNetworkState(false, 0, 0); + base::RunLoop().RunUntilIdle(); +} + +TEST(DevToolsNetworkControllerTest, FailOnStart) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(true, 0, 0); + + int rv = helper.Start(false); + EXPECT_EQ(rv, net::ERR_INTERNET_DISCONNECTED); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(helper.callback()->run_count(), 0); +} + +TEST(DevToolsNetworkControllerTest, FailRunningTransaction) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(false, 0, 0); + TestCallback* callback = helper.callback(); + + int rv = helper.Start(false); + EXPECT_EQ(rv, net::OK); + + rv = helper.Read(); + EXPECT_EQ(rv, net::ERR_IO_PENDING); + EXPECT_EQ(callback->run_count(), 0); + + helper.SetNetworkState(true, 0, 0); + EXPECT_EQ(callback->run_count(), 0); + + // Wait until HttpTrancation completes reading and invokes callback. + // DevToolsNetworkTransaction should report error instead. + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(callback->run_count(), 1); + EXPECT_EQ(callback->value(), net::ERR_INTERNET_DISCONNECTED); + + // Check that transaction is not failed second time. + helper.SetNetworkState(false, 0, 0); + helper.SetNetworkState(true, 0, 0); + EXPECT_EQ(callback->run_count(), 1); +} + +TEST(DevToolsNetworkControllerTest, ReadAfterFail) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(false, 0, 0); + + int rv = helper.Start(false); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(helper.HasStarted()); + + helper.SetNetworkState(true, 0, 0); + // Not failed yet, as no IO was initiated. + EXPECT_FALSE(helper.HasFailed()); + + rv = helper.Read(); + // Fails on first IO. + EXPECT_EQ(rv, net::ERR_INTERNET_DISCONNECTED); + + // Check that callback is never invoked. + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(helper.callback()->run_count(), 0); +} + +TEST(DevToolsNetworkControllerTest, CancelTransaction) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(false, 0, 0); + + int rv = helper.Start(false); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(helper.HasStarted()); + helper.CancelTransaction(); + + // Should not crash. + helper.SetNetworkState(true, 0, 0); + helper.SetNetworkState(false, 0, 0); + base::RunLoop().RunUntilIdle(); +} + +TEST(DevToolsNetworkControllerTest, CancelFailedTransaction) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(true, 0, 0); + + int rv = helper.Start(false); + EXPECT_EQ(rv, net::ERR_INTERNET_DISCONNECTED); + EXPECT_TRUE(helper.HasStarted()); + helper.CancelTransaction(); + + // Should not crash. + helper.SetNetworkState(true, 0, 0); + helper.SetNetworkState(false, 0, 0); + base::RunLoop().RunUntilIdle(); +} + +TEST(DevToolsNetworkControllerTest, UploadDoesNotFail) { + DevToolsNetworkControllerHelper helper; + helper.SetNetworkState(true, 0, 0); + int rv = helper.Start(true); + EXPECT_EQ(rv, net::ERR_INTERNET_DISCONNECTED); + rv = helper.ReadUploadData(); + EXPECT_EQ(rv, static_cast<int>(arraysize(kUploadData))); +} + +TEST(DevToolsNetworkControllerTest, DownloadOnly) { + DevToolsNetworkControllerHelper helper; + TestCallback* callback = helper.callback(); + + helper.SetNetworkState(false, 10000000, 0); + int rv = helper.Start(false); + EXPECT_EQ(rv, net::ERR_IO_PENDING); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(callback->run_count(), 1); + EXPECT_GE(callback->value(), net::OK); + + rv = helper.Read(); + EXPECT_EQ(rv, net::ERR_IO_PENDING); + EXPECT_EQ(callback->run_count(), 1); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(callback->run_count(), 2); + EXPECT_GE(callback->value(), net::OK); +} + +TEST(DevToolsNetworkControllerTest, UploadOnly) { + DevToolsNetworkControllerHelper helper; + TestCallback* callback = helper.callback(); + + helper.SetNetworkState(false, 0, 1000000); + int rv = helper.Start(true); + EXPECT_EQ(rv, net::OK); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(callback->run_count(), 0); + + rv = helper.Read(); + EXPECT_EQ(rv, net::ERR_IO_PENDING); + EXPECT_EQ(callback->run_count(), 0); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(callback->run_count(), 1); + EXPECT_GE(callback->value(), net::OK); + + rv = helper.ReadUploadData(); + EXPECT_EQ(rv, net::ERR_IO_PENDING); + EXPECT_EQ(callback->run_count(), 1); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(callback->run_count(), 2); + EXPECT_EQ(callback->value(), static_cast<int>(arraysize(kUploadData))); +} + +} // namespace test diff --git a/chromium/chrome/browser/devtools/devtools_network_interceptor.cc b/chromium/chrome/browser/devtools/devtools_network_interceptor.cc new file mode 100644 index 00000000000..432bb6bcbc5 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_interceptor.cc @@ -0,0 +1,290 @@ +// 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/devtools_network_interceptor.h" + +#include <stddef.h> +#include <algorithm> +#include <limits> +#include <utility> + +#include "base/time/time.h" +#include "chrome/browser/devtools/devtools_network_conditions.h" +#include "net/base/net_errors.h" + +namespace { + +int64_t kPacketSize = 1500; + +base::TimeDelta CalculateTickLength(double throughput) { + if (!throughput) + return base::TimeDelta(); + int64_t us_tick_length = (1000000L * kPacketSize) / throughput; + DCHECK(us_tick_length != 0); + if (us_tick_length == 0) + us_tick_length = 1; + return base::TimeDelta::FromMicroseconds(us_tick_length); +} + +} // namespace + +DevToolsNetworkInterceptor::ThrottleRecord::ThrottleRecord() { +} + +DevToolsNetworkInterceptor::ThrottleRecord::ThrottleRecord( + const ThrottleRecord& other) = default; + +DevToolsNetworkInterceptor::ThrottleRecord::~ThrottleRecord() { +} + +DevToolsNetworkInterceptor::DevToolsNetworkInterceptor() + : conditions_(new DevToolsNetworkConditions()), + download_last_tick_(0), + upload_last_tick_(0), + weak_ptr_factory_(this) { +} + +DevToolsNetworkInterceptor::~DevToolsNetworkInterceptor() { +} + +base::WeakPtr<DevToolsNetworkInterceptor> +DevToolsNetworkInterceptor::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + +void DevToolsNetworkInterceptor::FinishRecords( + ThrottleRecords* records, bool offline) { + ThrottleRecords temp; + temp.swap(*records); + for (const ThrottleRecord& record : temp) { + bool failed = offline && !record.is_upload; + record.callback.Run( + failed ? net::ERR_INTERNET_DISCONNECTED : record.result, + record.bytes); + } +} + +void DevToolsNetworkInterceptor::UpdateConditions( + std::unique_ptr<DevToolsNetworkConditions> conditions) { + DCHECK(conditions); + base::TimeTicks now = base::TimeTicks::Now(); + if (conditions_->IsThrottling()) + UpdateThrottled(now); + + conditions_ = std::move(conditions); + + bool offline = conditions_->offline(); + if (offline || !conditions_->IsThrottling()) { + timer_.Stop(); + FinishRecords(&download_, offline); + FinishRecords(&upload_, offline); + FinishRecords(&suspended_, offline); + return; + } + + // Throttling. + DCHECK(conditions_->download_throughput() != 0 || + conditions_->upload_throughput() != 0); + offset_ = now; + + download_last_tick_ = 0; + download_tick_length_ = CalculateTickLength( + conditions_->download_throughput()); + + upload_last_tick_ = 0; + upload_tick_length_ = CalculateTickLength(conditions_->upload_throughput()); + + latency_length_ = base::TimeDelta(); + double latency = conditions_->latency(); + if (latency > 0) + latency_length_ = base::TimeDelta::FromMillisecondsD(latency); + ArmTimer(now); +} + +uint64_t DevToolsNetworkInterceptor::UpdateThrottledRecords( + base::TimeTicks now, + ThrottleRecords* records, + uint64_t last_tick, + base::TimeDelta tick_length) { + if (tick_length.is_zero()) { + DCHECK(records->empty()); + return last_tick; + } + + int64_t new_tick = (now - offset_) / tick_length; + int64_t ticks = new_tick - last_tick; + + int64_t length = records->size(); + if (!length) + return new_tick; + + int64_t shift = ticks % length; + for (int64_t i = 0; i < length; ++i) { + (*records)[i].bytes -= + (ticks / length) * kPacketSize + (i < shift ? kPacketSize : 0); + } + std::rotate(records->begin(), records->begin() + shift, records->end()); + return new_tick; +} + +void DevToolsNetworkInterceptor::UpdateThrottled(base::TimeTicks now) { + download_last_tick_ = UpdateThrottledRecords( + now, &download_, download_last_tick_, download_tick_length_); + upload_last_tick_ = UpdateThrottledRecords( + now, &upload_, upload_last_tick_, upload_tick_length_); + UpdateSuspended(now); +} + +void DevToolsNetworkInterceptor::UpdateSuspended(base::TimeTicks now) { + int64_t activation_baseline = + (now - latency_length_ - base::TimeTicks()).InMicroseconds(); + ThrottleRecords suspended; + for (const ThrottleRecord& record : suspended_) { + if (record.send_end <= activation_baseline) { + if (record.is_upload) + upload_.push_back(record); + else + download_.push_back(record); + } else { + suspended.push_back(record); + } + } + suspended_.swap(suspended); +} + +void DevToolsNetworkInterceptor::CollectFinished( + ThrottleRecords* records, ThrottleRecords* finished) { + ThrottleRecords active; + for (const ThrottleRecord& record : *records) { + if (record.bytes < 0) + finished->push_back(record); + else + active.push_back(record); + } + records->swap(active); +} + +void DevToolsNetworkInterceptor::OnTimer() { + base::TimeTicks now = base::TimeTicks::Now(); + UpdateThrottled(now); + + ThrottleRecords finished; + CollectFinished(&download_, &finished); + CollectFinished(&upload_, &finished); + for (const ThrottleRecord& record : finished) + record.callback.Run(record.result, record.bytes); + + ArmTimer(now); +} + +base::TimeTicks DevToolsNetworkInterceptor::CalculateDesiredTime( + const ThrottleRecords& records, + uint64_t last_tick, + base::TimeDelta tick_length) { + int64_t min_ticks_left = 0x10000L; + size_t count = records.size(); + for (size_t i = 0; i < count; ++i) { + int64_t packets_left = (records[i].bytes + kPacketSize - 1) / kPacketSize; + int64_t ticks_left = (i + 1) + count * (packets_left - 1); + if (i == 0 || ticks_left < min_ticks_left) + min_ticks_left = ticks_left; + } + return offset_ + tick_length * (last_tick + min_ticks_left); +} + +void DevToolsNetworkInterceptor::ArmTimer(base::TimeTicks now) { + size_t suspend_count = suspended_.size(); + if (download_.empty() && upload_.empty() && !suspend_count) + return; + + base::TimeTicks desired_time = CalculateDesiredTime( + download_, download_last_tick_, download_tick_length_); + + base::TimeTicks upload_time = CalculateDesiredTime( + upload_, upload_last_tick_, upload_tick_length_); + if (upload_time < desired_time) + desired_time = upload_time; + + int64_t min_baseline = std::numeric_limits<int64_t>::max(); + for (size_t i = 0; i < suspend_count; ++i) { + if (suspended_[i].send_end < min_baseline) + min_baseline = suspended_[i].send_end; + } + if (suspend_count) { + base::TimeTicks activation_time = base::TimeTicks() + + base::TimeDelta::FromMicroseconds(min_baseline) + latency_length_; + if (activation_time < desired_time) + desired_time = activation_time; + } + + timer_.Start( + FROM_HERE, + desired_time - now, + base::Bind( + &DevToolsNetworkInterceptor::OnTimer, + base::Unretained(this))); +} + +int DevToolsNetworkInterceptor::StartThrottle( + int result, + int64_t bytes, + base::TimeTicks send_end, + bool start, + bool is_upload, + const ThrottleCallback& callback) { + if (result < 0) + return result; + + if (conditions_->offline()) + return is_upload ? result : net::ERR_INTERNET_DISCONNECTED; + + if ((is_upload && !conditions_->upload_throughput()) || + (!is_upload && !conditions_->download_throughput())) { + return result; + } + + ThrottleRecord record; + record.result = result; + record.bytes = bytes; + record.callback = callback; + // TODO(dgozman): use upload throughput. + record.is_upload = is_upload; + + base::TimeTicks now = base::TimeTicks::Now(); + UpdateThrottled(now); + if (start && !latency_length_.is_zero()) { + record.send_end = (send_end - base::TimeTicks()).InMicroseconds(); + suspended_.push_back(record); + UpdateSuspended(now); + } else { + if (is_upload) + upload_.push_back(record); + else + download_.push_back(record); + } + ArmTimer(now); + + return net::ERR_IO_PENDING; +} + +void DevToolsNetworkInterceptor::StopThrottle( + const ThrottleCallback& callback) { + RemoveRecord(&download_, callback); + RemoveRecord(&upload_, callback); + RemoveRecord(&suspended_, callback); +} + +void DevToolsNetworkInterceptor::RemoveRecord( + ThrottleRecords* records, const ThrottleCallback& callback) { + records->erase( + std::remove_if(records->begin(), records->end(), + [&callback](const ThrottleRecord& record){ + return record.callback.Equals(callback); + }), + records->end()); +} + +bool DevToolsNetworkInterceptor::IsOffline() { + return conditions_->offline(); +} diff --git a/chromium/chrome/browser/devtools/devtools_network_interceptor.h b/chromium/chrome/browser/devtools/devtools_network_interceptor.h new file mode 100644 index 00000000000..34b7399707a --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_interceptor.h @@ -0,0 +1,103 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_INTERCEPTOR_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_INTERCEPTOR_H_ + +#include <stdint.h> + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/timer/timer.h" + +class DevToolsNetworkConditions; + +namespace base { +class TimeDelta; +class TimeTicks; +} + +// DevToolsNetworkInterceptor emulates network conditions for transactions with +// specific client id. +class DevToolsNetworkInterceptor { + public: + using ThrottleCallback = base::Callback<void(int, int64_t)>; + + DevToolsNetworkInterceptor(); + virtual ~DevToolsNetworkInterceptor(); + + base::WeakPtr<DevToolsNetworkInterceptor> GetWeakPtr(); + + // Applies network emulation configuration. + void UpdateConditions(std::unique_ptr<DevToolsNetworkConditions> conditions); + + // Throttles with |is_upload == true| always succeed, even in offline mode. + int StartThrottle(int result, + int64_t bytes, + base::TimeTicks send_end, + bool start, + bool is_upload, + const ThrottleCallback& callback); + void StopThrottle(const ThrottleCallback& callback); + + bool IsOffline(); + + private: + struct ThrottleRecord { + public: + ThrottleRecord(); + ThrottleRecord(const ThrottleRecord& other); + ~ThrottleRecord(); + int result; + int64_t bytes; + int64_t send_end; + bool is_upload; + ThrottleCallback callback; + }; + using ThrottleRecords = std::vector<ThrottleRecord>; + + void FinishRecords(ThrottleRecords* records, bool offline); + + uint64_t UpdateThrottledRecords(base::TimeTicks now, ThrottleRecords* records, + uint64_t last_tick, base::TimeDelta tick_length); + void UpdateThrottled(base::TimeTicks now); + void UpdateSuspended(base::TimeTicks now); + + void CollectFinished(ThrottleRecords* records, ThrottleRecords* finished); + void OnTimer(); + + base::TimeTicks CalculateDesiredTime(const ThrottleRecords& records, + uint64_t last_tick, base::TimeDelta tick_length); + void ArmTimer(base::TimeTicks now); + + void RemoveRecord(ThrottleRecords* records, const ThrottleCallback& callback); + + std::unique_ptr<DevToolsNetworkConditions> conditions_; + + // Throttables suspended for a "latency" period. + ThrottleRecords suspended_; + + // Throttables waiting certain amount of transfer to be "accounted". + ThrottleRecords download_; + ThrottleRecords upload_; + + base::OneShotTimer timer_; + base::TimeTicks offset_; + base::TimeDelta download_tick_length_; + base::TimeDelta upload_tick_length_; + base::TimeDelta latency_length_; + uint64_t download_last_tick_; + uint64_t upload_last_tick_; + + base::WeakPtrFactory<DevToolsNetworkInterceptor> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkInterceptor); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_INTERCEPTOR_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_protocol_handler.cc b/chromium/chrome/browser/devtools/devtools_network_protocol_handler.cc new file mode 100644 index 00000000000..2c39ad7ace6 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_protocol_handler.cc @@ -0,0 +1,116 @@ +// 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/devtools_network_protocol_handler.h" + +#include <utility> + +#include "base/values.h" +#include "chrome/browser/devtools/devtools_network_conditions.h" +#include "chrome/browser/devtools/devtools_network_controller_handle.h" +#include "chrome/browser/devtools/devtools_protocol_constants.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/web_contents.h" + +DevToolsNetworkProtocolHandler::DevToolsNetworkProtocolHandler() { +} + +DevToolsNetworkProtocolHandler::~DevToolsNetworkProtocolHandler() { +} + +base::DictionaryValue* DevToolsNetworkProtocolHandler::HandleCommand( + content::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; + + namespace network = ::chrome::devtools::Network; + + if (method == network::emulateNetworkConditions::kName) + return EmulateNetworkConditions(agent_host, id, params).release(); + + if (method == network::canEmulateNetworkConditions::kName) + return CanEmulateNetworkConditions(agent_host, id, params).release(); + + return nullptr; +} + +std::unique_ptr<base::DictionaryValue> +DevToolsNetworkProtocolHandler::CanEmulateNetworkConditions( + content::DevToolsAgentHost* agent_host, + int command_id, + base::DictionaryValue* params) { + std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue()); + result->SetBoolean(chrome::devtools::kResult, true); + return DevToolsProtocol::CreateSuccessResponse(command_id, std::move(result)); +} + +std::unique_ptr<base::DictionaryValue> +DevToolsNetworkProtocolHandler::EmulateNetworkConditions( + content::DevToolsAgentHost* agent_host, + int command_id, + base::DictionaryValue* params) { + namespace names = ::chrome::devtools::Network::emulateNetworkConditions; + + bool offline = false; + if (!params || !params->GetBoolean(names::kParamOffline, &offline)) { + return DevToolsProtocol::CreateInvalidParamsResponse( + command_id, names::kParamOffline); + } + double latency = 0.0; + if (!params->GetDouble(names::kParamLatency, &latency)) { + return DevToolsProtocol::CreateInvalidParamsResponse( + command_id, names::kParamLatency); + } + if (latency < 0.0) + latency = 0.0; + + double download_throughput = 0.0; + if (!params->GetDouble(names::kParamDownloadThroughput, + &download_throughput)) { + return DevToolsProtocol::CreateInvalidParamsResponse( + command_id, names::kParamDownloadThroughput); + } + if (download_throughput < 0.0) + download_throughput = 0.0; + + double upload_throughput = 0.0; + if (!params->GetDouble(names::kParamUploadThroughput, &upload_throughput)) { + return DevToolsProtocol::CreateInvalidParamsResponse( + command_id, names::kParamUploadThroughput); + } + if (upload_throughput < 0.0) + upload_throughput = 0.0; + + std::unique_ptr<DevToolsNetworkConditions> conditions( + new DevToolsNetworkConditions(offline, latency, download_throughput, + upload_throughput)); + + UpdateNetworkState(agent_host, std::move(conditions)); + return nullptr; // Fall-through. +} + +void DevToolsNetworkProtocolHandler::UpdateNetworkState( + content::DevToolsAgentHost* agent_host, + std::unique_ptr<DevToolsNetworkConditions> conditions) { + Profile* profile = Profile::FromBrowserContext( + agent_host->GetBrowserContext()); + if (!profile) + return; + profile->GetDevToolsNetworkControllerHandle()->SetNetworkState( + agent_host->GetId(), std::move(conditions)); +} + +void DevToolsNetworkProtocolHandler::DevToolsAgentStateChanged( + content::DevToolsAgentHost* agent_host, + bool attached) { + std::unique_ptr<DevToolsNetworkConditions> conditions; + if (attached) + conditions.reset(new DevToolsNetworkConditions()); + UpdateNetworkState(agent_host, std::move(conditions)); +} diff --git a/chromium/chrome/browser/devtools/devtools_network_protocol_handler.h b/chromium/chrome/browser/devtools/devtools_network_protocol_handler.h new file mode 100644 index 00000000000..e8d941b1d83 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_protocol_handler.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_PROTOCOL_HANDLER_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_PROTOCOL_HANDLER_H_ + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "chrome/browser/devtools/devtools_protocol.h" + +namespace content { +class DevToolsAgentHost; +} + +class DevToolsNetworkConditions; + +class DevToolsNetworkProtocolHandler { + public: + DevToolsNetworkProtocolHandler(); + ~DevToolsNetworkProtocolHandler(); + + void DevToolsAgentStateChanged(content::DevToolsAgentHost* agent_host, + bool attached); + base::DictionaryValue* HandleCommand( + content::DevToolsAgentHost* agent_host, + base::DictionaryValue* command_dict); + + private: + std::unique_ptr<base::DictionaryValue> CanEmulateNetworkConditions( + content::DevToolsAgentHost* agent_host, + int command_id, + base::DictionaryValue* params); + + std::unique_ptr<base::DictionaryValue> EmulateNetworkConditions( + content::DevToolsAgentHost* agent_host, + int command_id, + base::DictionaryValue* params); + + void UpdateNetworkState( + content::DevToolsAgentHost* agent_host, + std::unique_ptr<DevToolsNetworkConditions> conditions); + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkProtocolHandler); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_PROTOCOL_HANDLER_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_transaction.cc b/chromium/chrome/browser/devtools/devtools_network_transaction.cc new file mode 100644 index 00000000000..982bfab76dd --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_transaction.cc @@ -0,0 +1,303 @@ +// 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/devtools_network_transaction.h" + +#include <utility> + +#include "base/callback_helpers.h" +#include "chrome/browser/devtools/devtools_network_controller.h" +#include "chrome/browser/devtools/devtools_network_interceptor.h" +#include "chrome/browser/devtools/devtools_network_upload_data_stream.h" +#include "net/base/load_timing_info.h" +#include "net/base/net_errors.h" +#include "net/http/http_network_transaction.h" +#include "net/http/http_request_info.h" +#include "net/socket/connection_attempts.h" + +// Keep in sync with X_DevTools_Emulate_Network_Conditions_Client_Id defined in +// HTTPNames.json5. +const char + DevToolsNetworkTransaction::kDevToolsEmulateNetworkConditionsClientId[] = + "X-DevTools-Emulate-Network-Conditions-Client-Id"; + +DevToolsNetworkTransaction::DevToolsNetworkTransaction( + DevToolsNetworkController* controller, + std::unique_ptr<net::HttpTransaction> network_transaction) + : throttled_byte_count_(0), + controller_(controller), + network_transaction_(std::move(network_transaction)), + request_(nullptr), + failed_(false) { + DCHECK(controller); +} + +DevToolsNetworkTransaction::~DevToolsNetworkTransaction() { + if (interceptor_ && !throttle_callback_.is_null()) + interceptor_->StopThrottle(throttle_callback_); +} + +void DevToolsNetworkTransaction::IOCallback( + const net::CompletionCallback& callback, bool start, int result) { + result = Throttle(callback, start, result); + if (result != net::ERR_IO_PENDING) + callback.Run(result); +} + +int DevToolsNetworkTransaction::Throttle( + const net::CompletionCallback& callback, bool start, int result) { + if (failed_) + return net::ERR_INTERNET_DISCONNECTED; + if (!interceptor_ || result < 0) + return result; + + base::TimeTicks send_end; + if (start) { + throttled_byte_count_ += network_transaction_->GetTotalReceivedBytes(); + net::LoadTimingInfo load_timing_info; + if (GetLoadTimingInfo(&load_timing_info)) { + send_end = load_timing_info.send_end; + if (!load_timing_info.push_start.is_null()) + start = false; + } + if (send_end.is_null()) + send_end = base::TimeTicks::Now(); + } + if (result > 0) + throttled_byte_count_ += result; + + throttle_callback_ = base::Bind(&DevToolsNetworkTransaction::ThrottleCallback, + base::Unretained(this), callback); + int rv = interceptor_->StartThrottle(result, throttled_byte_count_, send_end, + start, false, throttle_callback_); + if (rv != net::ERR_IO_PENDING) + throttle_callback_.Reset(); + if (rv == net::ERR_INTERNET_DISCONNECTED) + Fail(); + return rv; +} + +void DevToolsNetworkTransaction::ThrottleCallback( + const net::CompletionCallback& callback, int result, int64_t bytes) { + DCHECK(!throttle_callback_.is_null()); + throttle_callback_.Reset(); + if (result == net::ERR_INTERNET_DISCONNECTED) + Fail(); + throttled_byte_count_ = bytes; + callback.Run(result); +} + +void DevToolsNetworkTransaction::Fail() { + DCHECK(request_); + DCHECK(!failed_); + failed_ = true; + network_transaction_->SetBeforeNetworkStartCallback( + BeforeNetworkStartCallback()); + if (interceptor_) + interceptor_.reset(); +} + +bool DevToolsNetworkTransaction::CheckFailed() { + if (failed_) + return true; + if (interceptor_ && interceptor_->IsOffline()) { + Fail(); + return true; + } + return false; +} + +int DevToolsNetworkTransaction::Start(const net::HttpRequestInfo* request, + const net::CompletionCallback& callback, + const net::NetLogWithSource& net_log) { + DCHECK(request); + request_ = request; + + std::string client_id; + bool has_devtools_client_id = request_->extra_headers.HasHeader( + kDevToolsEmulateNetworkConditionsClientId); + if (has_devtools_client_id) { + custom_request_.reset(new net::HttpRequestInfo(*request_)); + custom_request_->extra_headers.GetHeader( + kDevToolsEmulateNetworkConditionsClientId, &client_id); + custom_request_->extra_headers.RemoveHeader( + kDevToolsEmulateNetworkConditionsClientId); + + if (request_->upload_data_stream) { + custom_upload_data_stream_.reset( + new DevToolsNetworkUploadDataStream(request_->upload_data_stream)); + custom_request_->upload_data_stream = custom_upload_data_stream_.get(); + } + + request_ = custom_request_.get(); + } + + DevToolsNetworkInterceptor* interceptor = + controller_->GetInterceptor(client_id); + if (interceptor) { + interceptor_ = interceptor->GetWeakPtr(); + if (custom_upload_data_stream_) + custom_upload_data_stream_->SetInterceptor(interceptor); + } + + if (CheckFailed()) + return net::ERR_INTERNET_DISCONNECTED; + + if (!interceptor_) + return network_transaction_->Start(request_, callback, net_log); + + int result = network_transaction_->Start(request_, + base::Bind(&DevToolsNetworkTransaction::IOCallback, + base::Unretained(this), callback, true), + net_log); + return Throttle(callback, true, result); +} + +int DevToolsNetworkTransaction::RestartIgnoringLastError( + const net::CompletionCallback& callback) { + if (CheckFailed()) + return net::ERR_INTERNET_DISCONNECTED; + if (!interceptor_) + return network_transaction_->RestartIgnoringLastError(callback); + + int result = network_transaction_->RestartIgnoringLastError( + base::Bind(&DevToolsNetworkTransaction::IOCallback, + base::Unretained(this), callback, true)); + return Throttle(callback, true, result); +} + +int DevToolsNetworkTransaction::RestartWithCertificate( + net::X509Certificate* client_cert, + net::SSLPrivateKey* client_private_key, + const net::CompletionCallback& callback) { + if (CheckFailed()) + return net::ERR_INTERNET_DISCONNECTED; + if (!interceptor_) { + return network_transaction_->RestartWithCertificate( + client_cert, client_private_key, callback); + } + + int result = network_transaction_->RestartWithCertificate( + client_cert, client_private_key, + base::Bind(&DevToolsNetworkTransaction::IOCallback, + base::Unretained(this), callback, true)); + return Throttle(callback, true, result); +} + +int DevToolsNetworkTransaction::RestartWithAuth( + const net::AuthCredentials& credentials, + const net::CompletionCallback& callback) { + if (CheckFailed()) + return net::ERR_INTERNET_DISCONNECTED; + if (!interceptor_) + return network_transaction_->RestartWithAuth(credentials, callback); + + int result = network_transaction_->RestartWithAuth(credentials, + base::Bind(&DevToolsNetworkTransaction::IOCallback, + base::Unretained(this), callback, true)); + return Throttle(callback, true, result); +} + +bool DevToolsNetworkTransaction::IsReadyToRestartForAuth() { + return network_transaction_->IsReadyToRestartForAuth(); +} + +int DevToolsNetworkTransaction::Read( + net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback) { + if (CheckFailed()) + return net::ERR_INTERNET_DISCONNECTED; + if (!interceptor_) + return network_transaction_->Read(buf, buf_len, callback); + + int result = network_transaction_->Read(buf, buf_len, + base::Bind(&DevToolsNetworkTransaction::IOCallback, + base::Unretained(this), callback, false)); + // URLRequestJob relies on synchronous end-of-stream notification. + if (result == 0) + return result; + return Throttle(callback, false, result); +} + +void DevToolsNetworkTransaction::StopCaching() { + network_transaction_->StopCaching(); +} + +bool DevToolsNetworkTransaction::GetFullRequestHeaders( + net::HttpRequestHeaders* headers) const { + return network_transaction_->GetFullRequestHeaders(headers); +} + +int64_t DevToolsNetworkTransaction::GetTotalReceivedBytes() const { + return network_transaction_->GetTotalReceivedBytes(); +} + +int64_t DevToolsNetworkTransaction::GetTotalSentBytes() const { + return network_transaction_->GetTotalSentBytes(); +} + +void DevToolsNetworkTransaction::DoneReading() { + network_transaction_->DoneReading(); +} + +const net::HttpResponseInfo* +DevToolsNetworkTransaction::GetResponseInfo() const { + return network_transaction_->GetResponseInfo(); +} + +net::LoadState DevToolsNetworkTransaction::GetLoadState() const { + return network_transaction_->GetLoadState(); +} + +void DevToolsNetworkTransaction::SetQuicServerInfo( + net::QuicServerInfo* quic_server_info) { + network_transaction_->SetQuicServerInfo(quic_server_info); +} + +bool DevToolsNetworkTransaction::GetLoadTimingInfo( + net::LoadTimingInfo* load_timing_info) const { + return network_transaction_->GetLoadTimingInfo(load_timing_info); +} + +bool DevToolsNetworkTransaction::GetRemoteEndpoint( + net::IPEndPoint* endpoint) const { + return network_transaction_->GetRemoteEndpoint(endpoint); +} + +void DevToolsNetworkTransaction::PopulateNetErrorDetails( + net::NetErrorDetails* details) const { + return network_transaction_->PopulateNetErrorDetails(details); +} + +void DevToolsNetworkTransaction::SetPriority(net::RequestPriority priority) { + network_transaction_->SetPriority(priority); +} + +void DevToolsNetworkTransaction::SetWebSocketHandshakeStreamCreateHelper( + net::WebSocketHandshakeStreamBase::CreateHelper* create_helper) { + network_transaction_->SetWebSocketHandshakeStreamCreateHelper(create_helper); +} + +void DevToolsNetworkTransaction::SetBeforeNetworkStartCallback( + const BeforeNetworkStartCallback& callback) { + network_transaction_->SetBeforeNetworkStartCallback(callback); +} + +void DevToolsNetworkTransaction::SetBeforeHeadersSentCallback( + const BeforeHeadersSentCallback& callback) { + network_transaction_->SetBeforeHeadersSentCallback(callback); +} + +int DevToolsNetworkTransaction::ResumeNetworkStart() { + if (CheckFailed()) + return net::ERR_INTERNET_DISCONNECTED; + return network_transaction_->ResumeNetworkStart(); +} + +void +DevToolsNetworkTransaction::GetConnectionAttempts(net::ConnectionAttempts* out) +const { + network_transaction_->GetConnectionAttempts(out); +} diff --git a/chromium/chrome/browser/devtools/devtools_network_transaction.h b/chromium/chrome/browser/devtools/devtools_network_transaction.h new file mode 100644 index 00000000000..cb13c423c03 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_transaction.h @@ -0,0 +1,133 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_TRANSACTION_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_TRANSACTION_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/devtools/devtools_network_interceptor.h" +#include "net/base/completion_callback.h" +#include "net/base/load_states.h" +#include "net/base/net_error_details.h" +#include "net/base/request_priority.h" +#include "net/http/http_transaction.h" +#include "net/websockets/websocket_handshake_stream_base.h" + +class DevToolsNetworkController; +class DevToolsNetworkUploadDataStream; + +namespace net { +class AuthCredentials; +class HttpRequestHeaders; +struct HttpRequestInfo; +class HttpResponseInfo; +class IOBuffer; +struct LoadTimingInfo; +class NetLogWithSource; +class X509Certificate; +} // namespace net + +namespace test { +class DevToolsNetworkControllerHelper; +} + +// DevToolsNetworkTransaction is a wrapper for network transaction. All +// HttpTransaction methods are proxied to real transaction, but |callback| +// parameter is saved and replaced with proxy callback. Fail method should be +// used to simulate network outage. It runs saved callback (if any) with +// net::ERR_INTERNET_DISCONNECTED result value. +class DevToolsNetworkTransaction + : public net::HttpTransaction { + public: + static const char kDevToolsEmulateNetworkConditionsClientId[]; + + DevToolsNetworkTransaction( + DevToolsNetworkController* controller, + std::unique_ptr<net::HttpTransaction> network_transaction); + + ~DevToolsNetworkTransaction() override; + + // HttpTransaction methods: + int Start(const net::HttpRequestInfo* request, + const net::CompletionCallback& callback, + const net::NetLogWithSource& net_log) override; + int RestartIgnoringLastError( + const net::CompletionCallback& callback) override; + int RestartWithCertificate(net::X509Certificate* client_cert, + net::SSLPrivateKey* client_private_key, + const net::CompletionCallback& callback) override; + int RestartWithAuth(const net::AuthCredentials& credentials, + const net::CompletionCallback& callback) override; + bool IsReadyToRestartForAuth() override; + + int Read(net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback) override; + void StopCaching() override; + bool GetFullRequestHeaders(net::HttpRequestHeaders* headers) const override; + int64_t GetTotalReceivedBytes() const override; + int64_t GetTotalSentBytes() const override; + void DoneReading() override; + const net::HttpResponseInfo* GetResponseInfo() const override; + net::LoadState GetLoadState() const override; + void SetQuicServerInfo(net::QuicServerInfo* quic_server_info) override; + bool GetLoadTimingInfo(net::LoadTimingInfo* load_timing_info) const override; + bool GetRemoteEndpoint(net::IPEndPoint* endpoint) const override; + void PopulateNetErrorDetails(net::NetErrorDetails* details) const override; + void SetPriority(net::RequestPriority priority) override; + void SetWebSocketHandshakeStreamCreateHelper( + net::WebSocketHandshakeStreamBase::CreateHelper* create_helper) override; + void SetBeforeNetworkStartCallback( + const BeforeNetworkStartCallback& callback) override; + void SetBeforeHeadersSentCallback( + const BeforeHeadersSentCallback& callback) override; + int ResumeNetworkStart() override; + void GetConnectionAttempts(net::ConnectionAttempts* out) const override; + + protected: + friend class test::DevToolsNetworkControllerHelper; + + private: + void Fail(); + bool CheckFailed(); + + void IOCallback(const net::CompletionCallback& callback, + bool start, + int result); + int Throttle(const net::CompletionCallback& callback, + bool start, + int result); + void ThrottleCallback(const net::CompletionCallback& callback, + int result, + int64_t bytes); + + DevToolsNetworkInterceptor::ThrottleCallback throttle_callback_; + int64_t throttled_byte_count_; + + DevToolsNetworkController* controller_; + base::WeakPtr<DevToolsNetworkInterceptor> interceptor_; + + // Modified upload data stream. Should be destructed after |custom_request_|. + std::unique_ptr<DevToolsNetworkUploadDataStream> custom_upload_data_stream_; + + // Modified request. Should be destructed after |network_transaction_|. + std::unique_ptr<net::HttpRequestInfo> custom_request_; + + // Real network transaction. + std::unique_ptr<net::HttpTransaction> network_transaction_; + + const net::HttpRequestInfo* request_; + + // True if Fail was already invoked. + bool failed_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkTransaction); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_TRANSACTION_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_transaction_factory.cc b/chromium/chrome/browser/devtools/devtools_network_transaction_factory.cc new file mode 100644 index 00000000000..0b71ab2e9da --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_transaction_factory.cc @@ -0,0 +1,51 @@ +// 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/devtools_network_transaction_factory.h" + +#include <set> +#include <string> +#include <utility> + +#include "chrome/browser/devtools/devtools_network_controller.h" +#include "chrome/browser/devtools/devtools_network_transaction.h" +#include "content/public/browser/service_worker_context.h" +#include "net/base/net_errors.h" +#include "net/http/http_network_layer.h" +#include "net/http/http_network_transaction.h" + +DevToolsNetworkTransactionFactory::DevToolsNetworkTransactionFactory( + DevToolsNetworkController* controller, + net::HttpNetworkSession* session) + : controller_(controller), + network_layer_(new net::HttpNetworkLayer(session)) { + std::set<std::string> headers; + headers.insert( + DevToolsNetworkTransaction::kDevToolsEmulateNetworkConditionsClientId); + content::ServiceWorkerContext::AddExcludedHeadersForFetchEvent(headers); +} + +DevToolsNetworkTransactionFactory::~DevToolsNetworkTransactionFactory() { +} + +int DevToolsNetworkTransactionFactory::CreateTransaction( + net::RequestPriority priority, + std::unique_ptr<net::HttpTransaction>* trans) { + std::unique_ptr<net::HttpTransaction> network_transaction; + int rv = network_layer_->CreateTransaction(priority, &network_transaction); + if (rv != net::OK) { + return rv; + } + trans->reset(new DevToolsNetworkTransaction(controller_, + std::move(network_transaction))); + return net::OK; +} + +net::HttpCache* DevToolsNetworkTransactionFactory::GetCache() { + return network_layer_->GetCache(); +} + +net::HttpNetworkSession* DevToolsNetworkTransactionFactory::GetSession() { + return network_layer_->GetSession(); +} diff --git a/chromium/chrome/browser/devtools/devtools_network_transaction_factory.h b/chromium/chrome/browser/devtools/devtools_network_transaction_factory.h new file mode 100644 index 00000000000..c53d6fb4872 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_transaction_factory.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_TRANSACTION_FACTORY_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_TRANSACTION_FACTORY_H_ + +#include <memory> + +#include "base/macros.h" +#include "net/base/request_priority.h" +#include "net/http/http_transaction_factory.h" + +class DevToolsNetworkController; + +namespace net { +class HttpCache; +class HttpNetworkSession; +class HttpTransaction; +} + +// NetworkTransactionFactory wraps HttpNetworkTransactions. +class DevToolsNetworkTransactionFactory : public net::HttpTransactionFactory { + public: + DevToolsNetworkTransactionFactory( + DevToolsNetworkController* controller, + net::HttpNetworkSession* session); + ~DevToolsNetworkTransactionFactory() override; + + // net::HttpTransactionFactory methods: + int CreateTransaction(net::RequestPriority priority, + std::unique_ptr<net::HttpTransaction>* trans) override; + net::HttpCache* GetCache() override; + net::HttpNetworkSession* GetSession() override; + + private: + DevToolsNetworkController* controller_; + std::unique_ptr<net::HttpTransactionFactory> network_layer_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkTransactionFactory); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_TRANSACTION_FACTORY_H_ diff --git a/chromium/chrome/browser/devtools/devtools_network_upload_data_stream.cc b/chromium/chrome/browser/devtools/devtools_network_upload_data_stream.cc new file mode 100644 index 00000000000..a85193a9833 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_upload_data_stream.cc @@ -0,0 +1,92 @@ +// Copyright 2015 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/devtools_network_upload_data_stream.h" + +#include "net/base/net_errors.h" + +DevToolsNetworkUploadDataStream::DevToolsNetworkUploadDataStream( + net::UploadDataStream* upload_data_stream) + : net::UploadDataStream(upload_data_stream->is_chunked(), + upload_data_stream->identifier()), + throttle_callback_( + base::Bind(&DevToolsNetworkUploadDataStream::ThrottleCallback, + base::Unretained(this))), + throttled_byte_count_(0), + upload_data_stream_(upload_data_stream) { +} + +DevToolsNetworkUploadDataStream::~DevToolsNetworkUploadDataStream() { + if (interceptor_) + interceptor_->StopThrottle(throttle_callback_); +} + +void DevToolsNetworkUploadDataStream::SetInterceptor( + DevToolsNetworkInterceptor* interceptor) { + DCHECK(!interceptor_); + if (interceptor) + interceptor_ = interceptor->GetWeakPtr(); +} + +bool DevToolsNetworkUploadDataStream::IsInMemory() const { + return false; +} + +int DevToolsNetworkUploadDataStream::InitInternal( + const net::NetLogWithSource& net_log) { + throttled_byte_count_ = 0; + int result = upload_data_stream_->Init( + base::Bind(&DevToolsNetworkUploadDataStream::StreamInitCallback, + base::Unretained(this)), + net_log); + if (result == net::OK && !is_chunked()) + SetSize(upload_data_stream_->size()); + return result; +} + +void DevToolsNetworkUploadDataStream::StreamInitCallback(int result) { + if (!is_chunked()) + SetSize(upload_data_stream_->size()); + OnInitCompleted(result); +} + +int DevToolsNetworkUploadDataStream::ReadInternal( + net::IOBuffer* buf, int buf_len) { + int result = upload_data_stream_->Read(buf, buf_len, + base::Bind(&DevToolsNetworkUploadDataStream::StreamReadCallback, + base::Unretained(this))); + return ThrottleRead(result); +} + +void DevToolsNetworkUploadDataStream::StreamReadCallback(int result) { + result = ThrottleRead(result); + if (result != net::ERR_IO_PENDING) + OnReadCompleted(result); +} + +int DevToolsNetworkUploadDataStream::ThrottleRead(int result) { + if (is_chunked() && upload_data_stream_->IsEOF()) + SetIsFinalChunk(); + + if (!interceptor_ || result < 0) + return result; + + if (result > 0) + throttled_byte_count_ += result; + return interceptor_->StartThrottle(result, throttled_byte_count_, + base::TimeTicks(), false, true, throttle_callback_); +} + +void DevToolsNetworkUploadDataStream::ThrottleCallback( + int result, int64_t bytes) { + throttled_byte_count_ = bytes; + OnReadCompleted(result); +} + +void DevToolsNetworkUploadDataStream::ResetInternal() { + upload_data_stream_->Reset(); + throttled_byte_count_ = 0; + if (interceptor_) + interceptor_->StopThrottle(throttle_callback_); +} diff --git a/chromium/chrome/browser/devtools/devtools_network_upload_data_stream.h b/chromium/chrome/browser/devtools/devtools_network_upload_data_stream.h new file mode 100644 index 00000000000..f8d0194899c --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_network_upload_data_stream.h @@ -0,0 +1,51 @@ +// Copyright 2015 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_UPLOAD_DATA_STREAM_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_UPLOAD_DATA_STREAM_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/devtools/devtools_network_interceptor.h" +#include "net/base/completion_callback.h" +#include "net/base/upload_data_stream.h" + +class DevToolsNetworkInterceptor; + +// DevToolsNetworkUploadData is a wrapper for upload data stream, which proxies +// methods and throttles after the original method succeeds. +class DevToolsNetworkUploadDataStream : public net::UploadDataStream { + public: + // Supplied |upload_data_stream| must outlive this object. + explicit DevToolsNetworkUploadDataStream( + net::UploadDataStream* upload_data_stream); + ~DevToolsNetworkUploadDataStream() override; + + void SetInterceptor(DevToolsNetworkInterceptor* interceptor); + + private: + // net::UploadDataStream implementation. + bool IsInMemory() const override; + int InitInternal(const net::NetLogWithSource& net_log) override; + int ReadInternal(net::IOBuffer* buf, int buf_len) override; + void ResetInternal() override; + + void StreamInitCallback(int result); + void StreamReadCallback(int result); + + int ThrottleRead(int result); + void ThrottleCallback(int result, int64_t bytes); + + DevToolsNetworkInterceptor::ThrottleCallback throttle_callback_; + int64_t throttled_byte_count_; + + net::UploadDataStream* upload_data_stream_; + base::WeakPtr<DevToolsNetworkInterceptor> interceptor_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkUploadDataStream); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_UPLOAD_DATA_STREAM_H_ diff --git a/chromium/chrome/browser/devtools/devtools_protocol.cc b/chromium/chrome/browser/devtools/devtools_protocol.cc new file mode 100644 index 00000000000..c6c8dad7545 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_protocol.cc @@ -0,0 +1,149 @@ +// Copyright (c) 2013 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/devtools_protocol.h" + +#include <utility> + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/memory/ptr_util.h" +#include "base/strings/stringprintf.h" + +namespace { + +const char kErrorCodeParam[] = "code"; +const char kErrorParam[] = "error"; +const char kErrorMessageParam[] = "message"; +const char kIdParam[] = "id"; +const char kMethodParam[] = "method"; +const char kParamsParam[] = "params"; +const char kResultParam[] = "result"; + +// JSON RPC 2.0 spec: http://www.jsonrpc.org/specification#error_object +enum Error { kErrorInvalidParams = -32602, kErrorServerError = -32000 }; + +} // namespace + +// static +std::string DevToolsProtocol::SerializeCommand( + int command_id, + const std::string& method, + std::unique_ptr<base::DictionaryValue> params) { + base::DictionaryValue command; + command.SetInteger(kIdParam, command_id); + command.SetString(kMethodParam, method); + if (params) + command.Set(kParamsParam, std::move(params)); + + std::string json_command; + base::JSONWriter::Write(command, &json_command); + return json_command; +} + +// static +std::unique_ptr<base::DictionaryValue> +DevToolsProtocol::CreateInvalidParamsResponse(int command_id, + const std::string& param) { + std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue()); + response->SetInteger(kIdParam, command_id); + auto error_object = base::MakeUnique<base::DictionaryValue>(); + error_object->SetInteger(kErrorCodeParam, kErrorInvalidParams); + error_object->SetString(kErrorMessageParam, + base::StringPrintf("Missing or invalid '%s' parameter", param.c_str())); + response->Set(kErrorParam, std::move(error_object)); + + return response; +} + +// static +std::unique_ptr<base::DictionaryValue> DevToolsProtocol::CreateErrorResponse( + int command_id, + const std::string& error_message) { + std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue()); + response->SetInteger(kIdParam, command_id); + auto error_object = base::MakeUnique<base::DictionaryValue>(); + error_object->SetInteger(kErrorCodeParam, kErrorServerError); + error_object->SetString(kErrorMessageParam, error_message); + response->Set(kErrorParam, std::move(error_object)); + + return response; +} + +// static +std::unique_ptr<base::DictionaryValue> DevToolsProtocol::CreateSuccessResponse( + int command_id, + std::unique_ptr<base::DictionaryValue> result) { + std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue()); + response->SetInteger(kIdParam, command_id); + response->Set(kResultParam, result + ? std::move(result) + : base::MakeUnique<base::DictionaryValue>()); + + return response; +} + +// static +bool DevToolsProtocol::ParseCommand(base::DictionaryValue* command, + int* command_id, + std::string* method, + base::DictionaryValue** params) { + if (!command) + return false; + + if (!command->GetInteger(kIdParam, command_id) || *command_id < 0) + return false; + + if (!command->GetString(kMethodParam, method)) + return false; + + if (!command->GetDictionary(kParamsParam, params)) + *params = nullptr; + + return true; +} + +// static +bool DevToolsProtocol::ParseNotification( + const std::string& json, + std::string* method, + std::unique_ptr<base::DictionaryValue>* params) { + std::unique_ptr<base::Value> value = base::JSONReader::Read(json); + if (!value || !value->IsType(base::Value::Type::DICTIONARY)) + return false; + + std::unique_ptr<base::DictionaryValue> dict( + static_cast<base::DictionaryValue*>(value.release())); + + if (!dict->GetString(kMethodParam, method)) + return false; + + std::unique_ptr<base::Value> params_value; + dict->Remove(kParamsParam, ¶ms_value); + if (params_value && params_value->IsType(base::Value::Type::DICTIONARY)) + params->reset(static_cast<base::DictionaryValue*>(params_value.release())); + + return true; +} + +// static +bool DevToolsProtocol::ParseResponse(const std::string& json, + int* command_id, + int* error_code) { + std::unique_ptr<base::Value> value = base::JSONReader::Read(json); + if (!value || !value->IsType(base::Value::Type::DICTIONARY)) + return false; + + std::unique_ptr<base::DictionaryValue> dict( + static_cast<base::DictionaryValue*>(value.release())); + + if (!dict->GetInteger(kIdParam, command_id)) + return false; + + *error_code = 0; + base::DictionaryValue* error_dict = nullptr; + if (dict->GetDictionary(kErrorParam, &error_dict)) + error_dict->GetInteger(kErrorCodeParam, error_code); + return true; +} diff --git a/chromium/chrome/browser/devtools/devtools_protocol.h b/chromium/chrome/browser/devtools/devtools_protocol.h new file mode 100644 index 00000000000..7ba104df674 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_protocol.h @@ -0,0 +1,53 @@ +// Copyright (c) 2013 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_H_ + +#include <memory> +#include <string> + +#include "base/compiler_specific.h" +#include "base/values.h" + +// Utility class for processing DevTools remote debugging messages. +class DevToolsProtocol { + public: + // Caller maintains ownership of |command|. |*params| is owned by |command|. + static bool ParseCommand(base::DictionaryValue* command, + int* command_id, + std::string* method, + base::DictionaryValue** params); + + static bool ParseNotification(const std::string& json, + std::string* method, + std::unique_ptr<base::DictionaryValue>* params); + + static bool ParseResponse(const std::string& json, + int* command_id, + int* error_code); + + static std::string SerializeCommand( + int command_id, + const std::string& method, + std::unique_ptr<base::DictionaryValue> params); + + static std::unique_ptr<base::DictionaryValue> CreateSuccessResponse( + int command_id, + std::unique_ptr<base::DictionaryValue> result); + + static std::unique_ptr<base::DictionaryValue> CreateInvalidParamsResponse( + int command_id, + const std::string& param); + + static std::unique_ptr<base::DictionaryValue> CreateErrorResponse( + int command_id, + const std::string& error_message); + + private: + DevToolsProtocol() {} + ~DevToolsProtocol() {} +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_H_ diff --git a/chromium/chrome/browser/devtools/devtools_protocol_constants_generator.py b/chromium/chrome/browser/devtools/devtools_protocol_constants_generator.py new file mode 100755 index 00000000000..2ab803f3070 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_protocol_constants_generator.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +# 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. + +import sys +import string +import json + +package = sys.argv[1] +output_cc_path = sys.argv[2] +output_h_path = sys.argv[3] +blink_protocol_path = sys.argv[4] + +template_h = string.Template("""\ +// Copyright 2013 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. + +#ifndef ${PACKAGE}_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_CONSTANTS_H_ +#define ${PACKAGE}_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_CONSTANTS_H_ + +// THIS FILE IS AUTOGENERATED. DO NOT EDIT. +// Generated by +// chrome/browser/devtools/devtools_protocol_constants_generator.py from +// gen/blink/core/inspector/protocol.json + +#include <string> + +namespace $package { +namespace devtools { + +extern const char kProtocolVersion[]; + +bool IsSupportedProtocolVersion(const std::string& version); + +extern const char kResult[]; +$contents + +} // devtools +} // $package + +#endif // ${PACKAGE}_BROWSER_DEVTOOLS_DEVTOOLS_PROTOCOL_CONSTANTS_H_ +""") + +template_cc = string.Template("""\ +// Copyright 2013 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. + +// THIS FILE IS AUTOGENERATED. DO NOT EDIT. +// Generated by +// chrome/browser/devtools/devtools_protocol_constants_generator.py from +// gen/blink/core/inspector/protocol.json + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "$package/browser/devtools/devtools_protocol_constants.h" + +namespace $package { +namespace devtools { + +const char kProtocolVersion[] = "$major.$minor"; + +bool IsSupportedProtocolVersion(const std::string& version) { + std::vector<base::StringPiece> tokens = base::SplitStringPiece( + version, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + int major, minor; + return tokens.size() == 2 && + base::StringToInt(tokens[0], &major) && major == $major && + base::StringToInt(tokens[1], &minor) && minor <= $minor; +} + +const char kResult[] = "result"; +$contents + +} // devtools +} // $package +""") + +def Capitalize(s): + return s[:1].capitalize() + s[1:] + +def ToIdentifier(s): + return "".join([Capitalize(part) for part in s.split("-")]) + +references = [] + +def CreateNamespace(domain_name, data, keys, prefixes, name = None): + result = {} + if name: + result["kName"] = name + for i, key in enumerate(keys): + if key in data: + for parameter in data[key]: + parameter_name = parameter["name"]; + result[prefixes[i] + Capitalize(parameter_name)] = parameter_name + if "enum" in parameter: + enum_name = Capitalize(parameter_name) + result[enum_name] = {} + for enum in parameter["enum"]: + result[enum_name]["kEnum" + ToIdentifier(enum)] = enum + reference = "" + if "$ref" in parameter: + reference = parameter["$ref"] + if "items" in parameter and "$ref" in parameter["items"]: + reference = parameter["items"]["$ref"] + if reference: + if not "." in reference: + reference = domain_name + "." + reference + references.append(reference) + return result + +def FormatContents(tree, indent, format_string): + outer = dict((key, value) for key, value in tree.iteritems() + if not isinstance(value, dict)) + inner = dict((key, value) for key, value in tree.iteritems() + if isinstance(value, dict)) + body = "" + body += "".join(indent + format_string.format(key, value) + for (key, value) in sorted(outer.items())) + body += "".join(FormatNamespace(key, value, indent, format_string) + for (key, value) in sorted(inner.items())) + return body + +def FormatNamespace(title, tree, indent, format_string): + if (not tree): + return "" + body = '\n' + indent + "namespace " + title + " {\n" + body += FormatContents(tree, indent + " ", format_string) + body += indent + "} // " + title + "\n" + return body + +def CreateHeader(tree, output_file): + contents = FormatContents(tree, "", "extern const char {0}[];\n") + output_file.write(template_h.substitute({ + "contents": contents, + "package": package, + "PACKAGE": package.upper() + })) + +def CreateBody(tree, version, output_file): + contents = FormatContents(tree, "", "const char {0}[] = \"{1}\";\n") + output_file.write(template_cc.substitute({ + "major": version["major"], + "minor": version["minor"], + "contents": contents, + "package": package + })) + +blink_protocol_data = open(blink_protocol_path).read() +blink_protocol = json.loads(blink_protocol_data) +blink_version = blink_protocol["version"] + +domains = blink_protocol["domains"] + +namespace_tree = {} + +for domain in domains: + domain_value = {} + domain_namespace_name = Capitalize(domain["domain"]) + if "commands" in domain: + for command in domain["commands"]: + domain_value[command["name"]] = CreateNamespace(domain["domain"], + command, ["parameters", "returns"], ["kParam", "kResponse"], + domain_namespace_name + "." + command["name"]) + + if "events" in domain: + for event in domain["events"]: + domain_value[event["name"]] = CreateNamespace(domain["domain"], + event, ["parameters"], ["kParam"], + domain_namespace_name + "." + event["name"]) + if domain_value: + namespace_tree[domain_namespace_name] = domain_value + +while (references): + reference = references.pop(); + path = reference.split("."); + parent_namespace = namespace_tree; + for path_segment in path[0:-1]: + if path_segment not in parent_namespace: + parent_namespace[path_segment] = {} + parent_namespace = parent_namespace[path_segment] + if (path[-1] not in parent_namespace): + try: + domain = [d for d in domains if d["domain"] == path[0]][0] + ref_type = [t for t in domain["types"] if t["id"] == path[1]][0] + parent_namespace[ref_type["id"]] = CreateNamespace(path[0], + ref_type, ["properties"], ["kParam"]) + except IndexError: + sys.stderr.write("Failed to resolve type [{0}].\n".format(reference)) + sys.exit(1) + +for (namespace_name, namespace) in namespace_tree.items(): + namespace["kName"] = namespace_name + +with open(output_cc_path, "w") as f: + CreateBody(namespace_tree, blink_version, f) + +with open(output_h_path, "w") as f: + CreateHeader(namespace_tree, f) diff --git a/chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc b/chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc new file mode 100644 index 00000000000..5ac3e8a1a17 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc @@ -0,0 +1,2082 @@ +// Copyright (c) 2012 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 <stddef.h> + +#include <memory> +#include <vector> + +#include "base/bind.h" +#include "base/cancelable_callback.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task_scheduler/post_task.h" +#include "base/test/test_timeouts.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/devtools/device/tcp_device_provider.h" +#include "chrome/browser/devtools/devtools_window_testing.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/test_extension_dir.h" +#include "chrome/browser/extensions/unpacked_installer.h" +#include "chrome/browser/lifetime/application_lifetime.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_process_policy.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/test_chrome_web_ui_controller_factory.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/app_modal/javascript_app_modal_dialog.h" +#include "components/app_modal/native_app_modal_dialog.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/url_data_source.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui_controller.h" +#include "content/public/browser/worker_service.h" +#include "content/public/browser/worker_service_observer.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/url_constants.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_registry_observer.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/notification_types.h" +#include "extensions/browser/test_extension_registry_observer.h" +#include "extensions/common/switches.h" +#include "extensions/common/value_builder.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/spawned_test_server/spawned_test_server.h" +#include "net/test/url_request/url_request_mock_http_job.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_filter.h" +#include "net/url_request/url_request_http_job.h" +#include "third_party/WebKit/public/platform/WebInputEvent.h" +#include "ui/compositor/compositor_switches.h" +#include "ui/gl/gl_switches.h" +#include "url/gurl.h" + +using app_modal::AppModalDialog; +using app_modal::JavaScriptAppModalDialog; +using app_modal::NativeAppModalDialog; +using content::BrowserThread; +using content::DevToolsAgentHost; +using content::NavigationController; +using content::RenderFrameHost; +using content::RenderViewHost; +using content::WebContents; +using content::WorkerService; +using content::WorkerServiceObserver; +using extensions::Extension; + +namespace { + +const char kDebuggerTestPage[] = "files/devtools/debugger_test_page.html"; +const char kPauseWhenLoadingDevTools[] = + "files/devtools/pause_when_loading_devtools.html"; +const char kPauseWhenScriptIsRunning[] = + "files/devtools/pause_when_script_is_running.html"; +const char kPageWithContentScript[] = + "files/devtools/page_with_content_script.html"; +const char kNavigateBackTestPage[] = + "files/devtools/navigate_back.html"; +const char kWindowOpenTestPage[] = "files/devtools/window_open.html"; +const char kLatencyInfoTestPage[] = "files/devtools/latency_info.html"; +const char kChunkedTestPage[] = "chunked"; +const char kPushTestPage[] = "files/devtools/push_test_page.html"; +// The resource is not really pushed, but mock url request job pretends it is. +const char kPushTestResource[] = "devtools/image.png"; +const char kPushUseNullEndTime[] = "pushUseNullEndTime"; +const char kSlowTestPage[] = + "chunked?waitBeforeHeaders=100&waitBetweenChunks=100&chunksNumber=2"; +const char kSharedWorkerTestPage[] = + "files/workers/workers_ui_shared_worker.html"; +const char kSharedWorkerTestWorker[] = + "files/workers/workers_ui_shared_worker.js"; +const char kReloadSharedWorkerTestPage[] = + "files/workers/debug_shared_worker_initialization.html"; +const char kReloadSharedWorkerTestWorker[] = + "files/workers/debug_shared_worker_initialization.js"; +const char kEmulateNetworkConditionsPage[] = + "files/devtools/emulate_network_conditions.html"; + +template <typename... T> +void DispatchOnTestSuiteSkipCheck(DevToolsWindow* window, + const char* method, + T... args) { + RenderViewHost* rvh = DevToolsWindowTesting::Get(window) + ->main_web_contents() + ->GetRenderViewHost(); + std::string result; + const char* args_array[] = {method, args...}; + std::ostringstream script; + script << "uiTests.dispatchOnTestSuite(["; + for (size_t i = 0; i < arraysize(args_array); ++i) + script << (i ? "," : "") << '\"' << args_array[i] << '\"'; + script << "])"; + ASSERT_TRUE( + content::ExecuteScriptAndExtractString(rvh, script.str(), &result)); + EXPECT_EQ("[OK]", result); +} + +template <typename... T> +void DispatchOnTestSuite(DevToolsWindow* window, + const char* method, + T... args) { + std::string result; + RenderViewHost* rvh = DevToolsWindowTesting::Get(window) + ->main_web_contents() + ->GetRenderViewHost(); + // At first check that JavaScript part of the front-end is loaded by + // checking that global variable uiTests exists(it's created after all js + // files have been loaded) and has runTest method. + ASSERT_TRUE( + content::ExecuteScriptAndExtractString( + rvh, + "window.domAutomationController.send(" + " '' + (window.uiTests && (typeof uiTests.dispatchOnTestSuite)));", + &result)); + ASSERT_EQ("function", result) << "DevTools front-end is broken."; + DispatchOnTestSuiteSkipCheck(window, method, args...); +} + +void RunTestFunction(DevToolsWindow* window, const char* test_name) { + DispatchOnTestSuite(window, test_name); +} + +void SwitchToPanel(DevToolsWindow* window, const char* panel) { + DispatchOnTestSuite(window, "switchToPanel", panel); +} + +// Version of SwitchToPanel that works with extension-created panels. +void SwitchToExtensionPanel(DevToolsWindow* window, + const Extension* devtools_extension, + const char* panel_name) { + // The full name is the concatenation of the extension URL (stripped of its + // trailing '/') and the |panel_name| that was passed to panels.create(). + std::string prefix = base::TrimString(devtools_extension->url().spec(), "/", + base::TRIM_TRAILING) + .as_string(); + SwitchToPanel(window, (prefix + panel_name).c_str()); +} + +class PushTimesMockURLRequestJob : public net::URLRequestMockHTTPJob { + public: + PushTimesMockURLRequestJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + base::FilePath file_path) + : net::URLRequestMockHTTPJob( + request, + network_delegate, + file_path, + base::CreateTaskRunnerWithTraits( + {base::MayBlock(), base::TaskPriority::BACKGROUND, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {} + + void Start() override { + load_timing_info_.socket_reused = true; + load_timing_info_.request_start_time = base::Time::Now(); + load_timing_info_.request_start = base::TimeTicks::Now(); + load_timing_info_.send_start = base::TimeTicks::Now(); + load_timing_info_.send_end = base::TimeTicks::Now(); + load_timing_info_.receive_headers_end = base::TimeTicks::Now(); + + net::URLRequestMockHTTPJob::Start(); + } + + void GetLoadTimingInfo(net::LoadTimingInfo* load_timing_info) const override { + load_timing_info_.push_start = load_timing_info_.request_start - + base::TimeDelta::FromMilliseconds(100); + if (load_timing_info_.push_end.is_null() && + request()->url().query() != kPushUseNullEndTime) { + load_timing_info_.push_end = base::TimeTicks::Now(); + } + *load_timing_info = load_timing_info_; + } + + private: + mutable net::LoadTimingInfo load_timing_info_; + DISALLOW_COPY_AND_ASSIGN(PushTimesMockURLRequestJob); +}; + +class TestInterceptor : public net::URLRequestInterceptor { + public: + // Creates TestInterceptor and registers it with the URLRequestFilter, + // which takes ownership of it. + static void Register(const GURL& url, const base::FilePath& file_path) { + EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( + url.scheme(), url.host(), + base::WrapUnique(new TestInterceptor(url, file_path))); + } + + // Unregisters previously created TestInterceptor, which should delete it. + static void Unregister(const GURL& url) { + EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(url.scheme(), + url.host()); + } + + // net::URLRequestJobFactory::ProtocolHandler implementation: + net::URLRequestJob* MaybeInterceptRequest( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const override { + EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (request->url().path() != url_.path()) + return nullptr; + return new PushTimesMockURLRequestJob(request, network_delegate, + file_path_); + } + + private: + TestInterceptor(const GURL& url, const base::FilePath& file_path) + : url_(url), file_path_(file_path) {} + + const GURL url_; + const base::FilePath file_path_; + + DISALLOW_COPY_AND_ASSIGN(TestInterceptor); +}; + +} // namespace + +class DevToolsSanityTest : public InProcessBrowserTest { + public: + DevToolsSanityTest() : window_(NULL) {} + + void SetUpOnMainThread() override { + host_resolver()->AddRule("*", "127.0.0.1"); + } + + protected: + void RunTest(const std::string& test_name, const std::string& test_page) { + OpenDevToolsWindow(test_page, false); + RunTestFunction(window_, test_name.c_str()); + CloseDevToolsWindow(); + } + + template <typename... T> + void RunTestMethod(const char* method, T... args) { + DispatchOnTestSuiteSkipCheck(window_, method, args...); + } + + template <typename... T> + void DispatchAndWait(const char* method, T... args) { + DispatchOnTestSuiteSkipCheck(window_, "waitForAsync", method, args...); + } + + template <typename... T> + void DispatchInPageAndWait(const char* method, T... args) { + DispatchAndWait("invokePageFunctionAsync", method, args...); + } + + void LoadTestPage(const std::string& test_page) { + GURL url = spawned_test_server()->GetURL(test_page); + ui_test_utils::NavigateToURL(browser(), url); + } + + void OpenDevToolsWindow(const std::string& test_page, bool is_docked) { + ASSERT_TRUE(spawned_test_server()->Start()); + LoadTestPage(test_page); + + window_ = DevToolsWindowTesting::OpenDevToolsWindowSync(GetInspectedTab(), + is_docked); + } + + WebContents* GetInspectedTab() { + return browser()->tab_strip_model()->GetWebContentsAt(0); + } + + void CloseDevToolsWindow() { + DevToolsWindowTesting::CloseDevToolsWindowSync(window_); + } + + WebContents* main_web_contents() { + return DevToolsWindowTesting::Get(window_)->main_web_contents(); + } + + WebContents* toolbox_web_contents() { + return DevToolsWindowTesting::Get(window_)->toolbox_web_contents(); + } + + DevToolsWindow* window_; +}; + +// Used to block until a dev tools window gets beforeunload event. +class DevToolsWindowBeforeUnloadObserver + : public content::WebContentsObserver { + public: + explicit DevToolsWindowBeforeUnloadObserver(DevToolsWindow*); + void Wait(); + private: + // Invoked when the beforeunload handler fires. + void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; + + bool m_fired; + scoped_refptr<content::MessageLoopRunner> message_loop_runner_; + DISALLOW_COPY_AND_ASSIGN(DevToolsWindowBeforeUnloadObserver); +}; + +DevToolsWindowBeforeUnloadObserver::DevToolsWindowBeforeUnloadObserver( + DevToolsWindow* devtools_window) + : WebContentsObserver( + DevToolsWindowTesting::Get(devtools_window)->main_web_contents()), + m_fired(false) { +} + +void DevToolsWindowBeforeUnloadObserver::Wait() { + if (m_fired) + return; + message_loop_runner_ = new content::MessageLoopRunner; + message_loop_runner_->Run(); +} + +void DevToolsWindowBeforeUnloadObserver::BeforeUnloadFired( + const base::TimeTicks& proceed_time) { + m_fired = true; + if (message_loop_runner_.get()) + message_loop_runner_->Quit(); +} + +class DevToolsBeforeUnloadTest: public DevToolsSanityTest { + public: + void CloseInspectedTab() { + browser()->tab_strip_model()->CloseWebContentsAt(0, + TabStripModel::CLOSE_NONE); + } + + void CloseDevToolsWindowAsync() { + DevToolsWindowTesting::CloseDevToolsWindow(window_); + } + + void CloseInspectedBrowser() { + chrome::CloseWindow(browser()); + } + + protected: + void InjectBeforeUnloadListener(content::WebContents* web_contents) { + ASSERT_TRUE(content::ExecuteScript(web_contents->GetRenderViewHost(), + "window.addEventListener('beforeunload'," + "function(event) { event.returnValue = 'Foo'; });")); + content::PrepContentsForBeforeUnloadTest(web_contents); + } + + void RunBeforeUnloadSanityTest(bool is_docked, + base::Callback<void(void)> close_method, + bool wait_for_browser_close = true) { + OpenDevToolsWindow(kDebuggerTestPage, is_docked); + scoped_refptr<content::MessageLoopRunner> runner = + new content::MessageLoopRunner; + DevToolsWindowTesting::Get(window_)-> + SetCloseCallback(runner->QuitClosure()); + InjectBeforeUnloadListener(main_web_contents()); + { + DevToolsWindowBeforeUnloadObserver before_unload_observer(window_); + close_method.Run(); + CancelModalDialog(); + before_unload_observer.Wait(); + } + { + content::WindowedNotificationObserver close_observer( + chrome::NOTIFICATION_BROWSER_CLOSED, + content::Source<Browser>(browser())); + close_method.Run(); + AcceptModalDialog(); + if (wait_for_browser_close) + close_observer.Wait(); + } + runner->Run(); + } + + DevToolsWindow* OpenDevToolWindowOnWebContents( + content::WebContents* contents, bool is_docked) { + DevToolsWindow* window = + DevToolsWindowTesting::OpenDevToolsWindowSync(contents, is_docked); + return window; + } + + void OpenDevToolsPopupWindow(DevToolsWindow* devtools_window) { + content::WindowedNotificationObserver observer( + content::NOTIFICATION_LOAD_STOP, + content::NotificationService::AllSources()); + ASSERT_TRUE(content::ExecuteScript( + DevToolsWindowTesting::Get(devtools_window)-> + main_web_contents()->GetRenderViewHost(), + "window.open(\"\", \"\", \"location=0\");")); + observer.Wait(); + } + + void CloseDevToolsPopupWindow(DevToolsWindow* devtools_window) { + DevToolsWindowTesting::CloseDevToolsWindowSync(devtools_window); + } + + void AcceptModalDialog() { + NativeAppModalDialog* native_dialog = GetDialog(); + native_dialog->AcceptAppModalDialog(); + } + + void CancelModalDialog() { + NativeAppModalDialog* native_dialog = GetDialog(); + native_dialog->CancelAppModalDialog(); + } + + NativeAppModalDialog* GetDialog() { + AppModalDialog* dialog = ui_test_utils::WaitForAppModalDialog(); + EXPECT_TRUE(dialog->IsJavaScriptModalDialog()); + JavaScriptAppModalDialog* js_dialog = + static_cast<JavaScriptAppModalDialog*>(dialog); + NativeAppModalDialog* native_dialog = js_dialog->native_dialog(); + EXPECT_TRUE(native_dialog); + return native_dialog; + } +}; + +void TimeoutCallback(const std::string& timeout_message) { + ADD_FAILURE() << timeout_message; + base::MessageLoop::current()->QuitWhenIdle(); +} + +// Base class for DevTools tests that test devtools functionality for +// extensions and content scripts. +class DevToolsExtensionTest : public DevToolsSanityTest, + public content::NotificationObserver { + public: + DevToolsExtensionTest() : DevToolsSanityTest() { + PathService::Get(chrome::DIR_TEST_DATA, &test_extensions_dir_); + test_extensions_dir_ = test_extensions_dir_.AppendASCII("devtools"); + test_extensions_dir_ = test_extensions_dir_.AppendASCII("extensions"); + } + + protected: + // Load an extension from test\data\devtools\extensions\<extension_name> + void LoadExtension(const char* extension_name) { + base::FilePath path = test_extensions_dir_.AppendASCII(extension_name); + ASSERT_TRUE(LoadExtensionFromPath(path)) << "Failed to load extension."; + } + + const Extension* LoadExtensionFromPath(const base::FilePath& path) { + ExtensionService* service = extensions::ExtensionSystem::Get( + browser()->profile())->extension_service(); + extensions::ExtensionRegistry* registry = + extensions::ExtensionRegistry::Get(browser()->profile()); + extensions::TestExtensionRegistryObserver observer(registry); + extensions::UnpackedInstaller::Create(service)->Load(path); + observer.WaitForExtensionLoaded(); + + if (!WaitForExtensionViewsToLoad()) + return nullptr; + + return GetExtensionByPath(registry->enabled_extensions(), path); + } + + // Loads a dynamically generated extension populated with a bunch of test + // pages. |name| is the extension name to use in the manifest. + // |devtools_page|, if non-empty, indicates which test page should be be + // listed as a devtools_page in the manifest. If |devtools_page| is empty, a + // non-devtools extension is created instead. |panel_iframe_src| controls the + // src= attribute of the <iframe> element in the 'panel.html' test page. + const Extension* LoadExtensionForTest(const std::string& name, + const std::string& devtools_page, + const std::string& panel_iframe_src) { + test_extension_dirs_.push_back( + base::MakeUnique<extensions::TestExtensionDir>()); + extensions::TestExtensionDir* dir = test_extension_dirs_.back().get(); + + extensions::DictionaryBuilder manifest; + manifest.Set("name", name) + .Set("version", "1") + .Set("manifest_version", 2) + // simple_test_page.html is currently the only page referenced outside + // of its own extension in the tests + .Set("web_accessible_resources", + extensions::ListBuilder().Append("simple_test_page.html").Build()); + + // If |devtools_page| isn't empty, make it a devtools extension in the + // manifest. + if (!devtools_page.empty()) + manifest.Set("devtools_page", devtools_page); + + dir->WriteManifest(manifest.ToJSON()); + + GURL http_frame_url = + embedded_test_server()->GetURL("a.com", "/popup_iframe.html"); + + // If this is a devtools extension, |devtools_page| will indicate which of + // these devtools_pages will end up being used. Different tests use + // different devtools_pages. + dir->WriteFile(FILE_PATH_LITERAL("web_devtools_page.html"), + "<html><body><iframe src='" + http_frame_url.spec() + + "'></iframe></body></html>"); + + dir->WriteFile(FILE_PATH_LITERAL("simple_devtools_page.html"), + "<html><body></body></html>"); + + dir->WriteFile( + FILE_PATH_LITERAL("panel_devtools_page.html"), + "<html><head><script " + "src='panel_devtools_page.js'></script></head><body></body></html>"); + + dir->WriteFile(FILE_PATH_LITERAL("panel_devtools_page.js"), + "chrome.devtools.panels.create('iframe_panel',\n" + " null,\n" + " 'panel.html',\n" + " function(panel) {\n" + " chrome.devtools.inspectedWindow.eval(\n" + " 'console.log(\"PASS\")');\n" + " }\n" + ");\n"); + + dir->WriteFile(FILE_PATH_LITERAL("sidebarpane_devtools_page.html"), + "<html><head><script src='sidebarpane_devtools_page.js'>" + "</script></head><body></body></html>"); + + dir->WriteFile( + FILE_PATH_LITERAL("sidebarpane_devtools_page.js"), + "chrome.devtools.panels.elements.createSidebarPane('iframe_pane',\n" + " function(sidebar) {\n" + " chrome.devtools.inspectedWindow.eval(\n" + " 'console.log(\"PASS\")');\n" + " sidebar.setPage('panel.html');\n" + " }\n" + ");\n"); + + dir->WriteFile(FILE_PATH_LITERAL("panel.html"), + "<html><body><iframe src='" + panel_iframe_src + + "'></iframe></body></html>"); + + dir->WriteFile(FILE_PATH_LITERAL("simple_test_page.html"), + "<html><body>This is a test</body></html>"); + + GURL web_url = embedded_test_server()->GetURL("a.com", "/title3.html"); + + dir->WriteFile(FILE_PATH_LITERAL("multi_frame_page.html"), + "<html><body><iframe src='about:blank'>" + "</iframe><iframe src='data:text/html,foo'>" + "</iframe><iframe src='" + + web_url.spec() + "'></iframe></body></html>"); + + // Install the extension. + return LoadExtensionFromPath(dir->UnpackedPath()); + } + + private: + const Extension* GetExtensionByPath( + const extensions::ExtensionSet& extensions, + const base::FilePath& path) { + base::ThreadRestrictions::ScopedAllowIO allow_io; + base::FilePath extension_path = base::MakeAbsoluteFilePath(path); + EXPECT_TRUE(!extension_path.empty()); + for (const scoped_refptr<const Extension>& extension : extensions) { + if (extension->path() == extension_path) { + return extension.get(); + } + } + return nullptr; + } + + bool WaitForExtensionViewsToLoad() { + // Wait for all the extension render views that exist to finish loading. + // NOTE: This assumes that the extension views list is not changing while + // this method is running. + + content::NotificationRegistrar registrar; + registrar.Add(this, + extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD, + content::NotificationService::AllSources()); + base::CancelableClosure timeout( + base::Bind(&TimeoutCallback, "Extension host load timed out.")); + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, timeout.callback(), TestTimeouts::action_timeout()); + + extensions::ProcessManager* manager = + extensions::ProcessManager::Get(browser()->profile()); + extensions::ProcessManager::FrameSet all_frames = manager->GetAllFrames(); + for (extensions::ProcessManager::FrameSet::const_iterator iter = + all_frames.begin(); + iter != all_frames.end();) { + if (!content::WebContents::FromRenderFrameHost(*iter)->IsLoading()) + ++iter; + else + content::RunMessageLoop(); + } + + timeout.Cancel(); + return true; + } + + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override { + DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD, + type); + base::MessageLoopForUI::current()->QuitWhenIdle(); + } + + std::vector<std::unique_ptr<extensions::TestExtensionDir>> + test_extension_dirs_; + base::FilePath test_extensions_dir_; +}; + +class DevToolsExperimentalExtensionTest : public DevToolsExtensionTest { + public: + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch( + extensions::switches::kEnableExperimentalExtensionApis); + } +}; + +class WorkerDevToolsSanityTest : public InProcessBrowserTest { + public: + WorkerDevToolsSanityTest() : window_(NULL) {} + + protected: + class WorkerData : public base::RefCountedThreadSafe<WorkerData> { + public: + WorkerData() : worker_process_id(0), worker_route_id(0) {} + int worker_process_id; + int worker_route_id; + + private: + friend class base::RefCountedThreadSafe<WorkerData>; + ~WorkerData() {} + }; + + class WorkerCreationObserver : public WorkerServiceObserver { + public: + explicit WorkerCreationObserver(const std::string& path, + WorkerData* worker_data) + : path_(path), worker_data_(worker_data) {} + + private: + ~WorkerCreationObserver() override {} + + void WorkerCreated(const GURL& url, + const base::string16& name, + int process_id, + int route_id) override { + if (url.path().rfind(path_) == std::string::npos) + return; + worker_data_->worker_process_id = process_id; + worker_data_->worker_route_id = route_id; + WorkerService::GetInstance()->RemoveObserver(this); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::MessageLoop::QuitWhenIdleClosure()); + delete this; + } + std::string path_; + scoped_refptr<WorkerData> worker_data_; + }; + + class WorkerTerminationObserver : public WorkerServiceObserver { + public: + explicit WorkerTerminationObserver(WorkerData* worker_data) + : worker_data_(worker_data) { + } + + private: + ~WorkerTerminationObserver() override {} + + void WorkerDestroyed(int process_id, int route_id) override { + ASSERT_EQ(worker_data_->worker_process_id, process_id); + ASSERT_EQ(worker_data_->worker_route_id, route_id); + WorkerService::GetInstance()->RemoveObserver(this); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::MessageLoop::QuitWhenIdleClosure()); + delete this; + } + scoped_refptr<WorkerData> worker_data_; + }; + + void RunTest(const char* test_name, + const char* test_page, + const char* worker_path) { + ASSERT_TRUE(spawned_test_server()->Start()); + GURL url = spawned_test_server()->GetURL(test_page); + ui_test_utils::NavigateToURL(browser(), url); + + scoped_refptr<WorkerData> worker_data = + WaitForFirstSharedWorker(worker_path); + OpenDevToolsWindowForSharedWorker(worker_data.get()); + RunTestFunction(window_, test_name); + CloseDevToolsWindow(); + } + + static void TerminateWorkerOnIOThread(scoped_refptr<WorkerData> worker_data) { + if (!WorkerService::GetInstance()->TerminateWorker( + worker_data->worker_process_id, worker_data->worker_route_id)) + FAIL() << "Failed to terminate worker.\n"; + WorkerService::GetInstance()->AddObserver( + new WorkerTerminationObserver(worker_data.get())); + } + + static void TerminateWorker(scoped_refptr<WorkerData> worker_data) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&TerminateWorkerOnIOThread, worker_data)); + content::RunMessageLoop(); + } + + static void WaitForFirstSharedWorkerOnIOThread( + const std::string& path, + scoped_refptr<WorkerData> worker_data) { + std::vector<WorkerService::WorkerInfo> worker_info = + WorkerService::GetInstance()->GetWorkers(); + for (size_t i = 0; i < worker_info.size(); i++) { + if (worker_info[i].url.path().rfind(path) == std::string::npos) + continue; + worker_data->worker_process_id = worker_info[0].process_id; + worker_data->worker_route_id = worker_info[0].route_id; + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::MessageLoop::QuitWhenIdleClosure()); + return; + } + + WorkerService::GetInstance()->AddObserver( + new WorkerCreationObserver(path, worker_data.get())); + } + + static scoped_refptr<WorkerData> WaitForFirstSharedWorker(const char* path) { + scoped_refptr<WorkerData> worker_data(new WorkerData()); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&WaitForFirstSharedWorkerOnIOThread, path, worker_data)); + content::RunMessageLoop(); + return worker_data; + } + + void OpenDevToolsWindowForSharedWorker(WorkerData* worker_data) { + Profile* profile = browser()->profile(); + scoped_refptr<DevToolsAgentHost> agent_host( + DevToolsAgentHost::GetForWorker( + worker_data->worker_process_id, + worker_data->worker_route_id)); + window_ = DevToolsWindowTesting::OpenDevToolsWindowForWorkerSync( + profile, agent_host.get()); + } + + void CloseDevToolsWindow() { + DevToolsWindowTesting::CloseDevToolsWindowSync(window_); + } + + DevToolsWindow* window_; +}; + +// Tests that BeforeUnload event gets called on docked devtools if +// we try to close them. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, TestDockedDevToolsClose) { + RunBeforeUnloadSanityTest(true, base::Bind( + &DevToolsBeforeUnloadTest::CloseDevToolsWindowAsync, + base::Unretained(this)), false); +} + +// Tests that BeforeUnload event gets called on docked devtools if +// we try to close the inspected page. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + TestDockedDevToolsInspectedTabClose) { + RunBeforeUnloadSanityTest(true, base::Bind( + &DevToolsBeforeUnloadTest::CloseInspectedTab, + base::Unretained(this))); +} + +// Tests that BeforeUnload event gets called on docked devtools if +// we try to close the inspected browser. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + TestDockedDevToolsInspectedBrowserClose) { + RunBeforeUnloadSanityTest(true, base::Bind( + &DevToolsBeforeUnloadTest::CloseInspectedBrowser, + base::Unretained(this))); +} + +// Tests that BeforeUnload event gets called on undocked devtools if +// we try to close them. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, TestUndockedDevToolsClose) { + RunBeforeUnloadSanityTest(false, base::Bind( + &DevToolsBeforeUnloadTest::CloseDevToolsWindowAsync, + base::Unretained(this)), false); +} + +// Tests that BeforeUnload event gets called on undocked devtools if +// we try to close the inspected page. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + TestUndockedDevToolsInspectedTabClose) { + RunBeforeUnloadSanityTest(false, base::Bind( + &DevToolsBeforeUnloadTest::CloseInspectedTab, + base::Unretained(this))); +} + +// Tests that BeforeUnload event gets called on undocked devtools if +// we try to close the inspected browser. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + TestUndockedDevToolsInspectedBrowserClose) { + RunBeforeUnloadSanityTest(false, base::Bind( + &DevToolsBeforeUnloadTest::CloseInspectedBrowser, + base::Unretained(this))); +} + +// Tests that BeforeUnload event gets called on undocked devtools if +// we try to exit application. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + TestUndockedDevToolsApplicationClose) { + RunBeforeUnloadSanityTest(false, base::Bind( + &chrome::CloseAllBrowsers)); +} + +// Tests that inspected tab gets closed if devtools renderer +// becomes unresponsive during beforeunload event interception. +// @see http://crbug.com/322380 +// Disabled because of http://crbug.com/410327 +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + DISABLED_TestUndockedDevToolsUnresponsive) { + ASSERT_TRUE(spawned_test_server()->Start()); + LoadTestPage(kDebuggerTestPage); + DevToolsWindow* devtools_window = OpenDevToolWindowOnWebContents( + GetInspectedTab(), false); + + scoped_refptr<content::MessageLoopRunner> runner = + new content::MessageLoopRunner; + DevToolsWindowTesting::Get(devtools_window)->SetCloseCallback( + runner->QuitClosure()); + + ASSERT_TRUE(content::ExecuteScript( + DevToolsWindowTesting::Get(devtools_window)->main_web_contents()-> + GetRenderViewHost(), + "window.addEventListener('beforeunload'," + "function(event) { while (true); });")); + CloseInspectedTab(); + runner->Run(); +} + +// Tests that closing worker inspector window does not cause browser crash +// @see http://crbug.com/323031 +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + TestWorkerWindowClosing) { + ASSERT_TRUE(spawned_test_server()->Start()); + LoadTestPage(kDebuggerTestPage); + DevToolsWindow* devtools_window = OpenDevToolWindowOnWebContents( + GetInspectedTab(), false); + + OpenDevToolsPopupWindow(devtools_window); + CloseDevToolsPopupWindow(devtools_window); +} + +// Tests that BeforeUnload event gets called on devtools that are opened +// on another devtools. +IN_PROC_BROWSER_TEST_F(DevToolsBeforeUnloadTest, + TestDevToolsOnDevTools) { + ASSERT_TRUE(spawned_test_server()->Start()); + LoadTestPage(kDebuggerTestPage); + + std::vector<DevToolsWindow*> windows; + std::vector<content::WindowedNotificationObserver*> close_observers; + content::WebContents* inspected_web_contents = GetInspectedTab(); + for (int i = 0; i < 3; ++i) { + DevToolsWindow* devtools_window = OpenDevToolWindowOnWebContents( + inspected_web_contents, i == 0); + windows.push_back(devtools_window); + content::WindowedNotificationObserver* close_observer = + new content::WindowedNotificationObserver( + content::NOTIFICATION_WEB_CONTENTS_DESTROYED, + content::Source<content::WebContents>( + DevToolsWindowTesting::Get(devtools_window)-> + main_web_contents())); + close_observers.push_back(close_observer); + inspected_web_contents = + DevToolsWindowTesting::Get(devtools_window)->main_web_contents(); + } + + InjectBeforeUnloadListener( + DevToolsWindowTesting::Get(windows[0])->main_web_contents()); + InjectBeforeUnloadListener( + DevToolsWindowTesting::Get(windows[2])->main_web_contents()); + // Try to close second devtools. + { + content::WindowedNotificationObserver cancel_browser( + chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, + content::NotificationService::AllSources()); + chrome::CloseWindow(DevToolsWindowTesting::Get(windows[1])->browser()); + CancelModalDialog(); + cancel_browser.Wait(); + } + // Try to close browser window. + { + content::WindowedNotificationObserver cancel_browser( + chrome::NOTIFICATION_BROWSER_CLOSE_CANCELLED, + content::NotificationService::AllSources()); + chrome::CloseWindow(browser()); + AcceptModalDialog(); + CancelModalDialog(); + cancel_browser.Wait(); + } + // Try to exit application. + { + content::WindowedNotificationObserver close_observer( + chrome::NOTIFICATION_BROWSER_CLOSED, + content::Source<Browser>(browser())); + chrome::CloseAllBrowsers(); + AcceptModalDialog(); + AcceptModalDialog(); + close_observer.Wait(); + } + for (size_t i = 0; i < close_observers.size(); ++i) { + close_observers[i]->Wait(); + delete close_observers[i]; + } +} + +// Tests scripts panel showing. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestShowScriptsTab) { + RunTest("testShowScriptsTab", kDebuggerTestPage); +} + +// Tests that scripts tab is populated with inspected scripts even if it +// hadn't been shown by the moment inspected paged refreshed. +// @see http://crbug.com/26312 +IN_PROC_BROWSER_TEST_F( + DevToolsSanityTest, + TestScriptsTabIsPopulatedOnInspectedPageRefresh) { + RunTest("testScriptsTabIsPopulatedOnInspectedPageRefresh", + kDebuggerTestPage); +} + +// Tests that chrome.devtools extension is correctly exposed. +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + TestDevToolsExtensionAPI) { + LoadExtension("devtools_extension"); + RunTest("waitForTestResultsInConsole", std::string()); +} + +// Tests that http Iframes within the visible devtools panel for the devtools +// extension are rendered in their own processes and not in the devtools process +// or the extension's process. This is tested because this is one of the +// extension pages with devtools access +// (https://developer.chrome.com/extensions/devtools). Also tests that frames +// with data URLs and about:blank URLs are rendered in the devtools process, +// unless a web OOPIF navigates itself to about:blank, in which case it does not +// end up back in the devtools process. Also tests that when a web IFrame is +// navigated back to a devtools extension page, it gets put back in the devtools +// process. +// http://crbug.com/570483 +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + HttpIframeInDevToolsExtensionPanel) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // Install the dynamically-generated extension. + const Extension* extension = + LoadExtensionForTest("Devtools Extension", "panel_devtools_page.html", + "/multi_frame_page.html"); + ASSERT_TRUE(extension); + + OpenDevToolsWindow(kDebuggerTestPage, false); + + // Wait for the extension's panel to finish loading -- it'll output 'PASS' + // when it's installed. waitForTestResultsInConsole waits until that 'PASS'. + RunTestFunction(window_, "waitForTestResultsInConsole"); + + // Now that we know the panel is loaded, switch to it. + SwitchToExtensionPanel(window_, extension, "iframe_panel"); + content::WaitForLoadStop(main_web_contents()); + + std::vector<RenderFrameHost*> rfhs = main_web_contents()->GetAllFrames(); + EXPECT_EQ(7U, rfhs.size()); + + // This test creates a page with the following frame tree: + // - DevTools + // - devtools_page from DevTools extension + // - Panel (DevTools extension) + // - iframe (DevTools extension) + // - about:blank + // - data: + // - web URL + + RenderFrameHost* main_devtools_rfh = main_web_contents()->GetMainFrame(); + RenderFrameHost* devtools_extension_devtools_page_rfh = + ChildFrameAt(main_devtools_rfh, 0); + RenderFrameHost* devtools_extension_panel_rfh = + ChildFrameAt(main_devtools_rfh, 1); + RenderFrameHost* panel_frame_rfh = + ChildFrameAt(devtools_extension_panel_rfh, 0); + RenderFrameHost* about_blank_frame_rfh = ChildFrameAt(panel_frame_rfh, 0); + RenderFrameHost* data_frame_rfh = ChildFrameAt(panel_frame_rfh, 1); + RenderFrameHost* web_frame_rfh = ChildFrameAt(panel_frame_rfh, 2); + + GURL web_url = embedded_test_server()->GetURL("a.com", "/title3.html"); + GURL about_blank_url = GURL(url::kAboutBlankURL); + GURL data_url = GURL("data:text/html,foo"); + + EXPECT_TRUE(main_devtools_rfh->GetLastCommittedURL().SchemeIs( + content::kChromeDevToolsScheme)); + EXPECT_EQ(extension->GetResourceURL("/panel_devtools_page.html"), + devtools_extension_devtools_page_rfh->GetLastCommittedURL()); + EXPECT_EQ(extension->GetResourceURL("/panel.html"), + devtools_extension_panel_rfh->GetLastCommittedURL()); + EXPECT_EQ(extension->GetResourceURL("/multi_frame_page.html"), + panel_frame_rfh->GetLastCommittedURL()); + EXPECT_EQ(about_blank_url, about_blank_frame_rfh->GetLastCommittedURL()); + EXPECT_EQ(data_url, data_frame_rfh->GetLastCommittedURL()); + EXPECT_EQ(web_url, web_frame_rfh->GetLastCommittedURL()); + + content::SiteInstance* devtools_instance = + main_devtools_rfh->GetSiteInstance(); + EXPECT_TRUE( + devtools_instance->GetSiteURL().SchemeIs(content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_instance, + devtools_extension_devtools_page_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, devtools_extension_panel_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, panel_frame_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, about_blank_frame_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, data_frame_rfh->GetSiteInstance()); + EXPECT_EQ(web_url.host(), + web_frame_rfh->GetSiteInstance()->GetSiteURL().host()); + EXPECT_NE(devtools_instance, web_frame_rfh->GetSiteInstance()); + + // Check that if the web iframe navigates itself to about:blank, it stays in + // the web SiteInstance. + std::string about_blank_javascript = "location.href='about:blank';"; + + content::TestNavigationManager web_about_blank_manager(main_web_contents(), + about_blank_url); + + ASSERT_TRUE(content::ExecuteScript(web_frame_rfh, about_blank_javascript)); + + web_about_blank_manager.WaitForNavigationFinished(); + + EXPECT_EQ(about_blank_url, web_frame_rfh->GetLastCommittedURL()); + EXPECT_EQ(web_url.host(), + web_frame_rfh->GetSiteInstance()->GetSiteURL().host()); + EXPECT_NE(devtools_instance, web_frame_rfh->GetSiteInstance()); + + // Check that if the web IFrame is navigated back to a devtools extension + // page, it gets put back in the devtools process. + GURL extension_simple_url = + extension->GetResourceURL("/simple_test_page.html"); + std::string renavigation_javascript = + "location.href='" + extension_simple_url.spec() + "';"; + + content::TestNavigationManager renavigation_manager(main_web_contents(), + extension_simple_url); + + ASSERT_TRUE(content::ExecuteScript(web_frame_rfh, renavigation_javascript)); + + renavigation_manager.WaitForNavigationFinished(); + + // The old RFH is no longer valid after the renavigation, so we must get the + // new one. + RenderFrameHost* extension_simple_frame_rfh = + ChildFrameAt(panel_frame_rfh, 2); + + EXPECT_EQ(extension_simple_url, + extension_simple_frame_rfh->GetLastCommittedURL()); + EXPECT_EQ(devtools_instance, extension_simple_frame_rfh->GetSiteInstance()); +} + +// Tests that http Iframes within the sidebar pane page for the devtools +// extension that is visible in the elements panel are rendered in their own +// processes and not in the devtools process or the extension's process. This +// is tested because this is one of the extension pages with devtools access +// (https://developer.chrome.com/extensions/devtools). http://crbug.com/570483 +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + HttpIframeInDevToolsExtensionSideBarPane) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL web_url = embedded_test_server()->GetURL("a.com", "/title3.html"); + + // Install the dynamically-generated extension. + const Extension* extension = LoadExtensionForTest( + "Devtools Extension", "sidebarpane_devtools_page.html", web_url.spec()); + ASSERT_TRUE(extension); + + OpenDevToolsWindow(kDebuggerTestPage, false); + + // Wait for the extension's sidebarpane to finish loading -- it'll output + // 'PASS' when it's installed. waitForTestResultsInConsole waits until that + // 'PASS'. + RunTestFunction(window_, "waitForTestResultsInConsole"); + + // Now that we know the sidebarpane is loaded, switch to it. + content::TestNavigationManager web_manager(main_web_contents(), web_url); + SwitchToPanel(window_, "elements"); + // This is a bit of a hack to switch to the sidebar pane in the elements panel + // that the Iframe has been added to. + SwitchToPanel(window_, "iframe_pane"); + web_manager.WaitForNavigationFinished(); + + std::vector<RenderFrameHost*> rfhs = main_web_contents()->GetAllFrames(); + EXPECT_EQ(4U, rfhs.size()); + + RenderFrameHost* main_devtools_rfh = main_web_contents()->GetMainFrame(); + RenderFrameHost* devtools_extension_devtools_page_rfh = + ChildFrameAt(main_devtools_rfh, 0); + RenderFrameHost* devtools_sidebar_pane_extension_rfh = + ChildFrameAt(main_devtools_rfh, 1); + RenderFrameHost* http_iframe_rfh = + ChildFrameAt(devtools_sidebar_pane_extension_rfh, 0); + + EXPECT_TRUE(main_devtools_rfh->GetLastCommittedURL().SchemeIs( + content::kChromeDevToolsScheme)); + EXPECT_EQ(extension->GetResourceURL("/sidebarpane_devtools_page.html"), + devtools_extension_devtools_page_rfh->GetLastCommittedURL()); + EXPECT_EQ(extension->GetResourceURL("/panel.html"), + devtools_sidebar_pane_extension_rfh->GetLastCommittedURL()); + EXPECT_EQ(web_url, http_iframe_rfh->GetLastCommittedURL()); + + content::SiteInstance* devtools_instance = + main_devtools_rfh->GetSiteInstance(); + EXPECT_TRUE( + devtools_instance->GetSiteURL().SchemeIs(content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_instance, + devtools_extension_devtools_page_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, + devtools_sidebar_pane_extension_rfh->GetSiteInstance()); + EXPECT_EQ(web_url.host(), + http_iframe_rfh->GetSiteInstance()->GetSiteURL().host()); + EXPECT_NE(devtools_instance, http_iframe_rfh->GetSiteInstance()); +} + +// Tests that http Iframes within the devtools background page, which is +// different from the extension's background page, are rendered in their own +// processes and not in the devtools process or the extension's process. This +// is tested because this is one of the extension pages with devtools access +// (https://developer.chrome.com/extensions/devtools). http://crbug.com/570483 +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + HttpIframeInDevToolsExtensionDevtools) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // Install the dynamically-generated extension. + const Extension* extension = + LoadExtensionForTest("Devtools Extension", "web_devtools_page.html", + "" /* panel_iframe_src */); + ASSERT_TRUE(extension); + + // Wait for a 'DONE' message sent from popup_iframe.html, indicating that it + // loaded successfully. + content::DOMMessageQueue message_queue; + std::string message; + OpenDevToolsWindow(kDebuggerTestPage, false); + + while (true) { + ASSERT_TRUE(message_queue.WaitForMessage(&message)); + if (message == "\"DONE\"") + break; + } + + std::vector<RenderFrameHost*> rfhs = main_web_contents()->GetAllFrames(); + EXPECT_EQ(3U, rfhs.size()); + + RenderFrameHost* main_devtools_rfh = main_web_contents()->GetMainFrame(); + RenderFrameHost* devtools_extension_devtools_page_rfh = + ChildFrameAt(main_devtools_rfh, 0); + RenderFrameHost* http_iframe_rfh = + ChildFrameAt(devtools_extension_devtools_page_rfh, 0); + + GURL web_url = embedded_test_server()->GetURL("a.com", "/popup_iframe.html"); + + EXPECT_TRUE(main_devtools_rfh->GetLastCommittedURL().SchemeIs( + content::kChromeDevToolsScheme)); + EXPECT_EQ(extension->GetResourceURL("/web_devtools_page.html"), + devtools_extension_devtools_page_rfh->GetLastCommittedURL()); + EXPECT_EQ(web_url, http_iframe_rfh->GetLastCommittedURL()); + + content::SiteInstance* devtools_instance = + main_devtools_rfh->GetSiteInstance(); + EXPECT_TRUE( + devtools_instance->GetSiteURL().SchemeIs(content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_instance, + devtools_extension_devtools_page_rfh->GetSiteInstance()); + EXPECT_EQ(web_url.host(), + http_iframe_rfh->GetSiteInstance()->GetSiteURL().host()); + EXPECT_NE(devtools_instance, http_iframe_rfh->GetSiteInstance()); +} + +// Tests that iframes to a non-devtools extension embedded in a devtools +// extension will be isolated from devtools and the devtools extension. +// http://crbug.com/570483 +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + NonDevToolsExtensionInDevToolsExtension) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // Install the dynamically-generated non-devtools extension. + const Extension* non_devtools_extension = + LoadExtensionForTest("Non-DevTools Extension", "" /* devtools_page */, + "" /* panel_iframe_src */); + ASSERT_TRUE(non_devtools_extension); + + GURL non_dt_extension_test_url = + non_devtools_extension->GetResourceURL("/simple_test_page.html"); + + // Install the dynamically-generated devtools extension. + const Extension* devtools_extension = + LoadExtensionForTest("Devtools Extension", "panel_devtools_page.html", + non_dt_extension_test_url.spec()); + ASSERT_TRUE(devtools_extension); + + OpenDevToolsWindow(kDebuggerTestPage, false); + + // Wait for the extension's panel to finish loading -- it'll output 'PASS' + // when it's installed. waitForTestResultsInConsole waits until that 'PASS'. + RunTestFunction(window_, "waitForTestResultsInConsole"); + + // Now that we know the panel is loaded, switch to it. + content::TestNavigationManager non_devtools_manager( + main_web_contents(), non_dt_extension_test_url); + SwitchToExtensionPanel(window_, devtools_extension, "iframe_panel"); + non_devtools_manager.WaitForNavigationFinished(); + + std::vector<RenderFrameHost*> rfhs = main_web_contents()->GetAllFrames(); + EXPECT_EQ(4U, rfhs.size()); + + RenderFrameHost* main_devtools_rfh = main_web_contents()->GetMainFrame(); + RenderFrameHost* devtools_extension_devtools_page_rfh = + ChildFrameAt(main_devtools_rfh, 0); + RenderFrameHost* devtools_extension_panel_rfh = + ChildFrameAt(main_devtools_rfh, 1); + RenderFrameHost* non_devtools_extension_rfh = + ChildFrameAt(devtools_extension_panel_rfh, 0); + + EXPECT_TRUE(main_devtools_rfh->GetLastCommittedURL().SchemeIs( + content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_extension->GetResourceURL("/panel_devtools_page.html"), + devtools_extension_devtools_page_rfh->GetLastCommittedURL()); + EXPECT_EQ(devtools_extension->GetResourceURL("/panel.html"), + devtools_extension_panel_rfh->GetLastCommittedURL()); + EXPECT_EQ(non_dt_extension_test_url, + non_devtools_extension_rfh->GetLastCommittedURL()); + + // simple_test_page.html's frame should be in |non_devtools_extension|'s + // process, not in devtools or |devtools_extension|'s process. + content::SiteInstance* devtools_instance = + main_devtools_rfh->GetSiteInstance(); + EXPECT_TRUE( + devtools_instance->GetSiteURL().SchemeIs(content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_instance, + devtools_extension_devtools_page_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, devtools_extension_panel_rfh->GetSiteInstance()); + EXPECT_EQ(non_dt_extension_test_url.GetOrigin(), + non_devtools_extension_rfh->GetSiteInstance()->GetSiteURL()); + EXPECT_NE(devtools_instance, non_devtools_extension_rfh->GetSiteInstance()); +} + +// Tests that if a devtools extension's devtools panel page has a subframe to a +// page for another devtools extension, the subframe is rendered in the devtools +// process as well. http://crbug.com/570483 +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + DevToolsExtensionInDevToolsExtension) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // Install the dynamically-generated extension. + const Extension* devtools_b_extension = + LoadExtensionForTest("Devtools Extension B", "simple_devtools_page.html", + "" /* panel_iframe_src */); + ASSERT_TRUE(devtools_b_extension); + + GURL extension_b_page_url = + devtools_b_extension->GetResourceURL("/simple_test_page.html"); + + // Install another dynamically-generated extension. This extension's + // panel.html's iframe will point to an extension b URL. + const Extension* devtools_a_extension = + LoadExtensionForTest("Devtools Extension A", "panel_devtools_page.html", + extension_b_page_url.spec()); + ASSERT_TRUE(devtools_a_extension); + + OpenDevToolsWindow(kDebuggerTestPage, false); + + // Wait for the extension's panel to finish loading -- it'll output 'PASS' + // when it's installed. waitForTestResultsInConsole waits until that 'PASS'. + RunTestFunction(window_, "waitForTestResultsInConsole"); + + // Now that we know the panel is loaded, switch to it. + content::TestNavigationManager extension_b_manager(main_web_contents(), + extension_b_page_url); + SwitchToExtensionPanel(window_, devtools_a_extension, "iframe_panel"); + extension_b_manager.WaitForNavigationFinished(); + + std::vector<RenderFrameHost*> rfhs = main_web_contents()->GetAllFrames(); + EXPECT_EQ(5U, rfhs.size()); + + RenderFrameHost* main_devtools_rfh = main_web_contents()->GetMainFrame(); + + RenderFrameHost* devtools_extension_a_devtools_rfh = + content::FrameMatchingPredicate( + main_web_contents(), base::Bind(&content::FrameHasSourceUrl, + devtools_a_extension->GetResourceURL( + "/panel_devtools_page.html"))); + EXPECT_TRUE(devtools_extension_a_devtools_rfh); + RenderFrameHost* devtools_extension_b_devtools_rfh = + content::FrameMatchingPredicate( + main_web_contents(), base::Bind(&content::FrameHasSourceUrl, + devtools_b_extension->GetResourceURL( + "/simple_devtools_page.html"))); + EXPECT_TRUE(devtools_extension_b_devtools_rfh); + + RenderFrameHost* devtools_extension_a_panel_rfh = + ChildFrameAt(main_devtools_rfh, 2); + RenderFrameHost* devtools_extension_b_frame_rfh = + ChildFrameAt(devtools_extension_a_panel_rfh, 0); + + EXPECT_TRUE(main_devtools_rfh->GetLastCommittedURL().SchemeIs( + content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_a_extension->GetResourceURL("/panel_devtools_page.html"), + devtools_extension_a_devtools_rfh->GetLastCommittedURL()); + EXPECT_EQ(devtools_b_extension->GetResourceURL("/simple_devtools_page.html"), + devtools_extension_b_devtools_rfh->GetLastCommittedURL()); + EXPECT_EQ(devtools_a_extension->GetResourceURL("/panel.html"), + devtools_extension_a_panel_rfh->GetLastCommittedURL()); + EXPECT_EQ(extension_b_page_url, + devtools_extension_b_frame_rfh->GetLastCommittedURL()); + + // All frames should be in the devtools process. + content::SiteInstance* devtools_instance = + main_devtools_rfh->GetSiteInstance(); + EXPECT_TRUE( + devtools_instance->GetSiteURL().SchemeIs(content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_instance, + devtools_extension_a_devtools_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, + devtools_extension_b_devtools_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, + devtools_extension_a_panel_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, + devtools_extension_b_frame_rfh->GetSiteInstance()); +} + +// Tests that a devtools extension can still have subframes to itself in a +// "devtools page" and that they will be rendered within the devtools process as +// well, not in the extension process. http://crbug.com/570483 +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, DevToolsExtensionInItself) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // Install the dynamically-generated extension. + const Extension* extension = + LoadExtensionForTest("Devtools Extension", "panel_devtools_page.html", + "/simple_test_page.html"); + ASSERT_TRUE(extension); + + OpenDevToolsWindow(kDebuggerTestPage, false); + + // Wait for the extension's panel to finish loading -- it'll output 'PASS' + // when it's installed. waitForTestResultsInConsole waits until that 'PASS'. + RunTestFunction(window_, "waitForTestResultsInConsole"); + + // Now that we know the panel is loaded, switch to it. + GURL extension_test_url = extension->GetResourceURL("/simple_test_page.html"); + content::TestNavigationManager test_page_manager(main_web_contents(), + extension_test_url); + SwitchToExtensionPanel(window_, extension, "iframe_panel"); + test_page_manager.WaitForNavigationFinished(); + + std::vector<RenderFrameHost*> rfhs = main_web_contents()->GetAllFrames(); + EXPECT_EQ(4U, rfhs.size()); + + RenderFrameHost* main_devtools_rfh = main_web_contents()->GetMainFrame(); + RenderFrameHost* devtools_extension_devtools_page_rfh = + ChildFrameAt(main_devtools_rfh, 0); + RenderFrameHost* devtools_extension_panel_rfh = + ChildFrameAt(main_devtools_rfh, 1); + RenderFrameHost* devtools_extension_panel_frame_rfh = + ChildFrameAt(devtools_extension_panel_rfh, 0); + + // All frames should be in the devtools process, including + // simple_test_page.html + EXPECT_TRUE(main_devtools_rfh->GetLastCommittedURL().SchemeIs( + content::kChromeDevToolsScheme)); + EXPECT_EQ(extension->GetResourceURL("/panel_devtools_page.html"), + devtools_extension_devtools_page_rfh->GetLastCommittedURL()); + EXPECT_EQ(extension->GetResourceURL("/panel.html"), + devtools_extension_panel_rfh->GetLastCommittedURL()); + EXPECT_EQ(extension_test_url, + devtools_extension_panel_frame_rfh->GetLastCommittedURL()); + + content::SiteInstance* devtools_instance = + main_devtools_rfh->GetSiteInstance(); + EXPECT_TRUE( + devtools_instance->GetSiteURL().SchemeIs(content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_instance, + devtools_extension_devtools_page_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, devtools_extension_panel_rfh->GetSiteInstance()); + EXPECT_EQ(devtools_instance, + devtools_extension_panel_frame_rfh->GetSiteInstance()); +} + +// Tests that a devtools (not a devtools extension) Iframe can be injected into +// devtools. http://crbug.com/570483 +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, DevtoolsInDevTools) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL devtools_url = GURL(chrome::kChromeUIDevToolsURL); + + OpenDevToolsWindow(kDebuggerTestPage, false); + + std::string javascript = + "var devtoolsFrame = document.createElement('iframe');" + "document.body.appendChild(devtoolsFrame);" + "devtoolsFrame.src = '" + + devtools_url.spec() + "';"; + + RenderFrameHost* main_devtools_rfh = main_web_contents()->GetMainFrame(); + + content::TestNavigationManager manager(main_web_contents(), devtools_url); + ASSERT_TRUE(content::ExecuteScript(main_devtools_rfh, javascript)); + manager.WaitForNavigationFinished(); + + std::vector<RenderFrameHost*> rfhs = main_web_contents()->GetAllFrames(); + EXPECT_EQ(2U, rfhs.size()); + RenderFrameHost* devtools_iframe_rfh = ChildFrameAt(main_devtools_rfh, 0); + EXPECT_TRUE(main_devtools_rfh->GetLastCommittedURL().SchemeIs( + content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_url, devtools_iframe_rfh->GetLastCommittedURL()); + content::SiteInstance* devtools_instance = + main_devtools_rfh->GetSiteInstance(); + EXPECT_TRUE( + devtools_instance->GetSiteURL().SchemeIs(content::kChromeDevToolsScheme)); + EXPECT_EQ(devtools_instance, devtools_iframe_rfh->GetSiteInstance()); + + std::string message; + EXPECT_TRUE(ExecuteScriptAndExtractString( + devtools_iframe_rfh, "domAutomationController.send(document.origin)", + &message)); + EXPECT_EQ(devtools_url.GetOrigin().spec(), message + "/"); +} + +// Some web features, when used from an extension, are subject to browser-side +// security policy enforcement. Make sure they work properly from inside a +// devtools extension. +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + DevToolsExtensionSecurityPolicyGrants) { + ASSERT_TRUE(embedded_test_server()->Start()); + + std::unique_ptr<extensions::TestExtensionDir> dir( + new extensions::TestExtensionDir()); + + extensions::DictionaryBuilder manifest; + dir->WriteManifest(extensions::DictionaryBuilder() + .Set("name", "Devtools Panel") + .Set("version", "1") + // Whitelist the script we stuff into the 'blob:' URL: + .Set("content_security_policy", + "script-src 'self' " + "'sha256-95xJWHeV+" + "1zjAKQufDVW0misgmR4gCjgpipP2LJ5iis='; " + "object-src 'none'") + .Set("manifest_version", 2) + .Set("devtools_page", "devtools.html") + .ToJSON()); + + dir->WriteFile( + FILE_PATH_LITERAL("devtools.html"), + "<html><head><script src='devtools.js'></script></head></html>"); + + dir->WriteFile( + FILE_PATH_LITERAL("devtools.js"), + "chrome.devtools.panels.create('the_panel_name',\n" + " null,\n" + " 'panel.html',\n" + " function(panel) {\n" + " chrome.devtools.inspectedWindow.eval('console.log(\"PASS\")');\n" + " }\n" + ");\n"); + + dir->WriteFile(FILE_PATH_LITERAL("panel.html"), + "<html><body>A panel." + "<script src='blob_xhr.js'></script>" + "<script src='blob_iframe.js'></script>" + "</body></html>"); + // Creating blobs from chrome-extension:// origins is only permitted if the + // process has been granted permission to commit 'chrome-extension' schemes. + dir->WriteFile( + FILE_PATH_LITERAL("blob_xhr.js"), + "var blob_url = URL.createObjectURL(new Blob(['xhr blob contents']));\n" + "var xhr = new XMLHttpRequest();\n" + "xhr.open('GET', blob_url, true);\n" + "xhr.onload = function (e) {\n" + " domAutomationController.setAutomationId(0);\n" + " domAutomationController.send(xhr.response);\n" + "};\n" + "xhr.send(null);\n"); + dir->WriteFile( + FILE_PATH_LITERAL("blob_iframe.js"), + "var payload = `" + "<html><body>iframe blob contents" + "<script>" + " domAutomationController.setAutomationId(0);" + " domAutomationController.send(document.body.innerText);\n" + "</script></body></html>" + "`;" + "document.body.appendChild(document.createElement('iframe')).src =" + " URL.createObjectURL(new Blob([payload], {type: 'text/html'}));"); + // Install the extension. + const Extension* extension = LoadExtensionFromPath(dir->UnpackedPath()); + ASSERT_TRUE(extension); + + // Open a devtools window. + OpenDevToolsWindow(kDebuggerTestPage, false); + + // Wait for the panel extension to finish loading -- it'll output 'PASS' + // when it's installed. waitForTestResultsInConsole waits until that 'PASS'. + RunTestFunction(window_, "waitForTestResultsInConsole"); + + // Now that we know the panel is loaded, switch to it. We'll wait until we + // see a 'DONE' message sent from popup_iframe.html, indicating that it + // loaded successfully. + content::DOMMessageQueue message_queue; + SwitchToExtensionPanel(window_, extension, "the_panel_name"); + std::string message; + while (true) { + ASSERT_TRUE(message_queue.WaitForMessage(&message)); + if (message == "\"xhr blob contents\"") + break; + } + while (true) { + ASSERT_TRUE(message_queue.WaitForMessage(&message)); + if (message == "\"iframe blob contents\"") + break; + } +} + +// Disabled on Windows due to flakiness. http://crbug.com/183649 +#if defined(OS_WIN) +#define MAYBE_TestDevToolsExtensionMessaging DISABLED_TestDevToolsExtensionMessaging +#else +#define MAYBE_TestDevToolsExtensionMessaging TestDevToolsExtensionMessaging +#endif + +// Tests that chrome.devtools extension can communicate with background page +// using extension messaging. +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + MAYBE_TestDevToolsExtensionMessaging) { + LoadExtension("devtools_messaging"); + RunTest("waitForTestResultsInConsole", std::string()); +} + +// Tests that chrome.experimental.devtools extension is correctly exposed +// when the extension has experimental permission. +IN_PROC_BROWSER_TEST_F(DevToolsExperimentalExtensionTest, + TestDevToolsExperimentalExtensionAPI) { + LoadExtension("devtools_experimental"); + RunTest("waitForTestResultsInConsole", std::string()); +} + +// Tests that a content script is in the scripts list. +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + TestContentScriptIsPresent) { + LoadExtension("simple_content_script"); + RunTest("testContentScriptIsPresent", kPageWithContentScript); +} + +// Tests that console selector shows correct context names. +IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, + TestConsoleContextNames) { + LoadExtension("simple_content_script"); + RunTest("testConsoleContextNames", kPageWithContentScript); +} + +// Tests that scripts are not duplicated after Scripts Panel switch. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, + TestNoScriptDuplicatesOnPanelSwitch) { + RunTest("testNoScriptDuplicatesOnPanelSwitch", kDebuggerTestPage); +} + +// Tests that debugger works correctly if pause event occurs when DevTools +// frontend is being loaded. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, + TestPauseWhenLoadingDevTools) { + RunTest("testPauseWhenLoadingDevTools", kPauseWhenLoadingDevTools); +} + +// Tests that pressing 'Pause' will pause script execution if the script +// is already running. +#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY) +// Timing out on linux ARM bot: https://crbug/238453 +#define MAYBE_TestPauseWhenScriptIsRunning DISABLED_TestPauseWhenScriptIsRunning +#else +#define MAYBE_TestPauseWhenScriptIsRunning TestPauseWhenScriptIsRunning +#endif +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, + MAYBE_TestPauseWhenScriptIsRunning) { + RunTest("testPauseWhenScriptIsRunning", kPauseWhenScriptIsRunning); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestTempFileIncognito) { + GURL url("about:blank"); + ui_test_utils::BrowserAddedObserver window_observer; + chrome::NewEmptyWindow(browser()->profile()->GetOffTheRecordProfile()); + Browser* new_browser = window_observer.WaitForSingleNewBrowser(); + ui_test_utils::NavigateToURL(new_browser, url); + DevToolsWindow* window = DevToolsWindowTesting::OpenDevToolsWindowSync( + new_browser->tab_strip_model()->GetWebContentsAt(0), false); + RunTestFunction(window, "testTempFile"); + DevToolsWindowTesting::CloseDevToolsWindowSync(window); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestTempFile) { + RunTest("testTempFile", kDebuggerTestPage); +} + +// Tests network timing. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkTiming) { + RunTest("testNetworkTiming", kSlowTestPage); +} + +// Tests network size. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkSize) { + RunTest("testNetworkSize", kChunkedTestPage); +} + +// Tests raw headers text. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkSyncSize) { + RunTest("testNetworkSyncSize", kChunkedTestPage); +} + +// Tests raw headers text. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkRawHeadersText) { + RunTest("testNetworkRawHeadersText", kChunkedTestPage); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkPushTime) { + OpenDevToolsWindow(kPushTestPage, false); + GURL push_url = spawned_test_server()->GetURL(kPushTestResource); + base::FilePath file_path = + spawned_test_server()->document_root().AppendASCII(kPushTestResource); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&TestInterceptor::Register, push_url, file_path)); + + DispatchOnTestSuite(window_, "testPushTimes", push_url.spec().c_str()); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&TestInterceptor::Unregister, push_url)); + + CloseDevToolsWindow(); +} + +// Tests that console messages are not duplicated on navigation back. +#if defined(OS_WIN) +// Flaking on windows swarm try runs: crbug.com/409285. +#define MAYBE_TestConsoleOnNavigateBack DISABLED_TestConsoleOnNavigateBack +#else +#define MAYBE_TestConsoleOnNavigateBack TestConsoleOnNavigateBack +#endif +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, MAYBE_TestConsoleOnNavigateBack) { + RunTest("testConsoleOnNavigateBack", kNavigateBackTestPage); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestDeviceEmulation) { + RunTest("testDeviceMetricsOverrides", "about:blank"); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestDispatchKeyEventDoesNotCrash) { + RunTest("testDispatchKeyEventDoesNotCrash", "about:blank"); +} + +// Tests that settings are stored in profile correctly. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestSettings) { + OpenDevToolsWindow("about:blank", true); + RunTestFunction(window_, "testSettings"); + CloseDevToolsWindow(); +} + +// Tests that external navigation from inspector page is always handled by +// DevToolsWindow and results in inspected page navigation. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestDevToolsExternalNavigation) { + OpenDevToolsWindow(kDebuggerTestPage, true); + GURL url = spawned_test_server()->GetURL(kNavigateBackTestPage); + ui_test_utils::UrlLoadObserver observer(url, + content::NotificationService::AllSources()); + ASSERT_TRUE(content::ExecuteScript( + main_web_contents(), + std::string("window.location = \"") + url.spec() + "\"")); + observer.Wait(); + + ASSERT_TRUE(main_web_contents()->GetURL(). + SchemeIs(content::kChromeDevToolsScheme)); + ASSERT_EQ(url, GetInspectedTab()->GetURL()); + CloseDevToolsWindow(); +} + +// Tests that toolbox window is loaded when DevTools window is undocked. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestToolboxLoadedUndocked) { + OpenDevToolsWindow(kDebuggerTestPage, false); + ASSERT_TRUE(toolbox_web_contents()); + DevToolsWindow* on_self = + DevToolsWindowTesting::OpenDevToolsWindowSync(main_web_contents(), false); + ASSERT_FALSE(DevToolsWindowTesting::Get(on_self)->toolbox_web_contents()); + DevToolsWindowTesting::CloseDevToolsWindowSync(on_self); + CloseDevToolsWindow(); +} + +// Tests that toolbox window is not loaded when DevTools window is docked. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestToolboxNotLoadedDocked) { + OpenDevToolsWindow(kDebuggerTestPage, true); + ASSERT_FALSE(toolbox_web_contents()); + DevToolsWindow* on_self = + DevToolsWindowTesting::OpenDevToolsWindowSync(main_web_contents(), false); + ASSERT_FALSE(DevToolsWindowTesting::Get(on_self)->toolbox_web_contents()); + DevToolsWindowTesting::CloseDevToolsWindowSync(on_self); + CloseDevToolsWindow(); +} + +// Tests that inspector will reattach to inspected page when it is reloaded +// after a crash. See http://crbug.com/101952 +// Disabled. it doesn't check anything right now: http://crbug.com/461790 +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, DISABLED_TestReattachAfterCrash) { + RunTest("testReattachAfterCrash", std::string()); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestPageWithNoJavaScript) { + OpenDevToolsWindow("about:blank", false); + std::string result; + ASSERT_TRUE( + content::ExecuteScriptAndExtractString( + main_web_contents()->GetRenderViewHost(), + "window.domAutomationController.send(" + " '' + (window.uiTests && (typeof uiTests.dispatchOnTestSuite)));", + &result)); + ASSERT_EQ("function", result) << "DevTools front-end is broken."; + CloseDevToolsWindow(); +} + +class DevToolsAutoOpenerTest : public DevToolsSanityTest { + public: + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch(switches::kAutoOpenDevToolsForTabs); + observer_.reset(new DevToolsWindowCreationObserver()); + } + protected: + std::unique_ptr<DevToolsWindowCreationObserver> observer_; +}; + +IN_PROC_BROWSER_TEST_F(DevToolsAutoOpenerTest, TestAutoOpenForTabs) { + { + DevToolsWindowCreationObserver observer; + AddTabAtIndexToBrowser(browser(), 0, GURL("about:blank"), + ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false); + observer.WaitForLoad(); + } + Browser* new_browser = nullptr; + { + DevToolsWindowCreationObserver observer; + new_browser = CreateBrowser(browser()->profile()); + observer.WaitForLoad(); + } + { + DevToolsWindowCreationObserver observer; + AddTabAtIndexToBrowser(new_browser, 0, GURL("about:blank"), + ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false); + observer.WaitForLoad(); + } + observer_->CloseAllSync(); +} + +class DevToolsReattachAfterCrashTest : public DevToolsSanityTest { + protected: + void RunTestWithPanel(const char* panel_name) { + OpenDevToolsWindow("about:blank", false); + SwitchToPanel(window_, panel_name); + ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)); + + content::RenderProcessHostWatcher crash_observer( + GetInspectedTab(), + content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); + ui_test_utils::NavigateToURL(browser(), GURL(content::kChromeUICrashURL)); + crash_observer.Wait(); + content::TestNavigationObserver navigation_observer(GetInspectedTab(), 1); + chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB); + navigation_observer.Wait(); + } +}; + +IN_PROC_BROWSER_TEST_F(DevToolsReattachAfterCrashTest, + TestReattachAfterCrashOnTimeline) { + RunTestWithPanel("timeline"); +} + +IN_PROC_BROWSER_TEST_F(DevToolsReattachAfterCrashTest, + TestReattachAfterCrashOnNetwork) { + RunTestWithPanel("network"); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, AutoAttachToWindowOpen) { + OpenDevToolsWindow(kWindowOpenTestPage, false); + DispatchOnTestSuite(window_, "enableAutoAttachToCreatedPages"); + DevToolsWindowCreationObserver observer; + ASSERT_TRUE(content::ExecuteScript( + GetInspectedTab(), "window.open('window_open.html', '_blank');")); + observer.WaitForLoad(); + DispatchOnTestSuite(observer.devtools_window(), "waitForDebuggerPaused"); + DevToolsWindowTesting::CloseDevToolsWindowSync(observer.devtools_window()); + CloseDevToolsWindow(); +} + +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, SecondTabAfterDevTools) { + OpenDevToolsWindow(kDebuggerTestPage, true); + + ui_test_utils::NavigateToURLWithDisposition( + browser(), spawned_test_server()->GetURL(kDebuggerTestPage), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB | + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + WebContents* second = browser()->tab_strip_model()->GetActiveWebContents(); + + scoped_refptr<content::DevToolsAgentHost> agent( + content::DevToolsAgentHost::GetOrCreateFor(second)); + EXPECT_EQ("page", agent->GetType()); + + CloseDevToolsWindow(); +} + +IN_PROC_BROWSER_TEST_F(WorkerDevToolsSanityTest, InspectSharedWorker) { + RunTest("testSharedWorker", kSharedWorkerTestPage, kSharedWorkerTestWorker); +} + +// Flaky on multiple platforms. See http://crbug.com/432444 +IN_PROC_BROWSER_TEST_F(WorkerDevToolsSanityTest, + DISABLED_PauseInSharedWorkerInitialization) { + ASSERT_TRUE(spawned_test_server()->Start()); + GURL url = spawned_test_server()->GetURL(kReloadSharedWorkerTestPage); + ui_test_utils::NavigateToURL(browser(), url); + + scoped_refptr<WorkerData> worker_data = + WaitForFirstSharedWorker(kReloadSharedWorkerTestWorker); + OpenDevToolsWindowForSharedWorker(worker_data.get()); + + // We should make sure that the worker inspector has loaded before + // terminating worker. + RunTestFunction(window_, "testPauseInSharedWorkerInitialization1"); + + TerminateWorker(worker_data); + + // Reload page to restart the worker. + ui_test_utils::NavigateToURL(browser(), url); + + // Wait until worker script is paused on the debugger statement. + RunTestFunction(window_, "testPauseInSharedWorkerInitialization2"); + CloseDevToolsWindow(); +} + +class DevToolsAgentHostTest : public InProcessBrowserTest {}; + +// Tests DevToolsAgentHost retention by its target. +IN_PROC_BROWSER_TEST_F(DevToolsAgentHostTest, TestAgentHostReleased) { + ui_test_utils::NavigateToURL(browser(), GURL("about:blank")); + WebContents* web_contents = browser()->tab_strip_model()->GetWebContentsAt(0); + DevToolsAgentHost* agent_raw = + DevToolsAgentHost::GetOrCreateFor(web_contents).get(); + const std::string agent_id = agent_raw->GetId(); + ASSERT_EQ(agent_raw, DevToolsAgentHost::GetForId(agent_id).get()) + << "DevToolsAgentHost cannot be found by id"; + browser()->tab_strip_model()-> + CloseWebContentsAt(0, TabStripModel::CLOSE_NONE); + ASSERT_FALSE(DevToolsAgentHost::GetForId(agent_id).get()) + << "DevToolsAgentHost is not released when the tab is closed"; +} + +class RemoteDebuggingTest : public ExtensionApiTest { + void SetUpCommandLine(base::CommandLine* command_line) override { + ExtensionApiTest::SetUpCommandLine(command_line); + command_line->AppendSwitchASCII(switches::kRemoteDebuggingPort, "9222"); + + // Override the extension root path. + PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); + test_data_dir_ = test_data_dir_.AppendASCII("devtools"); + } +}; + +// Fails on CrOS. crbug.com/431399 +#if defined(OS_CHROMEOS) +#define MAYBE_RemoteDebugger DISABLED_RemoteDebugger +#else +#define MAYBE_RemoteDebugger RemoteDebugger +#endif +IN_PROC_BROWSER_TEST_F(RemoteDebuggingTest, MAYBE_RemoteDebugger) { + ASSERT_TRUE(RunExtensionTest("target_list")) << message_; +} + +using DevToolsPolicyTest = InProcessBrowserTest; +IN_PROC_BROWSER_TEST_F(DevToolsPolicyTest, PolicyTrue) { + browser()->profile()->GetPrefs()->SetBoolean(prefs::kDevToolsDisabled, true); + ui_test_utils::NavigateToURL(browser(), GURL("about:blank")); + content::WebContents* web_contents = + browser()->tab_strip_model()->GetWebContentsAt(0); + scoped_refptr<content::DevToolsAgentHost> agent( + content::DevToolsAgentHost::GetOrCreateFor(web_contents)); + DevToolsWindow::OpenDevToolsWindow(web_contents); + DevToolsWindow* window = DevToolsWindow::FindDevToolsWindow(agent.get()); + ASSERT_FALSE(window); +} + +class DevToolsPixelOutputTests : public DevToolsSanityTest { + public: + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch(switches::kEnablePixelOutputInTests); + command_line->AppendSwitch(switches::kUseGpuInTests); + } +}; + +// This test enables switches::kUseGpuInTests which causes false positives +// with MemorySanitizer. This is also flakey on many configurations. +#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) || \ + defined(OS_WIN)|| (defined(OS_CHROMEOS) && defined(OFFICIAL_BUILD)) +#define MAYBE_TestScreenshotRecording DISABLED_TestScreenshotRecording +#else +#define MAYBE_TestScreenshotRecording TestScreenshotRecording +#endif +IN_PROC_BROWSER_TEST_F(DevToolsPixelOutputTests, + MAYBE_TestScreenshotRecording) { + RunTest("testScreenshotRecording", std::string()); +} + +// This test enables switches::kUseGpuInTests which causes false positives +// with MemorySanitizer. +// Flaky on multiple platforms https://crbug.com/624215 +IN_PROC_BROWSER_TEST_F(DevToolsPixelOutputTests, + DISABLED_TestLatencyInfoInstrumentation) { + WebContents* web_contents = GetInspectedTab(); + OpenDevToolsWindow(kLatencyInfoTestPage, false); + DispatchAndWait("startTimeline"); + + for (int i = 0; i < 3; ++i) { + SimulateMouseEvent(web_contents, blink::WebInputEvent::kMouseMove, + gfx::Point(30, 60)); + DispatchInPageAndWait("waitForEvent", "mousemove"); + } + + SimulateMouseClickAt(web_contents, 0, + blink::WebPointerProperties::Button::kLeft, + gfx::Point(30, 60)); + DispatchInPageAndWait("waitForEvent", "click"); + + SimulateMouseWheelEvent(web_contents, gfx::Point(300, 100), + gfx::Vector2d(0, 120)); + DispatchInPageAndWait("waitForEvent", "wheel"); + + SimulateTapAt(web_contents, gfx::Point(30, 60)); + DispatchInPageAndWait("waitForEvent", "gesturetap"); + + DispatchAndWait("stopTimeline"); + RunTestMethod("checkInputEventsPresent", "MouseMove", "MouseDown", + "MouseWheel", "GestureTap"); + + CloseDevToolsWindow(); +} + +class DevToolsNetInfoTest : public DevToolsSanityTest { + protected: + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch(switches::kEnableNetworkInformation); + command_line->AppendSwitch( + switches::kEnableExperimentalWebPlatformFeatures); + } +}; + +IN_PROC_BROWSER_TEST_F(DevToolsNetInfoTest, EmulateNetworkConditions) { + RunTest("testEmulateNetworkConditions", kEmulateNetworkConditionsPage); +} + +class StaticURLDataSource : public content::URLDataSource { + public: + StaticURLDataSource(const std::string& source, const std::string& content) + : source_(source), content_(content) {} + + std::string GetSource() const override { return source_; } + void StartDataRequest( + const std::string& path, + const content::ResourceRequestInfo::WebContentsGetter& wc_getter, + const GotDataCallback& callback) override { + std::string data(content_); + callback.Run(base::RefCountedString::TakeString(&data)); + } + std::string GetMimeType(const std::string& path) const override { + return "text/html"; + } + bool ShouldAddContentSecurityPolicy() const override { return false; } + + private: + std::string source_; + std::string content_; + DISALLOW_COPY_AND_ASSIGN(StaticURLDataSource); +}; + +class MockWebUIProvider + : public TestChromeWebUIControllerFactory::WebUIProvider { + public: + MockWebUIProvider(const std::string& source, const std::string& content) + : source_(source), content_(content) {} + + content::WebUIController* NewWebUI(content::WebUI* web_ui, + const GURL& url) override { + content::URLDataSource::Add(Profile::FromWebUI(web_ui), + new StaticURLDataSource(source_, content_)); + return new content::WebUIController(web_ui); + } + + private: + std::string source_; + std::string content_; + DISALLOW_COPY_AND_ASSIGN(MockWebUIProvider); +}; + +// This tests checks that window is correctly initialized when DevTools is +// opened while navigation through history with forward and back actions. +// (crbug.com/627407) +// Flaky on Windows and ChromeOS. http://crbug.com/628174#c4 +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, + DISABLED_TestWindowInitializedOnNavigateBack) { + TestChromeWebUIControllerFactory test_factory; + MockWebUIProvider mock_provider("dummyurl", + "<script>\n" + " window.abc = 239;\n" + " console.log(abc);\n" + "</script>"); + test_factory.AddFactoryOverride(GURL("chrome://dummyurl").host(), + &mock_provider); + content::WebUIControllerFactory::RegisterFactory(&test_factory); + + ui_test_utils::NavigateToURL(browser(), GURL("chrome://dummyurl")); + DevToolsWindow* window = + DevToolsWindowTesting::OpenDevToolsWindowSync(GetInspectedTab(), true); + chrome::DuplicateTab(browser()); + chrome::SelectPreviousTab(browser()); + ui_test_utils::NavigateToURL(browser(), GURL("about:blank")); + chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB); + RunTestFunction(window, "testWindowInitializedOnNavigateBack"); + + DevToolsWindowTesting::CloseDevToolsWindowSync(window); + content::WebUIControllerFactory::UnregisterFactoryForTesting(&test_factory); +} + +// Tests scripts panel showing. +IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestDevToolsSharedWorker) { + RunTest("testDevToolsSharedWorker", url::kAboutBlankURL); +} diff --git a/chromium/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc b/chromium/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc new file mode 100644 index 00000000000..f8745e28fd9 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc @@ -0,0 +1,211 @@ +// Copyright 2017 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 "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "build/build_config.h" +#include "chrome/browser/devtools/chrome_devtools_manager_delegate.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" +#include "chrome/test/base/in_process_browser_test.h" + +#if defined(OS_MACOSX) +#include "ui/base/test/scoped_fake_nswindow_fullscreen.h" +#endif + +// Encapsulates waiting for the browser window to change state. This is +// needed for example on Chrome desktop linux, where window state change is done +// asynchronously as an event received from a different process. +class CheckWaiter { + public: + explicit CheckWaiter(base::Callback<bool()> callback, bool expected) + : callback_(callback), + expected_(expected), + timeout_(base::Time::NowFromSystemTime() + + base::TimeDelta::FromSeconds(1)) {} + ~CheckWaiter() = default; + + // Blocks until the browser window becomes maximized. + void Wait() { + if (Check()) + return; + + base::RunLoop run_loop; + quit_ = run_loop.QuitClosure(); + run_loop.Run(); + } + + private: + bool Check() { + if (callback_.Run() != expected_ && + base::Time::NowFromSystemTime() < timeout_) { + base::MessageLoop::current()->task_runner()->PostTask( + FROM_HERE, base::BindOnce(base::IgnoreResult(&CheckWaiter::Check), + base::Unretained(this))); + return false; + } + + // Quit the run_loop to end the wait. + if (!quit_.is_null()) + base::ResetAndReturn(&quit_).Run(); + return true; + } + + base::Callback<bool()> callback_; + bool expected_; + base::Time timeout_; + // The waiter's RunLoop quit closure. + base::Closure quit_; + + DISALLOW_COPY_AND_ASSIGN(CheckWaiter); +}; + +class DevToolsManagerDelegateTest : public InProcessBrowserTest { + public: + std::unique_ptr<base::DictionaryValue> SendCommand(std::string state) { + auto params = base::MakeUnique<base::DictionaryValue>(); + auto bounds_object = base::MakeUnique<base::DictionaryValue>(); + bounds_object->SetString("windowState", state); + params->Set("bounds", std::move(bounds_object)); + params->SetInteger("windowId", browser()->session_id().id()); + return ChromeDevToolsManagerDelegate::SetWindowBounds(0, params.get()); + } + + std::unique_ptr<base::DictionaryValue> UpdateBounds() { + auto params = base::MakeUnique<base::DictionaryValue>(); + auto bounds_object = base::MakeUnique<base::DictionaryValue>(); + bounds_object->SetString("windowState", "normal"); + bounds_object->SetInteger("left", 200); + bounds_object->SetInteger("height", 400); + params->Set("bounds", std::move(bounds_object)); + params->SetInteger("windowId", browser()->session_id().id()); + return ChromeDevToolsManagerDelegate::SetWindowBounds(0, params.get()); + } + + void CheckIsMaximized(bool maximized) { + CheckWaiter(base::Bind(&BrowserWindow::IsMaximized, + base::Unretained(browser()->window())), + maximized) + .Wait(); + EXPECT_EQ(maximized, browser()->window()->IsMaximized()); + } + + void CheckIsMinimized(bool minimized) { + CheckWaiter(base::Bind(&BrowserWindow::IsMinimized, + base::Unretained(browser()->window())), + minimized) + .Wait(); + EXPECT_EQ(minimized, browser()->window()->IsMinimized()); + } + + void CheckIsFullscreen(bool fullscreen) { + CheckWaiter(base::Bind(&BrowserWindow::IsFullscreen, + base::Unretained(browser()->window())), + fullscreen) + .Wait(); + EXPECT_EQ(fullscreen, browser()->window()->IsFullscreen()); + } + + bool IsWindowBoundsEqual(gfx::Rect expected) { + return browser()->window()->GetBounds() == expected; + } + + void CheckWindowBounds(gfx::Rect expected) { + CheckWaiter(base::Bind(&DevToolsManagerDelegateTest::IsWindowBoundsEqual, + base::Unretained(this), expected), + true) + .Wait(); + EXPECT_EQ(expected, browser()->window()->GetBounds()); + } +}; + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, NormalWindowChangeBounds) { + browser()->window()->SetBounds(gfx::Rect(100, 100, 500, 600)); + CheckWindowBounds(gfx::Rect(100, 100, 500, 600)); + UpdateBounds(); + CheckWindowBounds(gfx::Rect(200, 100, 500, 400)); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, NormalToMaximizedWindow) { + CheckIsMaximized(false); + SendCommand("maximized"); + CheckIsMaximized(true); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, NormalToMinimizedWindow) { + CheckIsMinimized(false); + SendCommand("minimized"); + CheckIsMinimized(true); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, NormalToFullscreenWindow) { +#if defined(OS_MACOSX) + ui::test::ScopedFakeNSWindowFullscreen faker; +#endif + CheckIsFullscreen(false); + SendCommand("fullscreen"); +#if defined(OS_MACOSX) + faker.FinishTransition(); +#endif + CheckIsFullscreen(true); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, + MaximizedToMinimizedWindow) { + browser()->window()->Maximize(); + CheckIsMaximized(true); + + CheckIsMinimized(false); + SendCommand("minimized"); + CheckIsMinimized(true); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, + MaximizedToFullscreenWindow) { + browser()->window()->Maximize(); + CheckIsMaximized(true); + +#if defined(OS_MACOSX) + ui::test::ScopedFakeNSWindowFullscreen faker; +#endif + CheckIsFullscreen(false); + SendCommand("fullscreen"); +#if defined(OS_MACOSX) + faker.FinishTransition(); +#endif + CheckIsFullscreen(true); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, ShowMinimizedWindow) { + browser()->window()->Minimize(); + CheckIsMinimized(true); + SendCommand("normal"); + CheckIsMinimized(false); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, RestoreMaximizedWindow) { + browser()->window()->Maximize(); + CheckIsMaximized(true); + SendCommand("normal"); + CheckIsMaximized(false); +} + +IN_PROC_BROWSER_TEST_F(DevToolsManagerDelegateTest, ExitFullscreenWindow) { +#if defined(OS_MACOSX) + ui::test::ScopedFakeNSWindowFullscreen faker; +#endif + browser()->window()->GetExclusiveAccessContext()->EnterFullscreen( + GURL(), EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE); +#if defined(OS_MACOSX) + faker.FinishTransition(); +#endif + CheckIsFullscreen(true); + SendCommand("normal"); +#if defined(OS_MACOSX) + faker.FinishTransition(); +#endif + CheckIsFullscreen(false); +} diff --git a/chromium/chrome/browser/devtools/devtools_targets_ui.cc b/chromium/chrome/browser/devtools/devtools_targets_ui.cc new file mode 100644 index 00000000000..ccd72ed9de3 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_targets_ui.cc @@ -0,0 +1,490 @@ +// Copyright 2013 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/devtools_targets_ui.h" + +#include <memory> +#include <utility> + +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/values.h" +#include "base/version.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" +#include "chrome/browser/devtools/serialize_host_descriptions.h" +#include "content/public/browser/browser_child_process_observer.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_data.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/worker_service.h" +#include "content/public/browser/worker_service_observer.h" +#include "content/public/common/process_type.h" +#include "net/base/escape.h" + +using content::BrowserThread; +using content::DevToolsAgentHost; + +namespace { + +const char kTargetSourceField[] = "source"; +const char kTargetSourceLocal[] = "local"; +const char kTargetSourceRemote[] = "remote"; + +const char kTargetIdField[] = "id"; +const char kTargetTypeField[] = "type"; +const char kAttachedField[] = "attached"; +const char kUrlField[] = "url"; +const char kNameField[] = "name"; +const char kFaviconUrlField[] = "faviconUrl"; +const char kDescriptionField[] = "description"; + +const char kGuestList[] = "guests"; + +const char kAdbModelField[] = "adbModel"; +const char kAdbConnectedField[] = "adbConnected"; +const char kAdbSerialField[] = "adbSerial"; +const char kAdbBrowsersList[] = "browsers"; +const char kAdbDeviceIdFormat[] = "device:%s"; + +const char kAdbBrowserNameField[] = "adbBrowserName"; +const char kAdbBrowserUserField[] = "adbBrowserUser"; +const char kAdbBrowserVersionField[] = "adbBrowserVersion"; +const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion"; +const char kAdbPagesList[] = "pages"; + +const char kAdbScreenWidthField[] = "adbScreenWidth"; +const char kAdbScreenHeightField[] = "adbScreenHeight"; + +const char kPortForwardingPorts[] = "ports"; +const char kPortForwardingBrowserId[] = "browserId"; + +// CancelableTimer ------------------------------------------------------------ + +class CancelableTimer { + public: + CancelableTimer(base::Closure callback, base::TimeDelta delay) + : callback_(callback), + weak_factory_(this) { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&CancelableTimer::Fire, weak_factory_.GetWeakPtr()), + delay); + } + + private: + void Fire() { callback_.Run(); } + + base::Closure callback_; + base::WeakPtrFactory<CancelableTimer> weak_factory_; +}; + +// WorkerObserver ------------------------------------------------------------- + +class WorkerObserver + : public content::WorkerServiceObserver, + public base::RefCountedThreadSafe<WorkerObserver> { + public: + WorkerObserver() {} + + void Start(base::Closure callback) { + DCHECK(callback_.is_null()); + DCHECK(!callback.is_null()); + callback_ = callback; + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&WorkerObserver::StartOnIOThread, this)); + } + + void Stop() { + DCHECK(!callback_.is_null()); + callback_ = base::Closure(); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::BindOnce(&WorkerObserver::StopOnIOThread, this)); + } + + private: + friend class base::RefCountedThreadSafe<WorkerObserver>; + ~WorkerObserver() override {} + + // content::WorkerServiceObserver overrides: + void WorkerCreated(const GURL& url, + const base::string16& name, + int process_id, + int route_id) override { + NotifyOnIOThread(); + } + + void WorkerDestroyed(int process_id, int route_id) override { + NotifyOnIOThread(); + } + + void StartOnIOThread() { + content::WorkerService::GetInstance()->AddObserver(this); + } + + void StopOnIOThread() { + content::WorkerService::GetInstance()->RemoveObserver(this); + } + + void NotifyOnIOThread() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::BindOnce(&WorkerObserver::NotifyOnUIThread, this)); + } + + void NotifyOnUIThread() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (callback_.is_null()) + return; + callback_.Run(); + } + + // Accessed on UI thread. + base::Closure callback_; +}; + +// LocalTargetsUIHandler --------------------------------------------- + +class LocalTargetsUIHandler + : public DevToolsTargetsUIHandler, + public content::NotificationObserver { + public: + explicit LocalTargetsUIHandler(const Callback& callback); + ~LocalTargetsUIHandler() override; + + // DevToolsTargetsUIHandler overrides. + void ForceUpdate() override; + +private: + // content::NotificationObserver overrides. + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + + void ScheduleUpdate(); + void UpdateTargets(); + void SendTargets(const DevToolsAgentHost::List& targets); + + content::NotificationRegistrar notification_registrar_; + std::unique_ptr<CancelableTimer> timer_; + scoped_refptr<WorkerObserver> observer_; + base::WeakPtrFactory<LocalTargetsUIHandler> weak_factory_; +}; + +LocalTargetsUIHandler::LocalTargetsUIHandler( + const Callback& callback) + : DevToolsTargetsUIHandler(kTargetSourceLocal, callback), + observer_(new WorkerObserver()), + weak_factory_(this) { + notification_registrar_.Add(this, + content::NOTIFICATION_WEB_CONTENTS_CONNECTED, + content::NotificationService::AllSources()); + notification_registrar_.Add(this, + content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, + content::NotificationService::AllSources()); + notification_registrar_.Add(this, + content::NOTIFICATION_WEB_CONTENTS_DESTROYED, + content::NotificationService::AllSources()); + observer_->Start(base::Bind(&LocalTargetsUIHandler::ScheduleUpdate, + base::Unretained(this))); + UpdateTargets(); +} + +LocalTargetsUIHandler::~LocalTargetsUIHandler() { + notification_registrar_.RemoveAll(); + observer_->Stop(); +} + +void LocalTargetsUIHandler::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + ScheduleUpdate(); +} + +void LocalTargetsUIHandler::ForceUpdate() { + ScheduleUpdate(); +} + +void LocalTargetsUIHandler::ScheduleUpdate() { + const int kUpdateDelay = 100; + timer_.reset( + new CancelableTimer( + base::Bind(&LocalTargetsUIHandler::UpdateTargets, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(kUpdateDelay))); +} + +void LocalTargetsUIHandler::UpdateTargets() { + SendTargets(DevToolsAgentHost::GetOrCreateAll()); +} + +void LocalTargetsUIHandler::SendTargets( + const content::DevToolsAgentHost::List& targets) { + std::vector<HostDescriptionNode> hosts; + hosts.reserve(targets.size()); + + targets_.clear(); + for (const scoped_refptr<DevToolsAgentHost>& host : targets) { + targets_[host->GetId()] = host; + hosts.push_back( + {host->GetId(), host->GetParentId(), *Serialize(host.get())}); + } + + SendSerializedTargets( + SerializeHostDescriptions(std::move(hosts), kGuestList)); +} + +// AdbTargetsUIHandler -------------------------------------------------------- + +class AdbTargetsUIHandler + : public DevToolsTargetsUIHandler, + public DevToolsAndroidBridge::DeviceListListener { + public: + AdbTargetsUIHandler(const Callback& callback, Profile* profile); + ~AdbTargetsUIHandler() override; + + void Open(const std::string& browser_id, const std::string& url) override; + + scoped_refptr<DevToolsAgentHost> GetBrowserAgentHost( + const std::string& browser_id) override; + + private: + // DevToolsAndroidBridge::Listener overrides. + void DeviceListChanged( + const DevToolsAndroidBridge::RemoteDevices& devices) override; + + DevToolsAndroidBridge* GetAndroidBridge(); + + Profile* const profile_; + DevToolsAndroidBridge* const android_bridge_; + + typedef std::map<std::string, + scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> > RemoteBrowsers; + RemoteBrowsers remote_browsers_; +}; + +AdbTargetsUIHandler::AdbTargetsUIHandler(const Callback& callback, + Profile* profile) + : DevToolsTargetsUIHandler(kTargetSourceRemote, callback), + profile_(profile), + android_bridge_( + DevToolsAndroidBridge::Factory::GetForProfile(profile_)) { + if (android_bridge_) + android_bridge_->AddDeviceListListener(this); +} + +AdbTargetsUIHandler::~AdbTargetsUIHandler() { + if (android_bridge_) + android_bridge_->RemoveDeviceListListener(this); +} + +void AdbTargetsUIHandler::Open(const std::string& browser_id, + const std::string& url) { + RemoteBrowsers::iterator it = remote_browsers_.find(browser_id); + if (it != remote_browsers_.end() && android_bridge_) + android_bridge_->OpenRemotePage(it->second, url); +} + +scoped_refptr<DevToolsAgentHost> +AdbTargetsUIHandler::GetBrowserAgentHost( + const std::string& browser_id) { + RemoteBrowsers::iterator it = remote_browsers_.find(browser_id); + if (it == remote_browsers_.end() || !android_bridge_) + return nullptr; + + return android_bridge_->GetBrowserAgentHost(it->second); +} + +void AdbTargetsUIHandler::DeviceListChanged( + const DevToolsAndroidBridge::RemoteDevices& devices) { + remote_browsers_.clear(); + targets_.clear(); + if (!android_bridge_) + return; + + base::ListValue device_list; + for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit = + devices.begin(); dit != devices.end(); ++dit) { + DevToolsAndroidBridge::RemoteDevice* device = dit->get(); + std::unique_ptr<base::DictionaryValue> device_data( + new base::DictionaryValue()); + device_data->SetString(kAdbModelField, device->model()); + device_data->SetString(kAdbSerialField, device->serial()); + device_data->SetBoolean(kAdbConnectedField, device->is_connected()); + std::string device_id = base::StringPrintf( + kAdbDeviceIdFormat, + device->serial().c_str()); + device_data->SetString(kTargetIdField, device_id); + auto browser_list = base::MakeUnique<base::ListValue>(); + + DevToolsAndroidBridge::RemoteBrowsers& browsers = device->browsers(); + for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit = + browsers.begin(); bit != browsers.end(); ++bit) { + DevToolsAndroidBridge::RemoteBrowser* browser = bit->get(); + std::unique_ptr<base::DictionaryValue> browser_data( + new base::DictionaryValue()); + browser_data->SetString(kAdbBrowserNameField, browser->display_name()); + browser_data->SetString(kAdbBrowserUserField, browser->user()); + browser_data->SetString(kAdbBrowserVersionField, browser->version()); + DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed = + browser->GetParsedVersion(); + browser_data->SetInteger( + kAdbBrowserChromeVersionField, + browser->IsChrome() && !parsed.empty() ? parsed[0] : 0); + std::string browser_id = browser->GetId(); + browser_data->SetString(kTargetIdField, browser_id); + browser_data->SetString(kTargetSourceField, source_id()); + + auto page_list = base::MakeUnique<base::ListValue>(); + remote_browsers_[browser_id] = browser; + for (const auto& page : browser->pages()) { + scoped_refptr<DevToolsAgentHost> host = page->CreateTarget(); + std::unique_ptr<base::DictionaryValue> target_data = + Serialize(host.get()); + // Pass the screen size in the target object to make sure that + // the caching logic does not prevent the target item from updating + // when the screen size changes. + gfx::Size screen_size = device->screen_size(); + target_data->SetInteger(kAdbScreenWidthField, screen_size.width()); + target_data->SetInteger(kAdbScreenHeightField, screen_size.height()); + targets_[host->GetId()] = host; + page_list->Append(std::move(target_data)); + } + browser_data->Set(kAdbPagesList, std::move(page_list)); + browser_list->Append(std::move(browser_data)); + } + + device_data->Set(kAdbBrowsersList, std::move(browser_list)); + device_list.Append(std::move(device_data)); + } + SendSerializedTargets(device_list); +} + +} // namespace + +// DevToolsTargetsUIHandler --------------------------------------------------- + +DevToolsTargetsUIHandler::DevToolsTargetsUIHandler( + const std::string& source_id, + const Callback& callback) + : source_id_(source_id), + callback_(callback) { +} + +DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() { +} + +// static +std::unique_ptr<DevToolsTargetsUIHandler> +DevToolsTargetsUIHandler::CreateForLocal( + const DevToolsTargetsUIHandler::Callback& callback) { + return std::unique_ptr<DevToolsTargetsUIHandler>( + new LocalTargetsUIHandler(callback)); +} + +// static +std::unique_ptr<DevToolsTargetsUIHandler> +DevToolsTargetsUIHandler::CreateForAdb( + const DevToolsTargetsUIHandler::Callback& callback, + Profile* profile) { + return std::unique_ptr<DevToolsTargetsUIHandler>( + new AdbTargetsUIHandler(callback, profile)); +} + +scoped_refptr<DevToolsAgentHost> DevToolsTargetsUIHandler::GetTarget( + const std::string& target_id) { + TargetMap::iterator it = targets_.find(target_id); + if (it != targets_.end()) + return it->second; + return NULL; +} + +void DevToolsTargetsUIHandler::Open(const std::string& browser_id, + const std::string& url) { +} + +scoped_refptr<DevToolsAgentHost> +DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) { + return NULL; +} + +std::unique_ptr<base::DictionaryValue> DevToolsTargetsUIHandler::Serialize( + DevToolsAgentHost* host) { + auto target_data = base::MakeUnique<base::DictionaryValue>(); + target_data->SetString(kTargetSourceField, source_id_); + target_data->SetString(kTargetIdField, host->GetId()); + target_data->SetString(kTargetTypeField, host->GetType()); + target_data->SetBoolean(kAttachedField, host->IsAttached()); + target_data->SetString(kUrlField, host->GetURL().spec()); + target_data->SetString(kNameField, host->GetTitle()); + target_data->SetString(kFaviconUrlField, host->GetFaviconURL().spec()); + target_data->SetString(kDescriptionField, host->GetDescription()); + return target_data; +} + +void DevToolsTargetsUIHandler::SendSerializedTargets( + const base::ListValue& list) { + callback_.Run(source_id_, list); +} + +void DevToolsTargetsUIHandler::ForceUpdate() { +} + +// PortForwardingStatusSerializer --------------------------------------------- + +PortForwardingStatusSerializer::PortForwardingStatusSerializer( + const Callback& callback, Profile* profile) + : callback_(callback), + profile_(profile) { + DevToolsAndroidBridge* android_bridge = + DevToolsAndroidBridge::Factory::GetForProfile(profile_); + if (android_bridge) + android_bridge->AddPortForwardingListener(this); +} + +PortForwardingStatusSerializer::~PortForwardingStatusSerializer() { + DevToolsAndroidBridge* android_bridge = + DevToolsAndroidBridge::Factory::GetForProfile(profile_); + if (android_bridge) + android_bridge->RemovePortForwardingListener(this); +} + +void PortForwardingStatusSerializer::PortStatusChanged( + const ForwardingStatus& status) { + base::DictionaryValue result; + for (ForwardingStatus::const_iterator sit = status.begin(); + sit != status.end(); ++sit) { + auto port_status_dict = base::MakeUnique<base::DictionaryValue>(); + const PortStatusMap& port_status_map = sit->second; + for (PortStatusMap::const_iterator it = port_status_map.begin(); + it != port_status_map.end(); ++it) { + port_status_dict->SetInteger(base::IntToString(it->first), it->second); + } + + auto device_status_dict = base::MakeUnique<base::DictionaryValue>(); + device_status_dict->Set(kPortForwardingPorts, std::move(port_status_dict)); + device_status_dict->SetString(kPortForwardingBrowserId, + sit->first->GetId()); + + std::string device_id = base::StringPrintf( + kAdbDeviceIdFormat, + sit->first->serial().c_str()); + result.Set(device_id, std::move(device_status_dict)); + } + callback_.Run(result); +} diff --git a/chromium/chrome/browser/devtools/devtools_targets_ui.h b/chromium/chrome/browser/devtools/devtools_targets_ui.h new file mode 100644 index 00000000000..245cc333a3a --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_targets_ui.h @@ -0,0 +1,82 @@ +// Copyright 2013 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_TARGETS_UI_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_TARGETS_UI_H_ + +#include <map> +#include <memory> +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" + +namespace base { +class ListValue; +class DictionaryValue; +} + +class Profile; + +class DevToolsTargetsUIHandler { + public: + typedef base::Callback<void(const std::string&, + const base::ListValue&)> Callback; + + DevToolsTargetsUIHandler(const std::string& source_id, + const Callback& callback); + virtual ~DevToolsTargetsUIHandler(); + + std::string source_id() const { return source_id_; } + + static std::unique_ptr<DevToolsTargetsUIHandler> CreateForLocal( + const Callback& callback); + + static std::unique_ptr<DevToolsTargetsUIHandler> CreateForAdb( + const Callback& callback, + Profile* profile); + + scoped_refptr<content::DevToolsAgentHost> GetTarget( + const std::string& target_id); + + virtual void Open(const std::string& browser_id, const std::string& url); + + virtual scoped_refptr<content::DevToolsAgentHost> GetBrowserAgentHost( + const std::string& browser_id); + + virtual void ForceUpdate(); + + protected: + std::unique_ptr<base::DictionaryValue> Serialize( + content::DevToolsAgentHost* host); + void SendSerializedTargets(const base::ListValue& list); + + using TargetMap = + std::map<std::string, scoped_refptr<content::DevToolsAgentHost>>; + TargetMap targets_; + + private: + const std::string source_id_; + Callback callback_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsTargetsUIHandler); +}; + +class PortForwardingStatusSerializer + : private DevToolsAndroidBridge::PortForwardingListener { + public: + typedef base::Callback<void(const base::Value&)> Callback; + + PortForwardingStatusSerializer(const Callback& callback, Profile* profile); + ~PortForwardingStatusSerializer() override; + + void PortStatusChanged(const ForwardingStatus& status) override; + + private: + Callback callback_; + Profile* profile_; +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_TARGETS_UI_H_ diff --git a/chromium/chrome/browser/devtools/devtools_toggle_action.cc b/chromium/chrome/browser/devtools/devtools_toggle_action.cc new file mode 100644 index 00000000000..1d49216c304 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_toggle_action.cc @@ -0,0 +1,74 @@ +// Copyright 2013 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/devtools_toggle_action.h" + +DevToolsToggleAction::RevealParams::RevealParams(const base::string16& url, + size_t line_number, + size_t column_number) + : url(url), line_number(line_number), column_number(column_number) { +} + +DevToolsToggleAction::RevealParams::~RevealParams() { +} + +DevToolsToggleAction::DevToolsToggleAction(Type type) : type_(type) { +} + +DevToolsToggleAction::DevToolsToggleAction(RevealParams* params) + : type_(kReveal), params_(params) { +} + +DevToolsToggleAction::DevToolsToggleAction(const DevToolsToggleAction& rhs) + : type_(rhs.type_), + params_(rhs.params_.get() ? new RevealParams(*rhs.params_) : NULL) { +} + +void DevToolsToggleAction::operator=(const DevToolsToggleAction& rhs) { + type_ = rhs.type_; + if (rhs.params_.get()) + params_.reset(new RevealParams(*rhs.params_)); +} + +DevToolsToggleAction::~DevToolsToggleAction() { +} + +// static +DevToolsToggleAction DevToolsToggleAction::Show() { + return DevToolsToggleAction(kShow); +} + +// static +DevToolsToggleAction DevToolsToggleAction::ShowConsolePanel() { + return DevToolsToggleAction(kShowConsolePanel); +} + +// static +DevToolsToggleAction DevToolsToggleAction::ShowElementsPanel() { + return DevToolsToggleAction(kShowElementsPanel); +} + +// static +DevToolsToggleAction DevToolsToggleAction::Inspect() { + return DevToolsToggleAction(kInspect); +} + +// static +DevToolsToggleAction DevToolsToggleAction::Toggle() { + return DevToolsToggleAction(kToggle); +} + +// static +DevToolsToggleAction DevToolsToggleAction::Reveal( + const base::string16& url, + size_t line_number, + size_t column_number) { + return DevToolsToggleAction( + new RevealParams(url, line_number, column_number)); +} + +// static +DevToolsToggleAction DevToolsToggleAction::NoOp() { + return DevToolsToggleAction(kNoOp); +} diff --git a/chromium/chrome/browser/devtools/devtools_toggle_action.h b/chromium/chrome/browser/devtools/devtools_toggle_action.h new file mode 100644 index 00000000000..ec5c28b5fde --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_toggle_action.h @@ -0,0 +1,65 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_TOGGLE_ACTION_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_TOGGLE_ACTION_H_ + +#include <stddef.h> + +#include <memory> + +#include "base/strings/string16.h" + +struct DevToolsToggleAction { + public: + enum Type { + kShow, + kShowConsolePanel, + kShowElementsPanel, + kInspect, + kToggle, + kReveal, + kNoOp + }; + + struct RevealParams { + RevealParams(const base::string16& url, + size_t line_number, + size_t column_number); + ~RevealParams(); + + base::string16 url; + size_t line_number; + size_t column_number; + }; + + void operator=(const DevToolsToggleAction& rhs); + DevToolsToggleAction(const DevToolsToggleAction& rhs); + ~DevToolsToggleAction(); + + static DevToolsToggleAction Show(); + static DevToolsToggleAction ShowConsolePanel(); + static DevToolsToggleAction ShowElementsPanel(); + static DevToolsToggleAction Inspect(); + static DevToolsToggleAction Toggle(); + static DevToolsToggleAction Reveal(const base::string16& url, + size_t line_number, + size_t column_number); + static DevToolsToggleAction NoOp(); + + Type type() const { return type_; } + const RevealParams* params() const { return params_.get(); } + + private: + explicit DevToolsToggleAction(Type type); + explicit DevToolsToggleAction(RevealParams* reveal_params); + + // The type of action. + Type type_; + + // Additional parameters for the Reveal action; NULL if of any other type. + std::unique_ptr<RevealParams> params_; +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_TOGGLE_ACTION_H_ diff --git a/chromium/chrome/browser/devtools/devtools_ui_bindings.cc b/chromium/chrome/browser/devtools/devtools_ui_bindings.cc new file mode 100644 index 00000000000..cbdff152343 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_ui_bindings.cc @@ -0,0 +1,1428 @@ +// 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/devtools_ui_bindings.h" + +#include <stddef.h> + +#include <memory> +#include <utility> + +#include "base/base64.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/json/string_escape.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "build/build_config.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/devtools/devtools_file_watcher.h" +#include "chrome/browser/devtools/devtools_protocol.h" +#include "chrome/browser/devtools/global_confirm_info_bar.h" +#include "chrome/browser/devtools/url_constants.h" +#include "chrome/browser/extensions/chrome_extension_web_contents_observer.h" +#include "chrome/browser/infobars/infobar_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/chrome_manifest_url_handlers.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "chrome/grit/generated_resources.h" +#include "components/infobars/core/confirm_infobar_delegate.h" +#include "components/infobars/core/infobar.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/sync_preferences/pref_service_syncable.h" +#include "components/zoom/page_zoom.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/devtools_external_agent_proxy.h" +#include "content/public/browser/devtools_external_agent_proxy_delegate.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/reload_type.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/common/renderer_preferences.h" +#include "content/public/common/url_constants.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/common/constants.h" +#include "extensions/common/permissions/permissions_data.h" +#include "ipc/ipc_channel.h" +#include "net/base/escape.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/url_util.h" +#include "net/cert/x509_certificate.h" +#include "net/http/http_response_headers.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_response_writer.h" +#include "third_party/WebKit/public/public_features.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/page_transition_types.h" + +using base::DictionaryValue; +using content::BrowserThread; + +namespace content { +struct LoadCommittedDetails; +struct FrameNavigateParams; +} + +namespace { + +static const char kFrontendHostId[] = "id"; +static const char kFrontendHostMethod[] = "method"; +static const char kFrontendHostParams[] = "params"; +static const char kTitleFormat[] = "Developer Tools - %s"; + +static const char kDevToolsActionTakenHistogram[] = "DevTools.ActionTaken"; +static const char kDevToolsPanelShownHistogram[] = "DevTools.PanelShown"; + +static const char kRemotePageActionInspect[] = "inspect"; +static const char kRemotePageActionReload[] = "reload"; +static const char kRemotePageActionActivate[] = "activate"; +static const char kRemotePageActionClose[] = "close"; + +static const char kConfigDiscoverUsbDevices[] = "discoverUsbDevices"; +static const char kConfigPortForwardingEnabled[] = "portForwardingEnabled"; +static const char kConfigPortForwardingConfig[] = "portForwardingConfig"; +static const char kConfigNetworkDiscoveryEnabled[] = "networkDiscoveryEnabled"; +static const char kConfigNetworkDiscoveryConfig[] = "networkDiscoveryConfig"; + +// This constant should be in sync with +// the constant at shell_devtools_frontend.cc. +const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4; + +typedef std::vector<DevToolsUIBindings*> DevToolsUIBindingsList; +base::LazyInstance<DevToolsUIBindingsList>::Leaky g_instances = + LAZY_INSTANCE_INITIALIZER; + +std::unique_ptr<base::DictionaryValue> CreateFileSystemValue( + DevToolsFileHelper::FileSystem file_system) { + auto file_system_value = base::MakeUnique<base::DictionaryValue>(); + file_system_value->SetString("fileSystemName", file_system.file_system_name); + file_system_value->SetString("rootURL", file_system.root_url); + file_system_value->SetString("fileSystemPath", file_system.file_system_path); + return file_system_value; +} + +Browser* FindBrowser(content::WebContents* web_contents) { + for (auto* browser : *BrowserList::GetInstance()) { + int tab_index = browser->tab_strip_model()->GetIndexOfWebContents( + web_contents); + if (tab_index != TabStripModel::kNoTab) + return browser; + } + return NULL; +} + +// DevToolsConfirmInfoBarDelegate --------------------------------------------- + +typedef base::Callback<void(bool)> InfoBarCallback; + +class DevToolsConfirmInfoBarDelegate : public ConfirmInfoBarDelegate { + public: + DevToolsConfirmInfoBarDelegate( + const InfoBarCallback& callback, + const base::string16& message); + ~DevToolsConfirmInfoBarDelegate() override; + + private: + infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override; + base::string16 GetMessageText() const override; + base::string16 GetButtonLabel(InfoBarButton button) const override; + bool Accept() override; + bool Cancel() override; + + InfoBarCallback callback_; + const base::string16 message_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsConfirmInfoBarDelegate); +}; + +DevToolsConfirmInfoBarDelegate::DevToolsConfirmInfoBarDelegate( + const InfoBarCallback& callback, + const base::string16& message) + : ConfirmInfoBarDelegate(), + callback_(callback), + message_(message) { +} + +DevToolsConfirmInfoBarDelegate::~DevToolsConfirmInfoBarDelegate() { + if (!callback_.is_null()) + callback_.Run(false); +} + +infobars::InfoBarDelegate::InfoBarIdentifier +DevToolsConfirmInfoBarDelegate::GetIdentifier() const { + return DEV_TOOLS_CONFIRM_INFOBAR_DELEGATE; +} + +base::string16 DevToolsConfirmInfoBarDelegate::GetMessageText() const { + return message_; +} + +base::string16 DevToolsConfirmInfoBarDelegate::GetButtonLabel( + InfoBarButton button) const { + return l10n_util::GetStringUTF16((button == BUTTON_OK) ? + IDS_DEV_TOOLS_CONFIRM_ALLOW_BUTTON : IDS_DEV_TOOLS_CONFIRM_DENY_BUTTON); +} + +bool DevToolsConfirmInfoBarDelegate::Accept() { + callback_.Run(true); + callback_.Reset(); + return true; +} + +bool DevToolsConfirmInfoBarDelegate::Cancel() { + callback_.Run(false); + callback_.Reset(); + return true; +} + +// DevToolsUIDefaultDelegate -------------------------------------------------- + +class DefaultBindingsDelegate : public DevToolsUIBindings::Delegate { + public: + explicit DefaultBindingsDelegate(content::WebContents* web_contents) + : web_contents_(web_contents) {} + + private: + ~DefaultBindingsDelegate() override {} + + void ActivateWindow() override; + void CloseWindow() override {} + void Inspect(scoped_refptr<content::DevToolsAgentHost> host) override {} + void SetInspectedPageBounds(const gfx::Rect& rect) override {} + void InspectElementCompleted() override {} + void SetIsDocked(bool is_docked) override {} + void OpenInNewTab(const std::string& url) override; + void SetWhitelistedShortcuts(const std::string& message) override {} + void SetEyeDropperActive(bool active) override {} + void OpenNodeFrontend() override {} + using DispatchCallback = + DevToolsEmbedderMessageDispatcher::Delegate::DispatchCallback; + + void InspectedContentsClosing() override; + void OnLoadCompleted() override {} + void ReadyForTest() override {} + InfoBarService* GetInfoBarService() override; + void RenderProcessGone(bool crashed) override {} + + content::WebContents* web_contents_; + DISALLOW_COPY_AND_ASSIGN(DefaultBindingsDelegate); +}; + +void DefaultBindingsDelegate::ActivateWindow() { + web_contents_->GetDelegate()->ActivateContents(web_contents_); + web_contents_->Focus(); +} + +void DefaultBindingsDelegate::OpenInNewTab(const std::string& url) { + content::OpenURLParams params(GURL(url), content::Referrer(), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui::PAGE_TRANSITION_LINK, false); + Browser* browser = FindBrowser(web_contents_); + browser->OpenURL(params); +} + +void DefaultBindingsDelegate::InspectedContentsClosing() { + web_contents_->ClosePage(); +} + +InfoBarService* DefaultBindingsDelegate::GetInfoBarService() { + return InfoBarService::FromWebContents(web_contents_); +} + +// ResponseWriter ------------------------------------------------------------- + +class ResponseWriter : public net::URLFetcherResponseWriter { + public: + ResponseWriter(base::WeakPtr<DevToolsUIBindings> bindings, int stream_id); + ~ResponseWriter() override; + + // URLFetcherResponseWriter overrides: + int Initialize(const net::CompletionCallback& callback) override; + int Write(net::IOBuffer* buffer, + int num_bytes, + const net::CompletionCallback& callback) override; + int Finish(int net_error, const net::CompletionCallback& callback) override; + + private: + base::WeakPtr<DevToolsUIBindings> bindings_; + int stream_id_; + + DISALLOW_COPY_AND_ASSIGN(ResponseWriter); +}; + +ResponseWriter::ResponseWriter(base::WeakPtr<DevToolsUIBindings> bindings, + int stream_id) + : bindings_(bindings), + stream_id_(stream_id) { +} + +ResponseWriter::~ResponseWriter() { +} + +int ResponseWriter::Initialize(const net::CompletionCallback& callback) { + return net::OK; +} + +int ResponseWriter::Write(net::IOBuffer* buffer, + int num_bytes, + const net::CompletionCallback& callback) { + std::string chunk = std::string(buffer->data(), num_bytes); + bool encoded = false; + if (!base::IsStringUTF8(chunk)) { + encoded = true; + base::Base64Encode(chunk, &chunk); + } + + base::Value* id = new base::Value(stream_id_); + base::Value* chunkValue = new base::Value(chunk); + base::Value* encodedValue = new base::Value(encoded); + + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::BindOnce(&DevToolsUIBindings::CallClientFunction, bindings_, + "DevToolsAPI.streamWrite", base::Owned(id), + base::Owned(chunkValue), base::Owned(encodedValue))); + return num_bytes; +} + +int ResponseWriter::Finish(int net_error, + const net::CompletionCallback& callback) { + return net::OK; +} + +GURL SanitizeFrontendURL( + const GURL& url, + const std::string& scheme, + const std::string& host, + const std::string& path, + bool allow_query); + +std::string SanitizeRevision(const std::string& revision) { + for (size_t i = 0; i < revision.length(); i++) { + if (!(revision[i] == '@' && i == 0) + && !(revision[i] >= '0' && revision[i] <= '9') + && !(revision[i] >= 'a' && revision[i] <= 'z') + && !(revision[i] >= 'A' && revision[i] <= 'Z')) { + return std::string(); + } + } + return revision; +} + +std::string SanitizeFrontendPath(const std::string& path) { + for (size_t i = 0; i < path.length(); i++) { + if (path[i] != '/' && path[i] != '-' && path[i] != '_' + && path[i] != '.' && path[i] != '@' + && !(path[i] >= '0' && path[i] <= '9') + && !(path[i] >= 'a' && path[i] <= 'z') + && !(path[i] >= 'A' && path[i] <= 'Z')) { + return std::string(); + } + } + return path; +} + +std::string SanitizeEndpoint(const std::string& value) { + if (value.find('&') != std::string::npos + || value.find('?') != std::string::npos) + return std::string(); + return value; +} + +std::string SanitizeRemoteBase(const std::string& value) { + GURL url(value); + std::string path = url.path(); + std::vector<std::string> parts = base::SplitString( + path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + std::string revision = parts.size() > 2 ? parts[2] : ""; + revision = SanitizeRevision(revision); + path = base::StringPrintf("/%s/%s/", kRemoteFrontendPath, revision.c_str()); + return SanitizeFrontendURL(url, url::kHttpsScheme, + kRemoteFrontendDomain, path, false).spec(); +} + +std::string SanitizeRemoteFrontendURL(const std::string& value) { + GURL url(net::UnescapeURLComponent(value, + net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS | + net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | + net::UnescapeRule::REPLACE_PLUS_WITH_SPACE)); + std::string path = url.path(); + std::vector<std::string> parts = base::SplitString( + path, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + std::string revision = parts.size() > 2 ? parts[2] : ""; + revision = SanitizeRevision(revision); + std::string filename = parts.size() ? parts[parts.size() - 1] : ""; + if (filename != "devtools.html") + filename = "inspector.html"; + path = base::StringPrintf("/serve_rev/%s/%s", + revision.c_str(), filename.c_str()); + std::string sanitized = SanitizeFrontendURL(url, url::kHttpsScheme, + kRemoteFrontendDomain, path, true).spec(); + return net::EscapeQueryParamValue(sanitized, false); +} + +std::string SanitizeFrontendQueryParam( + const std::string& key, + const std::string& value) { + // Convert boolean flags to true. + if (key == "can_dock" || key == "debugFrontend" || key == "experiments" || + key == "isSharedWorker" || key == "v8only" || key == "remoteFrontend" || + key == "nodeFrontend") + return "true"; + + // Pass connection endpoints as is. + if (key == "ws" || key == "service-backend") + return SanitizeEndpoint(value); + + // Only support undocked for old frontends. + if (key == "dockSide" && value == "undocked") + return value; + + if (key == "panel" && (value == "elements" || value == "console")) + return value; + + if (key == "remoteBase") + return SanitizeRemoteBase(value); + + if (key == "remoteFrontendUrl") + return SanitizeRemoteFrontendURL(value); + + return std::string(); +} + +GURL SanitizeFrontendURL( + const GURL& url, + const std::string& scheme, + const std::string& host, + const std::string& path, + bool allow_query) { + std::vector<std::string> query_parts; + if (allow_query) { + for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) { + std::string value = SanitizeFrontendQueryParam(it.GetKey(), + it.GetValue()); + if (!value.empty()) { + query_parts.push_back( + base::StringPrintf("%s=%s", it.GetKey().c_str(), value.c_str())); + } + } + } + std::string query = + query_parts.empty() ? "" : "?" + base::JoinString(query_parts, "&"); + std::string constructed = base::StringPrintf("%s://%s%s%s", + scheme.c_str(), host.c_str(), path.c_str(), query.c_str()); + GURL result = GURL(constructed); + if (!result.is_valid()) + return GURL(); + return result; +} + +} // namespace + +// DevToolsUIBindings::FrontendWebContentsObserver ---------------------------- + +class DevToolsUIBindings::FrontendWebContentsObserver + : public content::WebContentsObserver { + public: + explicit FrontendWebContentsObserver(DevToolsUIBindings* ui_bindings); + ~FrontendWebContentsObserver() override; + + private: + // contents::WebContentsObserver: + void RenderProcessGone(base::TerminationStatus status) override; + void ReadyToCommitNavigation( + content::NavigationHandle* navigation_handle) override; + void DocumentAvailableInMainFrame() override; + void DocumentOnLoadCompletedInMainFrame() override; + void DidFinishNavigation( + content::NavigationHandle* navigation_handle) override; + + DevToolsUIBindings* devtools_bindings_; + DISALLOW_COPY_AND_ASSIGN(FrontendWebContentsObserver); +}; + +DevToolsUIBindings::FrontendWebContentsObserver::FrontendWebContentsObserver( + DevToolsUIBindings* devtools_ui_bindings) + : WebContentsObserver(devtools_ui_bindings->web_contents()), + devtools_bindings_(devtools_ui_bindings) { +} + +DevToolsUIBindings::FrontendWebContentsObserver:: + ~FrontendWebContentsObserver() { +} + +// static +GURL DevToolsUIBindings::SanitizeFrontendURL(const GURL& url) { + return ::SanitizeFrontendURL(url, content::kChromeDevToolsScheme, + chrome::kChromeUIDevToolsHost, SanitizeFrontendPath(url.path()), true); +} + +bool DevToolsUIBindings::IsValidFrontendURL(const GURL& url) { + if (url.SchemeIs(content::kChromeUIScheme) && + url.host() == content::kChromeUITracingHost && + !url.has_query() && !url.has_ref()) { + return true; + } + + return SanitizeFrontendURL(url).spec() == url.spec(); +} + +void DevToolsUIBindings::FrontendWebContentsObserver::RenderProcessGone( + base::TerminationStatus status) { + bool crashed = true; + switch (status) { + case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: + case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: +#if defined(OS_CHROMEOS) + case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM: +#endif + case base::TERMINATION_STATUS_PROCESS_CRASHED: + case base::TERMINATION_STATUS_LAUNCH_FAILED: + if (devtools_bindings_->agent_host_.get()) + devtools_bindings_->Detach(); + break; + default: + crashed = false; + break; + } + devtools_bindings_->delegate_->RenderProcessGone(crashed); +} + +void DevToolsUIBindings::FrontendWebContentsObserver::ReadyToCommitNavigation( + content::NavigationHandle* navigation_handle) { + if (navigation_handle->IsInMainFrame()) + devtools_bindings_->UpdateFrontendHost(navigation_handle); +} + +void DevToolsUIBindings::FrontendWebContentsObserver:: + DocumentAvailableInMainFrame() { + devtools_bindings_->DocumentAvailableInMainFrame(); +} + +void DevToolsUIBindings::FrontendWebContentsObserver:: + DocumentOnLoadCompletedInMainFrame() { + devtools_bindings_->DocumentOnLoadCompletedInMainFrame(); +} + +void DevToolsUIBindings::FrontendWebContentsObserver::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + if (navigation_handle->IsInMainFrame() && navigation_handle->HasCommitted()) + devtools_bindings_->DidNavigateMainFrame(); +} + +// DevToolsUIBindings --------------------------------------------------------- + +DevToolsUIBindings* DevToolsUIBindings::ForWebContents( + content::WebContents* web_contents) { + if (g_instances == NULL) + return NULL; + DevToolsUIBindingsList* instances = g_instances.Pointer(); + for (DevToolsUIBindingsList::iterator it(instances->begin()); + it != instances->end(); ++it) { + if ((*it)->web_contents() == web_contents) + return *it; + } + return NULL; +} + +DevToolsUIBindings::DevToolsUIBindings(content::WebContents* web_contents) + : profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())), + android_bridge_(DevToolsAndroidBridge::Factory::GetForProfile(profile_)), + web_contents_(web_contents), + delegate_(new DefaultBindingsDelegate(web_contents_)), + devices_updates_enabled_(false), + frontend_loaded_(false), + reloading_(false), + weak_factory_(this) { + g_instances.Get().push_back(this); + frontend_contents_observer_.reset(new FrontendWebContentsObserver(this)); + web_contents_->GetMutableRendererPrefs()->can_accept_load_drops = false; + + file_helper_.reset(new DevToolsFileHelper(web_contents_, profile_, this)); + file_system_indexer_ = new DevToolsFileSystemIndexer(); + extensions::ChromeExtensionWebContentsObserver::CreateForWebContents( + web_contents_); + + // Register on-load actions. + embedder_message_dispatcher_.reset( + DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this)); +} + +DevToolsUIBindings::~DevToolsUIBindings() { + for (const auto& pair : pending_requests_) + delete pair.first; + + if (agent_host_.get()) + agent_host_->DetachClient(this); + + for (IndexingJobsMap::const_iterator jobs_it(indexing_jobs_.begin()); + jobs_it != indexing_jobs_.end(); ++jobs_it) { + jobs_it->second->Stop(); + } + indexing_jobs_.clear(); + SetDevicesUpdatesEnabled(false); + + // Remove self from global list. + DevToolsUIBindingsList* instances = g_instances.Pointer(); + DevToolsUIBindingsList::iterator it( + std::find(instances->begin(), instances->end(), this)); + DCHECK(it != instances->end()); + instances->erase(it); +} + +// content::DevToolsFrontendHost::Delegate implementation --------------------- +void DevToolsUIBindings::HandleMessageFromDevToolsFrontend( + const std::string& message) { + if (!frontend_host_) + return; + std::string method; + base::ListValue empty_params; + base::ListValue* params = &empty_params; + + base::DictionaryValue* dict = NULL; + std::unique_ptr<base::Value> parsed_message = base::JSONReader::Read(message); + if (!parsed_message || + !parsed_message->GetAsDictionary(&dict) || + !dict->GetString(kFrontendHostMethod, &method) || + (dict->HasKey(kFrontendHostParams) && + !dict->GetList(kFrontendHostParams, ¶ms))) { + LOG(ERROR) << "Invalid message was sent to embedder: " << message; + return; + } + int id = 0; + dict->GetInteger(kFrontendHostId, &id); + embedder_message_dispatcher_->Dispatch( + base::Bind(&DevToolsUIBindings::SendMessageAck, + weak_factory_.GetWeakPtr(), + id), + method, + params); +} + +// content::DevToolsAgentHostClient implementation -------------------------- +void DevToolsUIBindings::DispatchProtocolMessage( + content::DevToolsAgentHost* agent_host, const std::string& message) { + DCHECK(agent_host == agent_host_.get()); + if (!frontend_host_) + return; + + if (message.length() < kMaxMessageChunkSize) { + std::string param; + base::EscapeJSONString(message, true, ¶m); + base::string16 javascript = + base::UTF8ToUTF16("DevToolsAPI.dispatchMessage(" + param + ");"); + web_contents_->GetMainFrame()->ExecuteJavaScript(javascript); + return; + } + + base::Value total_size(static_cast<int>(message.length())); + for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) { + base::Value message_value(message.substr(pos, kMaxMessageChunkSize)); + CallClientFunction("DevToolsAPI.dispatchMessageChunk", + &message_value, pos ? NULL : &total_size, NULL); + } +} + +void DevToolsUIBindings::AgentHostClosed( + content::DevToolsAgentHost* agent_host, + bool replaced_with_another_client) { + DCHECK(agent_host == agent_host_.get()); + agent_host_ = NULL; + delegate_->InspectedContentsClosing(); +} + +void DevToolsUIBindings::SendMessageAck(int request_id, + const base::Value* arg) { + base::Value id_value(request_id); + CallClientFunction("DevToolsAPI.embedderMessageAck", + &id_value, arg, nullptr); +} + +// DevToolsEmbedderMessageDispatcher::Delegate implementation ----------------- + +void DevToolsUIBindings::ActivateWindow() { + delegate_->ActivateWindow(); +} + +void DevToolsUIBindings::CloseWindow() { + delegate_->CloseWindow(); +} + +void DevToolsUIBindings::LoadCompleted() { + FrontendLoaded(); +} + +void DevToolsUIBindings::SetInspectedPageBounds(const gfx::Rect& rect) { + delegate_->SetInspectedPageBounds(rect); +} + +void DevToolsUIBindings::SetIsDocked(const DispatchCallback& callback, + bool dock_requested) { + delegate_->SetIsDocked(dock_requested); + callback.Run(nullptr); +} + +void DevToolsUIBindings::InspectElementCompleted() { + delegate_->InspectElementCompleted(); +} + +void DevToolsUIBindings::InspectedURLChanged(const std::string& url) { + content::NavigationController& controller = web_contents()->GetController(); + content::NavigationEntry* entry = controller.GetActiveEntry(); + // DevTools UI is not localized. + web_contents()->UpdateTitleForEntry( + entry, base::UTF8ToUTF16(base::StringPrintf(kTitleFormat, url.c_str()))); +} + +void DevToolsUIBindings::LoadNetworkResource(const DispatchCallback& callback, + const std::string& url, + const std::string& headers, + int stream_id) { + GURL gurl(url); + if (!gurl.is_valid()) { + base::DictionaryValue response; + response.SetInteger("statusCode", 404); + callback.Run(&response); + return; + } + // Create traffic annotation tag. + net::NetworkTrafficAnnotationTag traffic_annotation = + net::DefineNetworkTrafficAnnotation("devtools_network_resource", R"( + semantics { + sender: "Developer Tools" + description: + "When user opens Developer Tools, the browser may fetch additional " + "resources from the network to enrich the debugging experience " + "(e.g. source map resources)." + trigger: "User opens Developer Tools to debug a web page." + data: "Any resources requested by Developer Tools." + destination: WEBSITE + } + policy { + cookies_allowed: true + cookies_store: "user" + setting: + "It's not possible to disable this feature from settings." + chrome_policy { + DeveloperToolsDisabled { + policy_options {mode: MANDATORY} + DeveloperToolsDisabled: true + } + } + })"); + + net::URLFetcher* fetcher = net::URLFetcher::Create(gurl, net::URLFetcher::GET, + this, traffic_annotation) + .release(); + pending_requests_[fetcher] = callback; + fetcher->SetRequestContext(profile_->GetRequestContext()); + fetcher->SetExtraRequestHeaders(headers); + fetcher->SaveResponseWithWriter( + std::unique_ptr<net::URLFetcherResponseWriter>( + new ResponseWriter(weak_factory_.GetWeakPtr(), stream_id))); + fetcher->Start(); +} + +void DevToolsUIBindings::OpenInNewTab(const std::string& url) { + delegate_->OpenInNewTab(url); +} + +void DevToolsUIBindings::SaveToFile(const std::string& url, + const std::string& content, + bool save_as) { + file_helper_->Save(url, content, save_as, + base::Bind(&DevToolsUIBindings::FileSavedAs, + weak_factory_.GetWeakPtr(), url), + base::Bind(&DevToolsUIBindings::CanceledFileSaveAs, + weak_factory_.GetWeakPtr(), url)); +} + +void DevToolsUIBindings::AppendToFile(const std::string& url, + const std::string& content) { + file_helper_->Append(url, content, + base::Bind(&DevToolsUIBindings::AppendedTo, + weak_factory_.GetWeakPtr(), url)); +} + +void DevToolsUIBindings::RequestFileSystems() { + CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); + std::vector<DevToolsFileHelper::FileSystem> file_systems = + file_helper_->GetFileSystems(); + base::ListValue file_systems_value; + for (size_t i = 0; i < file_systems.size(); ++i) + file_systems_value.Append(CreateFileSystemValue(file_systems[i])); + CallClientFunction("DevToolsAPI.fileSystemsLoaded", + &file_systems_value, NULL, NULL); +} + +void DevToolsUIBindings::AddFileSystem(const std::string& file_system_path) { + CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); + file_helper_->AddFileSystem( + file_system_path, + base::Bind(&DevToolsUIBindings::ShowDevToolsConfirmInfoBar, + weak_factory_.GetWeakPtr())); +} + +void DevToolsUIBindings::RemoveFileSystem(const std::string& file_system_path) { + CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); + file_helper_->RemoveFileSystem(file_system_path); +} + +void DevToolsUIBindings::UpgradeDraggedFileSystemPermissions( + const std::string& file_system_url) { + CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); + file_helper_->UpgradeDraggedFileSystemPermissions( + file_system_url, + base::Bind(&DevToolsUIBindings::ShowDevToolsConfirmInfoBar, + weak_factory_.GetWeakPtr())); +} + +void DevToolsUIBindings::IndexPath(int index_request_id, + const std::string& file_system_path) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); + if (!file_helper_->IsFileSystemAdded(file_system_path)) { + IndexingDone(index_request_id, file_system_path); + return; + } + if (indexing_jobs_.count(index_request_id) != 0) + return; + indexing_jobs_[index_request_id] = + scoped_refptr<DevToolsFileSystemIndexer::FileSystemIndexingJob>( + file_system_indexer_->IndexPath( + file_system_path, + Bind(&DevToolsUIBindings::IndexingTotalWorkCalculated, + weak_factory_.GetWeakPtr(), + index_request_id, + file_system_path), + Bind(&DevToolsUIBindings::IndexingWorked, + weak_factory_.GetWeakPtr(), + index_request_id, + file_system_path), + Bind(&DevToolsUIBindings::IndexingDone, + weak_factory_.GetWeakPtr(), + index_request_id, + file_system_path))); +} + +void DevToolsUIBindings::StopIndexing(int index_request_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + IndexingJobsMap::iterator it = indexing_jobs_.find(index_request_id); + if (it == indexing_jobs_.end()) + return; + it->second->Stop(); + indexing_jobs_.erase(it); +} + +void DevToolsUIBindings::SearchInPath(int search_request_id, + const std::string& file_system_path, + const std::string& query) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); + if (!file_helper_->IsFileSystemAdded(file_system_path)) { + SearchCompleted(search_request_id, + file_system_path, + std::vector<std::string>()); + return; + } + file_system_indexer_->SearchInPath(file_system_path, + query, + Bind(&DevToolsUIBindings::SearchCompleted, + weak_factory_.GetWeakPtr(), + search_request_id, + file_system_path)); +} + +void DevToolsUIBindings::SetWhitelistedShortcuts(const std::string& message) { + delegate_->SetWhitelistedShortcuts(message); +} + +void DevToolsUIBindings::SetEyeDropperActive(bool active) { + delegate_->SetEyeDropperActive(active); +} + +void DevToolsUIBindings::ShowCertificateViewer(const std::string& cert_chain) { + std::unique_ptr<base::Value> value = + base::JSONReader::Read(cert_chain); + if (!value || value->GetType() != base::Value::Type::LIST) { + NOTREACHED(); + return; + } + + std::unique_ptr<base::ListValue> list = + base::ListValue::From(std::move(value)); + std::vector<std::string> decoded; + for (size_t i = 0; i < list->GetSize(); ++i) { + base::Value* item; + if (!list->Get(i, &item) || item->GetType() != base::Value::Type::STRING) { + NOTREACHED(); + return; + } + std::string temp; + if (!item->GetAsString(&temp)) { + NOTREACHED(); + return; + } + if (!base::Base64Decode(temp, &temp)) { + NOTREACHED(); + return; + } + decoded.push_back(temp); + } + + std::vector<base::StringPiece> cert_string_piece; + for (const auto& str : decoded) + cert_string_piece.push_back(str); + scoped_refptr<net::X509Certificate> cert = + net::X509Certificate::CreateFromDERCertChain(cert_string_piece); + if (!cert) { + NOTREACHED(); + return; + } + + if (!agent_host_ || !agent_host_->GetWebContents()) + return; + content::WebContents* inspected_wc = agent_host_->GetWebContents(); + web_contents_->GetDelegate()->ShowCertificateViewerInDevTools( + inspected_wc, cert.get()); +} + +void DevToolsUIBindings::ZoomIn() { + zoom::PageZoom::Zoom(web_contents(), content::PAGE_ZOOM_IN); +} + +void DevToolsUIBindings::ZoomOut() { + zoom::PageZoom::Zoom(web_contents(), content::PAGE_ZOOM_OUT); +} + +void DevToolsUIBindings::ResetZoom() { + zoom::PageZoom::Zoom(web_contents(), content::PAGE_ZOOM_RESET); +} + +void DevToolsUIBindings::SetDevicesDiscoveryConfig( + bool discover_usb_devices, + bool port_forwarding_enabled, + const std::string& port_forwarding_config, + bool network_discovery_enabled, + const std::string& network_discovery_config) { + base::DictionaryValue* port_forwarding_dict = nullptr; + std::unique_ptr<base::Value> parsed_port_forwarding = + base::JSONReader::Read(port_forwarding_config); + if (!parsed_port_forwarding || + !parsed_port_forwarding->GetAsDictionary(&port_forwarding_dict)) { + return; + } + + base::ListValue* network_list = nullptr; + std::unique_ptr<base::Value> parsed_network = + base::JSONReader::Read(network_discovery_config); + if (!parsed_network || !parsed_network->GetAsList(&network_list)) + return; + + profile_->GetPrefs()->SetBoolean( + prefs::kDevToolsDiscoverUsbDevicesEnabled, discover_usb_devices); + profile_->GetPrefs()->SetBoolean( + prefs::kDevToolsPortForwardingEnabled, port_forwarding_enabled); + profile_->GetPrefs()->Set(prefs::kDevToolsPortForwardingConfig, + *port_forwarding_dict); + profile_->GetPrefs()->SetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled, + network_discovery_enabled); + profile_->GetPrefs()->Set(prefs::kDevToolsTCPDiscoveryConfig, *network_list); +} + +void DevToolsUIBindings::DevicesDiscoveryConfigUpdated() { + base::DictionaryValue config; + config.Set(kConfigDiscoverUsbDevices, + profile_->GetPrefs() + ->FindPreference(prefs::kDevToolsDiscoverUsbDevicesEnabled) + ->GetValue() + ->CreateDeepCopy()); + config.Set(kConfigPortForwardingEnabled, + profile_->GetPrefs() + ->FindPreference(prefs::kDevToolsPortForwardingEnabled) + ->GetValue() + ->CreateDeepCopy()); + config.Set(kConfigPortForwardingConfig, + profile_->GetPrefs() + ->FindPreference(prefs::kDevToolsPortForwardingConfig) + ->GetValue() + ->CreateDeepCopy()); + config.Set(kConfigNetworkDiscoveryEnabled, + profile_->GetPrefs() + ->FindPreference(prefs::kDevToolsDiscoverTCPTargetsEnabled) + ->GetValue() + ->CreateDeepCopy()); + config.Set(kConfigNetworkDiscoveryConfig, + profile_->GetPrefs() + ->FindPreference(prefs::kDevToolsTCPDiscoveryConfig) + ->GetValue() + ->CreateDeepCopy()); + CallClientFunction("DevToolsAPI.devicesDiscoveryConfigChanged", &config, + nullptr, nullptr); +} + +void DevToolsUIBindings::SendPortForwardingStatus(const base::Value& status) { + CallClientFunction("DevToolsAPI.devicesPortForwardingStatusChanged", &status, + nullptr, nullptr); +} + +void DevToolsUIBindings::SetDevicesUpdatesEnabled(bool enabled) { + if (devices_updates_enabled_ == enabled) + return; + devices_updates_enabled_ = enabled; + if (enabled) { + remote_targets_handler_ = DevToolsTargetsUIHandler::CreateForAdb( + base::Bind(&DevToolsUIBindings::DevicesUpdated, + base::Unretained(this)), + profile_); + pref_change_registrar_.Init(profile_->GetPrefs()); + pref_change_registrar_.Add(prefs::kDevToolsDiscoverUsbDevicesEnabled, + base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated, + base::Unretained(this))); + pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled, + base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated, + base::Unretained(this))); + pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig, + base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kDevToolsDiscoverTCPTargetsEnabled, + base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated, + base::Unretained(this))); + pref_change_registrar_.Add( + prefs::kDevToolsTCPDiscoveryConfig, + base::Bind(&DevToolsUIBindings::DevicesDiscoveryConfigUpdated, + base::Unretained(this))); + port_status_serializer_.reset(new PortForwardingStatusSerializer( + base::Bind(&DevToolsUIBindings::SendPortForwardingStatus, + base::Unretained(this)), + profile_)); + DevicesDiscoveryConfigUpdated(); + } else { + remote_targets_handler_.reset(); + port_status_serializer_.reset(); + pref_change_registrar_.RemoveAll(); + SendPortForwardingStatus(base::DictionaryValue()); + } +} + +void DevToolsUIBindings::PerformActionOnRemotePage(const std::string& page_id, + const std::string& action) { + if (!remote_targets_handler_) + return; + scoped_refptr<content::DevToolsAgentHost> host = + remote_targets_handler_->GetTarget(page_id); + if (!host) + return; + if (action == kRemotePageActionInspect) + delegate_->Inspect(host); + else if (action == kRemotePageActionReload) + host->Reload(); + else if (action == kRemotePageActionActivate) + host->Activate(); + else if (action == kRemotePageActionClose) + host->Close(); +} + +void DevToolsUIBindings::OpenRemotePage(const std::string& browser_id, + const std::string& url) { + if (!remote_targets_handler_) + return; + remote_targets_handler_->Open(browser_id, url); +} + +void DevToolsUIBindings::OpenNodeFrontend() { + delegate_->OpenNodeFrontend(); +} + +void DevToolsUIBindings::GetPreferences(const DispatchCallback& callback) { + const DictionaryValue* prefs = + profile_->GetPrefs()->GetDictionary(prefs::kDevToolsPreferences); + callback.Run(prefs); +} + +void DevToolsUIBindings::SetPreference(const std::string& name, + const std::string& value) { + DictionaryPrefUpdate update(profile_->GetPrefs(), + prefs::kDevToolsPreferences); + update.Get()->SetStringWithoutPathExpansion(name, value); +} + +void DevToolsUIBindings::RemovePreference(const std::string& name) { + DictionaryPrefUpdate update(profile_->GetPrefs(), + prefs::kDevToolsPreferences); + update.Get()->RemoveWithoutPathExpansion(name, nullptr); +} + +void DevToolsUIBindings::ClearPreferences() { + DictionaryPrefUpdate update(profile_->GetPrefs(), + prefs::kDevToolsPreferences); + update.Get()->Clear(); +} + +void DevToolsUIBindings::Reattach(const DispatchCallback& callback) { + if (agent_host_.get()) { + agent_host_->DetachClient(this); + agent_host_->AttachClient(this); + } + callback.Run(nullptr); +} + +void DevToolsUIBindings::ReadyForTest() { + delegate_->ReadyForTest(); +} + +void DevToolsUIBindings::DispatchProtocolMessageFromDevToolsFrontend( + const std::string& message) { + if (agent_host_.get()) + agent_host_->DispatchProtocolMessage(this, message); +} + +void DevToolsUIBindings::RecordEnumeratedHistogram(const std::string& name, + int sample, + int boundary_value) { + if (!frontend_host_) + return; + if (!(boundary_value >= 0 && boundary_value <= 100 && sample >= 0 && + sample < boundary_value)) { + // TODO(nick): Replace with chrome::bad_message::ReceivedBadMessage(). + frontend_host_->BadMessageRecieved(); + return; + } + // Each histogram name must follow a different code path in + // order to UMA_HISTOGRAM_EXACT_LINEAR work correctly. + if (name == kDevToolsActionTakenHistogram) + UMA_HISTOGRAM_EXACT_LINEAR(name, sample, boundary_value); + else if (name == kDevToolsPanelShownHistogram) + UMA_HISTOGRAM_EXACT_LINEAR(name, sample, boundary_value); + else + frontend_host_->BadMessageRecieved(); +} + +void DevToolsUIBindings::SendJsonRequest(const DispatchCallback& callback, + const std::string& browser_id, + const std::string& url) { + if (!android_bridge_) { + callback.Run(nullptr); + return; + } + android_bridge_->SendJsonRequest(browser_id, url, + base::Bind(&DevToolsUIBindings::JsonReceived, + weak_factory_.GetWeakPtr(), + callback)); +} + +void DevToolsUIBindings::JsonReceived(const DispatchCallback& callback, + int result, + const std::string& message) { + if (result != net::OK) { + callback.Run(nullptr); + return; + } + base::Value message_value(message); + callback.Run(&message_value); +} + +void DevToolsUIBindings::OnURLFetchComplete(const net::URLFetcher* source) { + DCHECK(source); + PendingRequestsMap::iterator it = pending_requests_.find(source); + DCHECK(it != pending_requests_.end()); + + base::DictionaryValue response; + auto headers = base::MakeUnique<base::DictionaryValue>(); + net::HttpResponseHeaders* rh = source->GetResponseHeaders(); + response.SetInteger("statusCode", rh ? rh->response_code() : 200); + + size_t iterator = 0; + std::string name; + std::string value; + while (rh && rh->EnumerateHeaderLines(&iterator, &name, &value)) + headers->SetString(name, value); + + response.Set("headers", std::move(headers)); + it->second.Run(&response); + pending_requests_.erase(it); + delete source; +} + +void DevToolsUIBindings::DeviceCountChanged(int count) { + base::Value value(count); + CallClientFunction("DevToolsAPI.deviceCountUpdated", &value, NULL, + NULL); +} + +void DevToolsUIBindings::DevicesUpdated( + const std::string& source, + const base::ListValue& targets) { + CallClientFunction("DevToolsAPI.devicesUpdated", &targets, NULL, + NULL); +} + +void DevToolsUIBindings::FileSavedAs(const std::string& url) { + base::Value url_value(url); + CallClientFunction("DevToolsAPI.savedURL", &url_value, NULL, NULL); +} + +void DevToolsUIBindings::CanceledFileSaveAs(const std::string& url) { + base::Value url_value(url); + CallClientFunction("DevToolsAPI.canceledSaveURL", + &url_value, NULL, NULL); +} + +void DevToolsUIBindings::AppendedTo(const std::string& url) { + base::Value url_value(url); + CallClientFunction("DevToolsAPI.appendedToURL", &url_value, NULL, + NULL); +} + +void DevToolsUIBindings::FileSystemAdded( + const DevToolsFileHelper::FileSystem& file_system) { + std::unique_ptr<base::DictionaryValue> file_system_value( + CreateFileSystemValue(file_system)); + CallClientFunction("DevToolsAPI.fileSystemAdded", + file_system_value.get(), NULL, NULL); +} + +void DevToolsUIBindings::FileSystemRemoved( + const std::string& file_system_path) { + base::Value file_system_path_value(file_system_path); + CallClientFunction("DevToolsAPI.fileSystemRemoved", + &file_system_path_value, NULL, NULL); +} + +void DevToolsUIBindings::FilePathsChanged( + const std::vector<std::string>& changed_paths, + const std::vector<std::string>& added_paths, + const std::vector<std::string>& removed_paths) { + base::ListValue changed, added, removed; + changed.AppendStrings(changed_paths); + added.AppendStrings(added_paths); + removed.AppendStrings(removed_paths); + + CallClientFunction("DevToolsAPI.fileSystemFilesChangedAddedRemoved", &changed, + &added, &removed); +} + +void DevToolsUIBindings::IndexingTotalWorkCalculated( + int request_id, + const std::string& file_system_path, + int total_work) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + base::Value request_id_value(request_id); + base::Value file_system_path_value(file_system_path); + base::Value total_work_value(total_work); + CallClientFunction("DevToolsAPI.indexingTotalWorkCalculated", + &request_id_value, &file_system_path_value, + &total_work_value); +} + +void DevToolsUIBindings::IndexingWorked(int request_id, + const std::string& file_system_path, + int worked) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + base::Value request_id_value(request_id); + base::Value file_system_path_value(file_system_path); + base::Value worked_value(worked); + CallClientFunction("DevToolsAPI.indexingWorked", &request_id_value, + &file_system_path_value, &worked_value); +} + +void DevToolsUIBindings::IndexingDone(int request_id, + const std::string& file_system_path) { + indexing_jobs_.erase(request_id); + DCHECK_CURRENTLY_ON(BrowserThread::UI); + base::Value request_id_value(request_id); + base::Value file_system_path_value(file_system_path); + CallClientFunction("DevToolsAPI.indexingDone", &request_id_value, + &file_system_path_value, NULL); +} + +void DevToolsUIBindings::SearchCompleted( + int request_id, + const std::string& file_system_path, + const std::vector<std::string>& file_paths) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + base::ListValue file_paths_value; + for (std::vector<std::string>::const_iterator it(file_paths.begin()); + it != file_paths.end(); ++it) { + file_paths_value.AppendString(*it); + } + base::Value request_id_value(request_id); + base::Value file_system_path_value(file_system_path); + CallClientFunction("DevToolsAPI.searchCompleted", &request_id_value, + &file_system_path_value, &file_paths_value); +} + +void DevToolsUIBindings::ShowDevToolsConfirmInfoBar( + const base::string16& message, + const InfoBarCallback& callback) { + if (!delegate_->GetInfoBarService()) { + callback.Run(false); + return; + } + std::unique_ptr<DevToolsConfirmInfoBarDelegate> delegate( + new DevToolsConfirmInfoBarDelegate(callback, message)); + GlobalConfirmInfoBar::Show(std::move(delegate)); +} + +void DevToolsUIBindings::UpdateFrontendHost( + content::NavigationHandle* navigation_handle) { + if (!IsValidFrontendURL(navigation_handle->GetURL())) { + LOG(ERROR) << "Attempt to navigate to an invalid DevTools front-end URL: " + << navigation_handle->GetURL().spec(); + frontend_host_.reset(); + return; + } + frontend_host_.reset(content::DevToolsFrontendHost::Create( + navigation_handle->GetRenderFrameHost(), + base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend, + base::Unretained(this)))); +} + +void DevToolsUIBindings::AddDevToolsExtensionsToClient() { + const extensions::ExtensionRegistry* registry = + extensions::ExtensionRegistry::Get(profile_->GetOriginalProfile()); + if (!registry) + return; + + base::ListValue results; + for (const scoped_refptr<const extensions::Extension>& extension : + registry->enabled_extensions()) { + if (extensions::chrome_manifest_urls::GetDevToolsPage(extension.get()) + .is_empty()) + continue; + + // Each devtools extension will need to be able to run in the devtools + // process. Grant each specific extension's origin permission to load + // documents. + content::ChildProcessSecurityPolicy::GetInstance()->GrantOrigin( + web_contents_->GetMainFrame()->GetProcess()->GetID(), + url::Origin(extension->url())); + + std::unique_ptr<base::DictionaryValue> extension_info( + new base::DictionaryValue()); + extension_info->SetString( + "startPage", + extensions::chrome_manifest_urls::GetDevToolsPage(extension.get()) + .spec()); + extension_info->SetString("name", extension->name()); + extension_info->SetBoolean("exposeExperimentalAPIs", + extension->permissions_data()->HasAPIPermission( + extensions::APIPermission::kExperimental)); + results.Append(std::move(extension_info)); + } + + CallClientFunction("DevToolsAPI.addExtensions", + &results, NULL, NULL); +} + +void DevToolsUIBindings::SetDelegate(Delegate* delegate) { + delegate_.reset(delegate); +} + +void DevToolsUIBindings::AttachTo( + const scoped_refptr<content::DevToolsAgentHost>& agent_host) { + if (agent_host_.get()) + Detach(); + agent_host_ = agent_host; + // DevToolsUIBindings terminates existing debugging connections and starts + // debugging. + agent_host_->ForceAttachClient(this); +} + +void DevToolsUIBindings::Reload() { + reloading_ = true; + web_contents_->GetController().Reload(content::ReloadType::NORMAL, false); +} + +void DevToolsUIBindings::Detach() { + if (agent_host_.get()) + agent_host_->DetachClient(this); + agent_host_ = NULL; +} + +bool DevToolsUIBindings::IsAttachedTo(content::DevToolsAgentHost* agent_host) { + return agent_host_.get() == agent_host; +} + +void DevToolsUIBindings::CallClientFunction(const std::string& function_name, + const base::Value* arg1, + const base::Value* arg2, + const base::Value* arg3) { + // If we're not exposing bindings, we shouldn't call functions either. + if (!frontend_host_) + return; + std::string javascript = function_name + "("; + if (arg1) { + std::string json; + base::JSONWriter::Write(*arg1, &json); + javascript.append(json); + if (arg2) { + base::JSONWriter::Write(*arg2, &json); + javascript.append(", ").append(json); + if (arg3) { + base::JSONWriter::Write(*arg3, &json); + javascript.append(", ").append(json); + } + } + } + javascript.append(");"); + web_contents_->GetMainFrame()->ExecuteJavaScript( + base::UTF8ToUTF16(javascript)); +} + +void DevToolsUIBindings::DocumentAvailableInMainFrame() { + if (!reloading_) + return; + reloading_ = false; + if (agent_host_.get()) { + agent_host_->DetachClient(this); + agent_host_->AttachClient(this); + } +} + +void DevToolsUIBindings::DocumentOnLoadCompletedInMainFrame() { + // In the DEBUG_DEVTOOLS mode, the DocumentOnLoadCompletedInMainFrame event + // arrives before the LoadCompleted event, thus it should not trigger the + // frontend load handling. +#if !BUILDFLAG(DEBUG_DEVTOOLS) + FrontendLoaded(); +#endif +} + +void DevToolsUIBindings::DidNavigateMainFrame() { + frontend_loaded_ = false; +} + +void DevToolsUIBindings::FrontendLoaded() { + if (frontend_loaded_) + return; + frontend_loaded_ = true; + + // Call delegate first - it seeds importants bit of information. + delegate_->OnLoadCompleted(); + + AddDevToolsExtensionsToClient(); +} diff --git a/chromium/chrome/browser/devtools/devtools_ui_bindings.h b/chromium/chrome/browser/devtools/devtools_ui_bindings.h new file mode 100644 index 00000000000..46e94ed4d55 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_ui_bindings.h @@ -0,0 +1,249 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_UI_BINDINGS_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_UI_BINDINGS_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "chrome/browser/devtools/device/devtools_android_bridge.h" +#include "chrome/browser/devtools/devtools_embedder_message_dispatcher.h" +#include "chrome/browser/devtools/devtools_file_helper.h" +#include "chrome/browser/devtools/devtools_file_system_indexer.h" +#include "chrome/browser/devtools/devtools_targets_ui.h" +#include "components/prefs/pref_change_registrar.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/devtools_frontend_host.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "ui/gfx/geometry/size.h" + +class DevToolsAndroidBridge; +class InfoBarService; +class Profile; +class PortForwardingStatusSerializer; + +namespace content { +class NavigationHandle; +class WebContents; +} + +// Base implementation of DevTools bindings around front-end. +class DevToolsUIBindings : public DevToolsEmbedderMessageDispatcher::Delegate, + public DevToolsAndroidBridge::DeviceCountListener, + public content::DevToolsAgentHostClient, + public net::URLFetcherDelegate, + public DevToolsFileHelper::Delegate { + public: + static DevToolsUIBindings* ForWebContents( + content::WebContents* web_contents); + + static GURL SanitizeFrontendURL(const GURL& url); + static bool IsValidFrontendURL(const GURL& url); + + class Delegate { + public: + virtual ~Delegate() {} + virtual void ActivateWindow() = 0; + virtual void CloseWindow() = 0; + virtual void Inspect(scoped_refptr<content::DevToolsAgentHost> host) = 0; + virtual void SetInspectedPageBounds(const gfx::Rect& rect) = 0; + virtual void InspectElementCompleted() = 0; + virtual void SetIsDocked(bool is_docked) = 0; + virtual void OpenInNewTab(const std::string& url) = 0; + virtual void SetWhitelistedShortcuts(const std::string& message) = 0; + virtual void SetEyeDropperActive(bool active) = 0; + virtual void OpenNodeFrontend() = 0; + + virtual void InspectedContentsClosing() = 0; + virtual void OnLoadCompleted() = 0; + virtual void ReadyForTest() = 0; + virtual InfoBarService* GetInfoBarService() = 0; + virtual void RenderProcessGone(bool crashed) = 0; + }; + + explicit DevToolsUIBindings(content::WebContents* web_contents); + ~DevToolsUIBindings() override; + + content::WebContents* web_contents() { return web_contents_; } + Profile* profile() { return profile_; } + content::DevToolsAgentHost* agent_host() { return agent_host_.get(); } + + // Takes ownership over the |delegate|. + void SetDelegate(Delegate* delegate); + void CallClientFunction(const std::string& function_name, + const base::Value* arg1, + const base::Value* arg2, + const base::Value* arg3); + void AttachTo(const scoped_refptr<content::DevToolsAgentHost>& agent_host); + void Reload(); + void Detach(); + bool IsAttachedTo(content::DevToolsAgentHost* agent_host); + + private: + void HandleMessageFromDevToolsFrontend(const std::string& message); + + // content::DevToolsAgentHostClient implementation. + void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, + const std::string& message) override; + void AgentHostClosed(content::DevToolsAgentHost* agent_host, + bool replaced_with_another_client) override; + + // DevToolsEmbedderMessageDispatcher::Delegate implementation. + void ActivateWindow() override; + void CloseWindow() override; + void LoadCompleted() override; + void SetInspectedPageBounds(const gfx::Rect& rect) override; + void InspectElementCompleted() override; + void InspectedURLChanged(const std::string& url) override; + void LoadNetworkResource(const DispatchCallback& callback, + const std::string& url, + const std::string& headers, + int stream_id) override; + void SetIsDocked(const DispatchCallback& callback, bool is_docked) override; + void OpenInNewTab(const std::string& url) override; + void SaveToFile(const std::string& url, + const std::string& content, + bool save_as) override; + void AppendToFile(const std::string& url, + const std::string& content) override; + void RequestFileSystems() override; + void AddFileSystem(const std::string& file_system_path) override; + void RemoveFileSystem(const std::string& file_system_path) override; + void UpgradeDraggedFileSystemPermissions( + const std::string& file_system_url) override; + void IndexPath(int index_request_id, + const std::string& file_system_path) override; + void StopIndexing(int index_request_id) override; + void SearchInPath(int search_request_id, + const std::string& file_system_path, + const std::string& query) override; + void SetWhitelistedShortcuts(const std::string& message) override; + void SetEyeDropperActive(bool active) override; + void ShowCertificateViewer(const std::string& cert_chain) override; + void ZoomIn() override; + void ZoomOut() override; + void ResetZoom() override; + void SetDevicesDiscoveryConfig( + bool discover_usb_devices, + bool port_forwarding_enabled, + const std::string& port_forwarding_config, + bool network_discovery_enabled, + const std::string& network_discovery_config) override; + void SetDevicesUpdatesEnabled(bool enabled) override; + void PerformActionOnRemotePage(const std::string& page_id, + const std::string& action) override; + void OpenRemotePage(const std::string& browser_id, + const std::string& url) override; + void OpenNodeFrontend() override; + void DispatchProtocolMessageFromDevToolsFrontend( + const std::string& message) override; + void RecordEnumeratedHistogram(const std::string& name, + int sample, + int boundary_value) override; + void SendJsonRequest(const DispatchCallback& callback, + const std::string& browser_id, + const std::string& url) override; + void GetPreferences(const DispatchCallback& callback) override; + void SetPreference(const std::string& name, + const std::string& value) override; + void RemovePreference(const std::string& name) override; + void ClearPreferences() override; + void Reattach(const DispatchCallback& callback) override; + void ReadyForTest() override; + + // net::URLFetcherDelegate overrides. + void OnURLFetchComplete(const net::URLFetcher* source) override; + + void EnableRemoteDeviceCounter(bool enable); + + void SendMessageAck(int request_id, + const base::Value* arg1); + + // DevToolsAndroidBridge::DeviceCountListener override: + void DeviceCountChanged(int count) override; + + // Forwards discovered devices to frontend. + virtual void DevicesUpdated(const std::string& source, + const base::ListValue& targets); + + void DocumentAvailableInMainFrame(); + void DocumentOnLoadCompletedInMainFrame(); + void DidNavigateMainFrame(); + void FrontendLoaded(); + + void JsonReceived(const DispatchCallback& callback, + int result, + const std::string& message); + void DevicesDiscoveryConfigUpdated(); + void SendPortForwardingStatus(const base::Value& status); + + // DevToolsFileHelper::Delegate overrides. + void FileSystemAdded( + const DevToolsFileHelper::FileSystem& file_system) override; + void FileSystemRemoved(const std::string& file_system_path) override; + void FilePathsChanged(const std::vector<std::string>& changed_paths, + const std::vector<std::string>& added_paths, + const std::vector<std::string>& removed_paths) override; + + // DevToolsFileHelper callbacks. + void FileSavedAs(const std::string& url); + void CanceledFileSaveAs(const std::string& url); + void AppendedTo(const std::string& url); + void IndexingTotalWorkCalculated(int request_id, + const std::string& file_system_path, + int total_work); + void IndexingWorked(int request_id, + const std::string& file_system_path, + int worked); + void IndexingDone(int request_id, const std::string& file_system_path); + void SearchCompleted(int request_id, + const std::string& file_system_path, + const std::vector<std::string>& file_paths); + typedef base::Callback<void(bool)> InfoBarCallback; + void ShowDevToolsConfirmInfoBar(const base::string16& message, + const InfoBarCallback& callback); + void UpdateFrontendHost(content::NavigationHandle* navigation_handle); + + // Extensions support. + void AddDevToolsExtensionsToClient(); + + class FrontendWebContentsObserver; + std::unique_ptr<FrontendWebContentsObserver> frontend_contents_observer_; + + Profile* profile_; + DevToolsAndroidBridge* android_bridge_; + content::WebContents* web_contents_; + std::unique_ptr<Delegate> delegate_; + scoped_refptr<content::DevToolsAgentHost> agent_host_; + std::unique_ptr<content::DevToolsFrontendHost> frontend_host_; + std::unique_ptr<DevToolsFileHelper> file_helper_; + scoped_refptr<DevToolsFileSystemIndexer> file_system_indexer_; + typedef std::map< + int, + scoped_refptr<DevToolsFileSystemIndexer::FileSystemIndexingJob> > + IndexingJobsMap; + IndexingJobsMap indexing_jobs_; + + bool devices_updates_enabled_; + bool frontend_loaded_; + bool reloading_; + std::unique_ptr<DevToolsTargetsUIHandler> remote_targets_handler_; + std::unique_ptr<PortForwardingStatusSerializer> port_status_serializer_; + PrefChangeRegistrar pref_change_registrar_; + std::unique_ptr<DevToolsEmbedderMessageDispatcher> + embedder_message_dispatcher_; + GURL url_; + using PendingRequestsMap = std::map<const net::URLFetcher*, DispatchCallback>; + PendingRequestsMap pending_requests_; + base::WeakPtrFactory<DevToolsUIBindings> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsUIBindings); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_UI_BINDINGS_H_ diff --git a/chromium/chrome/browser/devtools/devtools_ui_bindings_unittest.cc b/chromium/chrome/browser/devtools/devtools_ui_bindings_unittest.cc new file mode 100644 index 00000000000..f1754a328e2 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_ui_bindings_unittest.cc @@ -0,0 +1,102 @@ +// Copyright 2016 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/devtools_ui_bindings.h" +#include "testing/gtest/include/gtest/gtest.h" + +class DevToolsUIBindingsTest : public testing::Test { +}; + +TEST_F(DevToolsUIBindingsTest, SanitizeFrontendURL) { + std::vector<std::pair<std::string, std::string>> tests = { + {"random-string", + "chrome-devtools://devtools/"}, + {"http://valid.url/but/wrong", + "chrome-devtools://devtools/but/wrong"}, + {"chrome-devtools://wrong-domain/", + "chrome-devtools://devtools/"}, + {"chrome-devtools://devtools/bundled/devtools.html", + "chrome-devtools://devtools/bundled/devtools.html"}, + {"chrome-devtools://devtools:1234/bundled/devtools.html#hash", + "chrome-devtools://devtools/bundled/devtools.html"}, + {"chrome-devtools://devtools/some/random/path", + "chrome-devtools://devtools/some/random/path"}, + {"chrome-devtools://devtools/bundled/devtools.html?experiments=true", + "chrome-devtools://devtools/bundled/devtools.html?experiments=true"}, + {"chrome-devtools://devtools/bundled/devtools.html" + "?some-flag=flag&v8only=true&experiments=false&debugFrontend=a" + "&another-flag=another-flag&can_dock=false&isSharedWorker=notreally" + "&remoteFrontend=sure", + "chrome-devtools://devtools/bundled/devtools.html" + "?v8only=true&experiments=true&debugFrontend=true" + "&can_dock=true&isSharedWorker=true&remoteFrontend=true"}, + {"chrome-devtools://devtools/?ws=any-value-is-fine", + "chrome-devtools://devtools/?ws=any-value-is-fine"}, + {"chrome-devtools://devtools/" + "?service-backend=ws://localhost:9222/services", + "chrome-devtools://devtools/" + "?service-backend=ws://localhost:9222/services"}, + {"chrome-devtools://devtools/?dockSide=undocked", + "chrome-devtools://devtools/?dockSide=undocked"}, + {"chrome-devtools://devtools/?dockSide=dock-to-bottom", + "chrome-devtools://devtools/"}, + {"chrome-devtools://devtools/?dockSide=bottom", + "chrome-devtools://devtools/"}, + {"chrome-devtools://devtools/?remoteBase=" + "http://example.com:1234/remote-base#hash", + "chrome-devtools://devtools/?remoteBase=" + "https://chrome-devtools-frontend.appspot.com/" + "serve_file//"}, + {"chrome-devtools://devtools/?ws=1%26evil%3dtrue", + "chrome-devtools://devtools/?ws=1%26evil%3dtrue"}, + {"chrome-devtools://devtools/?remoteBase=" + "https://chrome-devtools-frontend.appspot.com/some/path/" + "@123719741873/more/path.html", + "chrome-devtools://devtools/?remoteBase=" + "https://chrome-devtools-frontend.appspot.com/serve_file/path/"}, + {"chrome-devtools://devtools/?remoteBase=" + "https://chrome-devtools-frontend.appspot.com/serve_file/" + "@123719741873/inspector.html%3FdebugFrontend%3Dfalse", + "chrome-devtools://devtools/?remoteBase=" + "https://chrome-devtools-frontend.appspot.com/serve_file/" + "@123719741873/"}, + {"chrome-devtools://devtools/bundled/inspector.html?" + "&remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/" + "@b4907cc5d602ff470740b2eb6344b517edecb7b9/&can_dock=true", + "chrome-devtools://devtools/bundled/inspector.html?" + "remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/" + "@b4907cc5d602ff470740b2eb6344b517edecb7b9/&can_dock=true"}, + {"chrome-devtools://devtools/?remoteFrontendUrl=" + "https://chrome-devtools-frontend.appspot.com/serve_rev/" + "@12345/inspector.html%3FdebugFrontend%3Dfalse", + "chrome-devtools://devtools/?remoteFrontendUrl=" + "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" + "%2F%4012345%2Finspector.html%3FdebugFrontend%3Dtrue"}, + {"chrome-devtools://devtools/?remoteFrontendUrl=" + "https://chrome-devtools-frontend.appspot.com/serve_rev/" + "@12345/inspector.html%22></iframe>something", + "chrome-devtools://devtools/?remoteFrontendUrl=" + "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" + "%2F%4012345%2Finspector.html"}, + {"chrome-devtools://devtools/?remoteFrontendUrl=" + "http://domain:1234/path/rev/a/filename.html%3Fparam%3Dvalue#hash", + "chrome-devtools://devtools/?remoteFrontendUrl=" + "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" + "%2Frev%2Finspector.html"}, + {"chrome-devtools://devtools/?experiments=whatever&remoteFrontendUrl=" + "https://chrome-devtools-frontend.appspot.com/serve_rev/" + "@12345/devtools.html%3Fws%3Danyvalue%26experiments%3Dlikely" + "&unencoded=value&debugFrontend=true", + "chrome-devtools://devtools/?experiments=true&remoteFrontendUrl=" + "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" + "%2F%4012345%2Fdevtools.html%3Fws%3Danyvalue%26experiments%3Dtrue" + "&debugFrontend=true"}, + }; + + for (const auto& pair : tests) { + GURL url = GURL(pair.first); + url = DevToolsUIBindings::SanitizeFrontendURL(url); + EXPECT_EQ(pair.second, url.spec()); + } +} diff --git a/chromium/chrome/browser/devtools/devtools_window.cc b/chromium/chrome/browser/devtools/devtools_window.cc new file mode 100644 index 00000000000..0ba79b03613 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_window.cc @@ -0,0 +1,1447 @@ +// Copyright (c) 2012 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/devtools_window.h" + +#include <algorithm> +#include <utility> + +#include "base/command_line.h" +#include "base/json/json_reader.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/user_metrics.h" +#include "base/time/time.h" +#include "base/values.h" +#include "chrome/browser/certificate_viewer.h" +#include "chrome/browser/data_use_measurement/data_use_web_contents_observer.h" +#include "chrome/browser/devtools/devtools_eye_dropper.h" +#include "chrome/browser/file_select_helper.h" +#include "chrome/browser/infobars/infobar_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sessions/session_tab_helper.h" +#include "chrome/browser/task_manager/web_contents_tags.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_dialogs.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/prefs/prefs_tab_helper.h" +#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/webui/devtools_ui.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "components/app_modal/javascript_dialog_manager.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/sync_preferences/pref_service_syncable.h" +#include "components/zoom/page_zoom.h" +#include "components/zoom/zoom_controller.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/keyboard_event_processing_result.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_client.h" +#include "content/public/common/url_constants.h" +#include "net/base/escape.h" +#include "third_party/WebKit/public/platform/WebGestureEvent.h" +#include "third_party/WebKit/public/platform/WebInputEvent.h" +#include "third_party/WebKit/public/public_features.h" +#include "ui/base/page_transition_types.h" +#include "ui/events/keycodes/dom/keycode_converter.h" +#include "ui/events/keycodes/keyboard_code_conversion.h" +#include "ui/events/keycodes/keyboard_codes.h" + +using base::DictionaryValue; +using blink::WebInputEvent; +using content::BrowserThread; +using content::DevToolsAgentHost; +using content::WebContents; + +namespace { + +typedef std::vector<DevToolsWindow*> DevToolsWindows; +base::LazyInstance<DevToolsWindows>::Leaky g_instances = + LAZY_INSTANCE_INITIALIZER; + +base::LazyInstance<std::vector<base::Callback<void(DevToolsWindow*)>>>::Leaky + g_creation_callbacks = LAZY_INSTANCE_INITIALIZER; + +static const char kKeyUpEventName[] = "keyup"; +static const char kKeyDownEventName[] = "keydown"; + +bool FindInspectedBrowserAndTabIndex( + WebContents* inspected_web_contents, Browser** browser, int* tab) { + if (!inspected_web_contents) + return false; + + for (auto* b : *BrowserList::GetInstance()) { + int tab_index = + b->tab_strip_model()->GetIndexOfWebContents(inspected_web_contents); + if (tab_index != TabStripModel::kNoTab) { + *browser = b; + *tab = tab_index; + return true; + } + } + return false; +} + +void SetPreferencesFromJson(Profile* profile, const std::string& json) { + base::DictionaryValue* dict = nullptr; + std::unique_ptr<base::Value> parsed = base::JSONReader::Read(json); + if (!parsed || !parsed->GetAsDictionary(&dict)) + return; + DictionaryPrefUpdate update(profile->GetPrefs(), prefs::kDevToolsPreferences); + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { + if (!it.value().IsType(base::Value::Type::STRING)) + continue; + update.Get()->SetWithoutPathExpansion( + it.key(), it.value().CreateDeepCopy()); + } +} + +// DevToolsToolboxDelegate ---------------------------------------------------- + +class DevToolsToolboxDelegate + : public content::WebContentsObserver, + public content::WebContentsDelegate { + public: + DevToolsToolboxDelegate( + WebContents* toolbox_contents, + DevToolsWindow::ObserverWithAccessor* web_contents_observer); + ~DevToolsToolboxDelegate() override; + + content::WebContents* OpenURLFromTab( + content::WebContents* source, + const content::OpenURLParams& params) override; + content::KeyboardEventProcessingResult PreHandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) override; + void HandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) override; + void WebContentsDestroyed() override; + + private: + BrowserWindow* GetInspectedBrowserWindow(); + DevToolsWindow::ObserverWithAccessor* inspected_contents_observer_; + DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate); +}; + +DevToolsToolboxDelegate::DevToolsToolboxDelegate( + WebContents* toolbox_contents, + DevToolsWindow::ObserverWithAccessor* web_contents_observer) + : WebContentsObserver(toolbox_contents), + inspected_contents_observer_(web_contents_observer) { +} + +DevToolsToolboxDelegate::~DevToolsToolboxDelegate() { +} + +content::WebContents* DevToolsToolboxDelegate::OpenURLFromTab( + content::WebContents* source, + const content::OpenURLParams& params) { + DCHECK(source == web_contents()); + if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) + return NULL; + content::NavigationController::LoadURLParams load_url_params(params.url); + source->GetController().LoadURLWithParams(load_url_params); + return source; +} + +content::KeyboardEventProcessingResult +DevToolsToolboxDelegate::PreHandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) { + BrowserWindow* window = GetInspectedBrowserWindow(); + if (window) + return window->PreHandleKeyboardEvent(event); + return content::KeyboardEventProcessingResult::NOT_HANDLED; +} + +void DevToolsToolboxDelegate::HandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) { + if (event.windows_key_code == 0x08) { + // Do not navigate back in history on Windows (http://crbug.com/74156). + return; + } + BrowserWindow* window = GetInspectedBrowserWindow(); + if (window) + window->HandleKeyboardEvent(event); +} + +void DevToolsToolboxDelegate::WebContentsDestroyed() { + delete this; +} + +BrowserWindow* DevToolsToolboxDelegate::GetInspectedBrowserWindow() { + WebContents* inspected_contents = + inspected_contents_observer_->web_contents(); + if (!inspected_contents) + return NULL; + Browser* browser = NULL; + int tab = 0; + if (FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab)) + return browser->window(); + return NULL; +} + +// static +GURL DecorateFrontendURL(const GURL& base_url) { + std::string frontend_url = base_url.spec(); + std::string url_string( + frontend_url + + ((frontend_url.find("?") == std::string::npos) ? "?" : "&") + + "dockSide=undocked"); // TODO(dgozman): remove this support in M38. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kEnableDevToolsExperiments)) + url_string += "&experiments=true"; + + if (command_line->HasSwitch(switches::kDevToolsFlags)) { + url_string += "&" + command_line->GetSwitchValueASCII( + switches::kDevToolsFlags); + } + +#if BUILDFLAG(DEBUG_DEVTOOLS) + url_string += "&debugFrontend=true"; +#endif // BUILDFLAG(DEBUG_DEVTOOLS) + + return GURL(url_string); +} + +} // namespace + +// DevToolsEventForwarder ----------------------------------------------------- + +class DevToolsEventForwarder { + public: + explicit DevToolsEventForwarder(DevToolsWindow* window) + : devtools_window_(window) {} + + // Registers whitelisted shortcuts with the forwarder. + // Only registered keys will be forwarded to the DevTools frontend. + void SetWhitelistedShortcuts(const std::string& message); + + // Forwards a keyboard event to the DevTools frontend if it is whitelisted. + // Returns |true| if the event has been forwarded, |false| otherwise. + bool ForwardEvent(const content::NativeWebKeyboardEvent& event); + + private: + static bool KeyWhitelistingAllowed(int key_code, int modifiers); + static int CombineKeyCodeAndModifiers(int key_code, int modifiers); + + DevToolsWindow* devtools_window_; + std::set<int> whitelisted_keys_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder); +}; + +void DevToolsEventForwarder::SetWhitelistedShortcuts( + const std::string& message) { + std::unique_ptr<base::Value> parsed_message = base::JSONReader::Read(message); + base::ListValue* shortcut_list; + if (!parsed_message || !parsed_message->GetAsList(&shortcut_list)) + return; + base::ListValue::iterator it = shortcut_list->begin(); + for (; it != shortcut_list->end(); ++it) { + base::DictionaryValue* dictionary; + if (!it->GetAsDictionary(&dictionary)) + continue; + int key_code = 0; + dictionary->GetInteger("keyCode", &key_code); + if (key_code == 0) + continue; + int modifiers = 0; + dictionary->GetInteger("modifiers", &modifiers); + if (!KeyWhitelistingAllowed(key_code, modifiers)) { + LOG(WARNING) << "Key whitelisting forbidden: " + << "(" << key_code << "," << modifiers << ")"; + continue; + } + whitelisted_keys_.insert(CombineKeyCodeAndModifiers(key_code, modifiers)); + } +} + +bool DevToolsEventForwarder::ForwardEvent( + const content::NativeWebKeyboardEvent& event) { + std::string event_type; + switch (event.GetType()) { + case WebInputEvent::kKeyDown: + case WebInputEvent::kRawKeyDown: + event_type = kKeyDownEventName; + break; + case WebInputEvent::kKeyUp: + event_type = kKeyUpEventName; + break; + default: + return false; + } + + int key_code = ui::LocatedToNonLocatedKeyboardCode( + static_cast<ui::KeyboardCode>(event.windows_key_code)); + int modifiers = event.GetModifiers() & + (WebInputEvent::kShiftKey | WebInputEvent::kControlKey | + WebInputEvent::kAltKey | WebInputEvent::kMetaKey); + int key = CombineKeyCodeAndModifiers(key_code, modifiers); + if (whitelisted_keys_.find(key) == whitelisted_keys_.end()) + return false; + + base::DictionaryValue event_data; + event_data.SetString("type", event_type); + event_data.SetString("key", ui::KeycodeConverter::DomKeyToKeyString( + static_cast<ui::DomKey>(event.dom_key))); + event_data.SetString("code", ui::KeycodeConverter::DomCodeToCodeString( + static_cast<ui::DomCode>(event.dom_code))); + event_data.SetInteger("keyCode", key_code); + event_data.SetInteger("modifiers", modifiers); + devtools_window_->bindings_->CallClientFunction( + "DevToolsAPI.keyEventUnhandled", &event_data, NULL, NULL); + return true; +} + +int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code, + int modifiers) { + return key_code | (modifiers << 16); +} + +bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code, + int modifiers) { + return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) || + modifiers != 0; +} + +void DevToolsWindow::OpenNodeFrontend() { + DevToolsWindow::OpenNodeFrontendWindow(profile_); +} + +// DevToolsWindow::ObserverWithAccessor ------------------------------- + +DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor( + WebContents* web_contents) + : WebContentsObserver(web_contents) { +} + +DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() { +} + +// DevToolsWindow ------------------------------------------------------------- + +const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp"; + +// static +void DevToolsWindow::AddCreationCallbackForTest( + const CreationCallback& callback) { + g_creation_callbacks.Get().push_back(callback); +} + +// static +void DevToolsWindow::RemoveCreationCallbackForTest( + const CreationCallback& callback) { + for (size_t i = 0; i < g_creation_callbacks.Get().size(); ++i) { + if (g_creation_callbacks.Get().at(i).Equals(callback)) { + g_creation_callbacks.Get().erase(g_creation_callbacks.Get().begin() + i); + return; + } + } +} + +DevToolsWindow::~DevToolsWindow() { + life_stage_ = kClosing; + + UpdateBrowserWindow(); + UpdateBrowserToolbar(); + + if (toolbox_web_contents_) + delete toolbox_web_contents_; + + DevToolsWindows* instances = g_instances.Pointer(); + DevToolsWindows::iterator it( + std::find(instances->begin(), instances->end(), this)); + DCHECK(it != instances->end()); + instances->erase(it); + + if (!close_callback_.is_null()) { + close_callback_.Run(); + close_callback_ = base::Closure(); + } +} + +// static +void DevToolsWindow::RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterDictionaryPref(prefs::kDevToolsEditedFiles); + registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths); + registry->RegisterStringPref(prefs::kDevToolsAdbKey, std::string()); + + registry->RegisterBooleanPref(prefs::kDevToolsDiscoverUsbDevicesEnabled, + true); + registry->RegisterBooleanPref(prefs::kDevToolsPortForwardingEnabled, false); + registry->RegisterBooleanPref(prefs::kDevToolsPortForwardingDefaultSet, + false); + registry->RegisterDictionaryPref(prefs::kDevToolsPortForwardingConfig); + registry->RegisterBooleanPref(prefs::kDevToolsDiscoverTCPTargetsEnabled, + true); + registry->RegisterListPref(prefs::kDevToolsTCPDiscoveryConfig); + registry->RegisterDictionaryPref(prefs::kDevToolsPreferences); +} + +// static +content::WebContents* DevToolsWindow::GetInTabWebContents( + WebContents* inspected_web_contents, + DevToolsContentsResizingStrategy* out_strategy) { + DevToolsWindow* window = GetInstanceForInspectedWebContents( + inspected_web_contents); + if (!window || window->life_stage_ == kClosing) + return NULL; + + // Not yet loaded window is treated as docked, but we should not present it + // until we decided on docking. + bool is_docked_set = window->life_stage_ == kLoadCompleted || + window->life_stage_ == kIsDockedSet; + if (!is_docked_set) + return NULL; + + // Undocked window should have toolbox web contents. + if (!window->is_docked_ && !window->toolbox_web_contents_) + return NULL; + + if (out_strategy) + out_strategy->CopyFrom(window->contents_resizing_strategy_); + + return window->is_docked_ ? window->main_web_contents_ : + window->toolbox_web_contents_; +} + +// static +DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents( + WebContents* inspected_web_contents) { + if (!inspected_web_contents || g_instances == NULL) + return NULL; + DevToolsWindows* instances = g_instances.Pointer(); + for (DevToolsWindows::iterator it(instances->begin()); it != instances->end(); + ++it) { + if ((*it)->GetInspectedWebContents() == inspected_web_contents) + return *it; + } + return NULL; +} + +// static +bool DevToolsWindow::IsDevToolsWindow(content::WebContents* web_contents) { + if (!web_contents || g_instances == NULL) + return false; + DevToolsWindows* instances = g_instances.Pointer(); + for (DevToolsWindows::iterator it(instances->begin()); it != instances->end(); + ++it) { + if ((*it)->main_web_contents_ == web_contents || + (*it)->toolbox_web_contents_ == web_contents) + return true; + } + return false; +} + +// static +void DevToolsWindow::OpenDevToolsWindowForWorker( + Profile* profile, + const scoped_refptr<DevToolsAgentHost>& worker_agent) { + DevToolsWindow* window = FindDevToolsWindow(worker_agent.get()); + if (!window) { + window = DevToolsWindow::CreateDevToolsWindowForWorker(profile); + if (!window) + return; + window->bindings_->AttachTo(worker_agent); + } + window->ScheduleShow(DevToolsToggleAction::Show()); +} + +// static +DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker( + Profile* profile) { + base::RecordAction(base::UserMetricsAction("DevTools_InspectWorker")); + return Create(profile, nullptr, kFrontendWorker, std::string(), false, "", + ""); +} + +// static +void DevToolsWindow::OpenDevToolsWindow( + content::WebContents* inspected_web_contents) { + ToggleDevToolsWindow( + inspected_web_contents, true, DevToolsToggleAction::Show(), ""); +} + +// static +void DevToolsWindow::OpenDevToolsWindow( + scoped_refptr<content::DevToolsAgentHost> agent_host, + Profile* profile) { + if (!profile) + profile = Profile::FromBrowserContext(agent_host->GetBrowserContext()); + + if (!profile) + return; + + std::string type = agent_host->GetType(); + + bool is_worker = type == DevToolsAgentHost::kTypeServiceWorker || + type == DevToolsAgentHost::kTypeSharedWorker; + + if (!agent_host->GetFrontendURL().empty()) { + FrontendType frontend_type = kFrontendRemote; + if (is_worker) { + frontend_type = kFrontendWorker; + } else if (type == "node") { + frontend_type = kFrontendV8; + } + DevToolsWindow::OpenExternalFrontend(profile, agent_host->GetFrontendURL(), + agent_host, frontend_type); + return; + } + + if (is_worker) { + DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host); + return; + } + + if (type == content::DevToolsAgentHost::kTypeFrame) { + DevToolsWindow::OpenDevToolsWindowForFrame(profile, agent_host); + return; + } + + content::WebContents* web_contents = agent_host->GetWebContents(); + if (web_contents) + DevToolsWindow::OpenDevToolsWindow(web_contents); +} + +// static +void DevToolsWindow::OpenDevToolsWindow( + content::WebContents* inspected_web_contents, + const DevToolsToggleAction& action) { + ToggleDevToolsWindow(inspected_web_contents, true, action, ""); +} + +// static +void DevToolsWindow::OpenDevToolsWindowForFrame( + Profile* profile, + const scoped_refptr<content::DevToolsAgentHost>& agent_host) { + DevToolsWindow* window = FindDevToolsWindow(agent_host.get()); + if (!window) { + window = DevToolsWindow::Create(profile, nullptr, kFrontendDefault, + std::string(), false, std::string(), + std::string()); + if (!window) + return; + window->bindings_->AttachTo(agent_host); + } + window->ScheduleShow(DevToolsToggleAction::Show()); +} + +// static +void DevToolsWindow::ToggleDevToolsWindow( + Browser* browser, + const DevToolsToggleAction& action) { + if (action.type() == DevToolsToggleAction::kToggle && + browser->is_devtools()) { + browser->tab_strip_model()->CloseAllTabs(); + return; + } + + ToggleDevToolsWindow( + browser->tab_strip_model()->GetActiveWebContents(), + action.type() == DevToolsToggleAction::kInspect, + action, ""); +} + +// static +void DevToolsWindow::OpenExternalFrontend( + Profile* profile, + const std::string& frontend_url, + const scoped_refptr<content::DevToolsAgentHost>& agent_host, + FrontendType frontend_type) { + DevToolsWindow* window = FindDevToolsWindow(agent_host.get()); + if (!window) { + window = Create(profile, nullptr, frontend_type, + DevToolsUI::GetProxyURL(frontend_url).spec(), false, + std::string(), std::string()); + if (!window) + return; + window->bindings_->AttachTo(agent_host); + window->close_on_detach_ = false; + } + + window->ScheduleShow(DevToolsToggleAction::Show()); +} + +// static +void DevToolsWindow::OpenNodeFrontendWindow(Profile* profile) { + for (DevToolsWindow* window : g_instances.Get()) { + if (window->frontend_type_ == kFrontendNode) { + window->ActivateWindow(); + return; + } + } + + DevToolsWindow* window = + Create(profile, nullptr, kFrontendNode, std::string(), false, + std::string(), std::string()); + if (!window) + return; + window->bindings_->AttachTo(DevToolsAgentHost::CreateForDiscovery()); + window->ScheduleShow(DevToolsToggleAction::Show()); +} + +// static +void DevToolsWindow::ToggleDevToolsWindow( + content::WebContents* inspected_web_contents, + bool force_open, + const DevToolsToggleAction& action, + const std::string& settings) { + scoped_refptr<DevToolsAgentHost> agent( + DevToolsAgentHost::GetOrCreateFor(inspected_web_contents)); + DevToolsWindow* window = FindDevToolsWindow(agent.get()); + bool do_open = force_open; + if (!window) { + Profile* profile = Profile::FromBrowserContext( + inspected_web_contents->GetBrowserContext()); + base::RecordAction(base::UserMetricsAction("DevTools_InspectRenderer")); + std::string panel = ""; + switch (action.type()) { + case DevToolsToggleAction::kInspect: + case DevToolsToggleAction::kShowElementsPanel: + panel = "elements"; + break; + case DevToolsToggleAction::kShowConsolePanel: + panel = "console"; + break; + case DevToolsToggleAction::kShow: + case DevToolsToggleAction::kToggle: + case DevToolsToggleAction::kReveal: + case DevToolsToggleAction::kNoOp: + break; + } + window = Create(profile, inspected_web_contents, kFrontendDefault, + std::string(), true, settings, panel); + if (!window) + return; + window->bindings_->AttachTo(agent.get()); + do_open = true; + } + + // Update toolbar to reflect DevTools changes. + window->UpdateBrowserToolbar(); + + // If window is docked and visible, we hide it on toggle. If window is + // undocked, we show (activate) it. + if (!window->is_docked_ || do_open) + window->ScheduleShow(action); + else + window->CloseWindow(); +} + +// static +void DevToolsWindow::InspectElement( + content::RenderFrameHost* inspected_frame_host, + int x, + int y) { + WebContents* web_contents = + WebContents::FromRenderFrameHost(inspected_frame_host); + scoped_refptr<DevToolsAgentHost> agent( + DevToolsAgentHost::GetOrCreateFor(web_contents)); + bool should_measure_time = FindDevToolsWindow(agent.get()) == NULL; + base::TimeTicks start_time = base::TimeTicks::Now(); + // TODO(loislo): we should initiate DevTools window opening from within + // renderer. Otherwise, we still can hit a race condition here. + OpenDevToolsWindow(web_contents, DevToolsToggleAction::ShowElementsPanel()); + DevToolsWindow* window = FindDevToolsWindow(agent.get()); + if (window) { + agent->InspectElement(window->bindings_, x, y); + if (should_measure_time) + window->inspect_element_start_time_ = start_time; + } +} + +void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) { + if (life_stage_ == kLoadCompleted) { + Show(action); + return; + } + + // Action will be done only after load completed. + action_on_load_ = action; + + if (!can_dock_) { + // No harm to show always-undocked window right away. + is_docked_ = false; + Show(DevToolsToggleAction::Show()); + } +} + +void DevToolsWindow::Show(const DevToolsToggleAction& action) { + if (life_stage_ == kClosing) + return; + + if (action.type() == DevToolsToggleAction::kNoOp) + return; + if (is_docked_) { + DCHECK(can_dock_); + Browser* inspected_browser = NULL; + int inspected_tab_index = -1; + FindInspectedBrowserAndTabIndex(GetInspectedWebContents(), + &inspected_browser, + &inspected_tab_index); + DCHECK(inspected_browser); + DCHECK(inspected_tab_index != -1); + + // Tell inspected browser to update splitter and switch to inspected panel. + BrowserWindow* inspected_window = inspected_browser->window(); + main_web_contents_->SetDelegate(this); + + TabStripModel* tab_strip_model = inspected_browser->tab_strip_model(); + tab_strip_model->ActivateTabAt(inspected_tab_index, true); + + inspected_window->UpdateDevTools(); + main_web_contents_->SetInitialFocus(); + inspected_window->Show(); + // On Aura, focusing once is not enough. Do it again. + // Note that focusing only here but not before isn't enough either. We just + // need to focus twice. + main_web_contents_->SetInitialFocus(); + + PrefsTabHelper::CreateForWebContents(main_web_contents_); + main_web_contents_->GetRenderViewHost()->SyncRendererPrefs(); + + DoAction(action); + return; + } + + // Avoid consecutive window switching if the devtools window has been opened + // and the Inspect Element shortcut is pressed in the inspected tab. + bool should_show_window = + !browser_ || (action.type() != DevToolsToggleAction::kInspect); + + if (!browser_) + CreateDevToolsBrowser(); + + if (should_show_window) { + browser_->window()->Show(); + main_web_contents_->SetInitialFocus(); + } + if (toolbox_web_contents_) + UpdateBrowserWindow(); + + DoAction(action); +} + +// static +bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents, + bool proceed, bool* proceed_to_fire_unload) { + DevToolsWindow* window = AsDevToolsWindow(frontend_contents); + if (!window) + return false; + if (!window->intercepted_page_beforeunload_) + return false; + window->BeforeUnloadFired(frontend_contents, proceed, + proceed_to_fire_unload); + return true; +} + +// static +bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) { + DevToolsWindow* window = + DevToolsWindow::GetInstanceForInspectedWebContents(contents); + if (!window || window->intercepted_page_beforeunload_) + return false; + + // Not yet loaded frontend will not handle beforeunload. + if (window->life_stage_ != kLoadCompleted) + return false; + + window->intercepted_page_beforeunload_ = true; + // Handle case of devtools inspecting another devtools instance by passing + // the call up to the inspecting devtools instance. + if (!DevToolsWindow::InterceptPageBeforeUnload(window->main_web_contents_)) { + window->main_web_contents_->DispatchBeforeUnload(); + } + return true; +} + +// static +bool DevToolsWindow::NeedsToInterceptBeforeUnload( + WebContents* contents) { + DevToolsWindow* window = + DevToolsWindow::GetInstanceForInspectedWebContents(contents); + return window && !window->intercepted_page_beforeunload_ && + window->life_stage_ == kLoadCompleted; +} + +// static +bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser( + Browser* browser) { + DCHECK(browser->is_devtools()); + // When FastUnloadController is used, devtools frontend will be detached + // from the browser window at this point which means we've already fired + // beforeunload. + if (browser->tab_strip_model()->empty()) + return true; + WebContents* contents = + browser->tab_strip_model()->GetWebContentsAt(0); + DevToolsWindow* window = AsDevToolsWindow(contents); + if (!window) + return false; + return window->intercepted_page_beforeunload_; +} + +// static +void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) { + DevToolsWindow* window = + DevToolsWindow::GetInstanceForInspectedWebContents(contents); + if (!window) + return; + window->intercepted_page_beforeunload_ = false; + // Propagate to devtools opened on devtools if any. + DevToolsWindow::OnPageCloseCanceled(window->main_web_contents_); +} + +DevToolsWindow::DevToolsWindow(FrontendType frontend_type, + Profile* profile, + WebContents* main_web_contents, + DevToolsUIBindings* bindings, + WebContents* inspected_web_contents, + bool can_dock) + : frontend_type_(frontend_type), + profile_(profile), + main_web_contents_(main_web_contents), + toolbox_web_contents_(nullptr), + bindings_(bindings), + browser_(nullptr), + is_docked_(true), + can_dock_(can_dock), + close_on_detach_(true), + // This initialization allows external front-end to work without changes. + // We don't wait for docking call, but instead immediately show undocked. + // Passing "dockSide=undocked" parameter ensures proper UI. + life_stage_(can_dock ? kNotLoaded : kIsDockedSet), + action_on_load_(DevToolsToggleAction::NoOp()), + intercepted_page_beforeunload_(false), + ready_for_test_(false) { + // Set up delegate, so we get fully-functional window immediately. + // It will not appear in UI though until |life_stage_ == kLoadCompleted|. + main_web_contents_->SetDelegate(this); + // Bindings take ownership over devtools as its delegate. + bindings_->SetDelegate(this); + data_use_measurement::DataUseWebContentsObserver::CreateForWebContents( + main_web_contents_); + // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a + // ZoomController. + zoom::ZoomController::CreateForWebContents(main_web_contents_); + zoom::ZoomController::FromWebContents(main_web_contents_) + ->SetShowsNotificationBubble(false); + + g_instances.Get().push_back(this); + + // There is no inspected_web_contents in case of various workers. + if (inspected_web_contents) + inspected_contents_observer_.reset( + new ObserverWithAccessor(inspected_web_contents)); + + // Initialize docked page to be of the right size. + if (can_dock_ && inspected_web_contents) { + content::RenderWidgetHostView* inspected_view = + inspected_web_contents->GetRenderWidgetHostView(); + if (inspected_view && main_web_contents_->GetRenderWidgetHostView()) { + gfx::Size size = inspected_view->GetViewBounds().size(); + main_web_contents_->GetRenderWidgetHostView()->SetSize(size); + } + } + + event_forwarder_.reset(new DevToolsEventForwarder(this)); + + // Tag the DevTools main WebContents with its TaskManager specific UserData + // so that it shows up in the task manager. + task_manager::WebContentsTags::CreateForDevToolsContents(main_web_contents_); + + std::vector<base::Callback<void(DevToolsWindow*)>> copy( + g_creation_callbacks.Get()); + for (const auto& callback : copy) + callback.Run(this); +} + +// static +DevToolsWindow* DevToolsWindow::Create( + Profile* profile, + content::WebContents* inspected_web_contents, + FrontendType frontend_type, + const std::string& frontend_url, + bool can_dock, + const std::string& settings, + const std::string& panel) { + if (profile->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled) || + base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) + return nullptr; + + if (inspected_web_contents) { + // Check for a place to dock. + Browser* browser = nullptr; + int tab; + if (!FindInspectedBrowserAndTabIndex(inspected_web_contents, + &browser, &tab) || + browser->is_type_popup()) { + can_dock = false; + } + } + + // Create WebContents with devtools. + GURL url( + GetDevToolsURL(profile, frontend_type, frontend_url, can_dock, panel)); + std::unique_ptr<WebContents> main_web_contents( + WebContents::Create(WebContents::CreateParams(profile))); + main_web_contents->GetController().LoadURL( + DecorateFrontendURL(url), content::Referrer(), + ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string()); + DevToolsUIBindings* bindings = + DevToolsUIBindings::ForWebContents(main_web_contents.get()); + if (!bindings) + return nullptr; + if (!settings.empty()) + SetPreferencesFromJson(profile, settings); + return new DevToolsWindow(frontend_type, profile, main_web_contents.release(), + bindings, inspected_web_contents, can_dock); +} + +// static +GURL DevToolsWindow::GetDevToolsURL(Profile* profile, + FrontendType frontend_type, + const std::string& frontend_url, + bool can_dock, + const std::string& panel) { + std::string url(!frontend_url.empty() ? frontend_url + : chrome::kChromeUIDevToolsURL); + std::string url_string(url + + ((url.find("?") == std::string::npos) ? "?" : "&")); + switch (frontend_type) { + case kFrontendRemote: + url_string += "&remoteFrontend=true"; + break; + case kFrontendWorker: + url_string += "&isSharedWorker=true"; + break; + case kFrontendNode: + url_string += "&nodeFrontend=true"; + // Fall through + case kFrontendV8: + url_string += "&v8only=true"; + break; + case kFrontendDefault: + default: + break; + } + + if (frontend_url.empty()) + url_string += "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec(); + if (can_dock) + url_string += "&can_dock=true"; + if (panel.size()) + url_string += "&panel=" + panel; + return DevToolsUIBindings::SanitizeFrontendURL(GURL(url_string)); +} + +// static +DevToolsWindow* DevToolsWindow::FindDevToolsWindow( + DevToolsAgentHost* agent_host) { + if (!agent_host || g_instances == NULL) + return NULL; + DevToolsWindows* instances = g_instances.Pointer(); + for (DevToolsWindows::iterator it(instances->begin()); it != instances->end(); + ++it) { + if ((*it)->bindings_->IsAttachedTo(agent_host)) + return *it; + } + return NULL; +} + +// static +DevToolsWindow* DevToolsWindow::AsDevToolsWindow( + content::WebContents* web_contents) { + if (!web_contents || g_instances == NULL) + return NULL; + DevToolsWindows* instances = g_instances.Pointer(); + for (DevToolsWindows::iterator it(instances->begin()); it != instances->end(); + ++it) { + if ((*it)->main_web_contents_ == web_contents) + return *it; + } + return NULL; +} + +WebContents* DevToolsWindow::OpenURLFromTab( + WebContents* source, + const content::OpenURLParams& params) { + DCHECK(source == main_web_contents_); + if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) { + WebContents* inspected_web_contents = GetInspectedWebContents(); + return inspected_web_contents ? + inspected_web_contents->OpenURL(params) : NULL; + } + bindings_->Reload(); + return main_web_contents_; +} + +void DevToolsWindow::ShowCertificateViewer( + scoped_refptr<net::X509Certificate> certificate) { + WebContents* inspected_contents = is_docked_ ? + GetInspectedWebContents() : main_web_contents_; + Browser* browser = NULL; + int tab = 0; + if (!FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab)) + return; + gfx::NativeWindow parent = browser->window()->GetNativeWindow(); + ::ShowCertificateViewer(inspected_contents, parent, certificate.get()); +} + +void DevToolsWindow::ActivateContents(WebContents* contents) { + if (is_docked_) { + WebContents* inspected_tab = GetInspectedWebContents(); + if (inspected_tab) + inspected_tab->GetDelegate()->ActivateContents(inspected_tab); + } else if (browser_) { + browser_->window()->Activate(); + } +} + +void DevToolsWindow::AddNewContents(WebContents* source, + WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_rect, + bool user_gesture, + bool* was_blocked) { + if (new_contents == toolbox_web_contents_) { + toolbox_web_contents_->SetDelegate( + new DevToolsToolboxDelegate(toolbox_web_contents_, + inspected_contents_observer_.get())); + if (main_web_contents_->GetRenderWidgetHostView() && + toolbox_web_contents_->GetRenderWidgetHostView()) { + gfx::Size size = + main_web_contents_->GetRenderWidgetHostView()->GetViewBounds().size(); + toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size); + } + UpdateBrowserWindow(); + return; + } + + WebContents* inspected_web_contents = GetInspectedWebContents(); + if (inspected_web_contents) { + inspected_web_contents->GetDelegate()->AddNewContents( + source, new_contents, disposition, initial_rect, user_gesture, + was_blocked); + } +} + +void DevToolsWindow::WebContentsCreated( + WebContents* source_contents, + int opener_render_process_id, + int opener_render_frame_id, + const std::string& frame_name, + const GURL& target_url, + WebContents* new_contents, + const base::Optional<content::WebContents::CreateParams>& create_params) { + if (target_url.SchemeIs(content::kChromeDevToolsScheme) && + target_url.path().rfind("toolbox.html") != std::string::npos) { + CHECK(can_dock_); + if (toolbox_web_contents_) + delete toolbox_web_contents_; + toolbox_web_contents_ = new_contents; + + // Tag the DevTools toolbox WebContents with its TaskManager specific + // UserData so that it shows up in the task manager. + task_manager::WebContentsTags::CreateForDevToolsContents( + toolbox_web_contents_); + data_use_measurement::DataUseWebContentsObserver::CreateForWebContents( + toolbox_web_contents_); + } +} + +void DevToolsWindow::CloseContents(WebContents* source) { + CHECK(is_docked_); + life_stage_ = kClosing; + UpdateBrowserWindow(); + // In case of docked main_web_contents_, we own it so delete here. + // Embedding DevTools window will be deleted as a result of + // DevToolsUIBindings destruction. + delete main_web_contents_; +} + +void DevToolsWindow::ContentsZoomChange(bool zoom_in) { + DCHECK(is_docked_); + zoom::PageZoom::Zoom(main_web_contents_, zoom_in ? content::PAGE_ZOOM_IN + : content::PAGE_ZOOM_OUT); +} + +void DevToolsWindow::BeforeUnloadFired(WebContents* tab, + bool proceed, + bool* proceed_to_fire_unload) { + if (!intercepted_page_beforeunload_) { + // Docked devtools window closed directly. + if (proceed) + bindings_->Detach(); + *proceed_to_fire_unload = proceed; + } else { + // Inspected page is attempting to close. + WebContents* inspected_web_contents = GetInspectedWebContents(); + if (proceed) { + inspected_web_contents->DispatchBeforeUnload(); + } else { + bool should_proceed; + inspected_web_contents->GetDelegate()->BeforeUnloadFired( + inspected_web_contents, false, &should_proceed); + DCHECK(!should_proceed); + } + *proceed_to_fire_unload = false; + } +} + +content::KeyboardEventProcessingResult DevToolsWindow::PreHandleKeyboardEvent( + WebContents* source, + const content::NativeWebKeyboardEvent& event) { + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) { + return inspected_window->PreHandleKeyboardEvent(event); + } + return content::KeyboardEventProcessingResult::NOT_HANDLED; +} + +void DevToolsWindow::HandleKeyboardEvent( + WebContents* source, + const content::NativeWebKeyboardEvent& event) { + if (event.windows_key_code == 0x08) { + // Do not navigate back in history on Windows (http://crbug.com/74156). + return; + } + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + inspected_window->HandleKeyboardEvent(event); +} + +content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager( + WebContents* source) { + return app_modal::JavaScriptDialogManager::GetInstance(); +} + +content::ColorChooser* DevToolsWindow::OpenColorChooser( + WebContents* web_contents, + SkColor initial_color, + const std::vector<content::ColorSuggestion>& suggestions) { + return chrome::ShowColorChooser(web_contents, initial_color); +} + +void DevToolsWindow::RunFileChooser(content::RenderFrameHost* render_frame_host, + const content::FileChooserParams& params) { + FileSelectHelper::RunFileChooser(render_frame_host, params); +} + +bool DevToolsWindow::PreHandleGestureEvent( + WebContents* source, + const blink::WebGestureEvent& event) { + // Disable pinch zooming. + return blink::WebInputEvent::IsPinchGestureEventType(event.GetType()); +} + +void DevToolsWindow::ShowCertificateViewerInDevTools( + content::WebContents* web_contents, + scoped_refptr<net::X509Certificate> certificate) { + ShowCertificateViewer(certificate); +} + +void DevToolsWindow::ActivateWindow() { + if (life_stage_ != kLoadCompleted) + return; + if (is_docked_ && GetInspectedBrowserWindow()) + main_web_contents_->Focus(); + else if (!is_docked_ && !browser_->window()->IsActive()) + browser_->window()->Activate(); +} + +void DevToolsWindow::CloseWindow() { + DCHECK(is_docked_); + life_stage_ = kClosing; + main_web_contents_->DispatchBeforeUnload(); +} + +void DevToolsWindow::Inspect(scoped_refptr<content::DevToolsAgentHost> host) { + DevToolsWindow::OpenDevToolsWindow(host, profile_); +} + +void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect& rect) { + DevToolsContentsResizingStrategy strategy(rect); + if (contents_resizing_strategy_.Equals(strategy)) + return; + + contents_resizing_strategy_.CopyFrom(strategy); + UpdateBrowserWindow(); +} + +void DevToolsWindow::InspectElementCompleted() { + if (!inspect_element_start_time_.is_null()) { + UMA_HISTOGRAM_TIMES("DevTools.InspectElement", + base::TimeTicks::Now() - inspect_element_start_time_); + inspect_element_start_time_ = base::TimeTicks(); + } +} + +void DevToolsWindow::SetIsDocked(bool dock_requested) { + if (life_stage_ == kClosing) + return; + + DCHECK(can_dock_ || !dock_requested); + if (!can_dock_) + dock_requested = false; + + bool was_docked = is_docked_; + is_docked_ = dock_requested; + + if (life_stage_ != kLoadCompleted) { + // This is a first time call we waited for to initialize. + life_stage_ = life_stage_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet; + if (life_stage_ == kLoadCompleted) + LoadCompleted(); + return; + } + + if (dock_requested == was_docked) + return; + + if (dock_requested && !was_docked) { + // Detach window from the external devtools browser. It will lead to + // the browser object's close and delete. Remove observer first. + TabStripModel* tab_strip_model = browser_->tab_strip_model(); + tab_strip_model->DetachWebContentsAt( + tab_strip_model->GetIndexOfWebContents(main_web_contents_)); + browser_ = NULL; + } else if (!dock_requested && was_docked) { + UpdateBrowserWindow(); + } + + Show(DevToolsToggleAction::Show()); +} + +void DevToolsWindow::OpenInNewTab(const std::string& url) { + content::OpenURLParams params(GURL(url), content::Referrer(), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui::PAGE_TRANSITION_LINK, false); + WebContents* inspected_web_contents = GetInspectedWebContents(); + if (!inspected_web_contents || !inspected_web_contents->OpenURL(params)) { + chrome::ScopedTabbedBrowserDisplayer displayer(profile_); + chrome::AddSelectedTabWithURL(displayer.browser(), GURL(url), + ui::PAGE_TRANSITION_LINK); + } +} + +void DevToolsWindow::SetWhitelistedShortcuts( + const std::string& message) { + event_forwarder_->SetWhitelistedShortcuts(message); +} + +void DevToolsWindow::SetEyeDropperActive(bool active) { + WebContents* web_contents = GetInspectedWebContents(); + if (!web_contents) + return; + if (active) { + eye_dropper_.reset(new DevToolsEyeDropper( + web_contents, base::Bind(&DevToolsWindow::ColorPickedInEyeDropper, + base::Unretained(this)))); + } else { + eye_dropper_.reset(); + } +} + +void DevToolsWindow::ColorPickedInEyeDropper(int r, int g, int b, int a) { + base::DictionaryValue color; + color.SetInteger("r", r); + color.SetInteger("g", g); + color.SetInteger("b", b); + color.SetInteger("a", a); + bindings_->CallClientFunction("DevToolsAPI.eyeDropperPickedColor", &color, + nullptr, nullptr); +} + +void DevToolsWindow::InspectedContentsClosing() { + if (!close_on_detach_) + return; + intercepted_page_beforeunload_ = false; + life_stage_ = kClosing; + main_web_contents_->ClosePage(); +} + +InfoBarService* DevToolsWindow::GetInfoBarService() { + return is_docked_ ? + InfoBarService::FromWebContents(GetInspectedWebContents()) : + InfoBarService::FromWebContents(main_web_contents_); +} + +void DevToolsWindow::RenderProcessGone(bool crashed) { + // Docked DevToolsWindow owns its main_web_contents_ and must delete it. + // Undocked main_web_contents_ are owned and handled by browser. + // see crbug.com/369932 + if (is_docked_) { + CloseContents(main_web_contents_); + } else if (browser_ && crashed) { + browser_->window()->Close(); + } +} + +void DevToolsWindow::OnLoadCompleted() { + // First seed inspected tab id for extension APIs. + WebContents* inspected_web_contents = GetInspectedWebContents(); + if (inspected_web_contents) { + SessionTabHelper* session_tab_helper = + SessionTabHelper::FromWebContents(inspected_web_contents); + if (session_tab_helper) { + base::Value tabId(session_tab_helper->session_id().id()); + bindings_->CallClientFunction("DevToolsAPI.setInspectedTabId", + &tabId, NULL, NULL); + } + } + + if (life_stage_ == kClosing) + return; + + // We could be in kLoadCompleted state already if frontend reloads itself. + if (life_stage_ != kLoadCompleted) { + // Load is completed when both kIsDockedSet and kOnLoadFired happened. + // Here we set kOnLoadFired. + life_stage_ = life_stage_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired; + } + if (life_stage_ == kLoadCompleted) + LoadCompleted(); +} + +void DevToolsWindow::ReadyForTest() { + ready_for_test_ = true; + if (!ready_for_test_callback_.is_null()) { + ready_for_test_callback_.Run(); + ready_for_test_callback_ = base::Closure(); + } +} + +void DevToolsWindow::CreateDevToolsBrowser() { + PrefService* prefs = profile_->GetPrefs(); + if (!prefs->GetDictionary(prefs::kAppWindowPlacement)->HasKey(kDevToolsApp)) { + DictionaryPrefUpdate update(prefs, prefs::kAppWindowPlacement); + base::DictionaryValue* wp_prefs = update.Get(); + auto dev_tools_defaults = base::MakeUnique<base::DictionaryValue>(); + dev_tools_defaults->SetInteger("left", 100); + dev_tools_defaults->SetInteger("top", 100); + dev_tools_defaults->SetInteger("right", 740); + dev_tools_defaults->SetInteger("bottom", 740); + dev_tools_defaults->SetBoolean("maximized", false); + dev_tools_defaults->SetBoolean("always_on_top", false); + wp_prefs->Set(kDevToolsApp, std::move(dev_tools_defaults)); + } + + browser_ = new Browser(Browser::CreateParams::CreateForDevTools(profile_)); + browser_->tab_strip_model()->AddWebContents( + main_web_contents_, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL, + TabStripModel::ADD_ACTIVE); + main_web_contents_->GetRenderViewHost()->SyncRendererPrefs(); +} + +BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() { + Browser* browser = NULL; + int tab; + return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(), + &browser, &tab) ? + browser->window() : NULL; +} + +void DevToolsWindow::DoAction(const DevToolsToggleAction& action) { + switch (action.type()) { + case DevToolsToggleAction::kInspect: + bindings_->CallClientFunction("DevToolsAPI.enterInspectElementMode", NULL, + NULL, NULL); + break; + + case DevToolsToggleAction::kShowElementsPanel: + case DevToolsToggleAction::kShowConsolePanel: + case DevToolsToggleAction::kShow: + case DevToolsToggleAction::kToggle: + // Do nothing. + break; + + case DevToolsToggleAction::kReveal: { + const DevToolsToggleAction::RevealParams* params = + action.params(); + CHECK(params); + base::Value url_value(params->url); + base::Value line_value(static_cast<int>(params->line_number)); + base::Value column_value(static_cast<int>(params->column_number)); + bindings_->CallClientFunction("DevToolsAPI.revealSourceLine", + &url_value, &line_value, &column_value); + break; + } + default: + NOTREACHED(); + break; + } +} + +void DevToolsWindow::UpdateBrowserToolbar() { + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + inspected_window->UpdateToolbar(NULL); +} + +void DevToolsWindow::UpdateBrowserWindow() { + BrowserWindow* inspected_window = GetInspectedBrowserWindow(); + if (inspected_window) + inspected_window->UpdateDevTools(); +} + +WebContents* DevToolsWindow::GetInspectedWebContents() { + return inspected_contents_observer_ + ? inspected_contents_observer_->web_contents() + : NULL; +} + +void DevToolsWindow::LoadCompleted() { + Show(action_on_load_); + action_on_load_ = DevToolsToggleAction::NoOp(); + if (!load_completed_callback_.is_null()) { + load_completed_callback_.Run(); + load_completed_callback_ = base::Closure(); + } +} + +void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) { + if (life_stage_ == kLoadCompleted || life_stage_ == kClosing) { + if (!closure.is_null()) + closure.Run(); + return; + } + load_completed_callback_ = closure; +} + +bool DevToolsWindow::ForwardKeyboardEvent( + const content::NativeWebKeyboardEvent& event) { + return event_forwarder_->ForwardEvent(event); +} + +bool DevToolsWindow::ReloadInspectedWebContents(bool bypass_cache) { + // Only route reload via front-end if the agent is attached. + WebContents* wc = GetInspectedWebContents(); + if (!wc || wc->GetCrashedStatus() != base::TERMINATION_STATUS_STILL_RUNNING) + return false; + base::Value bypass_cache_value(bypass_cache); + bindings_->CallClientFunction("DevToolsAPI.reloadInspectedPage", + &bypass_cache_value, nullptr, nullptr); + return true; +} diff --git a/chromium/chrome/browser/devtools/devtools_window.h b/chromium/chrome/browser/devtools/devtools_window.h new file mode 100644 index 00000000000..8058d9c827f --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_window.h @@ -0,0 +1,383 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_ + +#include "base/macros.h" +#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h" +#include "chrome/browser/devtools/devtools_toggle_action.h" +#include "chrome/browser/devtools/devtools_ui_bindings.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/browser/web_contents_observer.h" + +class Browser; +class BrowserWindow; +class DevToolsWindowTesting; +class DevToolsEventForwarder; +class DevToolsEyeDropper; + +namespace content { +class DevToolsAgentHost; +struct NativeWebKeyboardEvent; +class RenderFrameHost; +} + +namespace user_prefs { +class PrefRegistrySyncable; +} + +class DevToolsWindow : public DevToolsUIBindings::Delegate, + public content::WebContentsDelegate { + public: + class ObserverWithAccessor : public content::WebContentsObserver { + public: + explicit ObserverWithAccessor(content::WebContents* web_contents); + ~ObserverWithAccessor() override; + + private: + DISALLOW_COPY_AND_ASSIGN(ObserverWithAccessor); + }; + + static const char kDevToolsApp[]; + + ~DevToolsWindow() override; + + static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); + + // Return the DevToolsWindow for the given WebContents if one exists, + // otherwise NULL. + static DevToolsWindow* GetInstanceForInspectedWebContents( + content::WebContents* inspected_web_contents); + + // Return the docked DevTools WebContents for the given inspected WebContents + // if one exists and should be shown in browser window, otherwise NULL. + // This method will return only fully initialized window ready to be + // presented in UI. + // If |out_strategy| is not NULL, it will contain resizing strategy. + // For immediately-ready-to-use but maybe not yet fully initialized DevTools + // use |GetInstanceForInspectedRenderViewHost| instead. + static content::WebContents* GetInTabWebContents( + content::WebContents* inspected_tab, + DevToolsContentsResizingStrategy* out_strategy); + + static bool IsDevToolsWindow(content::WebContents* web_contents); + static DevToolsWindow* AsDevToolsWindow(content::WebContents* web_contents); + static DevToolsWindow* FindDevToolsWindow(content::DevToolsAgentHost*); + + // Open or reveal DevTools window, and perform the specified action. + // How to get pointer to the created window see comments for + // ToggleDevToolsWindow(). + static void OpenDevToolsWindow(content::WebContents* inspected_web_contents, + const DevToolsToggleAction& action); + + // Open or reveal DevTools window, with no special action. + // How to get pointer to the created window see comments for + // ToggleDevToolsWindow(). + static void OpenDevToolsWindow(content::WebContents* inspected_web_contents); + + // Open or reveal DevTools window, with no special action. Use |profile| to + // open client window in, default to |host|'s profile if none given. + static void OpenDevToolsWindow( + scoped_refptr<content::DevToolsAgentHost> host, + Profile* profile); + + // Perform specified action for current WebContents inside a |browser|. + // This may close currently open DevTools window. + // If DeveloperToolsDisabled policy is set, no DevTools window created. + // In case if needed pointer to the created window one should use + // DevToolsAgentHost and DevToolsWindow::FindDevToolsWindow(). E.g.: + // + // scoped_refptr<content::DevToolsAgentHost> agent( + // content::DevToolsAgentHost::GetOrCreateFor(inspected_web_contents)); + // DevToolsWindow::ToggleDevToolsWindow( + // inspected_web_contents, DevToolsToggleAction::Show()); + // DevToolsWindow* window = DevToolsWindow::FindDevToolsWindow(agent.get()); + // + static void ToggleDevToolsWindow( + Browser* browser, + const DevToolsToggleAction& action); + + // Node frontend is always undocked. + static void OpenNodeFrontendWindow(Profile* profile); + + // Worker frontend is always undocked. + static void OpenDevToolsWindowForWorker( + Profile* profile, + const scoped_refptr<content::DevToolsAgentHost>& worker_agent); + + static void InspectElement(content::RenderFrameHost* inspected_frame_host, + int x, + int y); + + // Sets closure to be called after load is done. If already loaded, calls + // closure immediately. + void SetLoadCompletedCallback(const base::Closure& closure); + + // Forwards an unhandled keyboard event to the DevTools frontend. + bool ForwardKeyboardEvent(const content::NativeWebKeyboardEvent& event); + + // Reloads inspected web contents as if it was triggered from DevTools. + // Returns true if it has successfully handled reload, false if the caller + // is to proceed reload without DevTools interception. + bool ReloadInspectedWebContents(bool bypass_cache); + + content::WebContents* OpenURLFromTab( + content::WebContents* source, + const content::OpenURLParams& params) override; + + void ShowCertificateViewer(scoped_refptr<net::X509Certificate> certificate); + + // BeforeUnload interception //////////////////////////////////////////////// + + // In order to preserve any edits the user may have made in devtools, the + // beforeunload event of the inspected page is hooked - devtools gets the + // first shot at handling beforeunload and presents a dialog to the user. If + // the user accepts the dialog then the script is given a chance to handle + // it. This way 2 dialogs may be displayed: one from the devtools asking the + // user to confirm that they're ok with their devtools edits going away and + // another from the webpage as the result of its beforeunload handler. + // The following set of methods handle beforeunload event flow through + // devtools window. When the |contents| with devtools opened on them are + // getting closed, the following sequence of calls takes place: + // 1. |DevToolsWindow::InterceptPageBeforeUnload| is called and indicates + // whether devtools intercept the beforeunload event. + // If InterceptPageBeforeUnload() returns true then the following steps + // will take place; otherwise only step 4 will be reached and none of the + // corresponding functions in steps 2 & 3 will get called. + // 2. |DevToolsWindow::InterceptPageBeforeUnload| fires beforeunload event + // for devtools frontend, which will asynchronously call + // |WebContentsDelegate::BeforeUnloadFired| method. + // In case of docked devtools window, devtools are set as a delegate for + // its frontend, so method |DevToolsWindow::BeforeUnloadFired| will be + // called directly. + // If devtools window is undocked it's not set as the delegate so the call + // to BeforeUnloadFired is proxied through HandleBeforeUnload() rather + // than getting called directly. + // 3a. If |DevToolsWindow::BeforeUnloadFired| is called with |proceed|=false + // it calls throught to the content's BeforeUnloadFired(), which from the + // WebContents perspective looks the same as the |content|'s own + // beforeunload dialog having had it's 'stay on this page' button clicked. + // 3b. If |proceed| = true, then it fires beforeunload event on |contents| + // and everything proceeds as it normally would without the Devtools + // interception. + // 4. If the user cancels the dialog put up by either the WebContents or + // devtools frontend, then |contents|'s |BeforeUnloadFired| callback is + // called with the proceed argument set to false, this causes + // |DevToolsWindow::OnPageCloseCancelled| to be called. + + // Devtools window in undocked state is not set as a delegate of + // its frontend. Instead, an instance of browser is set as the delegate, and + // thus beforeunload event callback from devtools frontend is not delivered + // to the instance of devtools window, which is solely responsible for + // managing custom beforeunload event flow. + // This is a helper method to route callback from + // |Browser::BeforeUnloadFired| back to |DevToolsWindow::BeforeUnloadFired|. + // * |proceed| - true if the user clicked 'ok' in the beforeunload dialog, + // false otherwise. + // * |proceed_to_fire_unload| - output parameter, whether we should continue + // to fire the unload event or stop things here. + // Returns true if devtools window is in a state of intercepting beforeunload + // event and if it will manage unload process on its own. + static bool HandleBeforeUnload(content::WebContents* contents, + bool proceed, + bool* proceed_to_fire_unload); + + // Returns true if this contents beforeunload event was intercepted by + // devtools and false otherwise. If the event was intercepted, caller should + // not fire beforeunlaod event on |contents| itself as devtools window will + // take care of it, otherwise caller should continue handling the event as + // usual. + static bool InterceptPageBeforeUnload(content::WebContents* contents); + + // Returns true if devtools browser has already fired its beforeunload event + // as a result of beforeunload event interception. + static bool HasFiredBeforeUnloadEventForDevToolsBrowser(Browser* browser); + + // Returns true if devtools window would like to hook beforeunload event + // of this |contents|. + static bool NeedsToInterceptBeforeUnload(content::WebContents* contents); + + // Notify devtools window that closing of |contents| was cancelled + // by user. + static void OnPageCloseCanceled(content::WebContents* contents); + + content::WebContents* GetInspectedWebContents(); + + private: + friend class DevToolsWindowTesting; + friend class DevToolsWindowCreationObserver; + + using CreationCallback = base::Callback<void(DevToolsWindow*)>; + static void AddCreationCallbackForTest(const CreationCallback& callback); + static void RemoveCreationCallbackForTest(const CreationCallback& callback); + + static void OpenDevToolsWindowForFrame( + Profile* profile, + const scoped_refptr<content::DevToolsAgentHost>& agent_host); + + // DevTools lifecycle typically follows this way: + // - Toggle/Open: client call; + // - Create; + // - ScheduleShow: setup window to be functional, but not yet show; + // - DocumentOnLoadCompletedInMainFrame: frontend loaded; + // - SetIsDocked: frontend decided on docking state; + // - OnLoadCompleted: ready to present frontend; + // - Show: actually placing frontend WebContents to a Browser or docked place; + // - DoAction: perform action passed in Toggle/Open; + // - ...; + // - CloseWindow: initiates before unload handling; + // - CloseContents: destroys frontend; + // - DevToolsWindow is dead once it's main_web_contents dies. + enum LifeStage { + kNotLoaded, + kOnLoadFired, // Implies SetIsDocked was not yet called. + kIsDockedSet, // Implies DocumentOnLoadCompleted was not yet called. + kLoadCompleted, + kClosing + }; + + enum FrontendType { + kFrontendDefault, + kFrontendRemote, + kFrontendWorker, + kFrontendV8, + kFrontendNode + }; + + DevToolsWindow(FrontendType frontend_type, + Profile* profile, + content::WebContents* main_web_contents, + DevToolsUIBindings* bindings, + content::WebContents* inspected_web_contents, + bool can_dock); + + // External frontend is always undocked. + static void OpenExternalFrontend( + Profile* profile, + const std::string& frontend_uri, + const scoped_refptr<content::DevToolsAgentHost>& agent_host, + FrontendType frontend_type); + + static DevToolsWindow* Create(Profile* profile, + content::WebContents* inspected_web_contents, + FrontendType frontend_type, + const std::string& frontend_url, + bool can_dock, + const std::string& settings, + const std::string& panel); + static GURL GetDevToolsURL(Profile* profile, + FrontendType frontend_type, + const std::string& frontend_url, + bool can_dock, + const std::string& panel); + + static DevToolsWindow* CreateDevToolsWindowForWorker(Profile* profile); + static void ToggleDevToolsWindow( + content::WebContents* web_contents, + bool force_open, + const DevToolsToggleAction& action, + const std::string& settings); + + // content::WebContentsDelegate: + void ActivateContents(content::WebContents* contents) override; + void AddNewContents(content::WebContents* source, + content::WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_rect, + bool user_gesture, + bool* was_blocked) override; + void WebContentsCreated( + content::WebContents* source_contents, + int opener_render_process_id, + int opener_render_frame_id, + const std::string& frame_name, + const GURL& target_url, + content::WebContents* new_contents, + const base::Optional<content::WebContents::CreateParams>& create_params) + override; + void CloseContents(content::WebContents* source) override; + void ContentsZoomChange(bool zoom_in) override; + void BeforeUnloadFired(content::WebContents* tab, + bool proceed, + bool* proceed_to_fire_unload) override; + content::KeyboardEventProcessingResult PreHandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) override; + void HandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) override; + content::JavaScriptDialogManager* GetJavaScriptDialogManager( + content::WebContents* source) override; + content::ColorChooser* OpenColorChooser( + content::WebContents* web_contents, + SkColor color, + const std::vector<content::ColorSuggestion>& suggestions) override; + void RunFileChooser(content::RenderFrameHost* render_frame_host, + const content::FileChooserParams& params) override; + bool PreHandleGestureEvent(content::WebContents* source, + const blink::WebGestureEvent& event) override; + void ShowCertificateViewerInDevTools( + content::WebContents* web_contents, + scoped_refptr<net::X509Certificate> certificate) override; + + // content::DevToolsUIBindings::Delegate overrides + void ActivateWindow() override; + void CloseWindow() override; + void Inspect(scoped_refptr<content::DevToolsAgentHost> host) override; + void SetInspectedPageBounds(const gfx::Rect& rect) override; + void InspectElementCompleted() override; + void SetIsDocked(bool is_docked) override; + void OpenInNewTab(const std::string& url) override; + void SetWhitelistedShortcuts(const std::string& message) override; + void SetEyeDropperActive(bool active) override; + void OpenNodeFrontend() override; + void InspectedContentsClosing() override; + void OnLoadCompleted() override; + void ReadyForTest() override; + InfoBarService* GetInfoBarService() override; + void RenderProcessGone(bool crashed) override; + + void ColorPickedInEyeDropper(int r, int g, int b, int a); + void CreateDevToolsBrowser(); + BrowserWindow* GetInspectedBrowserWindow(); + void ScheduleShow(const DevToolsToggleAction& action); + void Show(const DevToolsToggleAction& action); + void DoAction(const DevToolsToggleAction& action); + void LoadCompleted(); + void UpdateBrowserToolbar(); + void UpdateBrowserWindow(); + + std::unique_ptr<ObserverWithAccessor> inspected_contents_observer_; + + FrontendType frontend_type_; + Profile* profile_; + content::WebContents* main_web_contents_; + content::WebContents* toolbox_web_contents_; + DevToolsUIBindings* bindings_; + Browser* browser_; + bool is_docked_; + const bool can_dock_; + bool close_on_detach_; + LifeStage life_stage_; + DevToolsToggleAction action_on_load_; + DevToolsContentsResizingStrategy contents_resizing_strategy_; + // True if we're in the process of handling a beforeunload event originating + // from the inspected webcontents, see InterceptPageBeforeUnload for details. + bool intercepted_page_beforeunload_; + base::Closure load_completed_callback_; + base::Closure close_callback_; + bool ready_for_test_; + base::Closure ready_for_test_callback_; + + base::TimeTicks inspect_element_start_time_; + std::unique_ptr<DevToolsEventForwarder> event_forwarder_; + std::unique_ptr<DevToolsEyeDropper> eye_dropper_; + + friend class DevToolsEventForwarder; + DISALLOW_COPY_AND_ASSIGN(DevToolsWindow); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_H_ diff --git a/chromium/chrome/browser/devtools/devtools_window_testing.cc b/chromium/chrome/browser/devtools/devtools_window_testing.cc new file mode 100644 index 00000000000..e46c36a02e4 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_window_testing.cc @@ -0,0 +1,207 @@ +// 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/devtools_window_testing.h" + +#include "base/lazy_instance.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/devtools/devtools_window.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tabs/tab_strip_model.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 "content/public/test/test_utils.h" + +namespace { + +const char kHarnessScript[] = "Tests.js"; + +typedef std::vector<DevToolsWindowTesting*> DevToolsWindowTestings; +base::LazyInstance<DevToolsWindowTestings>::Leaky g_instances = + LAZY_INSTANCE_INITIALIZER; + +} + +DevToolsWindowTesting::DevToolsWindowTesting(DevToolsWindow* window) + : devtools_window_(window) { + DCHECK(window); + window->close_callback_ = + base::Bind(&DevToolsWindowTesting::WindowClosed, window); + g_instances.Get().push_back(this); +} + +DevToolsWindowTesting::~DevToolsWindowTesting() { + DevToolsWindowTestings* instances = g_instances.Pointer(); + DevToolsWindowTestings::iterator it( + std::find(instances->begin(), instances->end(), this)); + DCHECK(it != instances->end()); + instances->erase(it); + if (!close_callback_.is_null()) { + close_callback_.Run(); + close_callback_ = base::Closure(); + } +} + +// static +DevToolsWindowTesting* DevToolsWindowTesting::Get(DevToolsWindow* window) { + DevToolsWindowTesting* testing = DevToolsWindowTesting::Find(window); + if (!testing) + testing = new DevToolsWindowTesting(window); + return testing; +} + +// static +DevToolsWindowTesting* DevToolsWindowTesting::Find(DevToolsWindow* window) { + if (g_instances == NULL) + return NULL; + DevToolsWindowTestings* instances = g_instances.Pointer(); + for (DevToolsWindowTestings::iterator it(instances->begin()); + it != instances->end(); + ++it) { + if ((*it)->devtools_window_ == window) + return *it; + } + return NULL; +} + +Browser* DevToolsWindowTesting::browser() { + return devtools_window_->browser_; +} + +content::WebContents* DevToolsWindowTesting::main_web_contents() { + return devtools_window_->main_web_contents_; +} + +content::WebContents* DevToolsWindowTesting::toolbox_web_contents() { + return devtools_window_->toolbox_web_contents_; +} + +void DevToolsWindowTesting::SetInspectedPageBounds(const gfx::Rect& bounds) { + devtools_window_->SetInspectedPageBounds(bounds); +} + +void DevToolsWindowTesting::SetCloseCallback(const base::Closure& closure) { + close_callback_ = closure; +} + +// static +void DevToolsWindowTesting::WindowClosed(DevToolsWindow* window) { + DevToolsWindowTesting* testing = DevToolsWindowTesting::Find(window); + if (testing) + delete testing; +} + +// static +void DevToolsWindowTesting::WaitForDevToolsWindowLoad(DevToolsWindow* window) { + if (!window->ready_for_test_) { + scoped_refptr<content::MessageLoopRunner> runner = + new content::MessageLoopRunner; + window->ready_for_test_callback_ = runner->QuitClosure(); + runner->Run(); + } + base::string16 harness = base::UTF8ToUTF16( + content::DevToolsFrontendHost::GetFrontendResource(kHarnessScript)); + window->main_web_contents_->GetMainFrame()->ExecuteJavaScript(harness); +} + +// static +DevToolsWindow* DevToolsWindowTesting::OpenDevToolsWindowSync( + content::WebContents* inspected_web_contents, + bool is_docked) { + std::string settings = is_docked ? + "{\"isUnderTest\": true, \"currentDockState\":\"\\\"bottom\\\"\"}" : + "{\"isUnderTest\": true, \"currentDockState\":\"\\\"undocked\\\"\"}"; + scoped_refptr<content::DevToolsAgentHost> agent( + content::DevToolsAgentHost::GetOrCreateFor(inspected_web_contents)); + DevToolsWindow::ToggleDevToolsWindow( + inspected_web_contents, true, DevToolsToggleAction::Show(), settings); + DevToolsWindow* window = DevToolsWindow::FindDevToolsWindow(agent.get()); + WaitForDevToolsWindowLoad(window); + return window; +} + +// static +DevToolsWindow* DevToolsWindowTesting::OpenDevToolsWindowSync( + Browser* browser, + bool is_docked) { + return OpenDevToolsWindowSync( + browser->tab_strip_model()->GetActiveWebContents(), is_docked); +} + +// static +DevToolsWindow* DevToolsWindowTesting::OpenDevToolsWindowForWorkerSync( + Profile* profile, content::DevToolsAgentHost* worker_agent) { + DevToolsWindow::OpenDevToolsWindowForWorker( + profile, worker_agent); + DevToolsWindow* window = DevToolsWindow::FindDevToolsWindow(worker_agent); + WaitForDevToolsWindowLoad(window); + return window; +} + +// static +void DevToolsWindowTesting::CloseDevToolsWindow( + DevToolsWindow* window) { + if (window->is_docked_) { + window->CloseWindow(); + } else { + window->browser_->window()->Close(); + } +} + +// static +void DevToolsWindowTesting::CloseDevToolsWindowSync( + DevToolsWindow* window) { + scoped_refptr<content::MessageLoopRunner> runner = + new content::MessageLoopRunner; + DevToolsWindowTesting::Get(window)->SetCloseCallback(runner->QuitClosure()); + CloseDevToolsWindow(window); + runner->Run(); +} + +// DevToolsWindowCreationObserver --------------------------------------------- + +DevToolsWindowCreationObserver::DevToolsWindowCreationObserver() + : creation_callback_(base::Bind( + &DevToolsWindowCreationObserver::DevToolsWindowCreated, + base::Unretained(this))) { + DevToolsWindow::AddCreationCallbackForTest(creation_callback_); +} + +DevToolsWindowCreationObserver::~DevToolsWindowCreationObserver() { + DevToolsWindow::RemoveCreationCallbackForTest(creation_callback_); +} + +void DevToolsWindowCreationObserver::Wait() { + if (devtools_windows_.size()) + return; + runner_ = new content::MessageLoopRunner(); + runner_->Run(); +} + +void DevToolsWindowCreationObserver::WaitForLoad() { + Wait(); + if (devtools_window()) + DevToolsWindowTesting::WaitForDevToolsWindowLoad(devtools_window()); +} + +void DevToolsWindowCreationObserver::DevToolsWindowCreated( + DevToolsWindow* devtools_window) { + devtools_windows_.push_back(devtools_window); + if (runner_.get()) { + runner_->QuitClosure().Run(); + runner_ = nullptr; + } +} + +DevToolsWindow* DevToolsWindowCreationObserver::devtools_window() { + return !devtools_windows_.empty() ? devtools_windows_.back() : nullptr; +} + +void DevToolsWindowCreationObserver::CloseAllSync() { + for (DevToolsWindow* window : devtools_windows_) + DevToolsWindowTesting::CloseDevToolsWindowSync(window); + devtools_windows_.clear(); +} diff --git a/chromium/chrome/browser/devtools/devtools_window_testing.h b/chromium/chrome/browser/devtools/devtools_window_testing.h new file mode 100644 index 00000000000..19fd9ee55d8 --- /dev/null +++ b/chromium/chrome/browser/devtools/devtools_window_testing.h @@ -0,0 +1,88 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_TESTING_H_ +#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_TESTING_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "chrome/browser/devtools/devtools_window.h" +#include "ui/gfx/geometry/rect.h" + +class Browser; +class Profile; + +namespace content { +class DevToolsAgentHost; +class MessageLoopRunner; +class WebContents; +} + +class DevToolsWindowTesting { + public: + virtual ~DevToolsWindowTesting(); + + // The following methods block until DevToolsWindow is completely loaded. + static DevToolsWindow* OpenDevToolsWindowSync( + content::WebContents* inspected_web_contents, + bool is_docked); + static DevToolsWindow* OpenDevToolsWindowSync( + Browser* browser, bool is_docked); + static DevToolsWindow* OpenDevToolsWindowForWorkerSync( + Profile* profile, content::DevToolsAgentHost* worker_agent); + + // Closes the window like it was user-initiated. + static void CloseDevToolsWindow(DevToolsWindow* window); + // Blocks until window is closed. + static void CloseDevToolsWindowSync(DevToolsWindow* window); + + static DevToolsWindowTesting* Get(DevToolsWindow* window); + + Browser* browser(); + content::WebContents* main_web_contents(); + content::WebContents* toolbox_web_contents(); + void SetInspectedPageBounds(const gfx::Rect& bounds); + void SetCloseCallback(const base::Closure& closure); + + private: + friend class DevToolsWindow; + friend class DevToolsWindowCreationObserver; + + explicit DevToolsWindowTesting(DevToolsWindow* window); + static void WaitForDevToolsWindowLoad(DevToolsWindow* window); + static void WindowClosed(DevToolsWindow* window); + static DevToolsWindowTesting* Find(DevToolsWindow* window); + + DevToolsWindow* devtools_window_; + base::Closure close_callback_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsWindowTesting); +}; + +class DevToolsWindowCreationObserver { + public: + DevToolsWindowCreationObserver(); + ~DevToolsWindowCreationObserver(); + + using DevToolsWindows = std::vector<DevToolsWindow*>; + const DevToolsWindows& devtools_windows() { return devtools_windows_; } + DevToolsWindow* devtools_window(); + + void Wait(); + void WaitForLoad(); + void CloseAllSync(); + + private: + friend class DevToolsWindow; + + void DevToolsWindowCreated(DevToolsWindow* devtools_window); + + base::Callback<void(DevToolsWindow*)> creation_callback_; + DevToolsWindows devtools_windows_; + scoped_refptr<content::MessageLoopRunner> runner_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsWindowCreationObserver); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_WINDOW_TESTING_H_ diff --git a/chromium/chrome/browser/devtools/frontend/devtools_discovery_page.html b/chromium/chrome/browser/devtools/frontend/devtools_discovery_page.html new file mode 100644 index 00000000000..d10b8588f21 --- /dev/null +++ b/chromium/chrome/browser/devtools/frontend/devtools_discovery_page.html @@ -0,0 +1,197 @@ +<html> +<head> +<title>Inspectable pages</title> +<style> +body { + color: #222; + font-family: Helvetica, Arial, sans-serif; + margin: 0; + text-shadow: rgba(255, 255, 255, 0.496094) 0px 1px 0px; +} + +#caption { + font-size: 16px; + margin-top: 15px; + margin-bottom: 10px; + margin-left: 20px; + height: 20px; + text-align: left; +} + +#items { + display: flex; + flex-direction: column; + margin: 10px; +} + +.item { + color: #222; + display: flex; + flex-direction: row; + text-decoration: none; + padding: 10px; + transition-property: background-color, border-color; + transition: background-color 0.15s, 0.15s; + transition-delay: 0ms, 0ms; +} + +.item:not(.connected):hover { + background-color: rgba(242, 242, 242, 1); + border-color: rgba(110, 116, 128, 1); + color: black; +} + +.item.connected:hover { + border-color: rgba(184, 184, 184, 1); + color: rgb(110, 116, 128); +} + +.item.custom { + cursor: pointer; +} + +.description { + display: flex; + flex-direction: column; +} + +.title, .subtitle, .custom-url { + font-size: 13px; + margin: 4px 0px 0px 6px; + overflow: hidden; + padding-left: 20px; +} + +.title { + background-repeat: no-repeat; + background-size: 16px; + font-size: 15px; +} + +.custom-url { + display: flex; +} + +.custom-url-label { + flex: 0 0 auto; +} + +.custom-url-value { + font-family: monospace; + margin-left: 1em; +} + +</style> + +<script> + +function onLoad() { + var tabsListRequest = new XMLHttpRequest(); + tabsListRequest.open('GET', '/json/list', true); + tabsListRequest.onreadystatechange = onReady; + tabsListRequest.send(); +} + +function onReady() { + if(this.readyState == 4 && this.status == 200) { + if(this.response != null) + var responseJSON = JSON.parse(this.response); + for (var i = 0; i < responseJSON.length; ++i) + appendItem(responseJSON[i]); + } +} + +function customFrontendURL(url) { + if (!url || !window.location.hash) + return null; + + var hashParams = new URLSearchParams(location.hash.substring(1)); + if (!hashParams.get("custom")) + return null; + + var searchIndex = url.indexOf("?"); + if (searchIndex === -1) + return null; + var originalParams = url.substring(searchIndex + 1); + if (hashParams.get("experiments")) + originalParams += "&experiments=true"; + + return "chrome-devtools://devtools/custom/inspector.html?" + originalParams; +} + +function appendItem(item_object) { + var item_element; + var customURL = customFrontendURL(item_object.devtoolsFrontendUrl); + if (customURL) { + item_element = document.createElement('div'); + item_element.title = item_object.title; + item_element.className = 'custom'; + } else if (item_object.devtoolsFrontendUrl) { + item_element = document.createElement('a'); + item_element.href = item_object.devtoolsFrontendUrl; + item_element.title = item_object.title; + } else { + item_element = document.createElement('div'); + item_element.className = 'connected'; + item_element.title = 'The tab already has an active debug session'; + } + item_element.classList.add('item'); + + var description = document.createElement('div'); + description.className = 'description'; + + var title = document.createElement('div'); + title.className = 'title'; + title.textContent = item_object.description || item_object.title; + if (item_object.faviconUrl) { + title.style.cssText = 'background-image:url(' + + item_object.faviconUrl + ')'; + } + description.appendChild(title); + + var subtitle = document.createElement('div'); + subtitle.className = 'subtitle'; + subtitle.textContent = (item_object.url || '').substring(0, 300); + description.appendChild(subtitle); + + if (customURL) { + var urlContainer = document.createElement('div'); + urlContainer.classList.add("custom-url"); + var urlLabel = document.createElement('div'); + urlLabel.classList.add("custom-url-label"); + urlLabel.textContent = "Click to copy URL:"; + urlContainer.appendChild(urlLabel); + var urlValue = document.createElement('div'); + urlValue.classList.add("custom-url-value"); + urlValue.textContent = customURL; + urlContainer.appendChild(urlValue); + description.appendChild(urlContainer); + item_element.addEventListener('click', selectNodeText.bind(null, urlValue)); + } + + item_element.appendChild(description); + + document.getElementById('items').appendChild(item_element); +} + +function selectNodeText(selectElement, event) +{ + var selection = window.getSelection(); + if (!selection.isCollapsed) + return; + var range = document.createRange(); + range.selectNode(selectElement); + selection.removeAllRanges(); + selection.addRange(range); + event.stopPropagation(); + event.preventDefault(); +} +</script> +</head> +<body onload='onLoad()'> + <div id='caption'>Inspectable pages</div> + <hr> + <div id='items'> + </div> +</body> +</html> diff --git a/chromium/chrome/browser/devtools/global_confirm_info_bar.cc b/chromium/chrome/browser/devtools/global_confirm_info_bar.cc new file mode 100644 index 00000000000..a1851f9f2df --- /dev/null +++ b/chromium/chrome/browser/devtools/global_confirm_info_bar.cc @@ -0,0 +1,224 @@ +// Copyright 2015 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/global_confirm_info_bar.h" + +#include <utility> + +#include "base/macros.h" +#include "base/stl_util.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "components/infobars/core/infobar.h" +#include "ui/gfx/image/image.h" + +// InfoBarDelegateProxy ------------------------------------------------------- + +class GlobalConfirmInfoBar::DelegateProxy : public ConfirmInfoBarDelegate { + public: + explicit DelegateProxy(base::WeakPtr<GlobalConfirmInfoBar> global_info_bar); + ~DelegateProxy() override; + void Detach(); + + private: + friend class GlobalConfirmInfoBar; + + // ConfirmInfoBarDelegate overrides + infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override; + base::string16 GetMessageText() const override; + int GetButtons() const override; + base::string16 GetButtonLabel(InfoBarButton button) const override; + bool Accept() override; + bool Cancel() override; + base::string16 GetLinkText() const override; + GURL GetLinkURL() const override; + bool LinkClicked(WindowOpenDisposition disposition) override; + bool EqualsDelegate(infobars::InfoBarDelegate* delegate) const override; + void InfoBarDismissed() override; + + infobars::InfoBar* info_bar_; + base::WeakPtr<GlobalConfirmInfoBar> global_info_bar_; + + DISALLOW_COPY_AND_ASSIGN(DelegateProxy); +}; + +GlobalConfirmInfoBar::DelegateProxy::DelegateProxy( + base::WeakPtr<GlobalConfirmInfoBar> global_info_bar) + : info_bar_(nullptr), + global_info_bar_(global_info_bar) { +} + +GlobalConfirmInfoBar::DelegateProxy::~DelegateProxy() { +} + +infobars::InfoBarDelegate::InfoBarIdentifier +GlobalConfirmInfoBar::DelegateProxy::GetIdentifier() const { + return global_info_bar_ ? global_info_bar_->delegate_->GetIdentifier() + : INVALID; +} + +base::string16 GlobalConfirmInfoBar::DelegateProxy::GetMessageText() const { + return global_info_bar_ ? global_info_bar_->delegate_->GetMessageText() + : base::string16(); +} + +int GlobalConfirmInfoBar::DelegateProxy::GetButtons() const { + return global_info_bar_ ? global_info_bar_->delegate_->GetButtons() + : 0; +} + +base::string16 GlobalConfirmInfoBar::DelegateProxy::GetButtonLabel( + InfoBarButton button) const { + return global_info_bar_ ? global_info_bar_->delegate_->GetButtonLabel(button) + : base::string16(); +} + +bool GlobalConfirmInfoBar::DelegateProxy::Accept() { + base::WeakPtr<GlobalConfirmInfoBar> info_bar = global_info_bar_; + // Remove the current InfoBar (the one whose Accept button is being clicked) + // from the control of GlobalConfirmInfoBar. This InfoBar will be closed by + // caller of this method, and we don't need GlobalConfirmInfoBar to close it. + // Furthermore, letting GlobalConfirmInfoBar close the current InfoBar can + // cause memory corruption when InfoBar animation is disabled. + if (info_bar) { + info_bar->OnInfoBarRemoved(info_bar_, false); + info_bar->delegate_->Accept(); + } + // Could be destroyed after this point. + if (info_bar) + info_bar->Close(); + return true; +} + +bool GlobalConfirmInfoBar::DelegateProxy::Cancel() { + base::WeakPtr<GlobalConfirmInfoBar> info_bar = global_info_bar_; + // See comments in GlobalConfirmInfoBar::DelegateProxy::Accept(). + if (info_bar) { + info_bar->OnInfoBarRemoved(info_bar_, false); + info_bar->delegate_->Cancel(); + } + // Could be destroyed after this point. + if (info_bar) + info_bar->Close(); + return true; +} + +base::string16 GlobalConfirmInfoBar::DelegateProxy::GetLinkText() const { + return global_info_bar_ ? global_info_bar_->delegate_->GetLinkText() + : base::string16(); +} + +GURL GlobalConfirmInfoBar::DelegateProxy::GetLinkURL() const { + return global_info_bar_ ? global_info_bar_->delegate_->GetLinkURL() + : GURL(); +} + +bool GlobalConfirmInfoBar::DelegateProxy::LinkClicked( + WindowOpenDisposition disposition) { + return global_info_bar_ ? + global_info_bar_->delegate_->LinkClicked(disposition) : false; +} + +bool GlobalConfirmInfoBar::DelegateProxy::EqualsDelegate( + infobars::InfoBarDelegate* delegate) const { + return delegate == this; +} + +void GlobalConfirmInfoBar::DelegateProxy::InfoBarDismissed() { + base::WeakPtr<GlobalConfirmInfoBar> info_bar = global_info_bar_; + // See comments in GlobalConfirmInfoBar::DelegateProxy::Accept(). + if (info_bar) { + info_bar->OnInfoBarRemoved(info_bar_, false); + info_bar->delegate_->InfoBarDismissed(); + } + // Could be destroyed after this point. + if (info_bar) + info_bar->Close(); +} + +void GlobalConfirmInfoBar::DelegateProxy::Detach() { + global_info_bar_.reset(); +} + +// GlobalConfirmInfoBar ------------------------------------------------------- + +// static +base::WeakPtr<GlobalConfirmInfoBar> GlobalConfirmInfoBar::Show( + std::unique_ptr<ConfirmInfoBarDelegate> delegate) { + GlobalConfirmInfoBar* info_bar = + new GlobalConfirmInfoBar(std::move(delegate)); + return info_bar->weak_factory_.GetWeakPtr(); +} + +void GlobalConfirmInfoBar::Close() { + delete this; +} + +GlobalConfirmInfoBar::GlobalConfirmInfoBar( + std::unique_ptr<ConfirmInfoBarDelegate> delegate) + : delegate_(std::move(delegate)), + browser_tab_strip_tracker_(this, nullptr, nullptr), + weak_factory_(this) { + browser_tab_strip_tracker_.Init(); +} + +GlobalConfirmInfoBar::~GlobalConfirmInfoBar() { + while (!proxies_.empty()) { + auto it = proxies_.begin(); + it->second->Detach(); + it->first->RemoveObserver(this); + it->first->RemoveInfoBar(it->second->info_bar_); + proxies_.erase(it); + } +} + +void GlobalConfirmInfoBar::TabInsertedAt(TabStripModel* tab_strip_model, + content::WebContents* web_contents, + int index, + bool foreground) { + MaybeAddInfoBar(web_contents); +} + +void GlobalConfirmInfoBar::TabChangedAt(content::WebContents* web_contents, + int index, + TabChangeType change_type) { + MaybeAddInfoBar(web_contents); +} + +void GlobalConfirmInfoBar::OnInfoBarRemoved(infobars::InfoBar* info_bar, + bool animate) { + // Do not process alien infobars. + for (auto it : proxies_) { + if (it.second->info_bar_ == info_bar) { + OnManagerShuttingDown(info_bar->owner()); + break; + } + } +} + +void GlobalConfirmInfoBar::OnManagerShuttingDown( + infobars::InfoBarManager* manager) { + manager->RemoveObserver(this); + proxies_.erase(manager); +} + +void GlobalConfirmInfoBar::MaybeAddInfoBar(content::WebContents* web_contents) { + InfoBarService* infobar_service = + InfoBarService::FromWebContents(web_contents); + // WebContents from the tab strip must have the infobar service. + DCHECK(infobar_service); + if (ContainsKey(proxies_, infobar_service)) + return; + + std::unique_ptr<GlobalConfirmInfoBar::DelegateProxy> proxy( + new GlobalConfirmInfoBar::DelegateProxy(weak_factory_.GetWeakPtr())); + GlobalConfirmInfoBar::DelegateProxy* proxy_ptr = proxy.get(); + infobars::InfoBar* added_bar = infobar_service->AddInfoBar( + infobar_service->CreateConfirmInfoBar(std::move(proxy))); + + proxy_ptr->info_bar_ = added_bar; + DCHECK(added_bar); + proxies_[infobar_service] = proxy_ptr; + infobar_service->AddObserver(this); +} diff --git a/chromium/chrome/browser/devtools/global_confirm_info_bar.h b/chromium/chrome/browser/devtools/global_confirm_info_bar.h new file mode 100644 index 00000000000..06e27f3a7eb --- /dev/null +++ b/chromium/chrome/browser/devtools/global_confirm_info_bar.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_GLOBAL_CONFIRM_INFO_BAR_H_ +#define CHROME_BROWSER_DEVTOOLS_GLOBAL_CONFIRM_INFO_BAR_H_ + +#include <map> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/infobars/infobar_service.h" +#include "chrome/browser/ui/browser_tab_strip_tracker.h" +#include "chrome/browser/ui/tabs/tab_strip_model_observer.h" +#include "components/infobars/core/confirm_infobar_delegate.h" + +namespace content { +class WebContents; +} + +// GlobalConfirmInfoBar is shown for every tab in every browser until it +// is dismissed or the close method is called. +// It listens to all tabs in all browsers and adds/removes confirm infobar +// to each of the tabs. +class GlobalConfirmInfoBar : public TabStripModelObserver, + public infobars::InfoBarManager::Observer { + public: + static base::WeakPtr<GlobalConfirmInfoBar> Show( + std::unique_ptr<ConfirmInfoBarDelegate> delegate); + void Close(); + + // infobars::InfoBarManager::Observer: + void OnInfoBarRemoved(infobars::InfoBar* info_bar, bool animate) override; + void OnManagerShuttingDown(infobars::InfoBarManager* manager) override; + + private: + explicit GlobalConfirmInfoBar( + std::unique_ptr<ConfirmInfoBarDelegate> delegate); + ~GlobalConfirmInfoBar() override; + class DelegateProxy; + + // TabStripModelObserver: + void TabInsertedAt(TabStripModel* tab_strip_model, + content::WebContents* web_contents, + int index, + bool foreground) override; + void TabChangedAt(content::WebContents* web_contents, + int index, + TabChangeType change_type) override; + + // Adds the info bar to the tab if it is missing. + void MaybeAddInfoBar(content::WebContents* web_contents); + + std::unique_ptr<ConfirmInfoBarDelegate> delegate_; + std::map<infobars::InfoBarManager*, DelegateProxy*> proxies_; + BrowserTabStripTracker browser_tab_strip_tracker_; + + base::WeakPtrFactory<GlobalConfirmInfoBar> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(GlobalConfirmInfoBar); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_GLOBAL_CONFIRM_INFO_BAR_H_ diff --git a/chromium/chrome/browser/devtools/remote_debugging_server.cc b/chromium/chrome/browser/devtools/remote_debugging_server.cc new file mode 100644 index 00000000000..2cc82280262 --- /dev/null +++ b/chromium/chrome/browser/devtools/remote_debugging_server.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2012 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/remote_debugging_server.h" + +#include <utility> + +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/path_service.h" +#include "base/strings/string_number_conversions.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/devtools/devtools_window.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/common/chrome_content_client.h" +#include "chrome/common/chrome_paths.h" +#include "components/version_info/version_info.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/devtools_socket_factory.h" +#include "net/base/net_errors.h" +#include "net/log/net_log_source.h" +#include "net/socket/tcp_server_socket.h" +#include "third_party/WebKit/public/public_features.h" +#include "ui/base/resource/resource_bundle.h" + +namespace { + +base::LazyInstance<bool>::Leaky g_tethering_enabled = LAZY_INSTANCE_INITIALIZER; + +const uint16_t kMinTetheringPort = 9333; +const uint16_t kMaxTetheringPort = 9444; +const int kBackLog = 10; + +class TCPServerSocketFactory + : public content::DevToolsSocketFactory { + public: + TCPServerSocketFactory(const std::string& address, uint16_t port) + : address_(address), + port_(port), + last_tethering_port_(kMinTetheringPort) {} + + private: + std::unique_ptr<net::ServerSocket> CreateLocalHostServerSocket(int port) { + std::unique_ptr<net::ServerSocket> socket( + new net::TCPServerSocket(nullptr, net::NetLogSource())); + if (socket->ListenWithAddressAndPort( + "127.0.0.1", port, kBackLog) == net::OK) + return socket; + if (socket->ListenWithAddressAndPort("::1", port, kBackLog) == net::OK) + return socket; + return std::unique_ptr<net::ServerSocket>(); + } + + // content::DevToolsSocketFactory. + std::unique_ptr<net::ServerSocket> CreateForHttpServer() override { + std::unique_ptr<net::ServerSocket> socket( + new net::TCPServerSocket(nullptr, net::NetLogSource())); + if (address_.empty()) + return CreateLocalHostServerSocket(port_); + if (socket->ListenWithAddressAndPort(address_, port_, kBackLog) == net::OK) + return socket; + return std::unique_ptr<net::ServerSocket>(); + } + + std::unique_ptr<net::ServerSocket> CreateForTethering( + std::string* name) override { + if (!g_tethering_enabled.Get()) + return std::unique_ptr<net::ServerSocket>(); + + if (last_tethering_port_ == kMaxTetheringPort) + last_tethering_port_ = kMinTetheringPort; + uint16_t port = ++last_tethering_port_; + *name = base::UintToString(port); + return CreateLocalHostServerSocket(port); + } + + std::string address_; + uint16_t port_; + uint16_t last_tethering_port_; + + DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory); +}; + +} // namespace + +// static +void RemoteDebuggingServer::EnableTetheringForDebug() { + g_tethering_enabled.Get() = true; +} + +RemoteDebuggingServer::RemoteDebuggingServer(const std::string& ip, + uint16_t port) { + base::FilePath output_dir; + if (!port) { + // The client requested an ephemeral port. Must write the selected + // port to a well-known location in the profile directory to + // bootstrap the connection process. + bool result = PathService::Get(chrome::DIR_USER_DATA, &output_dir); + DCHECK(result); + } + + base::FilePath debug_frontend_dir; +#if BUILDFLAG(DEBUG_DEVTOOLS) + PathService::Get(chrome::DIR_INSPECTOR_DEBUG, &debug_frontend_dir); +#endif + + content::DevToolsAgentHost::StartRemoteDebuggingServer( + base::MakeUnique<TCPServerSocketFactory>(ip, port), + std::string(), + output_dir, + debug_frontend_dir, + version_info::GetProductNameAndVersionForUserAgent(), + ::GetUserAgent()); +} + +RemoteDebuggingServer::~RemoteDebuggingServer() { + // Ensure Profile is alive, because the whole DevTools subsystem + // accesses it during shutdown. + DCHECK(g_browser_process->profile_manager()); + content::DevToolsAgentHost::StopRemoteDebuggingServer(); +} diff --git a/chromium/chrome/browser/devtools/remote_debugging_server.h b/chromium/chrome/browser/devtools/remote_debugging_server.h new file mode 100644 index 00000000000..e2ba3e78440 --- /dev/null +++ b/chromium/chrome/browser/devtools/remote_debugging_server.h @@ -0,0 +1,28 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_REMOTE_DEBUGGING_SERVER_H_ +#define CHROME_BROWSER_DEVTOOLS_REMOTE_DEBUGGING_SERVER_H_ + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/macros.h" + +class RemoteDebuggingServer { + public: + static void EnableTetheringForDebug(); + + // Bind remote debugging service to the given |ip| and |port|. + // Empty |ip| stands for 127.0.0.1 or ::1. + RemoteDebuggingServer(const std::string& ip, uint16_t port); + virtual ~RemoteDebuggingServer(); + + private: + DISALLOW_COPY_AND_ASSIGN(RemoteDebuggingServer); +}; + +#endif // CHROME_BROWSER_DEVTOOLS_REMOTE_DEBUGGING_SERVER_H_ diff --git a/chromium/chrome/browser/devtools/serialize_host_descriptions.cc b/chromium/chrome/browser/devtools/serialize_host_descriptions.cc new file mode 100644 index 00000000000..26aa0206369 --- /dev/null +++ b/chromium/chrome/browser/devtools/serialize_host_descriptions.cc @@ -0,0 +1,106 @@ +// Copyright 2017 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/serialize_host_descriptions.h" + +#include <map> +#include <unordered_set> +#include <utility> + +#include "base/memory/ptr_util.h" +#include "base/strings/string_piece.h" + +namespace { + +// Returns the serialization of |root|. It expects |children[x]| to be the +// vector of child nodes for all descendants |x| of |root|. The serialization +// consists of taking the |representation| value of each node, starting in +// leaves, and injecting children's representations into a ListValue under the +// key |child_key| in the parent's |representation|. This is desctructive to the +// representation stored with the nodes (which gets moved out of them). +base::DictionaryValue Serialize( + base::StringPiece child_key, + base::DictionaryValue* root, + const std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>>& + children) { + auto children_list = base::MakeUnique<base::ListValue>(); + auto child_it = children.find(root); + if (child_it != children.end()) { + for (base::DictionaryValue* child : child_it->second) { + children_list->base::Value::GetList().push_back( + Serialize(child_key, child, children)); + } + } + + if (!children_list->empty()) + root->Set(child_key, std::move(children_list)); + return std::move(*root); +} + +// Takes a vector of host description and converts it into: +// |children|: a map from a host's representation to representations of its +// children, +// |roots|: a set of representations of hosts with no parents, and +// |representations|: a vector actually storing all those representations to +// which the rest just points. +void CreateDictionaryForest( + std::vector<HostDescriptionNode> hosts, + std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>>* + children, + std::unordered_set<base::DictionaryValue*>* roots, + std::vector<base::DictionaryValue>* representations) { + representations->reserve(hosts.size()); + children->clear(); + roots->clear(); + representations->clear(); + + std::map<base::StringPiece, base::DictionaryValue*> name_to_representation; + + // First move the representations and map the names to them. + for (HostDescriptionNode& node : hosts) { + representations->push_back(std::move(node.representation)); + // If there are multiple nodes with the same name, subsequent insertions + // will be ignored, so only the first node with a given name will be + // referenced by |roots| and |children|. + name_to_representation.emplace(node.name, &representations->back()); + } + + // Now compute children. + for (HostDescriptionNode& node : hosts) { + base::DictionaryValue* node_rep = name_to_representation[node.name]; + base::StringPiece parent_name = node.parent_name; + if (parent_name.empty()) { + roots->insert(node_rep); + continue; + } + auto node_it = name_to_representation.find(parent_name); + if (node_it == name_to_representation.end()) { + roots->insert(node_rep); + continue; + } + (*children)[name_to_representation[parent_name]].push_back(node_rep); + } +} + +} // namespace + +base::ListValue SerializeHostDescriptions( + std::vector<HostDescriptionNode> hosts, + base::StringPiece child_key) { + // |representations| must outlive |children| and |roots|, which contain + // pointers to objects in |representations|. + std::vector<base::DictionaryValue> representations; + std::map<base::DictionaryValue*, std::vector<base::DictionaryValue*>> + children; + std::unordered_set<base::DictionaryValue*> roots; + + CreateDictionaryForest(std::move(hosts), &children, &roots, &representations); + + base::ListValue list_value; + for (auto* root : roots) { + list_value.base::Value::GetList().push_back( + Serialize(child_key, root, children)); + } + return list_value; +} diff --git a/chromium/chrome/browser/devtools/serialize_host_descriptions.h b/chromium/chrome/browser/devtools/serialize_host_descriptions.h new file mode 100644 index 00000000000..41779014748 --- /dev/null +++ b/chromium/chrome/browser/devtools/serialize_host_descriptions.h @@ -0,0 +1,29 @@ +// Copyright 2017 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_SERIALIZE_HOST_DESCRIPTIONS_H_ +#define CHROME_BROWSER_DEVTOOLS_SERIALIZE_HOST_DESCRIPTIONS_H_ + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "base/values.h" + +// DevToolsAgentHost description to be serialized by SerializeHostDescriptions. +struct HostDescriptionNode { + std::string name; + std::string parent_name; + base::DictionaryValue representation; +}; + +// A helper function taking a HostDescriptionNode representation of hosts and +// producing a ListValue representation. The representation contains a list of +// DictionaryValue for each rooti host, and has DictionaryValues of children +// injected into a ListValue keyed |child_key| in the parent's DictionaryValue. +base::ListValue SerializeHostDescriptions( + std::vector<HostDescriptionNode> hosts, + base::StringPiece child_key); + +#endif // CHROME_BROWSER_DEVTOOLS_SERIALIZE_HOST_DESCRIPTIONS_H_ diff --git a/chromium/chrome/browser/devtools/serialize_host_descriptions_unittest.cc b/chromium/chrome/browser/devtools/serialize_host_descriptions_unittest.cc new file mode 100644 index 00000000000..856dc1a68ba --- /dev/null +++ b/chromium/chrome/browser/devtools/serialize_host_descriptions_unittest.cc @@ -0,0 +1,138 @@ +// Copyright 2017 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/serialize_host_descriptions.h" + +#include <utility> +#include <vector> + +#include "base/values.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::UnorderedElementsAre; + +namespace { + +HostDescriptionNode GetNodeWithLabel(const char* name, int label) { + HostDescriptionNode node = {name, std::string(), base::DictionaryValue()}; + node.representation.SetInteger("label", label); + return node; +} + +// Returns the list of children of |arg|. +const base::Value* GetChildren(const base::Value& arg) { + const base::DictionaryValue* dict = nullptr; + EXPECT_TRUE(arg.GetAsDictionary(&dict)); + + const base::Value* children = nullptr; + if (!dict->Get("children", &children)) + return nullptr; + EXPECT_EQ(base::Value::Type::LIST, children->type()); + return children; +} + +// Checks that |arg| is a description of a node with label |l|. +bool CheckLabel(const base::Value& arg, int l) { + const base::DictionaryValue* dict = nullptr; + EXPECT_TRUE(arg.GetAsDictionary(&dict)); + int result = 0; + if (!dict->GetInteger("label", &result)) + return false; + return l == result; +} + +// Matches every |arg| with label |label| and checks that it has no children. +MATCHER_P(EmptyNode, label, "") { + if (!CheckLabel(arg, label)) + return false; + EXPECT_FALSE(GetChildren(arg)); + return true; +} + +} // namespace + +TEST(SerializeHostDescriptionTest, Empty) { + base::ListValue result = + SerializeHostDescriptions(std::vector<HostDescriptionNode>(), "123"); + EXPECT_THAT(result.base::Value::GetList(), ::testing::IsEmpty()); +} + +// Test serializing a forest of stubs (no edges). +TEST(SerializeHostDescriptionTest, Stubs) { + base::ListValue result = SerializeHostDescriptions( + {GetNodeWithLabel("1", 1), GetNodeWithLabel("2", 2), + GetNodeWithLabel("3", 3)}, + "children"); + EXPECT_THAT(result.base::Value::GetList(), + UnorderedElementsAre(EmptyNode(1), EmptyNode(2), EmptyNode(3))); +} + +// Test handling multiple nodes sharing the same name. +TEST(SerializeHostDescriptionTest, SameNames) { + std::vector<HostDescriptionNode> nodes = { + GetNodeWithLabel("A", 1), GetNodeWithLabel("A", 2), + GetNodeWithLabel("A", 3), GetNodeWithLabel("B", 4), + GetNodeWithLabel("C", 5)}; + + base::ListValue result = + SerializeHostDescriptions(std::move(nodes), "children"); + + // Only the first node called "A", and both nodes "B" and "C" should be + // returned. + EXPECT_THAT(result.base::Value::GetList(), + UnorderedElementsAre(EmptyNode(1), EmptyNode(4), EmptyNode(5))); +} + +// Test serializing a small forest, of this structure: +// 5 -- 2 -- 4 +// 0 -- 6 +// \ 1 +// \ 3 + +namespace { + +// Matchers for non-empty nodes specifically in this test: +MATCHER(Node2, "") { + if (!CheckLabel(arg, 2)) + return false; + EXPECT_THAT(GetChildren(arg)->GetList(), UnorderedElementsAre(EmptyNode(4))); + return true; +} + +MATCHER(Node5, "") { + if (!CheckLabel(arg, 5)) + return false; + EXPECT_THAT(GetChildren(arg)->GetList(), UnorderedElementsAre(Node2())); + return true; +} + +MATCHER(Node0, "") { + if (!CheckLabel(arg, 0)) + return false; + EXPECT_THAT(GetChildren(arg)->GetList(), + UnorderedElementsAre(EmptyNode(1), EmptyNode(3), EmptyNode(6))); + return true; +} + +} // namespace + +TEST(SerializeHostDescriptionTest, Forest) { + std::vector<HostDescriptionNode> nodes(7); + const char* kNames[] = {"0", "1", "2", "3", "4", "5", "6"}; + for (size_t i = 0; i < 7; ++i) { + nodes[i] = GetNodeWithLabel(kNames[i], i); + } + nodes[2].parent_name = "5"; + nodes[4].parent_name = "2"; + nodes[6].parent_name = "0"; + nodes[1].parent_name = "0"; + nodes[3].parent_name = "0"; + + base::ListValue result = + SerializeHostDescriptions(std::move(nodes), "children"); + + EXPECT_THAT(result.base::Value::GetList(), + UnorderedElementsAre(Node0(), Node5())); +} diff --git a/chromium/chrome/browser/devtools/url_constants.cc b/chromium/chrome/browser/devtools/url_constants.cc new file mode 100644 index 00000000000..bab37d36dd1 --- /dev/null +++ b/chromium/chrome/browser/devtools/url_constants.cc @@ -0,0 +1,10 @@ +// Copyright 2016 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/url_constants.h" + +const char kRemoteFrontendDomain[] = "chrome-devtools-frontend.appspot.com"; +const char kRemoteFrontendBase[] = + "https://chrome-devtools-frontend.appspot.com/"; +const char kRemoteFrontendPath[] = "serve_file"; diff --git a/chromium/chrome/browser/devtools/url_constants.h b/chromium/chrome/browser/devtools/url_constants.h new file mode 100644 index 00000000000..48361e57e8b --- /dev/null +++ b/chromium/chrome/browser/devtools/url_constants.h @@ -0,0 +1,12 @@ +// Copyright 2016 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. + +#ifndef CHROME_BROWSER_DEVTOOLS_URL_CONSTANTS_H_ +#define CHROME_BROWSER_DEVTOOLS_URL_CONSTANTS_H_ + +extern const char kRemoteFrontendDomain[]; +extern const char kRemoteFrontendBase[]; +extern const char kRemoteFrontendPath[]; + +#endif // CHROME_BROWSER_DEVTOOLS_URL_CONSTANTS_H_ |