Skip to content

Commit 0e2323c

Browse files
committed
browser: add webContents.debugger api
1 parent 2e8a2c3 commit 0e2323c

6 files changed

+311
-1
lines changed

atom/browser/api/atom_api_debugger.cc

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (c) 2015 GitHub, Inc.
2+
// Use of this source code is governed by the MIT license that can be
3+
// found in the LICENSE file.
4+
5+
#include "atom/browser/api/atom_api_debugger.h"
6+
7+
#include <string>
8+
9+
#include "atom/common/native_mate_converters/callback.h"
10+
#include "atom/common/native_mate_converters/value_converter.h"
11+
#include "base/json/json_reader.h"
12+
#include "base/json/json_writer.h"
13+
#include "content/public/browser/devtools_agent_host.h"
14+
#include "content/public/browser/web_contents.h"
15+
#include "native_mate/object_template_builder.h"
16+
17+
using content::DevToolsAgentHost;
18+
19+
namespace atom {
20+
21+
namespace api {
22+
23+
Debugger::Debugger(content::WebContents* web_contents)
24+
: web_contents_(web_contents),
25+
previous_request_id_(0) {
26+
}
27+
28+
Debugger::~Debugger() {
29+
}
30+
31+
void Debugger::AgentHostClosed(DevToolsAgentHost* agent_host,
32+
bool replaced_with_another_client) {
33+
std::string detach_reason = "target closed";
34+
if (replaced_with_another_client)
35+
detach_reason = "replaced with devtools";
36+
if (!detach_callback_.is_null())
37+
detach_callback_.Run(detach_reason);
38+
}
39+
40+
void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host,
41+
const std::string& message) {
42+
DCHECK(agent_host == agent_host_.get());
43+
44+
scoped_ptr<base::Value> parsed_message(base::JSONReader::Read(message));
45+
if (!parsed_message->IsType(base::Value::TYPE_DICTIONARY))
46+
return;
47+
48+
base::DictionaryValue* dict =
49+
static_cast<base::DictionaryValue*>(parsed_message.get());
50+
int id;
51+
if (!dict->GetInteger("id", &id)) {
52+
std::string method;
53+
if (!dict->GetString("method", &method))
54+
return;
55+
base::DictionaryValue* params = nullptr;
56+
dict->GetDictionary("params", &params);
57+
if (!response_callback_.is_null())
58+
response_callback_.Run(method, *params);
59+
} else {
60+
auto send_command_callback = pending_requests_[id];
61+
pending_requests_.erase(id);
62+
if (send_command_callback.is_null())
63+
return;
64+
base::DictionaryValue* result = nullptr;
65+
dict->GetDictionary("result", &result);
66+
send_command_callback.Run(*result);
67+
}
68+
}
69+
70+
void Debugger::Attach(mate::Arguments* args) {
71+
std::string protocol_version;
72+
args->GetNext(&protocol_version);
73+
74+
if (!protocol_version.empty() &&
75+
!DevToolsAgentHost::IsSupportedProtocolVersion(protocol_version)) {
76+
args->ThrowError("Requested protocol version is not supported");
77+
return;
78+
}
79+
agent_host_ = DevToolsAgentHost::GetOrCreateFor(web_contents_);
80+
if (!agent_host_.get()) {
81+
args->ThrowError("No target available");
82+
return;
83+
}
84+
if (agent_host_->IsAttached()) {
85+
args->ThrowError("Another debugger is already attached to this target");
86+
return;
87+
}
88+
89+
agent_host_->AttachClient(this);
90+
}
91+
92+
void Debugger::Detach() {
93+
agent_host_->DetachClient();
94+
agent_host_ = nullptr;
95+
}
96+
97+
void Debugger::SendCommand(mate::Arguments* args) {
98+
if (!agent_host_.get())
99+
args->ThrowError("Debugger is not attached to a target");
100+
101+
std::string method;
102+
if (!args->GetNext(&method)) {
103+
args->ThrowError();
104+
return;
105+
}
106+
base::DictionaryValue command_params;
107+
args->GetNext(&command_params);
108+
SendCommandCallback callback;
109+
args->GetNext(&callback);
110+
111+
base::DictionaryValue request;
112+
int request_id = ++previous_request_id_;
113+
pending_requests_[request_id] = callback;
114+
request.SetInteger("id", request_id);
115+
request.SetString("method", method);
116+
if (!command_params.empty())
117+
request.Set("params", command_params.DeepCopy());
118+
119+
std::string json_args;
120+
base::JSONWriter::Write(request, &json_args);
121+
agent_host_->DispatchProtocolMessage(json_args);
122+
}
123+
124+
void Debugger::OnDetach(const DetachCallback& callback) {
125+
detach_callback_ = callback;
126+
}
127+
128+
void Debugger::OnEvent(const ResponseCallback& callback) {
129+
response_callback_ = callback;
130+
}
131+
132+
// static
133+
mate::Handle<Debugger> Debugger::Create(
134+
v8::Isolate* isolate,
135+
content::WebContents* web_contents) {
136+
return mate::CreateHandle(isolate, new Debugger(web_contents));
137+
}
138+
139+
// static
140+
void Debugger::BuildPrototype(v8::Isolate* isolate,
141+
v8::Local<v8::ObjectTemplate> prototype) {
142+
mate::ObjectTemplateBuilder(isolate, prototype)
143+
.SetMethod("attach", &Debugger::Attach)
144+
.SetMethod("detach", &Debugger::Detach)
145+
.SetMethod("sendCommand", &Debugger::SendCommand)
146+
.SetMethod("onDetach", &Debugger::OnDetach)
147+
.SetMethod("onEvent", &Debugger::OnEvent);
148+
}
149+
150+
} // namespace api
151+
152+
} // namespace atom

atom/browser/api/atom_api_debugger.h

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) 2015 GitHub, Inc.
2+
// Use of this source code is governed by the MIT license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_
6+
#define ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_
7+
8+
#include <map>
9+
#include <string>
10+
11+
#include "atom/browser/api/trackable_object.h"
12+
#include "base/callback.h"
13+
#include "base/values.h"
14+
#include "content/public/browser/devtools_agent_host_client.h"
15+
#include "native_mate/handle.h"
16+
17+
namespace content {
18+
class DevToolsAgentHost;
19+
class WebContents;
20+
}
21+
22+
namespace mate {
23+
class Arguments;
24+
}
25+
26+
namespace atom {
27+
28+
namespace api {
29+
30+
class Debugger: public mate::TrackableObject<Debugger>,
31+
public content::DevToolsAgentHostClient {
32+
public:
33+
using ResponseCallback =
34+
base::Callback<void(const std::string&, const base::DictionaryValue&)>;
35+
using SendCommandCallback =
36+
base::Callback<void(const base::DictionaryValue&)>;
37+
using DetachCallback = base::Callback<void(const std::string&)>;
38+
39+
static mate::Handle<Debugger> Create(
40+
v8::Isolate* isolate, content::WebContents* web_contents);
41+
42+
// mate::TrackableObject:
43+
static void BuildPrototype(v8::Isolate* isolate,
44+
v8::Local<v8::ObjectTemplate> prototype);
45+
46+
protected:
47+
explicit Debugger(content::WebContents* web_contents);
48+
~Debugger();
49+
50+
// content::DevToolsAgentHostClient:
51+
void AgentHostClosed(content::DevToolsAgentHost* agent_host,
52+
bool replaced_with_another_client) override;
53+
void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host,
54+
const std::string& message) override;
55+
56+
private:
57+
using PendingRequestMap = std::map<int, SendCommandCallback>;
58+
59+
void Attach(mate::Arguments* args);
60+
void Detach();
61+
void SendCommand(mate::Arguments* args);
62+
void OnDetach(const DetachCallback& callback);
63+
void OnEvent(const ResponseCallback& callback);
64+
65+
content::WebContents* web_contents_; // Weak Reference.
66+
scoped_refptr<content::DevToolsAgentHost> agent_host_;
67+
68+
DetachCallback detach_callback_;
69+
ResponseCallback response_callback_;
70+
71+
PendingRequestMap pending_requests_;
72+
int previous_request_id_;
73+
74+
DISALLOW_COPY_AND_ASSIGN(Debugger);
75+
};
76+
77+
} // namespace api
78+
79+
} // namespace atom
80+
81+
#endif // ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_

atom/browser/api/atom_api_web_contents.cc

+11-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <set>
88
#include <string>
99

10+
#include "atom/browser/api/atom_api_debugger.h"
1011
#include "atom/browser/api/atom_api_session.h"
1112
#include "atom/browser/api/atom_api_window.h"
1213
#include "atom/browser/atom_browser_client.h"
@@ -1076,6 +1077,14 @@ v8::Local<v8::Value> WebContents::DevToolsWebContents(v8::Isolate* isolate) {
10761077
return v8::Local<v8::Value>::New(isolate, devtools_web_contents_);
10771078
}
10781079

1080+
v8::Local<v8::Value> WebContents::Debugger(v8::Isolate* isolate) {
1081+
if (debugger_.IsEmpty()) {
1082+
auto handle = atom::api::Debugger::Create(isolate, web_contents());
1083+
debugger_.Reset(isolate, handle.ToV8());
1084+
}
1085+
return v8::Local<v8::Value>::New(isolate, debugger_);
1086+
}
1087+
10791088
// static
10801089
void WebContents::BuildPrototype(v8::Isolate* isolate,
10811090
v8::Local<v8::ObjectTemplate> prototype) {
@@ -1144,7 +1153,8 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
11441153
.SetMethod("addWorkSpace", &WebContents::AddWorkSpace)
11451154
.SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace)
11461155
.SetProperty("session", &WebContents::Session)
1147-
.SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents);
1156+
.SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents)
1157+
.SetProperty("debugger", &WebContents::Debugger);
11481158
}
11491159

11501160
AtomBrowserContext* WebContents::GetBrowserContext() const {

atom/browser/api/atom_api_web_contents.h

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
142142
// Properties.
143143
v8::Local<v8::Value> Session(v8::Isolate* isolate);
144144
v8::Local<v8::Value> DevToolsWebContents(v8::Isolate* isolate);
145+
v8::Local<v8::Value> Debugger(v8::Isolate* isolate);
145146

146147
// mate::TrackableObject:
147148
static void BuildPrototype(v8::Isolate* isolate,
@@ -265,6 +266,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
265266

266267
v8::Global<v8::Value> session_;
267268
v8::Global<v8::Value> devtools_web_contents_;
269+
v8::Global<v8::Value> debugger_;
268270

269271
scoped_ptr<WebViewGuestDelegate> guest_delegate_;
270272

docs/api/web-contents.md

+63
Original file line numberDiff line numberDiff line change
@@ -833,3 +833,66 @@ Get the `WebContents` of DevTools for this `WebContents`.
833833

834834
**Note:** Users should never store this object because it may become `null`
835835
when the DevTools has been closed.
836+
837+
### `webContents.debugger`
838+
839+
Debugger API serves as an alternate transport for remote debugging protocol.
840+
841+
```javascript
842+
try {
843+
win.webContents.debugger.attach("1.1");
844+
} catch(err) {
845+
console.log("Debugger attach failed : ", err);
846+
};
847+
848+
win.webContents.debugger.onDetach(function(reason) {
849+
console.log("Debugger detached due to : ", reason);
850+
});
851+
852+
win.webContents.debugger.onEvent(function(method, params) {
853+
if (method == "Network.requestWillBeSent") {
854+
if (params.request.url == "https://www.github.com")
855+
win.webContents.debugger.detach();
856+
}
857+
})
858+
859+
win.webContents.debugger.sendCommand("Network.enable");
860+
```
861+
862+
#### `webContents.debugger.attach([protocolVersion])`
863+
864+
* `protocolVersion` String - Required debugging protocol version.
865+
866+
Attaches the debugger to the `webContents`.
867+
868+
#### `webContents.debugger.detach()`
869+
870+
Detaches the debugger from the `webContents`.
871+
872+
#### `webContents.debugger.sendCommand(method[, commandParams, callback])`
873+
874+
* `method` String - Method name, should be one of the methods defined by the
875+
remote debugging protocol.
876+
* `commandParams` Object - JSON object with request parameters.
877+
* `callback` Function - Response
878+
* `result` Object - Response defined by the 'returns' attribute of
879+
the command description in the remote debugging protocol.
880+
881+
Send given command to the debugging target.
882+
883+
#### `webContents.debugger.onDetach(callback)`
884+
885+
* `callback` Function
886+
* `reason` String - Reason for detaching debugger.
887+
888+
`callback` is fired when debugging session is terminated. This happens either when
889+
`webContents` is closed or devtools is invoked for the attached `webContents`.
890+
891+
#### `webContents.debugger.onEvent(callback)`
892+
893+
* `callback` Function
894+
* `method` String - Method name.
895+
* `params` Object - Event parameters defined by the 'parameters'
896+
attribute in the remote debugging protocol.
897+
898+
`callback` is fired whenever debugging target issues instrumentation event.

filenames.gypi

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
'atom/browser/api/atom_api_content_tracing.cc',
8484
'atom/browser/api/atom_api_cookies.cc',
8585
'atom/browser/api/atom_api_cookies.h',
86+
'atom/browser/api/atom_api_debugger.cc',
87+
'atom/browser/api/atom_api_debugger.h',
8688
'atom/browser/api/atom_api_desktop_capturer.cc',
8789
'atom/browser/api/atom_api_desktop_capturer.h',
8890
'atom/browser/api/atom_api_download_item.cc',

0 commit comments

Comments
 (0)