feat: promisify debugger.sendCommand() (#16861)

* remove duplicate entry for desktopCapturer.getSources

* feat: promisify debugger.sendCommand
This commit is contained in:
Milan Burda 2019-02-13 18:23:53 +01:00 committed by John Kleinschmidt
parent ee4c9aa3d0
commit 1f458eb177
7 changed files with 101 additions and 47 deletions

View file

@ -61,23 +61,26 @@ void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host,
params.Swap(params_value); params.Swap(params_value);
Emit("message", method, params); Emit("message", method, params);
} else { } else {
auto send_command_callback = pending_requests_[id]; auto it = pending_requests_.find(id);
pending_requests_.erase(id); if (it == pending_requests_.end())
if (send_command_callback.is_null())
return; 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; scoped_refptr<atom::util::Promise> promise = it->second;
base::DictionaryValue result; pending_requests_.erase(it);
if (dict->GetDictionary("result", &result_body))
result.Swap(result_body); base::DictionaryValue* error = nullptr;
send_command_callback.Run(has_error ? error.Clone() : base::Value(), if (dict->GetDictionary("error", &error)) {
result); 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);
}
} }
} }
@ -125,23 +128,26 @@ void Debugger::Detach() {
AgentHostClosed(agent_host_.get()); AgentHostClosed(agent_host_.get());
} }
void Debugger::SendCommand(mate::Arguments* args) { v8::Local<v8::Promise> Debugger::SendCommand(mate::Arguments* args) {
if (!agent_host_) scoped_refptr<util::Promise> promise = new util::Promise(isolate());
return;
if (!agent_host_) {
promise->RejectWithErrorMessage("No target available");
return promise->GetHandle();
}
std::string method; std::string method;
if (!args->GetNext(&method)) { if (!args->GetNext(&method)) {
args->ThrowError(); promise->RejectWithErrorMessage("Invalid method");
return; return promise->GetHandle();
} }
base::DictionaryValue command_params; base::DictionaryValue command_params;
args->GetNext(&command_params); args->GetNext(&command_params);
SendCommandCallback callback;
args->GetNext(&callback);
base::DictionaryValue request; base::DictionaryValue request;
int request_id = ++previous_request_id_; int request_id = ++previous_request_id_;
pending_requests_[request_id] = callback; pending_requests_[request_id] = promise;
request.SetInteger("id", request_id); request.SetInteger("id", request_id);
request.SetString("method", method); request.SetString("method", method);
if (!command_params.empty()) if (!command_params.empty())
@ -151,16 +157,13 @@ void Debugger::SendCommand(mate::Arguments* args) {
std::string json_args; std::string json_args;
base::JSONWriter::Write(request, &json_args); base::JSONWriter::Write(request, &json_args);
agent_host_->DispatchProtocolMessage(this, json_args); agent_host_->DispatchProtocolMessage(this, json_args);
return promise->GetHandle();
} }
void Debugger::ClearPendingRequests() { 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_) for (const auto& it : pending_requests_)
it.second.Run(error, base::Value()); it.second->RejectWithErrorMessage("target closed while handling command");
} }
// static // static

View file

@ -9,6 +9,7 @@
#include <string> #include <string>
#include "atom/browser/api/trackable_object.h" #include "atom/browser/api/trackable_object.h"
#include "atom/common/promise_util.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/values.h" #include "base/values.h"
#include "content/public/browser/devtools_agent_host_client.h" #include "content/public/browser/devtools_agent_host_client.h"
@ -32,9 +33,6 @@ class Debugger : public mate::TrackableObject<Debugger>,
public content::DevToolsAgentHostClient, public content::DevToolsAgentHostClient,
public content::WebContentsObserver { public content::WebContentsObserver {
public: public:
using SendCommandCallback =
base::Callback<void(const base::Value&, const base::Value&)>;
static mate::Handle<Debugger> Create(v8::Isolate* isolate, static mate::Handle<Debugger> Create(v8::Isolate* isolate,
content::WebContents* web_contents); content::WebContents* web_contents);
@ -56,12 +54,12 @@ class Debugger : public mate::TrackableObject<Debugger>,
content::RenderFrameHost* new_rfh) override; content::RenderFrameHost* new_rfh) override;
private: private:
using PendingRequestMap = std::map<int, SendCommandCallback>; using PendingRequestMap = std::map<int, scoped_refptr<atom::util::Promise>>;
void Attach(mate::Arguments* args); void Attach(mate::Arguments* args);
bool IsAttached(); bool IsAttached();
void Detach(); void Detach();
void SendCommand(mate::Arguments* args); v8::Local<v8::Promise> SendCommand(mate::Arguments* args);
void ClearPendingRequests(); void ClearPendingRequests();
content::WebContents* web_contents_; // Weak Reference. content::WebContents* web_contents_; // Weak Reference.

View file

@ -60,6 +60,20 @@ Detaches the debugger from the `webContents`.
Send given command to the debugging target. 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 ### Instance Events
#### Event: 'detach' #### Event: 'detach'

View file

@ -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) - [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) - [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.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.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) - [dialog.showMessageBox([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showMessageBox)
@ -45,10 +44,10 @@ 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.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.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) - [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) - [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) - [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) - [shell.openExternal(url[, options, callback])](https://github.com/electron/electron/blob/master/docs/api/shell.md#openExternal)
- [webviewTag.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#capturePage) - [webviewTag.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#capturePage)
- [webviewTag.printToPDF(options, callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#printToPDF) - [webviewTag.printToPDF(options, callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#printToPDF)
- [win.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/browser-window.md#capturePage) - [win.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/browser-window.md#capturePage)
- [desktopCapturer.getSources(options, callback)](https://github.com/electron/electron/blob/master/docs/api/desktop-capturer.md#getSources)

View file

@ -483,6 +483,8 @@ WebContents.prototype._init = function () {
// JavaScript wrapper of Debugger. // JavaScript wrapper of Debugger.
const { Debugger } = process.atomBinding('debugger') const { Debugger } = process.atomBinding('debugger')
Debugger.prototype.sendCommand = deprecate.promisify(Debugger.prototype.sendCommand)
Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype) Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype)
// Public APIs. // Public APIs.

View file

@ -2,6 +2,7 @@ const chai = require('chai')
const dirtyChai = require('dirty-chai') const dirtyChai = require('dirty-chai')
const http = require('http') const http = require('http')
const path = require('path') const path = require('path')
const { emittedOnce } = require('./events-helpers')
const { closeWindow } = require('./window-helpers') const { closeWindow } = require('./window-helpers')
const { BrowserWindow } = require('electron').remote const { BrowserWindow } = require('electron').remote
@ -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') w.webContents.loadURL('about:blank')
try { try {
w.webContents.debugger.attach() w.webContents.debugger.attach()
@ -123,7 +138,24 @@ describe('debugger module', () => {
w.webContents.debugger.sendCommand('Runtime.evaluate', params, callback) 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') w.webContents.loadURL('about:blank')
try { try {
w.webContents.debugger.attach() w.webContents.debugger.attach()
@ -169,7 +201,18 @@ describe('debugger module', () => {
w.webContents.debugger.sendCommand('Console.enable') 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') w.webContents.loadURL('about:blank')
try { try {
w.webContents.debugger.attach() w.webContents.debugger.attach()
@ -177,9 +220,8 @@ describe('debugger module', () => {
done(`unexpected error : ${err}`) done(`unexpected error : ${err}`)
} }
w.webContents.debugger.sendCommand('Test', err => { w.webContents.debugger.sendCommand('Test', (err, res) => {
expect(err).to.not.be.null() expect(err).to.be.an.instanceOf(Error).with.property('message', "'Test' wasn't found")
expect(err.message).to.equal("'Test' wasn't found")
w.webContents.debugger.detach() w.webContents.debugger.detach()
done() done()
}) })

View file

@ -1515,11 +1515,7 @@ describe('font fallback', () => {
try { try {
await w.loadURL(`data:text/html,${html}`) await w.loadURL(`data:text/html,${html}`)
w.webContents.debugger.attach() w.webContents.debugger.attach()
const sendCommand = (...args) => new Promise((resolve, reject) => { const sendCommand = (...args) => w.webContents.debugger.sendCommand(...args)
w.webContents.debugger.sendCommand(...args, (e, r) => {
if (e) { reject(e) } else { resolve(r) }
})
})
const { nodeId } = (await sendCommand('DOM.getDocument')).root.children[0] const { nodeId } = (await sendCommand('DOM.getDocument')).root.children[0]
await sendCommand('CSS.enable') await sendCommand('CSS.enable')
const { fonts } = await sendCommand('CSS.getPlatformFontsForNode', { nodeId }) const { fonts } = await sendCommand('CSS.getPlatformFontsForNode', { nodeId })