Skip to content

Commit

Permalink
feat(firefox): implement execution contexts (#3962)
Browse files Browse the repository at this point in the history
  • Loading branch information
aslushnikov committed Feb 9, 2019
1 parent 5696096 commit a987535
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 51 deletions.
64 changes: 64 additions & 0 deletions experimental/puppeteer-firefox/lib/ExecutionContext.js
@@ -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};
43 changes: 31 additions & 12 deletions experimental/puppeteer-firefox/lib/JSHandle.js
Expand Up @@ -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;
Expand All @@ -20,6 +20,13 @@ class JSHandle {
};
}

/**
* @return {ExecutionContext}
*/
executionContext() {
return this._context;
}

/**
* @override
* @return {string}
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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],
Expand All @@ -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}
*/
Expand Down Expand Up @@ -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) {
Expand Down
44 changes: 10 additions & 34 deletions experimental/puppeteer-firefox/lib/Page.js
Expand Up @@ -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);
Expand Down Expand Up @@ -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))));
}

/**
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion experimental/puppeteer-firefox/package.json
Expand Up @@ -9,7 +9,7 @@
"node": ">=8.9.4"
},
"puppeteer": {
"firefox_revision": "74896383109c18e133bd317b7b2959ae2376f51d"
"firefox_revision": "ed8e119ec1279c3db3638e90e910edb3816e0280"
},
"scripts": {
"install": "node install.js",
Expand Down
2 changes: 1 addition & 1 deletion test/frame.spec.js
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions test/waittask.spec.js
Expand Up @@ -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];
Expand All @@ -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];
Expand Down Expand Up @@ -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];
Expand Down

0 comments on commit a987535

Please sign in to comment.