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 {
/**
* @param {!Frame} frame
* @param {!ExecutionContext} context
* @param {*} payload
*/
constructor(frame, payload) {
this._frame = frame;
this._session = this._frame._session;
this._frameId = this._frame._frameId;
constructor(context, payload) {
this._context = context;
this._session = this._context._session;
this._executionContextId = this._context._executionContextId;
this._objectId = payload.objectId;
this._type = payload.type;
this._subtype = payload.subtype;
@ -20,6 +20,13 @@ class JSHandle {
};
}
/**
* @return {ExecutionContext}
*/
executionContext() {
return this._context;
}
/**
* @override
* @return {string}
@ -35,7 +42,7 @@ class JSHandle {
* @return {!Promise<?JSHandle>}
*/
async getProperty(propertyName) {
const objectHandle = await this._frame.evaluateHandle((object, propertyName) => {
const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
const result = {__proto__: null};
result[propertyName] = object[propertyName];
return result;
@ -51,12 +58,12 @@ class JSHandle {
*/
async getProperties() {
const response = await this._session.send('Page.getObjectProperties', {
frameId: this._frameId,
executionContextId: this._executionContextId,
objectId: this._objectId,
});
const result = new Map();
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;
}
@ -77,7 +84,7 @@ class JSHandle {
if (!this._objectId)
return this._deserializeValue(this._protocolValue);
const simpleValue = await this._session.send('Page.evaluate', {
frameId: this._frameId,
executionContextId: this._executionContextId,
returnByValue: true,
functionText: (e => e).toString(),
args: [this._protocolValue],
@ -96,13 +103,24 @@ class JSHandle {
if (!this._objectId)
return;
await this._session.send('Page.disposeObject', {
frameId: this._frameId,
executionContextId: this._executionContextId,
objectId: this._objectId,
});
}
}
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}
*/
@ -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.value)
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
else
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) {

View File

@ -9,6 +9,7 @@ const util = require('util');
const EventEmitter = require('events');
const {JSHandle, createHandle} = require('./JSHandle');
const {Events} = require('./Events');
const {ExecutionContext} = require('./ExecutionContext');
const writeFileAsync = util.promisify(fs.writeFile);
const readFileAsync = util.promisify(fs.readFile);
@ -600,7 +601,7 @@ class Page extends EventEmitter {
_onConsole({type, args, 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>} */
this._waitTasks = new Set();
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) {
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;
}
return this._executionContext.evaluate(pageFunction, ...args);
}
_document() {
@ -970,29 +968,7 @@ class Frame {
}
async evaluateHandle(pageFunction, ...args) {
if (helper.isString(pageFunction)) {
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);
return this._executionContext.evaluateHandle(pageFunction, ...args);
}
/**

View File

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

View File

@ -22,7 +22,7 @@ module.exports.addTests = function({testRunner, expect}) {
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
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 utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(page.frames().length).toBe(2);

View File

@ -251,7 +251,7 @@ module.exports.addTests = function({testRunner, expect, product, Errors}) {
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 utils.attachFrame(page, 'frame1', server.EMPTY_PAGE2);
const otherFrame = page.frames()[1];
@ -262,7 +262,7 @@ module.exports.addTests = function({testRunner, expect, product, Errors}) {
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, 'frame2', server.EMPTY_PAGE2);
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).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, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1];