From 0e2323c9c8753e6eb61e10b90a5a06357f2ec749 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 21 Jan 2016 23:52:23 +0530 Subject: [PATCH] browser: add webContents.debugger api --- atom/browser/api/atom_api_debugger.cc | 152 ++++++++++++++++++++++ atom/browser/api/atom_api_debugger.h | 81 ++++++++++++ atom/browser/api/atom_api_web_contents.cc | 12 +- atom/browser/api/atom_api_web_contents.h | 2 + docs/api/web-contents.md | 63 +++++++++ filenames.gypi | 2 + 6 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 atom/browser/api/atom_api_debugger.cc create mode 100644 atom/browser/api/atom_api_debugger.h diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc new file mode 100644 index 00000000000..39fb5782ff0 --- /dev/null +++ b/atom/browser/api/atom_api_debugger.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_debugger.h" + +#include + +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/web_contents.h" +#include "native_mate/object_template_builder.h" + +using content::DevToolsAgentHost; + +namespace atom { + +namespace api { + +Debugger::Debugger(content::WebContents* web_contents) + : web_contents_(web_contents), + previous_request_id_(0) { +} + +Debugger::~Debugger() { +} + +void Debugger::AgentHostClosed(DevToolsAgentHost* agent_host, + bool replaced_with_another_client) { + std::string detach_reason = "target closed"; + if (replaced_with_another_client) + detach_reason = "replaced with devtools"; + if (!detach_callback_.is_null()) + detach_callback_.Run(detach_reason); +} + +void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, + const std::string& message) { + DCHECK(agent_host == agent_host_.get()); + + scoped_ptr parsed_message(base::JSONReader::Read(message)); + if (!parsed_message->IsType(base::Value::TYPE_DICTIONARY)) + return; + + base::DictionaryValue* dict = + static_cast(parsed_message.get()); + int id; + if (!dict->GetInteger("id", &id)) { + std::string method; + if (!dict->GetString("method", &method)) + return; + base::DictionaryValue* params = nullptr; + dict->GetDictionary("params", ¶ms); + if (!response_callback_.is_null()) + response_callback_.Run(method, *params); + } else { + auto send_command_callback = pending_requests_[id]; + pending_requests_.erase(id); + if (send_command_callback.is_null()) + return; + base::DictionaryValue* result = nullptr; + dict->GetDictionary("result", &result); + send_command_callback.Run(*result); + } +} + +void Debugger::Attach(mate::Arguments* args) { + std::string protocol_version; + args->GetNext(&protocol_version); + + if (!protocol_version.empty() && + !DevToolsAgentHost::IsSupportedProtocolVersion(protocol_version)) { + args->ThrowError("Requested protocol version is not supported"); + return; + } + agent_host_ = DevToolsAgentHost::GetOrCreateFor(web_contents_); + if (!agent_host_.get()) { + args->ThrowError("No target available"); + return; + } + if (agent_host_->IsAttached()) { + args->ThrowError("Another debugger is already attached to this target"); + return; + } + + agent_host_->AttachClient(this); +} + +void Debugger::Detach() { + agent_host_->DetachClient(); + agent_host_ = nullptr; +} + +void Debugger::SendCommand(mate::Arguments* args) { + if (!agent_host_.get()) + args->ThrowError("Debugger is not attached to a target"); + + std::string method; + if (!args->GetNext(&method)) { + args->ThrowError(); + return; + } + base::DictionaryValue command_params; + args->GetNext(&command_params); + SendCommandCallback callback; + args->GetNext(&callback); + + base::DictionaryValue request; + int request_id = ++previous_request_id_; + pending_requests_[request_id] = callback; + request.SetInteger("id", request_id); + request.SetString("method", method); + if (!command_params.empty()) + request.Set("params", command_params.DeepCopy()); + + std::string json_args; + base::JSONWriter::Write(request, &json_args); + agent_host_->DispatchProtocolMessage(json_args); +} + +void Debugger::OnDetach(const DetachCallback& callback) { + detach_callback_ = callback; +} + +void Debugger::OnEvent(const ResponseCallback& callback) { + response_callback_ = callback; +} + +// static +mate::Handle Debugger::Create( + v8::Isolate* isolate, + content::WebContents* web_contents) { + return mate::CreateHandle(isolate, new Debugger(web_contents)); +} + +// static +void Debugger::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("attach", &Debugger::Attach) + .SetMethod("detach", &Debugger::Detach) + .SetMethod("sendCommand", &Debugger::SendCommand) + .SetMethod("onDetach", &Debugger::OnDetach) + .SetMethod("onEvent", &Debugger::OnEvent); +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/atom_api_debugger.h b/atom/browser/api/atom_api_debugger.h new file mode 100644 index 00000000000..51b5749d2b1 --- /dev/null +++ b/atom/browser/api/atom_api_debugger.h @@ -0,0 +1,81 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_ +#define ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_ + +#include +#include + +#include "atom/browser/api/trackable_object.h" +#include "base/callback.h" +#include "base/values.h" +#include "content/public/browser/devtools_agent_host_client.h" +#include "native_mate/handle.h" + +namespace content { +class DevToolsAgentHost; +class WebContents; +} + +namespace mate { +class Arguments; +} + +namespace atom { + +namespace api { + +class Debugger: public mate::TrackableObject, + public content::DevToolsAgentHostClient { + public: + using ResponseCallback = + base::Callback; + using SendCommandCallback = + base::Callback; + using DetachCallback = base::Callback; + + static mate::Handle Create( + v8::Isolate* isolate, content::WebContents* web_contents); + + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit Debugger(content::WebContents* web_contents); + ~Debugger(); + + // content::DevToolsAgentHostClient: + void AgentHostClosed(content::DevToolsAgentHost* agent_host, + bool replaced_with_another_client) override; + void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, + const std::string& message) override; + + private: + using PendingRequestMap = std::map; + + void Attach(mate::Arguments* args); + void Detach(); + void SendCommand(mate::Arguments* args); + void OnDetach(const DetachCallback& callback); + void OnEvent(const ResponseCallback& callback); + + content::WebContents* web_contents_; // Weak Reference. + scoped_refptr agent_host_; + + DetachCallback detach_callback_; + ResponseCallback response_callback_; + + PendingRequestMap pending_requests_; + int previous_request_id_; + + DISALLOW_COPY_AND_ASSIGN(Debugger); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_ diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 0173abf4eef..2b14bdc60d8 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -7,6 +7,7 @@ #include #include +#include "atom/browser/api/atom_api_debugger.h" #include "atom/browser/api/atom_api_session.h" #include "atom/browser/api/atom_api_window.h" #include "atom/browser/atom_browser_client.h" @@ -1076,6 +1077,14 @@ v8::Local WebContents::DevToolsWebContents(v8::Isolate* isolate) { return v8::Local::New(isolate, devtools_web_contents_); } +v8::Local WebContents::Debugger(v8::Isolate* isolate) { + if (debugger_.IsEmpty()) { + auto handle = atom::api::Debugger::Create(isolate, web_contents()); + debugger_.Reset(isolate, handle.ToV8()); + } + return v8::Local::New(isolate, debugger_); +} + // static void WebContents::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { @@ -1144,7 +1153,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) .SetProperty("session", &WebContents::Session) - .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents); + .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents) + .SetProperty("debugger", &WebContents::Debugger); } AtomBrowserContext* WebContents::GetBrowserContext() const { diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index bcef57b9a4a..10ac7a4f769 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -142,6 +142,7 @@ class WebContents : public mate::TrackableObject, // Properties. v8::Local Session(v8::Isolate* isolate); v8::Local DevToolsWebContents(v8::Isolate* isolate); + v8::Local Debugger(v8::Isolate* isolate); // mate::TrackableObject: static void BuildPrototype(v8::Isolate* isolate, @@ -265,6 +266,7 @@ class WebContents : public mate::TrackableObject, v8::Global session_; v8::Global devtools_web_contents_; + v8::Global debugger_; scoped_ptr guest_delegate_; diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 48658a29459..c64f1886aba 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -833,3 +833,66 @@ Get the `WebContents` of DevTools for this `WebContents`. **Note:** Users should never store this object because it may become `null` when the DevTools has been closed. + +### `webContents.debugger` + +Debugger API serves as an alternate transport for remote debugging protocol. + +```javascript +try { + win.webContents.debugger.attach("1.1"); +} catch(err) { + console.log("Debugger attach failed : ", err); +}; + +win.webContents.debugger.onDetach(function(reason) { + console.log("Debugger detached due to : ", reason); +}); + +win.webContents.debugger.onEvent(function(method, params) { + if (method == "Network.requestWillBeSent") { + if (params.request.url == "https://www.github.com") + win.webContents.debugger.detach(); + } +}) + +win.webContents.debugger.sendCommand("Network.enable"); +``` + +#### `webContents.debugger.attach([protocolVersion])` + +* `protocolVersion` String - Required debugging protocol version. + +Attaches the debugger to the `webContents`. + +#### `webContents.debugger.detach()` + +Detaches the debugger from the `webContents`. + +#### `webContents.debugger.sendCommand(method[, commandParams, callback])` + +* `method` String - Method name, should be one of the methods defined by the + remote debugging protocol. +* `commandParams` Object - JSON object with request parameters. +* `callback` Function - Response + * `result` Object - Response defined by the 'returns' attribute of + the command description in the remote debugging protocol. + +Send given command to the debugging target. + +#### `webContents.debugger.onDetach(callback)` + +* `callback` Function + * `reason` String - Reason for detaching debugger. + +`callback` is fired when debugging session is terminated. This happens either when +`webContents` is closed or devtools is invoked for the attached `webContents`. + +#### `webContents.debugger.onEvent(callback)` + +* `callback` Function + * `method` String - Method name. + * `params` Object - Event parameters defined by the 'parameters' + attribute in the remote debugging protocol. + +`callback` is fired whenever debugging target issues instrumentation event. diff --git a/filenames.gypi b/filenames.gypi index 61aa3d43d4a..4aa47fc3580 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -83,6 +83,8 @@ 'atom/browser/api/atom_api_content_tracing.cc', 'atom/browser/api/atom_api_cookies.cc', 'atom/browser/api/atom_api_cookies.h', + 'atom/browser/api/atom_api_debugger.cc', + 'atom/browser/api/atom_api_debugger.h', 'atom/browser/api/atom_api_desktop_capturer.cc', 'atom/browser/api/atom_api_desktop_capturer.h', 'atom/browser/api/atom_api_download_item.cc',