feat(firefox): implement execution contexts (#3962)

This commit is contained in:
Andrey Lushnikov 2019-02-08 17:15:52 -08:00 committed by GitHub
parent 569609636e
commit a9875359aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 51 deletions

View File

@ -0,0 +1,64 @@
const {helper, assert, debugError} = require('./helper');
const {JSHandle, createHandle} = require('./JSHandle');
class ExecutionContext {
/**
* @param {!PageSession} session
* @param {?Frame} frame
* @param {string} executionContextId
*/
constructor(session, frame, executionContextId) {
this._session = session;
this._frame = frame;
this._executionContextId = executionContextId;
}
async evaluateHandle(pageFunction, ...args) {
if (helper.isString(pageFunction)) {
const payload = await this._session.send('Page.evaluate', {
script: pageFunction,
executionContextId: this._executionContextId,
});
return createHandle(this, payload.result, payload.exceptionDetails);
}
args = args.map(arg => {
if (arg instanceof JSHandle)
return arg._protocolValue;
if (Object.is(arg, Infinity))
return {unserializableValue: 'Infinity'};
if (Object.is(arg, -Infinity))
return {unserializableValue: '-Infinity'};
if (Object.is(arg, -0))
return {unserializableValue: '-0'};
if (Object.is(arg, NaN))
return {unserializableValue: 'NaN'};
return {value: arg};
});
const payload = await this._session.send('Page.evaluate', {
functionText: pageFunction.toString(),
args,
executionContextId: this._executionContextId
});
return createHandle(this, payload.result, payload.exceptionDetails);
}
frame() {
return this._frame;
}
async evaluate(pageFunction, ...args) {
try {
const handle = await this.evaluateHandle(pageFunction, ...args);
const result = await handle.jsonValue();
await handle.dispose();
return result;
} catch (e) {
if (e.message.includes('cyclic object value') || e.message.includes('Object is not serializable'))
return undefined;
throw e;
}
}
}
module.exports = {ExecutionContext};

View File

@ -3,13 +3,13 @@ const {assert, debugError} = require('./helper');
class JSHandle { class JSHandle {
/** /**
* @param {!Frame} frame * @param {!ExecutionContext} context
* @param {*} payload * @param {*} payload
*/ */
constructor(frame, payload) { constructor(context, payload) {
this._frame = frame; this._context = context;
this._session = this._frame._session; this._session = this._context._session;
this._frameId = this._frame._frameId; this._executionContextId = this._context._executionContextId;
this._objectId = payload.objectId; this._objectId = payload.objectId;
this._type = payload.type; this._type = payload.type;
this._subtype = payload.subtype; this._subtype = payload.subtype;
@ -20,6 +20,13 @@ class JSHandle {
}; };
} }
/**
* @return {ExecutionContext}
*/
executionContext() {
return this._context;
}
/** /**
* @override * @override
* @return {string} * @return {string}
@ -35,7 +42,7 @@ class JSHandle {
* @return {!Promise<?JSHandle>} * @return {!Promise<?JSHandle>}
*/ */
async getProperty(propertyName) { async getProperty(propertyName) {
const objectHandle = await this._frame.evaluateHandle((object, propertyName) => { const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
const result = {__proto__: null}; const result = {__proto__: null};
result[propertyName] = object[propertyName]; result[propertyName] = object[propertyName];
return result; return result;
@ -51,12 +58,12 @@ class JSHandle {
*/ */
async getProperties() { async getProperties() {
const response = await this._session.send('Page.getObjectProperties', { const response = await this._session.send('Page.getObjectProperties', {
frameId: this._frameId, executionContextId: this._executionContextId,
objectId: this._objectId, objectId: this._objectId,
}); });
const result = new Map(); const result = new Map();
for (const property of response.properties) { for (const property of response.properties) {
result.set(property.name, createHandle(this._frame, property.value, null)); result.set(property.name, createHandle(this._context, property.value, null));
} }
return result; return result;
} }
@ -77,7 +84,7 @@ class JSHandle {
if (!this._objectId) if (!this._objectId)
return this._deserializeValue(this._protocolValue); return this._deserializeValue(this._protocolValue);
const simpleValue = await this._session.send('Page.evaluate', { const simpleValue = await this._session.send('Page.evaluate', {
frameId: this._frameId, executionContextId: this._executionContextId,
returnByValue: true, returnByValue: true,
functionText: (e => e).toString(), functionText: (e => e).toString(),
args: [this._protocolValue], args: [this._protocolValue],
@ -96,13 +103,24 @@ class JSHandle {
if (!this._objectId) if (!this._objectId)
return; return;
await this._session.send('Page.disposeObject', { await this._session.send('Page.disposeObject', {
frameId: this._frameId, executionContextId: this._executionContextId,
objectId: this._objectId, objectId: this._objectId,
}); });
} }
} }
class ElementHandle extends JSHandle { class ElementHandle extends JSHandle {
/**
* @param {Frame} frame
* @param {ExecutionContext} context
* @param {*} payload
*/
constructor(frame, context, payload) {
super(context, payload);
this._frame = frame;
this._frameId = frame._frameId;
}
/** /**
* @return {?Frame} * @return {?Frame}
*/ */
@ -355,14 +373,15 @@ class ElementHandle extends JSHandle {
} }
} }
function createHandle(frame, result, exceptionDetails) { function createHandle(context, result, exceptionDetails) {
const frame = context.frame();
if (exceptionDetails) { if (exceptionDetails) {
if (exceptionDetails.value) if (exceptionDetails.value)
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value)); throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
else else
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack); throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
} }
return result.subtype === 'node' ? new ElementHandle(frame, result) : new JSHandle(frame, result); return result.subtype === 'node' ? new ElementHandle(frame, context, result) : new JSHandle(context, result);
} }
function computeQuadArea(quad) { function computeQuadArea(quad) {

View File

@ -9,6 +9,7 @@ const util = require('util');
const EventEmitter = require('events'); const EventEmitter = require('events');
const {JSHandle, createHandle} = require('./JSHandle'); const {JSHandle, createHandle} = require('./JSHandle');
const {Events} = require('./Events'); const {Events} = require('./Events');
const {ExecutionContext} = require('./ExecutionContext');
const writeFileAsync = util.promisify(fs.writeFile); const writeFileAsync = util.promisify(fs.writeFile);
const readFileAsync = util.promisify(fs.readFile); const readFileAsync = util.promisify(fs.readFile);
@ -600,7 +601,7 @@ class Page extends EventEmitter {
_onConsole({type, args, frameId}) { _onConsole({type, args, frameId}) {
const frame = this._frames.get(frameId); const frame = this._frames.get(frameId);
this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => createHandle(frame, arg)))); this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => createHandle(frame._executionContext, arg))));
} }
/** /**
@ -689,6 +690,12 @@ class Frame {
/** @type {!Set<!WaitTask>} */ /** @type {!Set<!WaitTask>} */
this._waitTasks = new Set(); this._waitTasks = new Set();
this._documentPromise = null; this._documentPromise = null;
this._executionContext = new ExecutionContext(this._session, this, this._frameId);
}
async executionContext() {
return this._executionContext;
} }
/** /**
@ -902,16 +909,7 @@ class Frame {
} }
async evaluate(pageFunction, ...args) { async evaluate(pageFunction, ...args) {
try { return this._executionContext.evaluate(pageFunction, ...args);
const handle = await this.evaluateHandle(pageFunction, ...args);
const result = await handle.jsonValue();
await handle.dispose();
return result;
} catch (e) {
if (e.message.includes('cyclic object value') || e.message.includes('Object is not serializable'))
return undefined;
throw e;
}
} }
_document() { _document() {
@ -970,29 +968,7 @@ class Frame {
} }
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args) {
if (helper.isString(pageFunction)) { return this._executionContext.evaluateHandle(pageFunction, ...args);
const payload = await this._session.send('Page.evaluate', {script: pageFunction, frameId: this._frameId});
return createHandle(this, payload.result, payload.exceptionDetails);
}
args = args.map(arg => {
if (arg instanceof JSHandle)
return arg._protocolValue;
if (Object.is(arg, Infinity))
return {unserializableValue: 'Infinity'};
if (Object.is(arg, -Infinity))
return {unserializableValue: '-Infinity'};
if (Object.is(arg, -0))
return {unserializableValue: '-0'};
if (Object.is(arg, NaN))
return {unserializableValue: 'NaN'};
return {value: arg};
});
const payload = await this._session.send('Page.evaluate', {
functionText: pageFunction.toString(),
args,
frameId: this._frameId
});
return createHandle(this, payload.result, payload.exceptionDetails);
} }
/** /**

View File

@ -9,7 +9,7 @@
"node": ">=8.9.4" "node": ">=8.9.4"
}, },
"puppeteer": { "puppeteer": {
"firefox_revision": "74896383109c18e133bd317b7b2959ae2376f51d" "firefox_revision": "ed8e119ec1279c3db3638e90e910edb3816e0280"
}, },
"scripts": { "scripts": {
"install": "node install.js", "install": "node install.js",

View File

@ -22,7 +22,7 @@ module.exports.addTests = function({testRunner, expect}) {
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Frame.executionContext', function() { describe('Frame.executionContext', function() {
it_fails_ffox('should work', async({page, server}) => { it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);

View File

@ -251,7 +251,7 @@ module.exports.addTests = function({testRunner, expect, product, Errors}) {
await watchdog; await watchdog;
}); });
it_fails_ffox('Page.waitForSelector is shortcut for main frame', async({page, server}) => { it('Page.waitForSelector is shortcut for main frame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE2); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE2);
const otherFrame = page.frames()[1]; const otherFrame = page.frames()[1];
@ -262,7 +262,7 @@ module.exports.addTests = function({testRunner, expect, product, Errors}) {
expect(eHandle.executionContext().frame()).toBe(page.mainFrame()); expect(eHandle.executionContext().frame()).toBe(page.mainFrame());
}); });
it_fails_ffox('should run in specified frame', async({page, server}) => { it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE2); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE2);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE2); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE2);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1];
@ -401,7 +401,7 @@ module.exports.addTests = function({testRunner, expect, product, Errors}) {
expect(error.message).toContain('waiting for XPath "//div" failed: timeout'); expect(error.message).toContain('waiting for XPath "//div" failed: timeout');
expect(error).toBeInstanceOf(TimeoutError); expect(error).toBeInstanceOf(TimeoutError);
}); });
it_fails_ffox('should run in specified frame', async({page, server}) => { it('should run in specified frame', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1];