| // 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/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/guid.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/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.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/task/post_task.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_window.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 "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/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.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/file_url_loader.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/shared_cors_origin_access_list.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/browser/web_ui_url_loader_factory.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/common/url_utils.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/net_errors.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/cpp/simple_url_loader_stream_consumer.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "third_party/blink/public/mojom/renderer_preferences.mojom.h" |
| #include "third_party/blink/public/public_buildflags.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[] = "DevTools - %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 |
| // kShellMaxMessageChunkSize in content/shell/browser/shell_devtools_bindings.cc |
| // and |
| // kLayoutTestMaxMessageChunkSize in |
| // content/shell/browser/layout_test/devtools_protocol_test_bindings.cc. |
| const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4; |
| |
| typedef std::vector<DevToolsUIBindings*> DevToolsUIBindingsList; |
| base::LazyInstance<DevToolsUIBindingsList>::Leaky |
| g_devtools_ui_bindings_instances = LAZY_INSTANCE_INITIALIZER; |
| |
| std::unique_ptr<base::DictionaryValue> CreateFileSystemValue( |
| DevToolsFileHelper::FileSystem file_system) { |
| auto file_system_value = std::make_unique<base::DictionaryValue>(); |
| file_system_value->SetString("type", file_system.type); |
| 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; |
| } |
| |
| // 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 {} |
| void ConnectionReady() override {} |
| void SetOpenNewWindowForPopups(bool value) override {} |
| InfoBarService* GetInfoBarService() override; |
| void RenderProcessGone(bool crashed) override {} |
| void ShowCertificateViewer(const std::string& cert_chain) 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_); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> BuildObjectForResponse( |
| const net::HttpResponseHeaders* rh) { |
| auto response = std::make_unique<base::DictionaryValue>(); |
| response->SetInteger("statusCode", rh ? rh->response_code() : 200); |
| |
| auto headers = std::make_unique<base::DictionaryValue>(); |
| size_t iterator = 0; |
| std::string name; |
| std::string value; |
| // TODO(caseq): this probably needs to handle duplicate header names |
| // correctly by folding them. |
| while (rh && rh->EnumerateHeaderLines(&iterator, &name, &value)) |
| headers->SetString(name, value); |
| |
| response->Set("headers", std::move(headers)); |
| return response; |
| } |
| |
| GURL SanitizeFrontendURL(const GURL& url, |
| const std::string& scheme, |
| const std::string& host, |
| const std::string& path, |
| bool allow_query_and_fragment); |
| |
| 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 SanitizeRemoteVersion(const std::string& remoteVersion) { |
| for (size_t i = 0; i < remoteVersion.length(); i++) { |
| if (remoteVersion[i] != '.' && |
| !(remoteVersion[i] >= '0' && remoteVersion[i] <= '9')) |
| return std::string(); |
| } |
| return remoteVersion; |
| } |
| |
| 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::UnescapeBinaryURLComponent( |
| value, 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.empty() ? 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 SanitizeEnabledExperiments(const std::string& value) { |
| bool valid = std::find_if_not(value.begin(), value.end(), [](char ch) { |
| if (base::IsAsciiAlpha(ch) || base::IsAsciiDigit(ch) || |
| ch == ';' || ch == '_') |
| return true; |
| return false; |
| }) == value.end(); |
| if (!valid) { |
| return std::string(); |
| } |
| return value; |
| } |
| |
| 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" || key == "hasOtherClients" || key == "uiDevTools") |
| 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" || value == "sources")) |
| return value; |
| |
| if (key == "remoteBase") |
| return SanitizeRemoteBase(value); |
| |
| if (key == "remoteFrontendUrl") |
| return SanitizeRemoteFrontendURL(value); |
| |
| if (key == "remoteVersion") |
| return SanitizeRemoteVersion(value); |
| |
| if (key == "enabledExperiments") |
| return SanitizeEnabledExperiments(value); |
| |
| return std::string(); |
| } |
| |
| GURL SanitizeFrontendURL(const GURL& url, |
| const std::string& scheme, |
| const std::string& host, |
| const std::string& path, |
| bool allow_query_and_fragment) { |
| std::vector<std::string> query_parts; |
| std::string fragment; |
| if (allow_query_and_fragment) { |
| 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())); |
| } |
| } |
| if (url.has_ref() && url.ref_piece().find('\'') == base::StringPiece::npos) |
| fragment = '#' + url.ref(); |
| } |
| std::string query = |
| query_parts.empty() ? "" : "?" + base::JoinString(query_parts, "&"); |
| std::string constructed = |
| base::StringPrintf("%s://%s%s%s%s", scheme.c_str(), host.c_str(), |
| path.c_str(), query.c_str(), fragment.c_str()); |
| GURL result = GURL(constructed); |
| if (!result.is_valid()) |
| return GURL(); |
| return result; |
| } |
| |
| } // namespace |
| |
| class DevToolsUIBindings::NetworkResourceLoader |
| : public network::SimpleURLLoaderStreamConsumer { |
| public: |
| NetworkResourceLoader(int stream_id, |
| DevToolsUIBindings* bindings, |
| std::unique_ptr<network::SimpleURLLoader> loader, |
| network::mojom::URLLoaderFactory* url_loader_factory, |
| const DispatchCallback& callback) |
| : stream_id_(stream_id), |
| bindings_(bindings), |
| loader_(std::move(loader)), |
| callback_(callback) { |
| loader_->SetOnResponseStartedCallback(base::BindOnce( |
| &NetworkResourceLoader::OnResponseStarted, base::Unretained(this))); |
| loader_->DownloadAsStream(url_loader_factory, this); |
| } |
| |
| private: |
| void OnResponseStarted(const GURL& final_url, |
| const network::mojom::URLResponseHead& response_head) { |
| response_headers_ = response_head.headers; |
| } |
| |
| void OnDataReceived(base::StringPiece chunk, |
| base::OnceClosure resume) override { |
| base::Value chunkValue; |
| |
| bool encoded = !base::IsStringUTF8(chunk); |
| if (encoded) { |
| std::string encoded_string; |
| base::Base64Encode(chunk, &encoded_string); |
| chunkValue = base::Value(std::move(encoded_string)); |
| } else { |
| chunkValue = base::Value(chunk); |
| } |
| base::Value id(stream_id_); |
| base::Value encodedValue(encoded); |
| |
| bindings_->CallClientFunction("DevToolsAPI.streamWrite", &id, &chunkValue, |
| &encodedValue); |
| std::move(resume).Run(); |
| } |
| |
| void OnComplete(bool success) override { |
| auto response = BuildObjectForResponse(response_headers_.get()); |
| callback_.Run(response.get()); |
| |
| bindings_->loaders_.erase(bindings_->loaders_.find(this)); |
| } |
| |
| void OnRetry(base::OnceClosure start_retry) override { NOTREACHED(); } |
| |
| const int stream_id_; |
| DevToolsUIBindings* const bindings_; |
| std::unique_ptr<network::SimpleURLLoader> loader_; |
| DispatchCallback callback_; |
| scoped_refptr<net::HttpResponseHeaders> response_headers_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetworkResourceLoader); |
| }; |
| |
| // 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 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); |
| } |
| |
| // static |
| 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(); |
| } |
| |
| bool DevToolsUIBindings::IsValidRemoteFrontendURL(const GURL& url) { |
| return ::SanitizeFrontendURL(url, url::kHttpsScheme, kRemoteFrontendDomain, |
| url.path(), true) |
| .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) { |
| devtools_bindings_->ReadyToCommitNavigation(navigation_handle); |
| } |
| |
| 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_devtools_ui_bindings_instances.IsCreated()) |
| return NULL; |
| DevToolsUIBindingsList* instances = |
| g_devtools_ui_bindings_instances.Pointer(); |
| for (auto 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) { |
| g_devtools_ui_bindings_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_ = |
| DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this); |
| } |
| |
| DevToolsUIBindings::~DevToolsUIBindings() { |
| 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_devtools_ui_bindings_instances.Pointer(); |
| auto 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; |
| const std::string* method = nullptr; |
| base::Value* params = nullptr; |
| base::Optional<base::Value> parsed_message = base::JSONReader::Read(message); |
| if (parsed_message && parsed_message->is_dict()) { |
| method = parsed_message->FindStringKey(kFrontendHostMethod); |
| params = parsed_message->FindKey(kFrontendHostParams); |
| } |
| if (!method || (params && !params->is_list())) { |
| LOG(ERROR) << "Invalid message was sent to embedder: " << message; |
| return; |
| } |
| base::Value empty_params(base::Value::Type::LIST); |
| if (!params) { |
| params = &empty_params; |
| } |
| int id = parsed_message->FindIntKey(kFrontendHostId).value_or(0); |
| base::ListValue* params_list; |
| params->GetAsList(¶ms_list); |
| embedder_message_dispatcher_->Dispatch( |
| base::Bind(&DevToolsUIBindings::SendMessageAck, |
| weak_factory_.GetWeakPtr(), id), |
| *method, params_list); |
| } |
| |
| // content::DevToolsAgentHostClient implementation -------------------------- |
| void DevToolsUIBindings::DispatchProtocolMessage( |
| content::DevToolsAgentHost* agent_host, const std::string& message) { |
| DCHECK(agent_host == agent_host_.get()); |
| if (!frontend_host_) |
| return; |
| |
| if (message.length() < kMaxMessageChunkSize) { |
| std::string param; |
| base::EscapeJSONString(message, true, ¶m); |
| base::string16 javascript = |
| base::UTF8ToUTF16("DevToolsAPI.dispatchMessage(" + param + ");"); |
| web_contents_->GetMainFrame()->ExecuteJavaScript(javascript, |
| base::NullCallback()); |
| 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) { |
| 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); |
| } |
| |
| void DevToolsUIBindings::InnerAttach() { |
| DCHECK(agent_host_.get()); |
| // TODO(dgozman): handle return value of AttachClient. |
| agent_host_->AttachClient(this); |
| } |
| |
| // 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(); |
| |
| const std::string kHttpPrefix = "http://"; |
| const std::string kHttpsPrefix = "https://"; |
| const std::string simplified_url = |
| base::StartsWith(url, kHttpsPrefix, base::CompareCase::SENSITIVE) |
| ? url.substr(kHttpsPrefix.length()) |
| : base::StartsWith(url, kHttpPrefix, base::CompareCase::SENSITIVE) |
| ? url.substr(kHttpPrefix.length()) |
| : url; |
| // DevTools UI is not localized. |
| web_contents()->UpdateTitleForEntry( |
| entry, base::UTF8ToUTF16( |
| base::StringPrintf(kTitleFormat, simplified_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: YES |
| cookies_store: "user" |
| setting: |
| "It's not possible to disable this feature from settings." |
| chrome_policy { |
| DeveloperToolsAvailability { |
| policy_options {mode: MANDATORY} |
| DeveloperToolsAvailability: 2 |
| } |
| } |
| })"); |
| |
| auto resource_request = std::make_unique<network::ResourceRequest>(); |
| resource_request->url = gurl; |
| // TODO(caseq): this preserves behavior of URLFetcher-based implementation. |
| // We really need to pass proper first party origin from the front-end. |
| resource_request->site_for_cookies = gurl; |
| resource_request->headers.AddHeadersFromString(headers); |
| |
| std::unique_ptr<network::mojom::URLLoaderFactory> file_url_loader_factory; |
| scoped_refptr<network::SharedURLLoaderFactory> network_url_loader_factory; |
| std::unique_ptr<network::mojom::URLLoaderFactory> webui_url_loader_factory; |
| network::mojom::URLLoaderFactory* url_loader_factory; |
| if (gurl.SchemeIsFile()) { |
| file_url_loader_factory = content::CreateFileURLLoaderFactory( |
| base::FilePath() /* profile_path */, |
| nullptr /* shared_cors_origin_access_list */); |
| url_loader_factory = file_url_loader_factory.get(); |
| } else if (content::HasWebUIScheme(gurl)) { |
| content::WebContents* target_tab; |
| #ifndef NDEBUG |
| // In debug builds, allow retrieving files from the chrome:// and |
| // devtools:// schemes |
| target_tab = DevToolsWindow::AsDevToolsWindow(web_contents_) |
| ->GetInspectedWebContents(); |
| const bool allow_web_ui_scheme = |
| target_tab && content::HasWebUIScheme(target_tab->GetURL()); |
| #else |
| const bool allow_web_ui_scheme = false; |
| #endif |
| if (allow_web_ui_scheme) { |
| std::vector<std::string> allowed_webui_hosts; |
| content::RenderFrameHost* frame_host = web_contents()->GetMainFrame(); |
| webui_url_loader_factory = content::CreateWebUIURLLoader( |
| frame_host, target_tab->GetURL().scheme(), |
| std::move(allowed_webui_hosts)); |
| url_loader_factory = webui_url_loader_factory.get(); |
| } else { |
| base::DictionaryValue response; |
| response.SetInteger("statusCode", 403); |
| callback.Run(&response); |
| return; |
| } |
| } else { |
| auto* partition = content::BrowserContext::GetStoragePartitionForSite( |
| web_contents_->GetBrowserContext(), gurl); |
| network_url_loader_factory = |
| partition->GetURLLoaderFactoryForBrowserProcess(); |
| url_loader_factory = network_url_loader_factory.get(); |
| } |
| |
| auto simple_url_loader = network::SimpleURLLoader::Create( |
| std::move(resource_request), traffic_annotation); |
| auto resource_loader = std::make_unique<NetworkResourceLoader>( |
| stream_id, this, std::move(simple_url_loader), url_loader_factory, |
| callback); |
| loaders_.insert(std::move(resource_loader)); |
| } |
| |
| void DevToolsUIBindings::OpenInNewTab(const std::string& url) { |
| delegate_->OpenInNewTab(url); |
| } |
| |
| void DevToolsUIBindings::ShowItemInFolder(const std::string& file_system_path) { |
| CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); |
| file_helper_->ShowItemInFolder(file_system_path); |
| } |
| |
| 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& type) { |
| CHECK(IsValidFrontendURL(web_contents_->GetURL()) && frontend_host_); |
| file_helper_->AddFileSystem( |
| type, base::Bind(&DevToolsUIBindings::ShowDevToolsInfoBar, |
| 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::ShowDevToolsInfoBar, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void DevToolsUIBindings::IndexPath( |
| int index_request_id, |
| const std::string& file_system_path, |
| const std::string& excluded_folders_message) { |
| 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; |
| std::vector<std::string> excluded_folders; |
| base::Optional<base::Value> parsed_excluded_folders = |
| base::JSONReader::Read(excluded_folders_message); |
| if (parsed_excluded_folders && parsed_excluded_folders->is_list()) { |
| const std::vector<base::Value>& folder_paths = |
| parsed_excluded_folders->GetList(); |
| for (const base::Value& folder_path : folder_paths) { |
| if (folder_path.is_string()) |
| excluded_folders.push_back(folder_path.GetString()); |
| } |
| } |
| |
| indexing_jobs_[index_request_id] = |
| scoped_refptr<DevToolsFileSystemIndexer::FileSystemIndexingJob>( |
| file_system_indexer_->IndexPath( |
| file_system_path, excluded_folders, |
| 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); |
| auto 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) { |
| delegate_->ShowCertificateViewer(cert_chain); |
| } |
| |
| 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::Optional<base::Value> parsed_port_forwarding = |
| base::JSONReader::Read(port_forwarding_config); |
| if (!parsed_port_forwarding || !parsed_port_forwarding->is_dict()) |
| return; |
| base::Optional<base::Value> parsed_network = |
| base::JSONReader::Read(network_discovery_config); |
| if (!parsed_network || !parsed_network->is_list()) |
| return; |
| profile_->GetPrefs()->SetBoolean( |
| prefs::kDevToolsDiscoverUsbDevicesEnabled, discover_usb_devices); |
| profile_->GetPrefs()->SetBoolean( |
| prefs::kDevToolsPortForwardingEnabled, port_forwarding_enabled); |
| profile_->GetPrefs()->Set(prefs::kDevToolsPortForwardingConfig, |
| *parsed_port_forwarding); |
| profile_->GetPrefs()->SetBoolean(prefs::kDevToolsDiscoverTCPTargetsEnabled, |
| network_discovery_enabled); |
| profile_->GetPrefs()->Set(prefs::kDevToolsTCPDiscoveryConfig, |
| *parsed_network); |
| } |
| |
| 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()->SetKey(name, base::Value(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); |
| InnerAttach(); |
| } |
| callback.Run(nullptr); |
| } |
| |
| void DevToolsUIBindings::ReadyForTest() { |
| delegate_->ReadyForTest(); |
| } |
| |
| void DevToolsUIBindings::ConnectionReady() { |
| delegate_->ConnectionReady(); |
| } |
| |
| void DevToolsUIBindings::SetOpenNewWindowForPopups(bool value) { |
| delegate_->SetOpenNewWindowForPopups(value); |
| } |
| |
| 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::RecordPerformanceHistogram(const std::string& name, |
| double duration) { |
| if (!frontend_host_) |
| return; |
| if (duration < 0) { |
| return; |
| } |
| // Use histogram_functions.h instead of macros as the name comes from the |
| // DevTools frontend javascript and so will always have the same call site. |
| base::TimeDelta delta = base::TimeDelta::FromMilliseconds(duration); |
| base::UmaHistogramTimes(name, delta); |
| } |
| |
| void DevToolsUIBindings::RecordUserMetricsAction(const std::string& name) { |
| if (!frontend_host_) |
| return; |
| // Use RecordComputedAction instead of RecordAction as the name comes from |
| // DevTools frontend javascript and so will always have the same call site. |
| base::RecordComputedAction(name); |
| } |
| |
| 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::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, |
| const std::string& file_system_path) { |
| base::Value url_value(url); |
| base::Value file_system_path_value(file_system_path); |
| CallClientFunction("DevToolsAPI.savedURL", &url_value, |
| &file_system_path_value, 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 std::string& error, |
| const DevToolsFileHelper::FileSystem* file_system) { |
| base::Value error_value(error); |
| std::unique_ptr<base::DictionaryValue> file_system_value( |
| file_system ? CreateFileSystemValue(*file_system) : nullptr); |
| CallClientFunction("DevToolsAPI.fileSystemAdded", &error_value, |
| file_system_value.get(), 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) { |
| const int kMaxPathsPerMessage = 1000; |
| size_t changed_index = 0; |
| size_t added_index = 0; |
| size_t removed_index = 0; |
| // Dispatch limited amount of file paths in a time to avoid |
| // IPC max message size limit. See https://crbug.com/797817. |
| while (changed_index < changed_paths.size() || |
| added_index < added_paths.size() || |
| removed_index < removed_paths.size()) { |
| int budget = kMaxPathsPerMessage; |
| base::ListValue changed, added, removed; |
| while (budget > 0 && changed_index < changed_paths.size()) { |
| changed.AppendString(changed_paths[changed_index++]); |
| --budget; |
| } |
| while (budget > 0 && added_index < added_paths.size()) { |
| added.AppendString(added_paths[added_index++]); |
| --budget; |
| } |
| while (budget > 0 && removed_index < removed_paths.size()) { |
| removed.AppendString(removed_paths[removed_index++]); |
| --budget; |
| } |
| 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 (auto 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::ShowDevToolsInfoBar( |
| const base::string16& message, |
| const DevToolsInfoBarDelegate::Callback& callback) { |
| if (!delegate_->GetInfoBarService()) { |
| callback.Run(false); |
| return; |
| } |
| DevToolsInfoBarDelegate::Create(message, callback); |
| } |
| |
| 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 the devtools process the ability to request URLs from the |
| // extension. |
| content::ChildProcessSecurityPolicy::GetInstance()->GrantRequestOrigin( |
| web_contents_->GetMainFrame()->GetProcess()->GetID(), |
| url::Origin::Create(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::RegisterExtensionsAPI(const std::string& origin, |
| const std::string& script) { |
| extensions_api_[origin + "/"] = script; |
| } |
| |
| 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; |
| InnerAttach(); |
| } |
| |
| 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), base::NullCallback()); |
| } |
| |
| void DevToolsUIBindings::ReadyToCommitNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (navigation_handle->IsInMainFrame()) { |
| if (frontend_loaded_ && agent_host_.get()) { |
| agent_host_->DetachClient(this); |
| InnerAttach(); |
| } |
| 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; |
| } |
| if (navigation_handle->GetRenderFrameHost() == |
| web_contents_->GetMainFrame() && |
| frontend_host_) { |
| return; |
| } |
| if (content::RenderFrameHost* opener = web_contents_->GetOpener()) { |
| content::WebContents* opener_wc = |
| content::WebContents::FromRenderFrameHost(opener); |
| DevToolsUIBindings* opener_bindings = |
| opener_wc ? DevToolsUIBindings::ForWebContents(opener_wc) : nullptr; |
| if (!opener_bindings || !opener_bindings->frontend_host_) |
| return; |
| } |
| frontend_host_ = content::DevToolsFrontendHost::Create( |
| navigation_handle->GetRenderFrameHost(), |
| base::Bind(&DevToolsUIBindings::HandleMessageFromDevToolsFrontend, |
| base::Unretained(this))); |
| return; |
| } |
| |
| content::RenderFrameHost* frame = navigation_handle->GetRenderFrameHost(); |
| std::string origin = navigation_handle->GetURL().GetOrigin().spec(); |
| auto it = extensions_api_.find(origin); |
| if (it == extensions_api_.end()) |
| return; |
| std::string script = base::StringPrintf("%s(\"%s\")", it->second.c_str(), |
| base::GenerateGUID().c_str()); |
| content::DevToolsFrontendHost::SetupExtensionsAPI(frame, script); |
| } |
| |
| 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(); |
| } |