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 "content/public/browser/browser_thread.h"
15 #include "content/public/browser/devtools_http_handler.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/shell.h"
21 #include "content/shell/browser/shell_browser_context.h"
22 #include "content/shell/browser/shell_browser_main_parts.h"
23 #include "content/shell/browser/shell_content_browser_client.h"
24 #include "content/shell/browser/shell_devtools_manager_delegate.h"
25 #include "content/shell/browser/webkit_test_controller.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 DevToolsHttpHandler
* http_handler
= ShellContentBrowserClient::Get()
111 ->shell_browser_main_parts()
112 ->devtools_http_handler();
113 shell
->LoadURL(http_handler
->GetFrontendURL("/devtools/devtools.html"));
115 return devtools_frontend
;
118 void ShellDevToolsFrontend::Activate() {
119 frontend_shell_
->ActivateContents(web_contents());
122 void ShellDevToolsFrontend::Focus() {
123 web_contents()->Focus();
126 void ShellDevToolsFrontend::InspectElementAt(int x
, int y
) {
128 agent_host_
->InspectElement(x
, y
);
131 void ShellDevToolsFrontend::Close() {
132 frontend_shell_
->Close();
135 void ShellDevToolsFrontend::DisconnectFromTarget() {
138 agent_host_
->DetachClient();
142 ShellDevToolsFrontend::ShellDevToolsFrontend(Shell
* frontend_shell
,
143 WebContents
* inspected_contents
)
144 : WebContentsObserver(frontend_shell
->web_contents()),
145 frontend_shell_(frontend_shell
),
146 inspected_contents_(inspected_contents
),
147 weak_factory_(this) {
150 ShellDevToolsFrontend::~ShellDevToolsFrontend() {
151 for (const auto& pair
: pending_requests_
)
155 void ShellDevToolsFrontend::RenderViewCreated(
156 RenderViewHost
* render_view_host
) {
157 if (!frontend_host_
) {
158 frontend_host_
.reset(
159 DevToolsFrontendHost::Create(web_contents()->GetMainFrame(), this));
163 void ShellDevToolsFrontend::DocumentAvailableInMainFrame() {
164 agent_host_
= DevToolsAgentHost::GetOrCreateFor(inspected_contents_
);
165 agent_host_
->AttachClient(this);
168 void ShellDevToolsFrontend::WebContentsDestroyed() {
170 agent_host_
->DetachClient();
174 void ShellDevToolsFrontend::HandleMessageFromDevToolsFrontend(
175 const std::string
& message
) {
179 base::ListValue
* params
= NULL
;
180 base::DictionaryValue
* dict
= NULL
;
181 scoped_ptr
<base::Value
> parsed_message(base::JSONReader::Read(message
));
182 if (!parsed_message
||
183 !parsed_message
->GetAsDictionary(&dict
) ||
184 !dict
->GetString("method", &method
)) {
188 dict
->GetInteger("id", &request_id
);
189 dict
->GetList("params", ¶ms
);
191 std::string browser_message
;
192 if (method
== "sendMessageToBrowser" && params
&&
193 params
->GetSize() == 1 && params
->GetString(0, &browser_message
)) {
194 agent_host_
->DispatchProtocolMessage(browser_message
);
195 } else if (method
== "loadCompleted") {
196 web_contents()->GetMainFrame()->ExecuteJavaScript(
197 base::ASCIIToUTF16("DevToolsAPI.setUseSoftMenu(true);"));
198 } else if (method
== "loadNetworkResource" && params
->GetSize() == 3) {
199 // TODO(pfeldman): handle some of the embedder messages in content.
203 if (!params
->GetString(0, &url
) ||
204 !params
->GetString(1, &headers
) ||
205 !params
->GetInteger(2, &stream_id
)) {
210 if (!gurl
.is_valid()) {
211 base::DictionaryValue response
;
212 response
.SetInteger("statusCode", 404);
213 SendMessageAck(request_id
, &response
);
217 net::URLFetcher
* fetcher
=
218 net::URLFetcher::Create(gurl
, net::URLFetcher::GET
, this);
219 pending_requests_
[fetcher
] = request_id
;
220 fetcher
->SetRequestContext(web_contents()->GetBrowserContext()->
221 GetRequestContext());
222 fetcher
->SetExtraRequestHeaders(headers
);
223 fetcher
->SaveResponseWithWriter(scoped_ptr
<net::URLFetcherResponseWriter
>(
224 new ResponseWriter(weak_factory_
.GetWeakPtr(), stream_id
)));
232 SendMessageAck(request_id
, nullptr);
235 void ShellDevToolsFrontend::HandleMessageFromDevToolsFrontendToBackend(
236 const std::string
& message
) {
238 agent_host_
->DispatchProtocolMessage(message
);
241 void ShellDevToolsFrontend::DispatchProtocolMessage(
242 DevToolsAgentHost
* agent_host
, const std::string
& message
) {
244 if (message
.length() < kMaxMessageChunkSize
) {
245 base::string16 javascript
= base::UTF8ToUTF16(
246 "DevToolsAPI.dispatchMessage(" + message
+ ");");
247 web_contents()->GetMainFrame()->ExecuteJavaScript(javascript
);
251 base::FundamentalValue
total_size(static_cast<int>(message
.length()));
252 for (size_t pos
= 0; pos
< message
.length(); pos
+= kMaxMessageChunkSize
) {
253 base::StringValue
message_value(message
.substr(pos
, kMaxMessageChunkSize
));
255 base::JSONWriter::Write(&message_value
, ¶m
);
256 std::string code
= "DevToolsAPI.dispatchMessageChunk(" + param
+ ");";
257 base::string16 javascript
= base::UTF8ToUTF16(code
);
258 web_contents()->GetMainFrame()->ExecuteJavaScript(javascript
);
262 void ShellDevToolsFrontend::OnURLFetchComplete(const net::URLFetcher
* source
) {
263 // TODO(pfeldman): this is a copy of chrome's devtools_ui_bindings.cc.
264 // We should handle some of the commands including this one in content.
266 PendingRequestsMap::iterator it
= pending_requests_
.find(source
);
267 DCHECK(it
!= pending_requests_
.end());
269 base::DictionaryValue response
;
270 base::DictionaryValue
* headers
= new base::DictionaryValue();
271 net::HttpResponseHeaders
* rh
= source
->GetResponseHeaders();
272 response
.SetInteger("statusCode", rh
? rh
->response_code() : 200);
273 response
.Set("headers", headers
);
275 void* iterator
= NULL
;
278 while (rh
&& rh
->EnumerateHeaderLines(&iterator
, &name
, &value
))
279 headers
->SetString(name
, value
);
281 SendMessageAck(it
->second
, &response
);
282 pending_requests_
.erase(it
);
286 void ShellDevToolsFrontend::CallClientFunction(
287 const std::string
& function_name
,
288 const base::Value
* arg1
,
289 const base::Value
* arg2
,
290 const base::Value
* arg3
) {
291 std::string javascript
= function_name
+ "(";
294 base::JSONWriter::Write(arg1
, &json
);
295 javascript
.append(json
);
297 base::JSONWriter::Write(arg2
, &json
);
298 javascript
.append(", ").append(json
);
300 base::JSONWriter::Write(arg3
, &json
);
301 javascript
.append(", ").append(json
);
305 javascript
.append(");");
306 web_contents()->GetMainFrame()->ExecuteJavaScript(
307 base::UTF8ToUTF16(javascript
));
310 void ShellDevToolsFrontend::SendMessageAck(int request_id
,
311 const base::Value
* arg
) {
312 base::FundamentalValue
id_value(request_id
);
313 CallClientFunction("DevToolsAPI.embedderMessageAck",
314 &id_value
, arg
, nullptr);
317 void ShellDevToolsFrontend::AgentHostClosed(
318 DevToolsAgentHost
* agent_host
, bool replaced
) {
319 frontend_shell_
->Close();
322 } // namespace content