Skip to content

Commit

Permalink
feat: promisify debugger.sendCommand (#16931)
Browse files Browse the repository at this point in the history
  • Loading branch information
trop[bot] authored and John Kleinschmidt committed Feb 13, 2019
1 parent 4ba51db commit b4bd96b
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 46 deletions.
61 changes: 32 additions & 29 deletions atom/browser/api/atom_api_debugger.cc
Expand Up @@ -61,23 +61,26 @@ void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host,
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())
auto it = pending_requests_.find(id);
if (it == pending_requests_.end())
return;
base::DictionaryValue* error_body = nullptr;
base::DictionaryValue error;
bool has_error;
if ((has_error = 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(has_error ? error.Clone() : base::Value(),
result);
scoped_refptr<atom::util::Promise> promise = it->second;
pending_requests_.erase(it);

base::DictionaryValue* error = nullptr;
if (dict->GetDictionary("error", &error)) {
std::string message;
error->GetString("message", &message);
promise->RejectWithErrorMessage(message);
} else {
base::DictionaryValue* result_body = nullptr;
base::DictionaryValue result;
if (dict->GetDictionary("result", &result_body)) {
result.Swap(result_body);
}
promise->Resolve(result);
}
}
}

Expand Down Expand Up @@ -125,23 +128,26 @@ void Debugger::Detach() {
AgentHostClosed(agent_host_.get());
}

void Debugger::SendCommand(mate::Arguments* args) {
if (!agent_host_)
return;
v8::Local<v8::Promise> Debugger::SendCommand(mate::Arguments* args) {
scoped_refptr<util::Promise> promise = new util::Promise(isolate());

if (!agent_host_) {
promise->RejectWithErrorMessage("No target available");
return promise->GetHandle();
}

std::string method;
if (!args->GetNext(&method)) {
args->ThrowError();
return;
promise->RejectWithErrorMessage("Invalid method");
return promise->GetHandle();
}

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;
pending_requests_[request_id] = promise;
request.SetInteger("id", request_id);
request.SetString("method", method);
if (!command_params.empty())
Expand All @@ -151,16 +157,13 @@ void Debugger::SendCommand(mate::Arguments* args) {
std::string json_args;
base::JSONWriter::Write(request, &json_args);
agent_host_->DispatchProtocolMessage(this, json_args);

return promise->GetHandle();
}

void Debugger::ClearPendingRequests() {
if (pending_requests_.empty())
return;
base::Value error(base::Value::Type::DICTIONARY);
base::Value error_msg("target closed while handling command");
error.SetKey("message", std::move(error_msg));
for (const auto& it : pending_requests_)
it.second.Run(error, base::Value());
it.second->RejectWithErrorMessage("target closed while handling command");
}

// static
Expand Down
8 changes: 3 additions & 5 deletions atom/browser/api/atom_api_debugger.h
Expand Up @@ -9,6 +9,7 @@
#include <string>

#include "atom/browser/api/trackable_object.h"
#include "atom/common/promise_util.h"
#include "base/callback.h"
#include "base/values.h"
#include "content/public/browser/devtools_agent_host_client.h"
Expand All @@ -32,9 +33,6 @@ class Debugger : public mate::TrackableObject<Debugger>,
public content::DevToolsAgentHostClient,
public content::WebContentsObserver {
public:
using SendCommandCallback =
base::Callback<void(const base::Value&, const base::Value&)>;

static mate::Handle<Debugger> Create(v8::Isolate* isolate,
content::WebContents* web_contents);

Expand All @@ -56,12 +54,12 @@ class Debugger : public mate::TrackableObject<Debugger>,
content::RenderFrameHost* new_rfh) override;

private:
using PendingRequestMap = std::map<int, SendCommandCallback>;
using PendingRequestMap = std::map<int, scoped_refptr<atom::util::Promise>>;

void Attach(mate::Arguments* args);
bool IsAttached();
void Detach();
void SendCommand(mate::Arguments* args);
v8::Local<v8::Promise> SendCommand(mate::Arguments* args);
void ClearPendingRequests();

content::WebContents* web_contents_; // Weak Reference.
Expand Down
14 changes: 14 additions & 0 deletions docs/api/debugger.md
Expand Up @@ -60,6 +60,20 @@ Detaches the debugger from the `webContents`.

Send given command to the debugging target.

**[Deprecated Soon](promisification.md)**

#### `debugger.sendCommand(method[, commandParams])`

* `method` String - Method name, should be one of the methods defined by the
[remote debugging protocol][rdp].
* `commandParams` Object (optional) - JSON object with request parameters.

Returns `Promise<any>` - A promise that resolves with the response defined by
the 'returns' attribute of the command description in the remote debugging protocol
or is rejected indicating the failure of the command.

Send given command to the debugging target.

### Instance Events

#### Event: 'detach'
Expand Down
2 changes: 1 addition & 1 deletion docs/api/promisification.md
Expand Up @@ -10,7 +10,6 @@ When a majority of affected functions are migrated, this flag will be enabled by

- [app.importCertificate(options, callback)](https://github.com/electron/electron/blob/master/docs/api/app.md#importCertificate)
- [contentTracing.getTraceBufferUsage(callback)](https://github.com/electron/electron/blob/master/docs/api/content-tracing.md#getTraceBufferUsage)
- [debugger.sendCommand(method[, commandParams, callback])](https://github.com/electron/electron/blob/master/docs/api/debugger.md#sendCommand)
- [dialog.showOpenDialog([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showOpenDialog)
- [dialog.showSaveDialog([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showSaveDialog)
- [dialog.showMessageBox([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showMessageBox)
Expand Down Expand Up @@ -49,6 +48,7 @@ When a majority of affected functions are migrated, this flag will be enabled by
- [cookies.get(filter, callback)](https://github.com/electron/electron/blob/master/docs/api/cookies.md#get)
- [cookies.remove(url, name, callback)](https://github.com/electron/electron/blob/master/docs/api/cookies.md#remove)
- [cookies.set(details, callback)](https://github.com/electron/electron/blob/master/docs/api/cookies.md#set)
- [debugger.sendCommand(method[, commandParams, callback])](https://github.com/electron/electron/blob/master/docs/api/debugger.md#sendCommand)
- [desktopCapturer.getSources(options, callback)](https://github.com/electron/electron/blob/master/docs/api/desktop-capturer.md#getSources)
- [protocol.isProtocolHandled(scheme, callback)](https://github.com/electron/electron/blob/master/docs/api/protocol.md#isProtocolHandled)
- [shell.openExternal(url[, options, callback])](https://github.com/electron/electron/blob/master/docs/api/shell.md#openExternal)
Expand Down
2 changes: 2 additions & 0 deletions lib/browser/api/web-contents.js
Expand Up @@ -501,6 +501,8 @@ WebContents.prototype._init = function () {
// JavaScript wrapper of Debugger.
const { Debugger } = process.atomBinding('debugger')

Debugger.prototype.sendCommand = deprecate.promisify(Debugger.prototype.sendCommand)

Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype)

// Public APIs.
Expand Down
54 changes: 48 additions & 6 deletions spec/api-debugger-spec.js
Expand Up @@ -2,6 +2,7 @@ const chai = require('chai')
const dirtyChai = require('dirty-chai')
const http = require('http')
const path = require('path')
const { emittedOnce } = require('./events-helpers')
const { closeWindow } = require('./window-helpers')
const { BrowserWindow } = require('electron').remote

Expand Down Expand Up @@ -102,7 +103,21 @@ describe('debugger module', () => {
}
})

it('returns response', done => {
it('returns response', async () => {
w.webContents.loadURL('about:blank')
w.webContents.debugger.attach()

const params = { 'expression': '4+2' }
const res = await w.webContents.debugger.sendCommand('Runtime.evaluate', params)

expect(res.wasThrown).to.be.undefined()
expect(res.result.value).to.equal(6)

w.webContents.debugger.detach()
})

// TODO(miniak): remove when promisification is complete
it('returns response (callback)', done => {
w.webContents.loadURL('about:blank')
try {
w.webContents.debugger.attach()
Expand All @@ -123,7 +138,24 @@ describe('debugger module', () => {
w.webContents.debugger.sendCommand('Runtime.evaluate', params, callback)
})

it('returns response when devtools is opened', done => {
it('returns response when devtools is opened', async () => {
w.webContents.loadURL('about:blank')
w.webContents.debugger.attach()

w.webContents.openDevTools()
await emittedOnce(w.webContents, 'devtools-opened')

const params = { 'expression': '4+2' }
const res = await w.webContents.debugger.sendCommand('Runtime.evaluate', params)

expect(res.wasThrown).to.be.undefined()
expect(res.result.value).to.equal(6)

w.webContents.debugger.detach()
})

// TODO(miniak): remove when promisification is complete
it('returns response when devtools is opened (callback)', done => {
w.webContents.loadURL('about:blank')
try {
w.webContents.debugger.attach()
Expand Down Expand Up @@ -169,17 +201,27 @@ describe('debugger module', () => {
w.webContents.debugger.sendCommand('Console.enable')
})

it('returns error message when command fails', done => {
it('returns error message when command fails', async () => {
w.webContents.loadURL('about:blank')
w.webContents.debugger.attach()

const promise = w.webContents.debugger.sendCommand('Test')
await expect(promise).to.be.eventually.rejectedWith(Error, "'Test' wasn't found")

w.webContents.debugger.detach()
})

// TODO(miniak): remove when promisification is complete
it('returns error message when command fails (callback)', done => {
w.webContents.loadURL('about:blank')
try {
w.webContents.debugger.attach()
} catch (err) {
done(`unexpected error : ${err}`)
}

w.webContents.debugger.sendCommand('Test', err => {
expect(err).to.not.be.null()
expect(err.message).to.equal("'Test' wasn't found")
w.webContents.debugger.sendCommand('Test', (err, res) => {
expect(err).to.be.an.instanceOf(Error).with.property('message', "'Test' wasn't found")
w.webContents.debugger.detach()
done()
})
Expand Down
6 changes: 1 addition & 5 deletions spec/chromium-spec.js
Expand Up @@ -1515,11 +1515,7 @@ describe('font fallback', () => {
try {
await w.loadURL(`data:text/html,${html}`)
w.webContents.debugger.attach()
const sendCommand = (...args) => new Promise((resolve, reject) => {
w.webContents.debugger.sendCommand(...args, (e, r) => {
if (e) { reject(e) } else { resolve(r) }
})
})
const sendCommand = (...args) => w.webContents.debugger.sendCommand(...args)
const { nodeId } = (await sendCommand('DOM.getDocument')).root.children[0]
await sendCommand('CSS.enable')
const { fonts } = await sendCommand('CSS.getPlatformFontsForNode', { nodeId })
Expand Down

0 comments on commit b4bd96b

Please sign in to comment.