Roll chromium to 492629 (#230)
This patch - rolls chromium to 492629 - migrates connection establishing to use browser target. This migration means that now we have a single websocket connection to browser (implemented in Connection class). A connection to a particular target is incapsulated in a new Session class.
This commit is contained in:
parent
29adc5dc80
commit
fe06c896eb
@ -92,9 +92,10 @@ class Browser {
|
||||
await this._ensureChromeIsRunning();
|
||||
if (!this._chromeProcess || this._terminated)
|
||||
throw new Error('ERROR: this chrome instance is not alive any more!');
|
||||
let client = await Connection.create(this._remoteDebuggingPort, this._connectionDelay);
|
||||
let page = await Page.create(client, this._ignoreHTTPSErrors, this._screenshotTaskQueue);
|
||||
return page;
|
||||
|
||||
const {targetId} = await this._connection.send('Target.createTarget', {url: 'about:blank'});
|
||||
const client = await this._connection.createSession(targetId);
|
||||
return await Page.create(client, this._ignoreHTTPSErrors, this._screenshotTaskQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,15 +134,17 @@ class Browser {
|
||||
this._chromeProcess.stderr.pipe(this.stderr);
|
||||
this._chromeProcess.stdout.pipe(this.stdout);
|
||||
|
||||
this._remoteDebuggingPort = await waitForRemoteDebuggingPort(this._chromeProcess);
|
||||
let {port, browserTargetId} = await waitForRemoteDebuggingPort(this._chromeProcess);
|
||||
// Failed to connect to browser.
|
||||
if (this._remoteDebuggingPort === -1) {
|
||||
if (port === -1) {
|
||||
this._chromeProcess.kill();
|
||||
throw new Error('Failed to connect to chrome!');
|
||||
}
|
||||
|
||||
if (this._terminated)
|
||||
throw new Error('Failed to launch chrome! ' + stderr);
|
||||
this._remoteDebuggingPort = port;
|
||||
this._connection = await Connection.create(port, browserTargetId, this._connectionDelay);
|
||||
}
|
||||
|
||||
close() {
|
||||
@ -168,11 +171,11 @@ function waitForRemoteDebuggingPort(chromeProcess) {
|
||||
* @param {string} line
|
||||
*/
|
||||
function onLine(line) {
|
||||
const match = line.match(/^DevTools listening on .*:(\d+)$/);
|
||||
const match = line.match(/^DevTools listening on .*:(\d+)(\/.*)$/);
|
||||
if (!match)
|
||||
return;
|
||||
rl.removeListener('line', onLine);
|
||||
fulfill(Number.parseInt(match[1], 10));
|
||||
fulfill({port: Number.parseInt(match[1], 10), browserTargetId: match[2]});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -23,14 +23,11 @@ const COMMAND_TIMEOUT = 10000;
|
||||
class Connection extends EventEmitter {
|
||||
/**
|
||||
* @param {number} port
|
||||
* @param {string} targetId
|
||||
* @param {!WebSocket} ws
|
||||
* @param {number=} delay
|
||||
*/
|
||||
constructor(port, targetId, ws, delay) {
|
||||
constructor(ws, delay) {
|
||||
super();
|
||||
this._port = port;
|
||||
this._targetId = targetId;
|
||||
this._lastId = 0;
|
||||
/** @type {!Map<number, {resolve: function, reject: function, method: string}>}*/
|
||||
this._callbacks = new Map();
|
||||
@ -39,13 +36,8 @@ class Connection extends EventEmitter {
|
||||
this._ws = ws;
|
||||
this._ws.on('message', this._onMessage.bind(this));
|
||||
this._ws.on('close', this._onClose.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
targetId() {
|
||||
return this._targetId;
|
||||
/** @type {!Map<string, !Session>}*/
|
||||
this._sessions = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,22 +72,47 @@ class Connection extends EventEmitter {
|
||||
callback.resolve(object.result);
|
||||
} else {
|
||||
console.assert(!object.id);
|
||||
if (object.method === 'Target.receivedMessageFromTarget') {
|
||||
const session = this._sessions.get(object.params.sessionId);
|
||||
if (session)
|
||||
session._onMessage(object.params.message);
|
||||
} else if (object.method === 'Target.detachedFromTarget') {
|
||||
const session = this._sessions.get(object.params.sessionId);
|
||||
if (session)
|
||||
session._onClosed();
|
||||
this._sessions.delete(object.params.sessionId);
|
||||
} else {
|
||||
this.emit(object.method, object.params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onClose() {
|
||||
this._ws.removeAllListeners();
|
||||
this._ws.close();
|
||||
for (let callback of this._callbacks.values())
|
||||
callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`));
|
||||
this._callbacks.clear();
|
||||
for (let session of this._sessions.values())
|
||||
session._onClosed();
|
||||
this._sessions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async dispose() {
|
||||
await runJsonCommand(this._port, `close/${this._targetId}`);
|
||||
this._ws.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} targetId
|
||||
* @return {!Promise<!Session>}
|
||||
*/
|
||||
async createSession(targetId) {
|
||||
const {sessionId} = await this.send('Target.attachToTarget', {targetId});
|
||||
const session = new Session(this, targetId, sessionId);
|
||||
this._sessions.set(sessionId, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,13 +120,11 @@ class Connection extends EventEmitter {
|
||||
* @param {number=} delay
|
||||
* @return {!Promise<!Connection>}
|
||||
*/
|
||||
static async create(port, delay) {
|
||||
let newTab = await runJsonCommand(port, 'new');
|
||||
let url = newTab.webSocketDebuggerUrl;
|
||||
|
||||
static async create(port, targetId, delay) {
|
||||
const url = `ws://localhost:${port}${targetId}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
let ws = new WebSocket(url, { perMessageDeflate: false });
|
||||
ws.on('open', () => resolve(new Connection(port, newTab.id, ws, delay)));
|
||||
ws.on('open', () => resolve(new Connection(ws, delay)));
|
||||
ws.on('error', reject);
|
||||
});
|
||||
}
|
||||
@ -123,6 +138,82 @@ class Connection extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
class Session extends EventEmitter {
|
||||
/**
|
||||
* @param {!Connection} connection
|
||||
* @param {string} targetId
|
||||
* @param {string} sessionId
|
||||
*/
|
||||
constructor(connection, targetId, sessionId) {
|
||||
super();
|
||||
this._lastId = 0;
|
||||
/** @type {!Map<number, {resolve: function, reject: function, method: string}>}*/
|
||||
this._callbacks = new Map();
|
||||
this._connection = connection;
|
||||
this._targetId = targetId;
|
||||
this._sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
targetId() {
|
||||
return this._targetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} method
|
||||
* @param {!Object=} params
|
||||
* @return {!Promise<?Object>}
|
||||
*/
|
||||
async send(method, params = {}) {
|
||||
let id = ++this._lastId;
|
||||
let message = JSON.stringify({id, method, params});
|
||||
this._connection.send('Target.sendMessageToTarget', {sessionId: this._sessionId, message}).catch(e => {
|
||||
// The response from target might have been already dispatched.
|
||||
if (!this._callbacks.has(id))
|
||||
return;
|
||||
let callback = this._callbacks.get(id);
|
||||
this._callbacks.delete(object.id);
|
||||
callback.reject(e);
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, {resolve, reject, method});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
*/
|
||||
_onMessage(message) {
|
||||
let object = JSON.parse(message);
|
||||
if (object.id && this._callbacks.has(object.id)) {
|
||||
let callback = this._callbacks.get(object.id);
|
||||
this._callbacks.delete(object.id);
|
||||
if (object.error)
|
||||
callback.reject(new Error(`Protocol error (${callback.method}): ${object.error.message} ${object.error.data}`));
|
||||
else
|
||||
callback.resolve(object.result);
|
||||
} else {
|
||||
console.assert(!object.id);
|
||||
this.emit(object.method, object.params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async dispose() {
|
||||
await this._connection.send('Target.closeTarget', {targetId: this._targetId});
|
||||
}
|
||||
|
||||
_onClosed() {
|
||||
for (let callback of this._callbacks.values())
|
||||
callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`));
|
||||
this._callbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} port
|
||||
* @param {string} command
|
||||
|
@ -18,7 +18,7 @@ const helper = require('./helper');
|
||||
|
||||
class Dialog {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {!Dialog.Type} type
|
||||
* @param {string} message
|
||||
*/
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
class EmulationManager {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
*/
|
||||
constructor(client) {
|
||||
this._client = client;
|
||||
|
@ -21,7 +21,7 @@ const helper = require('./helper');
|
||||
|
||||
class FrameManager extends EventEmitter {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {!Object} frameTree
|
||||
* @param {!Mouse} mouse
|
||||
*/
|
||||
@ -144,7 +144,7 @@ FrameManager.Events = {
|
||||
*/
|
||||
class Frame {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {!Mouse} mouse
|
||||
* @param {?Frame} parentFrame
|
||||
* @param {string} frameId
|
||||
|
@ -18,7 +18,7 @@ const helper = require('./helper');
|
||||
|
||||
class Keyboard {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
*/
|
||||
constructor(client) {
|
||||
this._client = client;
|
||||
@ -95,7 +95,7 @@ class Keyboard {
|
||||
|
||||
class Mouse {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {!Keyboard} keyboard
|
||||
*/
|
||||
constructor(client, keyboard) {
|
||||
|
@ -18,7 +18,7 @@ const helper = require('./helper');
|
||||
|
||||
class NavigatorWatcher {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {boolean} ignoreHTTPSErrors
|
||||
* @param {!Object=} options
|
||||
*/
|
||||
|
@ -18,7 +18,7 @@ const helper = require('./helper');
|
||||
|
||||
class NetworkManager extends EventEmitter {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
*/
|
||||
constructor(client) {
|
||||
super();
|
||||
@ -165,7 +165,7 @@ helper.tracePublicAPI(Request);
|
||||
|
||||
class Response {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {?Request} request
|
||||
* @param {!Object} payload
|
||||
*/
|
||||
@ -223,7 +223,7 @@ helper.tracePublicAPI(Response);
|
||||
|
||||
class InterceptedRequest {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {string} interceptionId
|
||||
* @param {!Object} payload
|
||||
*/
|
||||
|
@ -28,7 +28,8 @@ const helper = require('./helper');
|
||||
|
||||
class Page extends EventEmitter {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {string} sessionId
|
||||
* @param {boolean} ignoreHTTPSErrors
|
||||
* @param {!TaskQueue} screenshotTaskQueue
|
||||
* @return {!Promise<!Page>}
|
||||
@ -50,7 +51,7 @@ class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {boolean} ignoreHTTPSErrors
|
||||
* @param {!TaskQueue} screenshotTaskQueue
|
||||
*/
|
||||
|
@ -18,7 +18,7 @@ const helper = require('./helper');
|
||||
|
||||
class Tracing {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
*/
|
||||
constructor(client) {
|
||||
this._client = client;
|
||||
|
@ -49,7 +49,7 @@ class Helper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {!Object} remoteObject
|
||||
* @return {!Promise<!Object>}
|
||||
*/
|
||||
@ -96,7 +96,7 @@ class Helper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Session} client
|
||||
* @param {!Object} remoteObject
|
||||
* @return {!Promise}
|
||||
*/
|
||||
|
@ -29,7 +29,7 @@
|
||||
"ws": "^3.0.0"
|
||||
},
|
||||
"puppeteer": {
|
||||
"chromium_revision": "491334"
|
||||
"chromium_revision": "492629"
|
||||
},
|
||||
"devDependencies": {
|
||||
"commonmark": "^0.27.0",
|
||||
|
24
test/test.js
24
test/test.js
@ -95,6 +95,15 @@ describe('Browser', function() {
|
||||
expect(response.ok).toBe(true);
|
||||
browser.close();
|
||||
}));
|
||||
it('should reject all promises when browser is closed', SX(async function() {
|
||||
let browser = new Browser(defaultBrowserOptions);
|
||||
let page = await browser.newPage();
|
||||
let error = null;
|
||||
let neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e);
|
||||
browser.close();
|
||||
await neverResolves;
|
||||
expect(error.message).toContain('Protocol error');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Page', function() {
|
||||
@ -131,6 +140,21 @@ describe('Page', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Page.close', function() {
|
||||
it('should reject all promises when page is closed', SX(async function() {
|
||||
let newPage = await browser.newPage();
|
||||
let neverResolves = newPage.evaluate(() => new Promise(r => {}));
|
||||
newPage.close();
|
||||
let error = null;
|
||||
try {
|
||||
await neverResolves;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toContain('Protocol error');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Page.evaluate', function() {
|
||||
it('should work', SX(async function() {
|
||||
let result = await page.evaluate(() => 7 * 3);
|
||||
|
@ -27,6 +27,7 @@ const EXCLUDE_CLASSES = new Set([
|
||||
'NavigatorWatcher',
|
||||
'NetworkManager',
|
||||
'ProxyStream',
|
||||
'Session',
|
||||
'TaskQueue',
|
||||
'WaitTask',
|
||||
]);
|
||||
|
Loading…
Reference in New Issue
Block a user