summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/devtools
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-09-12 10:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-09-12 08:34:13 +0000
commit53d399fe6415a96ea6986ec0d402a9c07da72453 (patch)
treece7dbd5d170326a7d1c5f69f5dcd841c178e0dc0 /chromium/chrome/browser/devtools
parenta3ee7849e3b0ad3d5f9595fa1cfd694c22dcee2a (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')
-rw-r--r--chromium/chrome/browser/devtools/OWNERS7
-rw-r--r--chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.cc494
-rw-r--r--chromium/chrome/browser/devtools/chrome_devtools_manager_delegate.h89
-rw-r--r--chromium/chrome/browser/devtools/device/adb/adb_client_socket.cc304
-rw-r--r--chromium/chrome/browser/devtools/device/adb/adb_client_socket.h63
-rw-r--r--chromium/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc159
-rw-r--r--chromium/chrome/browser/devtools/device/adb/adb_device_provider.cc69
-rw-r--r--chromium/chrome/browser/devtools/device/adb/adb_device_provider.h25
-rw-r--r--chromium/chrome/browser/devtools/device/adb/mock_adb_server.cc625
-rw-r--r--chromium/chrome/browser/devtools/device/adb/mock_adb_server.h52
-rw-r--r--chromium/chrome/browser/devtools/device/android_device_info_query.cc370
-rw-r--r--chromium/chrome/browser/devtools/device/android_device_manager.cc569
-rw-r--r--chromium/chrome/browser/devtools/device/android_device_manager.h249
-rw-r--r--chromium/chrome/browser/devtools/device/android_web_socket.cc226
-rw-r--r--chromium/chrome/browser/devtools/device/cast_device_provider.cc206
-rw-r--r--chromium/chrome/browser/devtools/device/cast_device_provider.h63
-rw-r--r--chromium/chrome/browser/devtools/device/cast_device_provider_unittest.cc109
-rw-r--r--chromium/chrome/browser/devtools/device/devtools_android_bridge.cc406
-rw-r--r--chromium/chrome/browser/devtools/device/devtools_android_bridge.h193
-rw-r--r--chromium/chrome/browser/devtools/device/devtools_android_bridge_browsertest.cc144
-rw-r--r--chromium/chrome/browser/devtools/device/devtools_device_discovery.cc626
-rw-r--r--chromium/chrome/browser/devtools/device/devtools_device_discovery.h145
-rw-r--r--chromium/chrome/browser/devtools/device/port_forwarding_browsertest.cc190
-rw-r--r--chromium/chrome/browser/devtools/device/port_forwarding_controller.cc500
-rw-r--r--chromium/chrome/browser/devtools/device/port_forwarding_controller.h53
-rw-r--r--chromium/chrome/browser/devtools/device/tcp_device_provider.cc140
-rw-r--r--chromium/chrome/browser/devtools/device/tcp_device_provider.h46
-rw-r--r--chromium/chrome/browser/devtools/device/usb/android_rsa.cc280
-rw-r--r--chromium/chrome/browser/devtools/device/usb/android_rsa.h24
-rw-r--r--chromium/chrome/browser/devtools/device/usb/android_usb_browsertest.cc790
-rw-r--r--chromium/chrome/browser/devtools/device/usb/android_usb_device.cc692
-rw-r--r--chromium/chrome/browser/devtools/device/usb/android_usb_device.h171
-rw-r--r--chromium/chrome/browser/devtools/device/usb/android_usb_socket.cc262
-rw-r--r--chromium/chrome/browser/devtools/device/usb/android_usb_socket.h85
-rw-r--r--chromium/chrome/browser/devtools/device/usb/usb_device_provider.cc154
-rw-r--r--chromium/chrome/browser/devtools/device/usb/usb_device_provider.h46
-rw-r--r--chromium/chrome/browser/devtools/devtools_auto_opener.cc24
-rw-r--r--chromium/chrome/browser/devtools/devtools_auto_opener.h28
-rw-r--r--chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.cc53
-rw-r--r--chromium/chrome/browser/devtools/devtools_contents_resizing_strategy.h48
-rw-r--r--chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc221
-rw-r--r--chromium/chrome/browser/devtools/devtools_embedder_message_dispatcher.h110
-rw-r--r--chromium/chrome/browser/devtools/devtools_eye_dropper.cc249
-rw-r--r--chromium/chrome/browser/devtools/devtools_eye_dropper.h55
-rw-r--r--chromium/chrome/browser/devtools/devtools_file_helper.cc465
-rw-r--r--chromium/chrome/browser/devtools/devtools_file_helper.h154
-rw-r--r--chromium/chrome/browser/devtools/devtools_file_system_indexer.cc459
-rw-r--r--chromium/chrome/browser/devtools/devtools_file_system_indexer.h108
-rw-r--r--chromium/chrome/browser/devtools/devtools_file_watcher.cc205
-rw-r--r--chromium/chrome/browser/devtools/devtools_file_watcher.h40
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_conditions.cc40
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_conditions.h41
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_controller.cc71
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_controller.h44
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_controller_handle.cc55
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_controller_handle.h42
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_controller_unittest.cc329
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_interceptor.cc290
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_interceptor.h103
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_protocol_handler.cc116
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_protocol_handler.h49
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_transaction.cc303
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_transaction.h133
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_transaction_factory.cc51
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_transaction_factory.h43
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_upload_data_stream.cc92
-rw-r--r--chromium/chrome/browser/devtools/devtools_network_upload_data_stream.h51
-rw-r--r--chromium/chrome/browser/devtools/devtools_protocol.cc149
-rw-r--r--chromium/chrome/browser/devtools/devtools_protocol.h53
-rwxr-xr-xchromium/chrome/browser/devtools/devtools_protocol_constants_generator.py202
-rw-r--r--chromium/chrome/browser/devtools/devtools_sanity_browsertest.cc2082
-rw-r--r--chromium/chrome/browser/devtools/devtools_sanity_interactive_browsertest.cc211
-rw-r--r--chromium/chrome/browser/devtools/devtools_targets_ui.cc490
-rw-r--r--chromium/chrome/browser/devtools/devtools_targets_ui.h82
-rw-r--r--chromium/chrome/browser/devtools/devtools_toggle_action.cc74
-rw-r--r--chromium/chrome/browser/devtools/devtools_toggle_action.h65
-rw-r--r--chromium/chrome/browser/devtools/devtools_ui_bindings.cc1428
-rw-r--r--chromium/chrome/browser/devtools/devtools_ui_bindings.h249
-rw-r--r--chromium/chrome/browser/devtools/devtools_ui_bindings_unittest.cc102
-rw-r--r--chromium/chrome/browser/devtools/devtools_window.cc1447
-rw-r--r--chromium/chrome/browser/devtools/devtools_window.h383
-rw-r--r--chromium/chrome/browser/devtools/devtools_window_testing.cc207
-rw-r--r--chromium/chrome/browser/devtools/devtools_window_testing.h88
-rw-r--r--chromium/chrome/browser/devtools/frontend/devtools_discovery_page.html197
-rw-r--r--chromium/chrome/browser/devtools/global_confirm_info_bar.cc224
-rw-r--r--chromium/chrome/browser/devtools/global_confirm_info_bar.h63
-rw-r--r--chromium/chrome/browser/devtools/remote_debugging_server.cc125
-rw-r--r--chromium/chrome/browser/devtools/remote_debugging_server.h28
-rw-r--r--chromium/chrome/browser/devtools/serialize_host_descriptions.cc106
-rw-r--r--chromium/chrome/browser/devtools/serialize_host_descriptions.h29
-rw-r--r--chromium/chrome/browser/devtools/serialize_host_descriptions_unittest.cc138
-rw-r--r--chromium/chrome/browser/devtools/url_constants.cc10
-rw-r--r--chromium/chrome/browser/devtools/url_constants.h12
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, &params))
+ 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(&params);
+ 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, &params))
+ 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(&registry_, 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, &current_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, &params))
+ 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, &params_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, &params))) {
+ 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, &param);
+ 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_