1 // Copyright 2013 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 "content/shell/browser/shell_devtools_frontend.h"
7 #include "base/command_line.h"
8 #include "base/json/json_reader.h"
9 #include "base/json/json_writer.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/values.h"
14 #include "components/devtools_http_handler/devtools_http_handler.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/render_frame_host.h"
17 #include "content/public/browser/render_view_host.h"
18 #include "content/public/browser/web_contents.h"
19 #include "content/public/common/content_client.h"
20 #include "content/shell/browser/blink_test_controller.h"
21 #include "content/shell/browser/shell.h"
22 #include "content/shell/browser/shell_browser_context.h"
23 #include "content/shell/browser/shell_browser_main_parts.h"
24 #include "content/shell/browser/shell_content_browser_client.h"
25 #include "content/shell/browser/shell_devtools_manager_delegate.h"
26 #include "content/shell/common/shell_switches.h"
27 #include "net/base/io_buffer.h"
28 #include "net/base/net_errors.h"
29 #include "net/http/http_response_headers.h"
30 #include "net/url_request/url_fetcher.h"
31 #include "net/url_request/url_fetcher_response_writer.h"
38 // ResponseWriter -------------------------------------------------------------
40 class ResponseWriter
: public net::URLFetcherResponseWriter
{
42 ResponseWriter(base::WeakPtr
<ShellDevToolsFrontend
> shell_devtools_
,
44 ~ResponseWriter() override
;
46 // URLFetcherResponseWriter overrides:
47 int Initialize(const net::CompletionCallback
& callback
) override
;
48 int Write(net::IOBuffer
* buffer
,
50 const net::CompletionCallback
& callback
) override
;
51 int Finish(const net::CompletionCallback
& callback
) override
;
54 base::WeakPtr
<ShellDevToolsFrontend
> shell_devtools_
;
57 DISALLOW_COPY_AND_ASSIGN(ResponseWriter
);
60 ResponseWriter::ResponseWriter(
61 base::WeakPtr
<ShellDevToolsFrontend
> shell_devtools
,
63 : shell_devtools_(shell_devtools
),
64 stream_id_(stream_id
) {
67 ResponseWriter::~ResponseWriter() {
70 int ResponseWriter::Initialize(const net::CompletionCallback
& callback
) {
74 int ResponseWriter::Write(net::IOBuffer
* buffer
,
76 const net::CompletionCallback
& callback
) {
77 base::FundamentalValue
* id
= new base::FundamentalValue(stream_id_
);
78 base::StringValue
* chunk
=
79 new base::StringValue(std::string(buffer
->data(), num_bytes
));
81 content::BrowserThread::PostTask(
82 content::BrowserThread::UI
, FROM_HERE
,
83 base::Bind(&ShellDevToolsFrontend::CallClientFunction
,
84 shell_devtools_
, "DevToolsAPI.streamWrite",
85 base::Owned(id
), base::Owned(chunk
), nullptr));
89 int ResponseWriter::Finish(const net::CompletionCallback
& callback
) {
95 // This constant should be in sync with
96 // the constant at devtools_ui_bindings.cc.
97 const size_t kMaxMessageChunkSize
= IPC::Channel::kMaximumMessageSize
/ 4;
100 ShellDevToolsFrontend
* ShellDevToolsFrontend::Show(
101 WebContents
* inspected_contents
) {
102 Shell
* shell
= Shell::CreateNewWindow(inspected_contents
->GetBrowserContext(),
106 ShellDevToolsFrontend
* devtools_frontend
= new ShellDevToolsFrontend(
110 devtools_http_handler::DevToolsHttpHandler
* http_handler
=
111 ShellContentBrowserClient::Get()
112 ->shell_browser_main_parts()
113 ->devtools_http_handler();
114 shell
->LoadURL(http_handler
->GetFrontendURL("/devtools/inspector.html"));
116 return devtools_frontend
;
119 void ShellDevToolsFrontend::Activate() {
120 frontend_shell_
->ActivateContents(web_contents());
123 void ShellDevToolsFrontend::Focus() {
124 web_contents()->Focus();
127 void ShellDevToolsFrontend::InspectElementAt(int x
, int y
) {
129 agent_host_
->InspectElement(x
, y
);
132 void ShellDevToolsFrontend::Close() {
133 frontend_shell_
->Close();
136 void ShellDevToolsFrontend::DisconnectFromTarget() {
139 agent_host_
->DetachClient();
143 ShellDevToolsFrontend::ShellDevToolsFrontend(Shell
* frontend_shell
,
144 WebContents
* inspected_contents
)
145 : WebContentsObserver(frontend_shell
->web_contents()),
146 frontend_shell_(frontend_shell
),
147 inspected_contents_(inspected_contents
),
148 weak_factory_(this) {
151 ShellDevToolsFrontend::~ShellDevToolsFrontend() {
152 for (const auto& pair
: pending_requests_
)
156 void ShellDevToolsFrontend::RenderViewCreated(
157 RenderViewHost
* render_view_host
) {
158 if (!frontend_host_
) {
159 frontend_host_
.reset(
160 DevToolsFrontendHost::Create(web_contents()->GetMainFrame(), this));
164 void ShellDevToolsFrontend::DocumentAvailableInMainFrame() {
165 agent_host_
= DevToolsAgentHost::GetOrCreateFor(inspected_contents_
);
166 agent_host_
->AttachClient(this);
169 void ShellDevToolsFrontend::WebContentsDestroyed() {
171 agent_host_
->DetachClient();
175 void ShellDevToolsFrontend::HandleMessageFromDevToolsFrontend(
176 const std::string
& message
) {
180 base::ListValue
* params
= NULL
;
181 base::DictionaryValue
* dict
= NULL
;
182 scoped_ptr
<base::Value
> parsed_message
= base::JSONReader::Read(message
);
183 if (!parsed_message
||
184 !parsed_message
->GetAsDictionary(&dict
) ||
185 !dict
->GetString("method", &method
)) {
189 dict
->GetInteger("id", &request_id
);
190 dict
->GetList("params", ¶ms
);
192 std::string browser_message
;
193 if (method
== "sendMessageToBrowser" && params
&&
194 params
->GetSize() == 1 && params
->GetString(0, &browser_message
)) {
195 agent_host_
->DispatchProtocolMessage(browser_message
);
196 } else if (method
== "loadCompleted") {
197 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
198 base::ASCIIToUTF16("DevToolsAPI.setUseSoftMenu(true);"));
199 } else if (method
== "loadNetworkResource" && params
->GetSize() == 3) {
200 // TODO(pfeldman): handle some of the embedder messages in content.
204 if (!params
->GetString(0, &url
) ||
205 !params
->GetString(1, &headers
) ||
206 !params
->GetInteger(2, &stream_id
)) {
211 if (!gurl
.is_valid()) {
212 base::DictionaryValue response
;
213 response
.SetInteger("statusCode", 404);
214 SendMessageAck(request_id
, &response
);
218 net::URLFetcher
* fetcher
=
219 net::URLFetcher::Create(gurl
, net::URLFetcher::GET
, this).release();
220 pending_requests_
[fetcher
] = request_id
;
221 fetcher
->SetRequestContext(web_contents()->GetBrowserContext()->
222 GetRequestContext());
223 fetcher
->SetExtraRequestHeaders(headers
);
224 fetcher
->SaveResponseWithWriter(scoped_ptr
<net::URLFetcherResponseWriter
>(
225 new ResponseWriter(weak_factory_
.GetWeakPtr(), stream_id
)));
228 } else if (method
== "getPreferences") {
229 SendMessageAck(request_id
, &preferences_
);
231 } else if (method
== "setPreference") {
234 if (!params
->GetString(0, &name
) ||
235 !params
->GetString(1, &value
)) {
238 preferences_
.SetStringWithoutPathExpansion(name
, value
);
239 } else if (method
== "removePreference") {
241 if (!params
->GetString(0, &name
))
243 preferences_
.RemoveWithoutPathExpansion(name
, nullptr);
249 SendMessageAck(request_id
, nullptr);
252 void ShellDevToolsFrontend::HandleMessageFromDevToolsFrontendToBackend(
253 const std::string
& message
) {
255 agent_host_
->DispatchProtocolMessage(message
);
258 void ShellDevToolsFrontend::DispatchProtocolMessage(
259 DevToolsAgentHost
* agent_host
, const std::string
& message
) {
261 if (message
.length() < kMaxMessageChunkSize
) {
262 base::string16 javascript
= base::UTF8ToUTF16(
263 "DevToolsAPI.dispatchMessage(" + message
+ ");");
264 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(javascript
);
268 base::FundamentalValue
total_size(static_cast<int>(message
.length()));
269 for (size_t pos
= 0; pos
< message
.length(); pos
+= kMaxMessageChunkSize
) {
271 base::JSONWriter::Write(
272 base::StringValue(message
.substr(pos
, kMaxMessageChunkSize
)), ¶m
);
273 std::string code
= "DevToolsAPI.dispatchMessageChunk(" + param
+ ");";
274 base::string16 javascript
= base::UTF8ToUTF16(code
);
275 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(javascript
);
279 void ShellDevToolsFrontend::OnURLFetchComplete(const net::URLFetcher
* source
) {
280 // TODO(pfeldman): this is a copy of chrome's devtools_ui_bindings.cc.
281 // We should handle some of the commands including this one in content.
283 PendingRequestsMap::iterator it
= pending_requests_
.find(source
);
284 DCHECK(it
!= pending_requests_
.end());
286 base::DictionaryValue response
;
287 base::DictionaryValue
* headers
= new base::DictionaryValue();
288 net::HttpResponseHeaders
* rh
= source
->GetResponseHeaders();
289 response
.SetInteger("statusCode", rh
? rh
->response_code() : 200);
290 response
.Set("headers", headers
);
292 void* iterator
= NULL
;
295 while (rh
&& rh
->EnumerateHeaderLines(&iterator
, &name
, &value
))
296 headers
->SetString(name
, value
);
298 SendMessageAck(it
->second
, &response
);
299 pending_requests_
.erase(it
);
303 void ShellDevToolsFrontend::CallClientFunction(
304 const std::string
& function_name
,
305 const base::Value
* arg1
,
306 const base::Value
* arg2
,
307 const base::Value
* arg3
) {
308 std::string javascript
= function_name
+ "(";
311 base::JSONWriter::Write(*arg1
, &json
);
312 javascript
.append(json
);
314 base::JSONWriter::Write(*arg2
, &json
);
315 javascript
.append(", ").append(json
);
317 base::JSONWriter::Write(*arg3
, &json
);
318 javascript
.append(", ").append(json
);
322 javascript
.append(");");
323 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
324 base::UTF8ToUTF16(javascript
));
327 void ShellDevToolsFrontend::SendMessageAck(int request_id
,
328 const base::Value
* arg
) {
329 base::FundamentalValue
id_value(request_id
);
330 CallClientFunction("DevToolsAPI.embedderMessageAck",
331 &id_value
, arg
, nullptr);
334 void ShellDevToolsFrontend::AgentHostClosed(
335 DevToolsAgentHost
* agent_host
, bool replaced
) {
336 frontend_shell_
->Close();
339 } // namespace content