| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/shell/browser/shell_devtools_frontend.h" | 5 #include "content/shell/browser/shell_devtools_frontend.h" |
| 6 | 6 |
| 7 #include <stddef.h> | |
| 8 | |
| 9 #include "base/json/json_reader.h" | |
| 10 #include "base/json/json_writer.h" | |
| 11 #include "base/json/string_escape.h" | |
| 12 #include "base/macros.h" | |
| 13 #include "base/strings/string_number_conversions.h" | 7 #include "base/strings/string_number_conversions.h" |
| 14 #include "base/strings/stringprintf.h" | 8 #include "base/strings/stringprintf.h" |
| 15 #include "base/strings/utf_string_conversions.h" | 9 #include "base/strings/utf_string_conversions.h" |
| 16 #include "base/values.h" | |
| 17 #include "content/public/browser/browser_thread.h" | |
| 18 #include "content/public/browser/render_frame_host.h" | |
| 19 #include "content/public/browser/render_view_host.h" | |
| 20 #include "content/public/browser/storage_partition.h" | |
| 21 #include "content/public/browser/web_contents.h" | 10 #include "content/public/browser/web_contents.h" |
| 22 #include "content/public/common/content_client.h" | 11 #include "content/public/browser/web_contents_observer.h" |
| 23 #include "content/shell/browser/shell.h" | 12 #include "content/shell/browser/shell.h" |
| 24 #include "content/shell/browser/shell_browser_context.h" | 13 #include "content/shell/browser/shell_browser_context.h" |
| 25 #include "content/shell/browser/shell_browser_main_parts.h" | 14 #include "content/shell/browser/shell_devtools_bindings.h" |
| 26 #include "content/shell/browser/shell_content_browser_client.h" | |
| 27 #include "content/shell/browser/shell_devtools_manager_delegate.h" | 15 #include "content/shell/browser/shell_devtools_manager_delegate.h" |
| 28 #include "net/base/io_buffer.h" | |
| 29 #include "net/base/net_errors.h" | |
| 30 #include "net/http/http_response_headers.h" | |
| 31 #include "net/url_request/url_fetcher.h" | |
| 32 #include "net/url_request/url_fetcher_response_writer.h" | |
| 33 | |
| 34 #if !defined(OS_ANDROID) | |
| 35 #include "content/public/browser/devtools_frontend_host.h" | |
| 36 #endif | |
| 37 | 16 |
| 38 namespace content { | 17 namespace content { |
| 39 | 18 |
| 40 namespace { | 19 namespace { |
| 41 | |
| 42 | |
| 43 // ResponseWriter ------------------------------------------------------------- | |
| 44 | |
| 45 class ResponseWriter : public net::URLFetcherResponseWriter { | |
| 46 public: | |
| 47 ResponseWriter(base::WeakPtr<ShellDevToolsFrontend> shell_devtools_, | |
| 48 int stream_id); | |
| 49 ~ResponseWriter() override; | |
| 50 | |
| 51 // URLFetcherResponseWriter overrides: | |
| 52 int Initialize(const net::CompletionCallback& callback) override; | |
| 53 int Write(net::IOBuffer* buffer, | |
| 54 int num_bytes, | |
| 55 const net::CompletionCallback& callback) override; | |
| 56 int Finish(int net_error, const net::CompletionCallback& callback) override; | |
| 57 | |
| 58 private: | |
| 59 base::WeakPtr<ShellDevToolsFrontend> shell_devtools_; | |
| 60 int stream_id_; | |
| 61 | |
| 62 DISALLOW_COPY_AND_ASSIGN(ResponseWriter); | |
| 63 }; | |
| 64 | |
| 65 ResponseWriter::ResponseWriter( | |
| 66 base::WeakPtr<ShellDevToolsFrontend> shell_devtools, | |
| 67 int stream_id) | |
| 68 : shell_devtools_(shell_devtools), | |
| 69 stream_id_(stream_id) { | |
| 70 } | |
| 71 | |
| 72 ResponseWriter::~ResponseWriter() { | |
| 73 } | |
| 74 | |
| 75 int ResponseWriter::Initialize(const net::CompletionCallback& callback) { | |
| 76 return net::OK; | |
| 77 } | |
| 78 | |
| 79 int ResponseWriter::Write(net::IOBuffer* buffer, | |
| 80 int num_bytes, | |
| 81 const net::CompletionCallback& callback) { | |
| 82 std::string chunk = std::string(buffer->data(), num_bytes); | |
| 83 if (!base::IsStringUTF8(chunk)) | |
| 84 return num_bytes; | |
| 85 | |
| 86 base::Value* id = new base::Value(stream_id_); | |
| 87 base::Value* chunkValue = new base::Value(chunk); | |
| 88 | |
| 89 content::BrowserThread::PostTask( | |
| 90 content::BrowserThread::UI, FROM_HERE, | |
| 91 base::Bind(&ShellDevToolsFrontend::CallClientFunction, | |
| 92 shell_devtools_, "DevToolsAPI.streamWrite", | |
| 93 base::Owned(id), base::Owned(chunkValue), nullptr)); | |
| 94 return num_bytes; | |
| 95 } | |
| 96 | |
| 97 int ResponseWriter::Finish(int net_error, | |
| 98 const net::CompletionCallback& callback) { | |
| 99 return net::OK; | |
| 100 } | |
| 101 | |
| 102 static GURL GetFrontendURL() { | 20 static GURL GetFrontendURL() { |
| 103 int port = ShellDevToolsManagerDelegate::GetHttpHandlerPort(); | 21 int port = ShellDevToolsManagerDelegate::GetHttpHandlerPort(); |
| 104 return GURL( | 22 return GURL( |
| 105 base::StringPrintf("http://127.0.0.1:%d/devtools/inspector.html", port)); | 23 base::StringPrintf("http://127.0.0.1:%d/devtools/inspector.html", port)); |
| 106 } | 24 } |
| 107 | |
| 108 } // namespace | 25 } // namespace |
| 109 | 26 |
| 110 // This constant should be in sync with | |
| 111 // the constant at devtools_ui_bindings.cc. | |
| 112 const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4; | |
| 113 | |
| 114 // static | 27 // static |
| 115 ShellDevToolsFrontend* ShellDevToolsFrontend::Show( | 28 ShellDevToolsFrontend* ShellDevToolsFrontend::Show( |
| 116 WebContents* inspected_contents) { | 29 WebContents* inspected_contents) { |
| 117 Shell* shell = Shell::CreateNewWindow(inspected_contents->GetBrowserContext(), | 30 Shell* shell = Shell::CreateNewWindow(inspected_contents->GetBrowserContext(), |
| 118 GURL(), | 31 GURL(), |
| 119 NULL, | 32 NULL, |
| 120 gfx::Size()); | 33 gfx::Size()); |
| 121 ShellDevToolsFrontend* devtools_frontend = new ShellDevToolsFrontend( | 34 ShellDevToolsFrontend* devtools_frontend = |
| 122 shell, | 35 new ShellDevToolsFrontend(shell, inspected_contents); |
| 123 inspected_contents); | |
| 124 shell->LoadURL(GetFrontendURL()); | 36 shell->LoadURL(GetFrontendURL()); |
| 125 return devtools_frontend; | 37 return devtools_frontend; |
| 126 } | 38 } |
| 127 | 39 |
| 128 void ShellDevToolsFrontend::Activate() { | 40 void ShellDevToolsFrontend::Activate() { |
| 129 frontend_shell_->ActivateContents(web_contents()); | 41 frontend_shell_->ActivateContents(frontend_shell_->web_contents()); |
| 130 } | 42 } |
| 131 | 43 |
| 132 void ShellDevToolsFrontend::Focus() { | 44 void ShellDevToolsFrontend::Focus() { |
| 133 web_contents()->Focus(); | 45 frontend_shell_->web_contents()->Focus(); |
| 134 } | 46 } |
| 135 | 47 |
| 136 void ShellDevToolsFrontend::InspectElementAt(int x, int y) { | 48 void ShellDevToolsFrontend::InspectElementAt(int x, int y) { |
| 137 if (agent_host_) { | 49 devtools_bindings_->InspectElementAt(x, y); |
| 138 agent_host_->InspectElement(this, x, y); | |
| 139 } else { | |
| 140 inspect_element_at_x_ = x; | |
| 141 inspect_element_at_y_ = y; | |
| 142 } | |
| 143 } | 50 } |
| 144 | 51 |
| 145 void ShellDevToolsFrontend::Close() { | 52 void ShellDevToolsFrontend::Close() { |
| 146 frontend_shell_->Close(); | 53 frontend_shell_->Close(); |
| 147 } | 54 } |
| 148 | 55 |
| 149 void ShellDevToolsFrontend::DisconnectFromTarget() { | 56 void ShellDevToolsFrontend::WebContentsDestroyed() { |
| 150 if (!agent_host_) | 57 delete this; |
| 151 return; | |
| 152 agent_host_->DetachClient(this); | |
| 153 agent_host_ = NULL; | |
| 154 } | 58 } |
| 155 | 59 |
| 156 ShellDevToolsFrontend::ShellDevToolsFrontend(Shell* frontend_shell, | 60 ShellDevToolsFrontend::ShellDevToolsFrontend(Shell* frontend_shell, |
| 157 WebContents* inspected_contents) | 61 WebContents* inspected_contents) |
| 158 : WebContentsObserver(frontend_shell->web_contents()), | 62 : WebContentsObserver(frontend_shell->web_contents()), |
| 159 frontend_shell_(frontend_shell), | 63 frontend_shell_(frontend_shell), |
| 160 inspected_contents_(inspected_contents), | 64 devtools_bindings_( |
| 161 inspect_element_at_x_(-1), | 65 new ShellDevToolsBindings(frontend_shell->web_contents(), |
| 162 inspect_element_at_y_(-1), | 66 inspected_contents, |
| 163 weak_factory_(this) { | 67 this)) {} |
| 164 } | |
| 165 | 68 |
| 166 ShellDevToolsFrontend::~ShellDevToolsFrontend() { | 69 ShellDevToolsFrontend::~ShellDevToolsFrontend() {} |
| 167 for (const auto& pair : pending_requests_) | |
| 168 delete pair.first; | |
| 169 } | |
| 170 | |
| 171 #if !defined(OS_ANDROID) | |
| 172 void ShellDevToolsFrontend::RenderViewCreated( | |
| 173 RenderViewHost* render_view_host) { | |
| 174 if (!frontend_host_) { | |
| 175 frontend_host_.reset(DevToolsFrontendHost::Create( | |
| 176 web_contents()->GetMainFrame(), | |
| 177 base::Bind(&ShellDevToolsFrontend::HandleMessageFromDevToolsFrontend, | |
| 178 base::Unretained(this)))); | |
| 179 } | |
| 180 } | |
| 181 #endif | |
| 182 | |
| 183 #if defined(OS_ANDROID) | |
| 184 void ShellDevToolsFrontend::RenderViewCreated( | |
| 185 RenderViewHost* render_view_host) { | |
| 186 // No devtools frontend for android | |
| 187 } | |
| 188 #endif | |
| 189 | |
| 190 void ShellDevToolsFrontend::DocumentAvailableInMainFrame() { | |
| 191 agent_host_ = DevToolsAgentHost::GetOrCreateFor(inspected_contents_); | |
| 192 agent_host_->AttachClient(this); | |
| 193 if (inspect_element_at_x_ != -1) { | |
| 194 agent_host_->InspectElement( | |
| 195 this, inspect_element_at_x_, inspect_element_at_y_); | |
| 196 inspect_element_at_x_ = -1; | |
| 197 inspect_element_at_y_ = -1; | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 void ShellDevToolsFrontend::WebContentsDestroyed() { | |
| 202 if (agent_host_) | |
| 203 agent_host_->DetachClient(this); | |
| 204 delete this; | |
| 205 } | |
| 206 | |
| 207 void ShellDevToolsFrontend::SetPreferences(const std::string& json) { | |
| 208 preferences_.Clear(); | |
| 209 if (json.empty()) | |
| 210 return; | |
| 211 base::DictionaryValue* dict = nullptr; | |
| 212 std::unique_ptr<base::Value> parsed = base::JSONReader::Read(json); | |
| 213 if (!parsed || !parsed->GetAsDictionary(&dict)) | |
| 214 return; | |
| 215 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | |
| 216 if (!it.value().IsType(base::Value::Type::STRING)) | |
| 217 continue; | |
| 218 preferences_.SetWithoutPathExpansion(it.key(), it.value().CreateDeepCopy()); | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 void ShellDevToolsFrontend::HandleMessageFromDevToolsFrontend( | |
| 223 const std::string& message) { | |
| 224 if (!agent_host_) | |
| 225 return; | |
| 226 std::string method; | |
| 227 base::ListValue* params = NULL; | |
| 228 base::DictionaryValue* dict = NULL; | |
| 229 std::unique_ptr<base::Value> parsed_message = base::JSONReader::Read(message); | |
| 230 if (!parsed_message || | |
| 231 !parsed_message->GetAsDictionary(&dict) || | |
| 232 !dict->GetString("method", &method)) { | |
| 233 return; | |
| 234 } | |
| 235 int request_id = 0; | |
| 236 dict->GetInteger("id", &request_id); | |
| 237 dict->GetList("params", ¶ms); | |
| 238 | |
| 239 if (method == "dispatchProtocolMessage" && params && params->GetSize() == 1) { | |
| 240 if (!agent_host_ || !agent_host_->IsAttached()) | |
| 241 return; | |
| 242 std::string protocol_message; | |
| 243 if (!params->GetString(0, &protocol_message)) | |
| 244 return; | |
| 245 agent_host_->DispatchProtocolMessage(this, protocol_message); | |
| 246 } else if (method == "loadCompleted") { | |
| 247 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests( | |
| 248 base::ASCIIToUTF16("DevToolsAPI.setUseSoftMenu(true);")); | |
| 249 } else if (method == "loadNetworkResource" && params->GetSize() == 3) { | |
| 250 // TODO(pfeldman): handle some of the embedder messages in content. | |
| 251 std::string url; | |
| 252 std::string headers; | |
| 253 int stream_id; | |
| 254 if (!params->GetString(0, &url) || | |
| 255 !params->GetString(1, &headers) || | |
| 256 !params->GetInteger(2, &stream_id)) { | |
| 257 return; | |
| 258 } | |
| 259 | |
| 260 GURL gurl(url); | |
| 261 if (!gurl.is_valid()) { | |
| 262 base::DictionaryValue response; | |
| 263 response.SetInteger("statusCode", 404); | |
| 264 SendMessageAck(request_id, &response); | |
| 265 return; | |
| 266 } | |
| 267 | |
| 268 net::URLFetcher* fetcher = | |
| 269 net::URLFetcher::Create(gurl, net::URLFetcher::GET, this).release(); | |
| 270 pending_requests_[fetcher] = request_id; | |
| 271 fetcher->SetRequestContext( | |
| 272 BrowserContext::GetDefaultStoragePartition( | |
| 273 web_contents()->GetBrowserContext())-> | |
| 274 GetURLRequestContext()); | |
| 275 fetcher->SetExtraRequestHeaders(headers); | |
| 276 fetcher->SaveResponseWithWriter( | |
| 277 std::unique_ptr<net::URLFetcherResponseWriter>( | |
| 278 new ResponseWriter(weak_factory_.GetWeakPtr(), stream_id))); | |
| 279 fetcher->Start(); | |
| 280 return; | |
| 281 } else if (method == "getPreferences") { | |
| 282 SendMessageAck(request_id, &preferences_); | |
| 283 return; | |
| 284 } else if (method == "setPreference") { | |
| 285 std::string name; | |
| 286 std::string value; | |
| 287 if (!params->GetString(0, &name) || | |
| 288 !params->GetString(1, &value)) { | |
| 289 return; | |
| 290 } | |
| 291 preferences_.SetStringWithoutPathExpansion(name, value); | |
| 292 } else if (method == "removePreference") { | |
| 293 std::string name; | |
| 294 if (!params->GetString(0, &name)) | |
| 295 return; | |
| 296 preferences_.RemoveWithoutPathExpansion(name, nullptr); | |
| 297 } else if (method == "requestFileSystems") { | |
| 298 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests( | |
| 299 base::ASCIIToUTF16("DevToolsAPI.fileSystemsLoaded([]);")); | |
| 300 } else if (method == "reattach") { | |
| 301 agent_host_->DetachClient(this); | |
| 302 agent_host_->AttachClient(this); | |
| 303 } else { | |
| 304 return; | |
| 305 } | |
| 306 | |
| 307 if (request_id) | |
| 308 SendMessageAck(request_id, nullptr); | |
| 309 } | |
| 310 | |
| 311 void ShellDevToolsFrontend::DispatchProtocolMessage( | |
| 312 DevToolsAgentHost* agent_host, const std::string& message) { | |
| 313 | |
| 314 if (message.length() < kMaxMessageChunkSize) { | |
| 315 std::string param; | |
| 316 base::EscapeJSONString(message, true, ¶m); | |
| 317 std::string code = "DevToolsAPI.dispatchMessage(" + param + ");"; | |
| 318 base::string16 javascript = base::UTF8ToUTF16(code); | |
| 319 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(javascript); | |
| 320 return; | |
| 321 } | |
| 322 | |
| 323 size_t total_size = message.length(); | |
| 324 for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) { | |
| 325 std::string param; | |
| 326 base::EscapeJSONString(message.substr(pos, kMaxMessageChunkSize), true, | |
| 327 ¶m); | |
| 328 std::string code = "DevToolsAPI.dispatchMessageChunk(" + param + "," + | |
| 329 std::to_string(pos ? 0 : total_size) + ");"; | |
| 330 base::string16 javascript = base::UTF8ToUTF16(code); | |
| 331 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(javascript); | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 void ShellDevToolsFrontend::OnURLFetchComplete(const net::URLFetcher* source) { | |
| 336 // TODO(pfeldman): this is a copy of chrome's devtools_ui_bindings.cc. | |
| 337 // We should handle some of the commands including this one in content. | |
| 338 DCHECK(source); | |
| 339 PendingRequestsMap::iterator it = pending_requests_.find(source); | |
| 340 DCHECK(it != pending_requests_.end()); | |
| 341 | |
| 342 base::DictionaryValue response; | |
| 343 base::DictionaryValue* headers = new base::DictionaryValue(); | |
| 344 net::HttpResponseHeaders* rh = source->GetResponseHeaders(); | |
| 345 response.SetInteger("statusCode", rh ? rh->response_code() : 200); | |
| 346 response.Set("headers", headers); | |
| 347 | |
| 348 size_t iterator = 0; | |
| 349 std::string name; | |
| 350 std::string value; | |
| 351 while (rh && rh->EnumerateHeaderLines(&iterator, &name, &value)) | |
| 352 headers->SetString(name, value); | |
| 353 | |
| 354 SendMessageAck(it->second, &response); | |
| 355 pending_requests_.erase(it); | |
| 356 delete source; | |
| 357 } | |
| 358 | |
| 359 void ShellDevToolsFrontend::CallClientFunction( | |
| 360 const std::string& function_name, | |
| 361 const base::Value* arg1, | |
| 362 const base::Value* arg2, | |
| 363 const base::Value* arg3) { | |
| 364 std::string javascript = function_name + "("; | |
| 365 if (arg1) { | |
| 366 std::string json; | |
| 367 base::JSONWriter::Write(*arg1, &json); | |
| 368 javascript.append(json); | |
| 369 if (arg2) { | |
| 370 base::JSONWriter::Write(*arg2, &json); | |
| 371 javascript.append(", ").append(json); | |
| 372 if (arg3) { | |
| 373 base::JSONWriter::Write(*arg3, &json); | |
| 374 javascript.append(", ").append(json); | |
| 375 } | |
| 376 } | |
| 377 } | |
| 378 javascript.append(");"); | |
| 379 web_contents()->GetMainFrame()->ExecuteJavaScriptForTests( | |
| 380 base::UTF8ToUTF16(javascript)); | |
| 381 } | |
| 382 | |
| 383 void ShellDevToolsFrontend::SendMessageAck(int request_id, | |
| 384 const base::Value* arg) { | |
| 385 base::Value id_value(request_id); | |
| 386 CallClientFunction("DevToolsAPI.embedderMessageAck", | |
| 387 &id_value, arg, nullptr); | |
| 388 } | |
| 389 | |
| 390 void ShellDevToolsFrontend::AgentHostClosed( | |
| 391 DevToolsAgentHost* agent_host, bool replaced) { | |
| 392 agent_host_ = nullptr; | |
| 393 frontend_shell_->Close(); | |
| 394 } | |
| 395 | 70 |
| 396 } // namespace content | 71 } // namespace content |
| OLD | NEW |