feat(firefox): implement execution contexts (#3962)
This commit is contained in:
parent
569609636e
commit
a9875359aa
64
experimental/puppeteer-firefox/lib/ExecutionContext.js
Normal file
64
experimental/puppeteer-firefox/lib/ExecutionContext.js
Normal 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};
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,7 +9,7 @@
|
||||
"node": ">=8.9.4"
|
||||
},
|
||||
"puppeteer": {
|
||||
"firefox_revision": "74896383109c18e133bd317b7b2959ae2376f51d"
|
||||
"firefox_revision": "ed8e119ec1279c3db3638e90e910edb3816e0280"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "node install.js",
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user