blob: d60f31318a8de3eb436d3d384637ed7114a3c6fe [file] [log] [blame]
// 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/user_metrics.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/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";
// 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 {}
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::FundamentalValue* id = new base::FundamentalValue(stream_id_);
base::StringValue* chunkValue = new base::StringValue(chunk);
base::FundamentalValue* encodedValue = new base::FundamentalValue(encoded);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&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")
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 DidNavigateMainFrame(
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) 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) {
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())
return;
devtools_bindings_->UpdateFrontendHost(navigation_handle);
}
void DevToolsUIBindings::FrontendWebContentsObserver::
DocumentAvailableInMainFrame() {
devtools_bindings_->DocumentAvailableInMainFrame();
}
void DevToolsUIBindings::FrontendWebContentsObserver::
DocumentOnLoadCompletedInMainFrame() {
devtools_bindings_->DocumentOnLoadCompletedInMainFrame();
}
void DevToolsUIBindings::FrontendWebContentsObserver::
DidNavigateMainFrame(const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) {
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) {
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 (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::FundamentalValue total_size(static_cast<int>(message.length()));
for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) {
base::StringValue 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::FundamentalValue 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;
}
net::URLFetcher* fetcher =
net::URLFetcher::Create(gurl, net::URLFetcher::GET, this).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(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
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(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
file_helper_->AddFileSystem(
file_system_path,
base::Bind(&DevToolsUIBindings::ShowDevToolsConfirmInfoBar,
weak_factory_.GetWeakPtr()));
}
void DevToolsUIBindings::RemoveFileSystem(const std::string& file_system_path) {
CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
file_helper_->RemoveFileSystem(file_system_path);
}
void DevToolsUIBindings::UpgradeDraggedFileSystemPermissions(
const std::string& file_system_url) {
CHECK(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
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(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
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(web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme));
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::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) {
base::DictionaryValue* config_dict = nullptr;
std::unique_ptr<base::Value> parsed_config =
base::JSONReader::Read(port_forwarding_config);
if (!parsed_config || !parsed_config->GetAsDictionary(&config_dict))
return;
profile_->GetPrefs()->SetBoolean(
prefs::kDevToolsDiscoverUsbDevicesEnabled, discover_usb_devices);
profile_->GetPrefs()->SetBoolean(
prefs::kDevToolsPortForwardingEnabled, port_forwarding_enabled);
profile_->GetPrefs()->Set(
prefs::kDevToolsPortForwardingConfig, *config_dict);
}
void DevToolsUIBindings::DevicesDiscoveryConfigUpdated() {
CallClientFunction(
"DevToolsAPI.devicesDiscoveryConfigChanged",
profile_->GetPrefs()->FindPreference(
prefs::kDevToolsDiscoverUsbDevicesEnabled)->GetValue(),
profile_->GetPrefs()->FindPreference(
prefs::kDevToolsPortForwardingEnabled)->GetValue(),
profile_->GetPrefs()->FindPreference(
prefs::kDevToolsPortForwardingConfig)->GetValue());
}
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)));
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::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_ENUMERATION work correctly.
if (name == kDevToolsActionTakenHistogram)
UMA_HISTOGRAM_ENUMERATION(name, sample, boundary_value);
else if (name == kDevToolsPanelShownHistogram)
UMA_HISTOGRAM_ENUMERATION(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::StringValue 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;
base::DictionaryValue* headers = new base::DictionaryValue();
net::HttpResponseHeaders* rh = source->GetResponseHeaders();
response.SetInteger("statusCode", rh ? rh->response_code() : 200);
response.Set("headers", headers);
size_t iterator = 0;
std::string name;
std::string value;
while (rh && rh->EnumerateHeaderLines(&iterator, &name, &value))
headers->SetString(name, value);
it->second.Run(&response);
pending_requests_.erase(it);
delete source;
}
void DevToolsUIBindings::DeviceCountChanged(int count) {
base::FundamentalValue 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::StringValue url_value(url);
CallClientFunction("DevToolsAPI.savedURL", &url_value, NULL, NULL);
}
void DevToolsUIBindings::CanceledFileSaveAs(const std::string& url) {
base::StringValue url_value(url);
CallClientFunction("DevToolsAPI.canceledSaveURL",
&url_value, NULL, NULL);
}
void DevToolsUIBindings::AppendedTo(const std::string& url) {
base::StringValue 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::StringValue 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 list;
for (auto path : changed_paths)
list.AppendString(path);
for (auto path : added_paths)
list.AppendString(path);
for (auto path : removed_paths)
list.AppendString(path);
CallClientFunction("DevToolsAPI.fileSystemFilesChanged",
&list, NULL, NULL);
}
void DevToolsUIBindings::IndexingTotalWorkCalculated(
int request_id,
const std::string& file_system_path,
int total_work) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::FundamentalValue request_id_value(request_id);
base::StringValue file_system_path_value(file_system_path);
base::FundamentalValue 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::FundamentalValue request_id_value(request_id);
base::StringValue file_system_path_value(file_system_path);
base::FundamentalValue 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::FundamentalValue request_id_value(request_id);
base::StringValue 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::FundamentalValue request_id_value(request_id);
base::StringValue 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;
std::unique_ptr<base::DictionaryValue> extension_info(
new base::DictionaryValue());
extension_info->Set(
"startPage",
new base::StringValue(extensions::chrome_manifest_urls::GetDevToolsPage(
extension.get()).spec()));
extension_info->Set("name", new base::StringValue(extension->name()));
extension_info->Set("exposeExperimentalAPIs",
new base::FundamentalValue(
extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kExperimental)));
results.Append(std::move(extension_info));
}
if (!results.empty()) {
// At least one devtools extension exists; it will need to run in the
// devtools process. Grant it permission to load documents with
// chrome-extension:// origins.
content::ChildProcessSecurityPolicy::GetInstance()->GrantScheme(
web_contents_->GetMainFrame()->GetProcess()->GetID(),
extensions::kExtensionScheme);
}
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(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 (!web_contents_->GetURL().SchemeIs(content::kChromeDevToolsScheme))
return;
// 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();
}