Skip to content

Commit

Permalink
feat(firefox): implement page.exposeFunction (#4052)
Browse files Browse the repository at this point in the history
  • Loading branch information
aslushnikov committed Feb 22, 2019
1 parent 7d39aca commit 5c81836
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 4 deletions.
88 changes: 87 additions & 1 deletion experimental/puppeteer-firefox/lib/Page.js
@@ -1,4 +1,4 @@
const {helper} = require('./helper');
const {helper, debugError} = require('./helper');
const {Keyboard, Mouse} = require('./Input');
const {Dialog} = require('./Dialog');
const {TimeoutError} = require('./Errors');
Expand Down Expand Up @@ -46,13 +46,16 @@ class Page extends EventEmitter {
this._keyboard = new Keyboard(session);
this._mouse = new Mouse(session, this._keyboard);
this._closed = false;
/** @type {!Map<string, Function>} */
this._pageBindings = new Map();
this._networkManager = new NetworkManager(session);
this._frameManager = new FrameManager(session, this, this._networkManager, this._timeoutSettings);
this._networkManager.setFrameManager(this._frameManager);
this._eventListeners = [
helper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)),
helper.addEventListener(this._session, 'Page.console', this._onConsole.bind(this)),
helper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)),
helper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)),
helper.addEventListener(this._frameManager, Events.FrameManager.Load, () => this.emit(Events.Page.Load)),
helper.addEventListener(this._frameManager, Events.FrameManager.DOMContentLoaded, () => this.emit(Events.Page.DOMContentLoaded)),
helper.addEventListener(this._frameManager, Events.FrameManager.FrameAttached, frame => this.emit(Events.Page.FrameAttached, frame)),
Expand Down Expand Up @@ -81,6 +84,89 @@ class Page extends EventEmitter {
await this._networkManager.setExtraHTTPHeaders(headers);
}

/**
* @param {string} name
* @param {Function} puppeteerFunction
*/
async exposeFunction(name, puppeteerFunction) {
if (this._pageBindings.has(name))
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
this._pageBindings.set(name, puppeteerFunction);

const expression = helper.evaluationString(addPageBinding, name);
await this._session.send('Page.addBinding', {name: name});
await this._session.send('Page.addScriptToEvaluateOnNewDocument', {script: expression});
await Promise.all(this.frames().map(frame => frame.evaluate(expression).catch(debugError)));

function addPageBinding(bindingName) {
const binding = window[bindingName];
window[bindingName] = (...args) => {
const me = window[bindingName];
let callbacks = me['callbacks'];
if (!callbacks) {
callbacks = new Map();
me['callbacks'] = callbacks;
}
const seq = (me['lastSeq'] || 0) + 1;
me['lastSeq'] = seq;
const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject}));
binding(JSON.stringify({name: bindingName, seq, args}));
return promise;
};
}
}

/**
* @param {!Protocol.Runtime.bindingCalledPayload} event
*/
async _onBindingCalled(event) {
const {name, seq, args} = JSON.parse(event.payload);
let expression = null;
try {
const result = await this._pageBindings.get(name)(...args);
expression = helper.evaluationString(deliverResult, name, seq, result);
} catch (error) {
if (error instanceof Error)
expression = helper.evaluationString(deliverError, name, seq, error.message, error.stack);
else
expression = helper.evaluationString(deliverErrorValue, name, seq, error);
}
this._session.send('Page.evaluate', { script: expression, executionContextId: event.frameId }).catch(debugError);

/**
* @param {string} name
* @param {number} seq
* @param {*} result
*/
function deliverResult(name, seq, result) {
window[name]['callbacks'].get(seq).resolve(result);
window[name]['callbacks'].delete(seq);
}

/**
* @param {string} name
* @param {number} seq
* @param {string} message
* @param {string} stack
*/
function deliverError(name, seq, message, stack) {
const error = new Error(message);
error.stack = stack;
window[name]['callbacks'].get(seq).reject(error);
window[name]['callbacks'].delete(seq);
}

/**
* @param {string} name
* @param {number} seq
* @param {*} value
*/
function deliverErrorValue(name, seq, value) {
window[name]['callbacks'].get(seq).reject(value);
window[name]['callbacks'].delete(seq);
}
}

/**
* @param {(string|Function)} urlOrPredicate
* @param {!{timeout?: number}=} options
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": "e78e4cefab9d40e70bb80b3e649dcba7a7c8ee8f"
"firefox_revision": "f8e2e3a2e86cd47766cd839624b3f08e093c1f27"
},
"scripts": {
"install": "node install.js",
Expand Down
2 changes: 1 addition & 1 deletion lib/Page.js
Expand Up @@ -435,7 +435,7 @@ class Page extends EventEmitter {

function addPageBinding(bindingName) {
const binding = window[bindingName];
window[bindingName] = async(...args) => {
window[bindingName] = (...args) => {
const me = window[bindingName];
let callbacks = me['callbacks'];
if (!callbacks) {
Expand Down
2 changes: 1 addition & 1 deletion test/page.spec.js
Expand Up @@ -538,7 +538,7 @@ module.exports.addTests = function({testRunner, expect, headless, Errors, Device
});
});

describe_fails_ffox('Page.exposeFunction', function() {
describe('Page.exposeFunction', function() {
it('should work', async({page, server}) => {
await page.exposeFunction('compute', function(a, b) {
return a * b;
Expand Down

0 comments on commit 5c81836

Please sign in to comment.