From 6546556a14a58cb23d2282a5a5ab715bd98482e9 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Wed, 5 Jun 2019 17:12:29 +0200 Subject: [PATCH] Add NetworkService support for DevTools source maps (fixes issue #2671, see issue #2622) --- libcef/browser/devtools/devtools_frontend.cc | 158 +++++++++++++++++-- libcef/browser/devtools/devtools_frontend.h | 6 + 2 files changed, 150 insertions(+), 14 deletions(-) diff --git a/libcef/browser/devtools/devtools_frontend.cc b/libcef/browser/devtools/devtools_frontend.cc index 98ec92178..4bb462de2 100644 --- a/libcef/browser/devtools/devtools_frontend.cc +++ b/libcef/browser/devtools/devtools_frontend.cc @@ -11,7 +11,9 @@ #include "libcef/browser/browser_context.h" #include "libcef/browser/devtools/devtools_manager_delegate.h" #include "libcef/browser/net/devtools_scheme_handler.h" +#include "libcef/common/net_service/util.h" +#include "base/base64.h" #include "base/guid.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" @@ -28,13 +30,16 @@ #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/file_url_loader.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_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/common/content_client.h" #include "content/public/common/url_constants.h" +#include "content/public/common/url_utils.h" #include "ipc/ipc_channel.h" #include "net/base/completion_once_callback.h" #include "net/base/io_buffer.h" @@ -43,6 +48,8 @@ #include "net/traffic_annotation/network_traffic_annotation.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_response_writer.h" +#include "services/network/public/cpp/simple_url_loader.h" +#include "services/network/public/cpp/simple_url_loader_stream_consumer.h" namespace { @@ -108,8 +115,87 @@ static std::string GetFrontendURL() { scheme::kChromeDevToolsHost); } +std::unique_ptr BuildObjectForResponse( + const net::HttpResponseHeaders* rh) { + auto response = std::make_unique(); + response->SetInteger("statusCode", rh ? rh->response_code() : 200); + + auto headers = std::make_unique(); + 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; +} + } // namespace +class CefDevToolsFrontend::NetworkResourceLoader + : public network::SimpleURLLoaderStreamConsumer { + public: + NetworkResourceLoader(int stream_id, + CefDevToolsFrontend* bindings, + std::unique_ptr loader, + network::mojom::URLLoaderFactory* url_loader_factory, + int request_id) + : stream_id_(stream_id), + bindings_(bindings), + loader_(std::move(loader)), + request_id_(request_id) { + loader_->SetOnResponseStartedCallback(base::BindOnce( + &NetworkResourceLoader::OnResponseStarted, base::Unretained(this))); + loader_->DownloadAsStream(url_loader_factory, this); + } + + private: + void OnResponseStarted(const GURL& final_url, + const network::ResourceResponseHead& 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()); + bindings_->SendMessageAck(request_id_, response.get()); + + bindings_->loaders_.erase(bindings_->loaders_.find(this)); + } + + void OnRetry(base::OnceClosure start_retry) override { NOTREACHED(); } + + const int stream_id_; + CefDevToolsFrontend* const bindings_; + std::unique_ptr loader_; + int request_id_; + scoped_refptr response_headers_; + + DISALLOW_COPY_AND_ASSIGN(NetworkResourceLoader); +}; + // This constant should be in sync with // the constant at devtools_ui_bindings.cc. const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4; @@ -310,20 +396,62 @@ void CefDevToolsFrontend::HandleMessageFromDevToolsFrontend( } } })"); - net::URLFetcher* fetcher = - net::URLFetcher::Create(gurl, net::URLFetcher::GET, this, - traffic_annotation) - .release(); - pending_requests_[fetcher] = request_id; - fetcher->SetRequestContext( - content::BrowserContext::GetDefaultStoragePartition( - web_contents()->GetBrowserContext()) - ->GetURLRequestContext()); - fetcher->SetExtraRequestHeaders(headers); - fetcher->SaveResponseWithWriter( - std::unique_ptr( - new ResponseWriter(weak_factory_.GetWeakPtr(), stream_id))); - fetcher->Start(); + + if (net_service::IsEnabled()) { + // Based on DevToolsUIBindings::LoadNetworkResource. + auto resource_request = std::make_unique(); + 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 file_url_loader_factory; + scoped_refptr network_url_loader_factory; + std::unique_ptr + 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)) { + base::DictionaryValue response; + response.SetInteger("statusCode", 403); + SendMessageAck(request_id, &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( + stream_id, this, std::move(simple_url_loader), url_loader_factory, + request_id); + loaders_.insert(std::move(resource_loader)); + } else { + net::URLFetcher* fetcher = + net::URLFetcher::Create(gurl, net::URLFetcher::GET, this, + traffic_annotation) + .release(); + pending_requests_[fetcher] = request_id; + fetcher->SetRequestContext( + content::BrowserContext::GetDefaultStoragePartition( + web_contents()->GetBrowserContext()) + ->GetURLRequestContext()); + fetcher->SetExtraRequestHeaders(headers); + fetcher->SaveResponseWithWriter( + std::unique_ptr( + new ResponseWriter(weak_factory_.GetWeakPtr(), stream_id))); + fetcher->Start(); + } return; } else if (method == "getPreferences") { SendMessageAck(request_id, @@ -409,6 +537,8 @@ void CefDevToolsFrontend::DispatchProtocolMessage( } void CefDevToolsFrontend::OnURLFetchComplete(const net::URLFetcher* source) { + DCHECK(!net_service::IsEnabled()); + // TODO(pfeldman): this is a copy of chrome's devtools_ui_bindings.cc. // We should handle some of the commands including this one in content. DCHECK(source); diff --git a/libcef/browser/devtools/devtools_frontend.h b/libcef/browser/devtools/devtools_frontend.h index 1f7f3678e..edc65cb48 100644 --- a/libcef/browser/devtools/devtools_frontend.h +++ b/libcef/browser/devtools/devtools_frontend.h @@ -90,8 +90,14 @@ class CefDevToolsFrontend : public content::WebContentsObserver, scoped_refptr agent_host_; CefPoint inspect_element_at_; std::unique_ptr frontend_host_; + using PendingRequestsMap = std::map; PendingRequestsMap pending_requests_; + + class NetworkResourceLoader; + std::set, base::UniquePtrComparator> + loaders_; + using ExtensionsAPIs = std::map; ExtensionsAPIs extensions_api_; CefDevToolsFileManager file_manager_;