From 0e2323c9c8753e6eb61e10b90a5a06357f2ec749 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 21 Jan 2016 23:52:23 +0530 Subject: [PATCH 1/4] 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 0000000000..39fb5782ff --- /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 0000000000..51b5749d2b --- /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 0173abf4ee..2b14bdc60d 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 bcef57b9a4..10ac7a4f76 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 48658a2945..c64f1886ab 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 61aa3d43d4..4aa47fc358 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', From df5bad3f89a2946691416121b47af3b8cfae190f Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 22 Jan 2016 10:27:25 +0530 Subject: [PATCH 2/4] fix api and docs --- atom/browser/api/atom_api_debugger.cc | 23 +++++++------------- atom/browser/api/atom_api_debugger.h | 10 +-------- atom/browser/api/lib/web-contents.js | 3 +++ docs/api/web-contents.md | 30 ++++++++++++++------------- 4 files changed, 27 insertions(+), 39 deletions(-) diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc index 39fb5782ff..76ea46df9f 100644 --- a/atom/browser/api/atom_api_debugger.cc +++ b/atom/browser/api/atom_api_debugger.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2015 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. @@ -33,8 +33,7 @@ void Debugger::AgentHostClosed(DevToolsAgentHost* agent_host, 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); + Emit("detach", detach_reason); } void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, @@ -54,8 +53,7 @@ void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, return; base::DictionaryValue* params = nullptr; dict->GetDictionary("params", ¶ms); - if (!response_callback_.is_null()) - response_callback_.Run(method, *params); + Emit("message", method, *params); } else { auto send_command_callback = pending_requests_[id]; pending_requests_.erase(id); @@ -90,7 +88,10 @@ void Debugger::Attach(mate::Arguments* args) { } void Debugger::Detach() { + if (!agent_host_.get()) + return; agent_host_->DetachClient(); + AgentHostClosed(agent_host_.get(), false); agent_host_ = nullptr; } @@ -121,14 +122,6 @@ void Debugger::SendCommand(mate::Arguments* 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, @@ -142,9 +135,7 @@ void Debugger::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("attach", &Debugger::Attach) .SetMethod("detach", &Debugger::Detach) - .SetMethod("sendCommand", &Debugger::SendCommand) - .SetMethod("onDetach", &Debugger::OnDetach) - .SetMethod("onEvent", &Debugger::OnEvent); + .SetMethod("sendCommand", &Debugger::SendCommand); } } // namespace api diff --git a/atom/browser/api/atom_api_debugger.h b/atom/browser/api/atom_api_debugger.h index 51b5749d2b..894c2bc319 100644 --- a/atom/browser/api/atom_api_debugger.h +++ b/atom/browser/api/atom_api_debugger.h @@ -1,4 +1,4 @@ -// Copyright (c) 2015 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. @@ -30,11 +30,8 @@ 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); @@ -59,15 +56,10 @@ class Debugger: public mate::TrackableObject, 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_; diff --git a/atom/browser/api/lib/web-contents.js b/atom/browser/api/lib/web-contents.js index 2cce6e6cb9..b71ddf4cb9 100644 --- a/atom/browser/api/lib/web-contents.js +++ b/atom/browser/api/lib/web-contents.js @@ -70,6 +70,9 @@ let wrapWebContents = function(webContents) { var controller, method, name, ref1; webContents.__proto__ = EventEmitter.prototype; + // webContents.debugger is an EventEmitter. + webContents.debugger.__proto__ = EventEmitter.prototype; + // Every remote callback from renderer process would add a listenter to the // render-view-deleted event, so ignore the listenters warning. webContents.setMaxListeners(0); diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index c64f1886ab..2bf593d10f 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -836,7 +836,7 @@ when the DevTools has been closed. ### `webContents.debugger` -Debugger API serves as an alternate transport for remote debugging protocol. +Debugger API serves as an alternate transport for [remote debugging protocol][rdp]. ```javascript try { @@ -845,11 +845,11 @@ try { console.log("Debugger attach failed : ", err); }; -win.webContents.debugger.onDetach(function(reason) { +win.webContents.debugger.on('detach', function(event, reason) { console.log("Debugger detached due to : ", reason); }); -win.webContents.debugger.onEvent(function(method, params) { +win.webContents.debugger.on('message', function(event, method, params) { if (method == "Network.requestWillBeSent") { if (params.request.url == "https://www.github.com") win.webContents.debugger.detach(); @@ -861,7 +861,7 @@ win.webContents.debugger.sendCommand("Network.enable"); #### `webContents.debugger.attach([protocolVersion])` -* `protocolVersion` String - Required debugging protocol version. +* `protocolVersion` String - Requested debugging protocol version. Attaches the debugger to the `webContents`. @@ -880,19 +880,21 @@ Detaches the debugger from the `webContents`. Send given command to the debugging target. -#### `webContents.debugger.onDetach(callback)` +#### Event: 'detach' -* `callback` Function - * `reason` String - Reason for detaching debugger. +* `event` Event +* `reason` String - Reason for detaching debugger. -`callback` is fired when debugging session is terminated. This happens either when +Emitted when debugging session is terminated. This happens either when `webContents` is closed or devtools is invoked for the attached `webContents`. -#### `webContents.debugger.onEvent(callback)` +#### Event: 'message' -* `callback` Function - * `method` String - Method name. - * `params` Object - Event parameters defined by the 'parameters' - attribute in the remote debugging protocol. +* `event` Event +* `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. +Emitted whenever debugging target issues instrumentation event. + +[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol From 3a60ab386c0d67d2126cff859ddbf20029001431 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 22 Jan 2016 14:17:23 +0530 Subject: [PATCH 3/4] add spec --- atom/browser/api/atom_api_debugger.cc | 28 ++++-- atom/browser/api/atom_api_debugger.h | 4 +- docs/api/web-contents.md | 11 ++- spec/api-debugger-spec.js | 136 ++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 spec/api-debugger-spec.js diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc index 76ea46df9f..b40bb41342 100644 --- a/atom/browser/api/atom_api_debugger.cc +++ b/atom/browser/api/atom_api_debugger.cc @@ -51,17 +51,26 @@ void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, std::string method; if (!dict->GetString("method", &method)) return; - base::DictionaryValue* params = nullptr; - dict->GetDictionary("params", ¶ms); - Emit("message", method, *params); + base::DictionaryValue* params_value = nullptr; + base::DictionaryValue params; + if (dict->GetDictionary("params", ¶ms_value)) + params.Swap(params_value); + Emit("message", 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); + base::DictionaryValue* error_body = nullptr; + base::DictionaryValue error; + if (dict->GetDictionary("error", &error_body)) + error.Swap(error_body); + + base::DictionaryValue* result_body = nullptr; + base::DictionaryValue result; + if (dict->GetDictionary("result", &result_body)) + result.Swap(result_body); + send_command_callback.Run(error, result); } } @@ -87,6 +96,10 @@ void Debugger::Attach(mate::Arguments* args) { agent_host_->AttachClient(this); } +bool Debugger::IsAttached() { + return agent_host_.get() ? agent_host_->IsAttached() : false; +} + void Debugger::Detach() { if (!agent_host_.get()) return; @@ -97,7 +110,7 @@ void Debugger::Detach() { void Debugger::SendCommand(mate::Arguments* args) { if (!agent_host_.get()) - args->ThrowError("Debugger is not attached to a target"); + return; std::string method; if (!args->GetNext(&method)) { @@ -134,6 +147,7 @@ void Debugger::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("attach", &Debugger::Attach) + .SetMethod("isAttached", &Debugger::IsAttached) .SetMethod("detach", &Debugger::Detach) .SetMethod("sendCommand", &Debugger::SendCommand); } diff --git a/atom/browser/api/atom_api_debugger.h b/atom/browser/api/atom_api_debugger.h index 894c2bc319..5454108e8b 100644 --- a/atom/browser/api/atom_api_debugger.h +++ b/atom/browser/api/atom_api_debugger.h @@ -31,7 +31,8 @@ class Debugger: public mate::TrackableObject, public content::DevToolsAgentHostClient { public: using SendCommandCallback = - base::Callback; + base::Callback; static mate::Handle Create( v8::Isolate* isolate, content::WebContents* web_contents); @@ -54,6 +55,7 @@ class Debugger: public mate::TrackableObject, using PendingRequestMap = std::map; void Attach(mate::Arguments* args); + bool IsAttached(); void Detach(); void SendCommand(mate::Arguments* args); diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 2bf593d10f..939a096299 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -861,10 +861,14 @@ win.webContents.debugger.sendCommand("Network.enable"); #### `webContents.debugger.attach([protocolVersion])` -* `protocolVersion` String - Requested debugging protocol version. +* `protocolVersion` String (optional) - Requested debugging protocol version. Attaches the debugger to the `webContents`. +#### `webContents.debugger.isAttached()` + +Returns a boolean indicating whether a debugger is attached to the `webContents`. + #### `webContents.debugger.detach()` Detaches the debugger from the `webContents`. @@ -873,8 +877,9 @@ Detaches the debugger from the `webContents`. * `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 +* `commandParams` Object (optional) - JSON object with request parameters. +* `callback` Function (optional) - Response + * `error` Object - Error message indicating the failure of the command. * `result` Object - Response defined by the 'returns' attribute of the command description in the remote debugging protocol. diff --git a/spec/api-debugger-spec.js b/spec/api-debugger-spec.js new file mode 100644 index 0000000000..7a16ca7263 --- /dev/null +++ b/spec/api-debugger-spec.js @@ -0,0 +1,136 @@ +var assert, path, remote, BrowserWindow; + +assert = require('assert'); + +path = require('path'); + +remote = require('electron').remote; + +BrowserWindow = remote.BrowserWindow; + +describe('debugger module', function() { + var fixtures, w; + fixtures = path.resolve(__dirname, 'fixtures'); + w = null; + beforeEach(function() { + if (w != null) { + w.destroy(); + } + w = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }); + }); + afterEach(function() { + if (w != null) { + w.destroy(); + } + w = null; + }); + + describe('debugger.attach', function() { + it('fails when devtools is already open', function(done) { + w.webContents.on('did-finish-load', function() { + w.webContents.openDevTools(); + try { + w.webContents.debugger.attach(); + } catch(err) { + assert(w.webContents.debugger.isAttached()); + done(); + } + }); + w.webContents.loadURL('file://' + path.join(fixtures, 'pages', 'a.html')); + }); + + it('fails when protocol version is not supported', function(done) { + try { + w.webContents.debugger.attach("2.0"); + } catch(err) { + assert(!w.webContents.debugger.isAttached()); + done(); + } + }); + + it('attaches when no protocol version is specified', function(done) { + try { + w.webContents.debugger.attach(); + } catch(err) { + done('unexpected error : ' + err); + } + assert(w.webContents.debugger.isAttached()); + done(); + }); + }); + + describe('debugger.detach', function() { + it('fires detach event', function(done) { + w.webContents.debugger.on('detach', function(e, reason) { + assert.equal(reason, 'target closed'); + assert(!w.webContents.debugger.isAttached()); + done(); + }); + try { + w.webContents.debugger.attach(); + } catch(err) { + done('unexpected error : ' + err); + } + w.webContents.debugger.detach(); + }); + }); + + describe('debugger.sendCommand', function() { + it('retuns response', function(done) { + w.webContents.loadURL('about:blank'); + try { + w.webContents.debugger.attach(); + } catch(err) { + done('unexpected error : ' + err); + } + var callback = function(err, res) { + assert(!res.wasThrown); + assert.equal(res.result.value, 6); + w.webContents.debugger.detach(); + done(); + }; + const params = { + "expression": "4+2", + }; + w.webContents.debugger.sendCommand("Runtime.evaluate", params, callback); + }); + + it('fires message event', function(done) { + var url = 'file://' + path.join(fixtures, 'pages', 'a.html'); + w.webContents.loadURL(url); + try { + w.webContents.debugger.attach(); + } catch(err) { + done('unexpected error : ' + err); + } + w.webContents.debugger.on('message', function(e, method, params) { + if(method == "Console.messageAdded") { + assert.equal(params.message.type, 'log'); + assert.equal(params.message.url, url); + assert.equal(params.message.text, 'a'); + w.webContents.debugger.detach(); + done(); + } + }); + w.webContents.debugger.sendCommand("Console.enable"); + }); + + it('returns error message when command fails', function(done) { + w.webContents.loadURL('about:blank'); + try { + w.webContents.debugger.attach(); + } catch(err) { + done('unexpected error : ' + err); + } + w.webContents.debugger.sendCommand("Test", function(err) { + assert.equal(err.message, '\'Test\' wasn\'t found'); + w.webContents.debugger.detach(); + done(); + }); + }); + }); +}); From d938dd68b01c5753e4bc7665c583b539b28e68f8 Mon Sep 17 00:00:00 2001 From: Robo Date: Sat, 23 Jan 2016 09:32:21 +0530 Subject: [PATCH 4/4] wrap debugger for lazy initialization --- atom/browser/api/atom_api_debugger.cc | 40 ++++++++++++++++++++++++++- atom/browser/api/lib/web-contents.js | 11 ++++++-- atom/common/node_bindings.cc | 1 + 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc index b40bb41342..eab60311f3 100644 --- a/atom/browser/api/atom_api_debugger.cc +++ b/atom/browser/api/atom_api_debugger.cc @@ -6,12 +6,15 @@ #include +#include "atom/browser/atom_browser_main_parts.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.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/dictionary.h" #include "native_mate/object_template_builder.h" using content::DevToolsAgentHost; @@ -20,6 +23,14 @@ namespace atom { namespace api { +namespace { + +// The wrapDebugger funtion which is implemented in JavaScript. +using WrapDebuggerCallback = base::Callback)>; +WrapDebuggerCallback g_wrap_debugger; + +} // namespace + Debugger::Debugger(content::WebContents* web_contents) : web_contents_(web_contents), previous_request_id_(0) { @@ -139,7 +150,9 @@ void Debugger::SendCommand(mate::Arguments* args) { mate::Handle Debugger::Create( v8::Isolate* isolate, content::WebContents* web_contents) { - return mate::CreateHandle(isolate, new Debugger(web_contents)); + auto handle = mate::CreateHandle(isolate, new Debugger(web_contents)); + g_wrap_debugger.Run(handle.ToV8()); + return handle; } // static @@ -152,6 +165,31 @@ void Debugger::BuildPrototype(v8::Isolate* isolate, .SetMethod("sendCommand", &Debugger::SendCommand); } +void ClearWrapDebugger() { + g_wrap_debugger.Reset(); +} + +void SetWrapDebugger(const WrapDebuggerCallback& callback) { + g_wrap_debugger = callback; + + // Cleanup the wrapper on exit. + atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( + base::Bind(ClearWrapDebugger)); +} + } // namespace api } // namespace atom + +namespace { + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.SetMethod("_setWrapDebugger", &atom::api::SetWrapDebugger); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_debugger, Initialize); diff --git a/atom/browser/api/lib/web-contents.js b/atom/browser/api/lib/web-contents.js index b71ddf4cb9..4f2e98a070 100644 --- a/atom/browser/api/lib/web-contents.js +++ b/atom/browser/api/lib/web-contents.js @@ -8,6 +8,7 @@ const NavigationController = require('electron').NavigationController; const Menu = require('electron').Menu; const binding = process.atomBinding('web_contents'); +const debuggerBinding = process.atomBinding('debugger'); let slice = [].slice; let nextId = 0; @@ -70,9 +71,6 @@ let wrapWebContents = function(webContents) { var controller, method, name, ref1; webContents.__proto__ = EventEmitter.prototype; - // webContents.debugger is an EventEmitter. - webContents.debugger.__proto__ = EventEmitter.prototype; - // Every remote callback from renderer process would add a listenter to the // render-view-deleted event, so ignore the listenters warning. webContents.setMaxListeners(0); @@ -219,7 +217,14 @@ let wrapWebContents = function(webContents) { }; }; +// Wrapper for native class. +let wrapDebugger = function(webContentsDebugger) { + // debugger is an EventEmitter. + webContentsDebugger.__proto__ = EventEmitter.prototype; +}; + binding._setWrapWebContents(wrapWebContents); +debuggerBinding._setWrapDebugger(wrapDebugger); module.exports.create = function(options) { if (options == null) { diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index b1cb84eead..69e7906ffb 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -35,6 +35,7 @@ REFERENCE_MODULE(atom_browser_app); REFERENCE_MODULE(atom_browser_auto_updater); REFERENCE_MODULE(atom_browser_content_tracing); REFERENCE_MODULE(atom_browser_dialog); +REFERENCE_MODULE(atom_browser_debugger); REFERENCE_MODULE(atom_browser_desktop_capturer); REFERENCE_MODULE(atom_browser_download_item); REFERENCE_MODULE(atom_browser_menu);