From 69dcbfc9cef38a54998ee624bb4b541a310cb86b Mon Sep 17 00:00:00 2001 From: Alexey Kozyatinskiy Date: Fri, 14 Sep 2018 14:33:55 -0700 Subject: [PATCH] inspector: add inspector.waitForConnection method This method might be useful in case when we need to do something with `inspector.url()` before waiting for connection, e.g. save it to file. With this method we can do something like: ``` inspector.open(); fs.writeFileSync('abc', inspector.url()); inspector.waitForConnection(); ``` --- doc/api/inspector.md | 7 ++ lib/inspector.js | 7 +- src/inspector_agent.cc | 3 +- src/inspector_js_api.cc | 11 ++ test/common/inspector-helper.js | 3 +- .../test-inspector-wait-for-connection.js | 115 ++++++++++++++++++ 6 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 test/sequential/test-inspector-wait-for-connection.js diff --git a/doc/api/inspector.md b/doc/api/inspector.md index 92fcb0e03f89e4..2a12ff0bf15557 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -49,6 +49,13 @@ and flow control has been passed to the debugger client. Return the URL of the active inspector, or `undefined` if there is none. +## inspector.waitForConnection() + +This method blocks until first client has connection, inspector should be open +before call to this method. + +Call to this method when inspector is closed will throw an exception. + ## Class: inspector.Session The `inspector.Session` is used for dispatching messages to the V8 inspector diff --git a/lib/inspector.js b/lib/inspector.js index 8827158757e126..061c24a6e1020b 100644 --- a/lib/inspector.js +++ b/lib/inspector.js @@ -11,7 +11,11 @@ const { } = require('internal/errors').codes; const { validateString } = require('internal/validators'); const util = require('util'); -const { Connection, open, url } = process.binding('inspector'); +const { + Connection, + open, + url, + waitForConnection } = process.binding('inspector'); const { originalConsole } = require('internal/process/per_thread'); if (!Connection || !require('internal/worker').isMainThread) @@ -103,6 +107,7 @@ module.exports = { open: (port, host, wait) => open(port, host, !!wait), close: process._debugEnd, url: url, + waitForConnection: waitForConnection, console: originalConsole, Session }; diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 2eb9339d3e1641..bda4bcaa62aa40 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -833,7 +833,8 @@ bool Agent::IsActive() { void Agent::WaitForConnect() { CHECK_NOT_NULL(client_); - client_->waitForFrontend(); + if (!client_->hasConnectedSessions()) + client_->waitForFrontend(); } SameThreadInspectorSession::~SameThreadInspectorSession() { diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 18b9e610750e93..28f69ad74030ef 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -259,6 +259,16 @@ void Open(const FunctionCallbackInfo& args) { agent->WaitForConnect(); } +void WaitForConnection(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Agent* agent = env->inspector_agent(); + if (!agent->IsActive()) { + env->ThrowError("inspector error, inspector.open should be called first"); + return; + } + agent->WaitForConnect(); +} + void Url(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Agent* agent = env->inspector_agent(); @@ -291,6 +301,7 @@ void Initialize(Local target, Local unused, env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart); env->SetMethod(target, "open", Open); env->SetMethodNoSideEffect(target, "url", Url); + env->SetMethod(target, "waitForConnection", WaitForConnection); env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper); env->SetMethod(target, "asyncTaskCanceled", diff --git a/test/common/inspector-helper.js b/test/common/inspector-helper.js index ae8fd9732d34f5..2c52240dc4d0ff 100644 --- a/test/common/inspector-helper.js +++ b/test/common/inspector-helper.js @@ -504,5 +504,6 @@ function fires(promise, error, timeoutMs) { } module.exports = { - NodeInstance + NodeInstance, + formatWSFrame }; diff --git a/test/sequential/test-inspector-wait-for-connection.js b/test/sequential/test-inspector-wait-for-connection.js new file mode 100644 index 00000000000000..59ff928607e1c4 --- /dev/null +++ b/test/sequential/test-inspector-wait-for-connection.js @@ -0,0 +1,115 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +common.skipIfInspectorDisabled(); + +// Test inspector waitForConnection() API. It uses ephemeral ports so can be +// run safely in parallel. + +const assert = require('assert'); +const fork = require('child_process').fork; +const http = require('http'); +const url = require('url'); + +if (process.env.BE_CHILD) + return beChild(); + +const { formatWSFrame } = require('../common/inspector-helper.js'); + +function waitForMessage(child) { + return new Promise(child.once.bind(child, 'message')); +} + +async function connect(wsUrl) { + const socket = await new Promise((resolve, reject) => { + const parsedUrl = url.parse(wsUrl); + const response = http.get({ + port: parsedUrl.port, + path: parsedUrl.path, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Key': 'key==' + } + }); + response.once('upgrade', (message, socket) => resolve(socket)); + response.once('responce', () => reject('Upgrade was not received')); + }); + return socket; +} + +function runIfWaitingForDebugger(socket) { + return new Promise((resolve) => socket.write(formatWSFrame({ + id: 1, + method: 'Runtime.runIfWaitingForDebugger' + }), resolve)); +} + +(async function() { + const child = fork(__filename, + { env: Object.assign({}, process.env, { BE_CHILD: 1 }) }); + const started = await waitForMessage(child); + assert.strictEqual(started.cmd, 'started'); + + child.send({ cmd: 'open', args: [0] }); + const { url } = await waitForMessage(child); + + // Wait for connection first time.. + child.send({ cmd: 'waitForConnection' }); + // .. connect .. + const socket = await connect(url); + runIfWaitingForDebugger(socket); + + // .. check that waitForConnection method is finished. + const awaited1 = await waitForMessage(child); + assert.strictEqual(awaited1.cmd, 'awaited'); + + // Wait for connection with existing connection .. + child.send({ cmd: 'waitForConnection' }); + const awaited2 = await waitForMessage(child); + // .. check that waitForConnection method is finished. + assert.strictEqual(awaited2.cmd, 'awaited'); + + socket.end(); + + // Close connection .. + child.send({ cmd: 'close' }); + const closed = await waitForMessage(child); + assert.strictEqual(closed.cmd, 'closed'); + + // .. call waitForConnection when inspector is closed .. + child.send({ cmd: 'waitForConnection' }); + const awaitedError = await waitForMessage(child); + // .. check error message. + assert.strictEqual(awaitedError.cmd, 'awaited'); + assert.strictEqual(awaitedError.error, + 'inspector error, inspector.open should be called first'); + + child.send({ cmd: 'exit' }); +})(); + +function beChild() { + const inspector = require('inspector'); + + process.send({ cmd: 'started' }); + + process.on('message', (msg) => { + if (msg.cmd === 'open') { + inspector.open(...msg.args); + process.send({ cmd: 'opened', url: inspector.url() }); + } else if (msg.cmd === 'close') { + inspector.close(); + process.send({ cmd: 'closed' }); + } else if (msg.cmd === 'waitForConnection') { + try { + inspector.waitForConnection(); + process.send({ cmd: 'awaited' }); + } catch (e) { + process.send({ cmd: 'awaited', error: e.message }); + } + } else if (msg.cmd === 'exit') { + process.exit(); + } + }); +}