1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/webui/devtools_ui.h"
7 #include "base/memory/ref_counted_memory.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/common/url_constants.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "content/public/browser/devtools_frontend_host.h"
14 #include "content/public/browser/url_data_source.h"
15 #include "content/public/browser/web_contents.h"
16 #include "content/public/browser/web_ui.h"
17 #include "content/public/common/user_agent.h"
18 #include "net/url_request/url_fetcher.h"
19 #include "net/url_request/url_fetcher_delegate.h"
20 #include "net/url_request/url_request_context_getter.h"
22 using content::BrowserThread
;
23 using content::WebContents
;
27 std::string
PathWithoutParams(const std::string
& path
) {
28 return GURL(std::string("chrome-devtools://devtools/") + path
)
32 const char kRemoteFrontendDomain
[] = "chrome-devtools-frontend.appspot.com";
33 const char kRemoteFrontendBase
[] =
34 "https://chrome-devtools-frontend.appspot.com/";
35 const char kRemoteFrontendPath
[] = "serve_file";
36 const char kHttpNotFound
[] = "HTTP/1.1 404 Not Found\n\n";
38 #if defined(DEBUG_DEVTOOLS)
39 // Local frontend url provided by InspectUI.
40 const char kFallbackFrontendURL
[] =
41 "chrome-devtools://devtools/bundled/inspector.html";
43 // URL causing the DevTools window to display a plain text warning.
44 const char kFallbackFrontendURL
[] =
45 "data:text/plain,Cannot load DevTools frontend from an untrusted origin";
46 #endif // defined(DEBUG_DEVTOOLS)
48 // DevToolsDataSource ---------------------------------------------------------
50 std::string
GetMimeTypeForPath(const std::string
& path
) {
51 std::string filename
= PathWithoutParams(path
);
52 if (base::EndsWith(filename
, ".html", base::CompareCase::INSENSITIVE_ASCII
)) {
54 } else if (base::EndsWith(filename
, ".css",
55 base::CompareCase::INSENSITIVE_ASCII
)) {
57 } else if (base::EndsWith(filename
, ".js",
58 base::CompareCase::INSENSITIVE_ASCII
)) {
59 return "application/javascript";
60 } else if (base::EndsWith(filename
, ".png",
61 base::CompareCase::INSENSITIVE_ASCII
)) {
63 } else if (base::EndsWith(filename
, ".gif",
64 base::CompareCase::INSENSITIVE_ASCII
)) {
66 } else if (base::EndsWith(filename
, ".svg",
67 base::CompareCase::INSENSITIVE_ASCII
)) {
68 return "image/svg+xml";
69 } else if (base::EndsWith(filename
, ".manifest",
70 base::CompareCase::INSENSITIVE_ASCII
)) {
71 return "text/cache-manifest";
76 // An URLDataSource implementation that handles chrome-devtools://devtools/
77 // requests. Three types of requests could be handled based on the URL path:
78 // 1. /bundled/: bundled DevTools frontend is served.
79 // 2. /remote/: remote DevTools frontend is served from App Engine.
80 class DevToolsDataSource
: public content::URLDataSource
,
81 public net::URLFetcherDelegate
{
83 using GotDataCallback
= content::URLDataSource::GotDataCallback
;
85 explicit DevToolsDataSource(net::URLRequestContextGetter
* request_context
);
87 // content::URLDataSource implementation.
88 std::string
GetSource() const override
;
90 void StartDataRequest(const std::string
& path
,
91 int render_process_id
,
93 const GotDataCallback
& callback
) override
;
96 // content::URLDataSource overrides.
97 std::string
GetMimeType(const std::string
& path
) const override
;
98 bool ShouldAddContentSecurityPolicy() const override
;
99 bool ShouldDenyXFrameOptions() const override
;
100 bool ShouldServeMimeTypeAsContentTypeHeader() const override
;
102 // net::URLFetcherDelegate overrides.
103 void OnURLFetchComplete(const net::URLFetcher
* source
) override
;
105 // Serves bundled DevTools frontend from ResourceBundle.
106 void StartBundledDataRequest(const std::string
& path
,
107 int render_process_id
,
109 const GotDataCallback
& callback
);
111 // Serves remote DevTools frontend from hard-coded App Engine domain.
112 void StartRemoteDataRequest(const std::string
& path
,
113 int render_process_id
,
115 const GotDataCallback
& callback
);
117 ~DevToolsDataSource() override
;
119 scoped_refptr
<net::URLRequestContextGetter
> request_context_
;
121 using PendingRequestsMap
= std::map
<const net::URLFetcher
*, GotDataCallback
>;
122 PendingRequestsMap pending_
;
124 DISALLOW_COPY_AND_ASSIGN(DevToolsDataSource
);
127 DevToolsDataSource::DevToolsDataSource(
128 net::URLRequestContextGetter
* request_context
)
129 : request_context_(request_context
) {
132 DevToolsDataSource::~DevToolsDataSource() {
133 for (const auto& pair
: pending_
) {
136 new base::RefCountedStaticMemory(kHttpNotFound
, strlen(kHttpNotFound
)));
140 std::string
DevToolsDataSource::GetSource() const {
141 return chrome::kChromeUIDevToolsHost
;
144 void DevToolsDataSource::StartDataRequest(
145 const std::string
& path
,
146 int render_process_id
,
148 const content::URLDataSource::GotDataCallback
& callback
) {
149 // Serve request from local bundle.
150 std::string
bundled_path_prefix(chrome::kChromeUIDevToolsBundledPath
);
151 bundled_path_prefix
+= "/";
152 if (base::StartsWith(path
, bundled_path_prefix
,
153 base::CompareCase::INSENSITIVE_ASCII
)) {
154 StartBundledDataRequest(path
.substr(bundled_path_prefix
.length()),
155 render_process_id
, render_frame_id
, callback
);
159 // Serve request from remote location.
160 std::string
remote_path_prefix(chrome::kChromeUIDevToolsRemotePath
);
161 remote_path_prefix
+= "/";
162 if (base::StartsWith(path
, remote_path_prefix
,
163 base::CompareCase::INSENSITIVE_ASCII
)) {
164 StartRemoteDataRequest(path
.substr(remote_path_prefix
.length()),
165 render_process_id
, render_frame_id
, callback
);
172 std::string
DevToolsDataSource::GetMimeType(const std::string
& path
) const {
173 return GetMimeTypeForPath(path
);
176 bool DevToolsDataSource::ShouldAddContentSecurityPolicy() const {
180 bool DevToolsDataSource::ShouldDenyXFrameOptions() const {
184 bool DevToolsDataSource::ShouldServeMimeTypeAsContentTypeHeader() const {
188 void DevToolsDataSource::StartBundledDataRequest(
189 const std::string
& path
,
190 int render_process_id
,
192 const content::URLDataSource::GotDataCallback
& callback
) {
193 std::string filename
= PathWithoutParams(path
);
194 base::StringPiece resource
=
195 content::DevToolsFrontendHost::GetFrontendResource(filename
);
197 DLOG_IF(WARNING
, resource
.empty())
198 << "Unable to find dev tool resource: " << filename
199 << ". If you compiled with debug_devtools=1, try running with "
201 scoped_refptr
<base::RefCountedStaticMemory
> bytes(
202 new base::RefCountedStaticMemory(resource
.data(), resource
.length()));
203 callback
.Run(bytes
.get());
206 void DevToolsDataSource::StartRemoteDataRequest(
207 const std::string
& path
,
208 int render_process_id
,
210 const content::URLDataSource::GotDataCallback
& callback
) {
211 GURL url
= GURL(kRemoteFrontendBase
+ path
);
212 CHECK_EQ(url
.host(), kRemoteFrontendDomain
);
213 if (!url
.is_valid()) {
215 new base::RefCountedStaticMemory(kHttpNotFound
, strlen(kHttpNotFound
)));
218 net::URLFetcher
* fetcher
=
219 net::URLFetcher::Create(url
, net::URLFetcher::GET
, this).release();
220 pending_
[fetcher
] = callback
;
221 fetcher
->SetRequestContext(request_context_
.get());
225 void DevToolsDataSource::OnURLFetchComplete(const net::URLFetcher
* source
) {
227 PendingRequestsMap::iterator it
= pending_
.find(source
);
228 DCHECK(it
!= pending_
.end());
229 std::string response
;
230 source
->GetResponseAsString(&response
);
232 it
->second
.Run(base::RefCountedString::TakeString(&response
));
238 // DevToolsUI -----------------------------------------------------------------
241 GURL
DevToolsUI::GetProxyURL(const std::string
& frontend_url
) {
242 GURL
url(frontend_url
);
243 if (!url
.is_valid() || url
.host() != kRemoteFrontendDomain
)
244 return GURL(kFallbackFrontendURL
);
245 return GURL(base::StringPrintf("%s://%s/%s/%s",
246 content::kChromeDevToolsScheme
,
247 chrome::kChromeUIDevToolsHost
,
248 chrome::kChromeUIDevToolsRemotePath
,
249 url
.path().substr(1).c_str()));
253 GURL
DevToolsUI::GetRemoteBaseURL() {
254 return GURL(base::StringPrintf(
258 content::GetWebKitRevision().c_str()));
261 DevToolsUI::DevToolsUI(content::WebUI
* web_ui
)
262 : WebUIController(web_ui
),
263 bindings_(web_ui
->GetWebContents()) {
264 web_ui
->SetBindings(0);
265 Profile
* profile
= Profile::FromWebUI(web_ui
);
266 content::URLDataSource::Add(
268 new DevToolsDataSource(profile
->GetRequestContext()));
271 DevToolsUI::~DevToolsUI() {