From 6347a049baa1028afb87cef52e822ac73360b410 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 10 Aug 2017 21:44:49 -0700 Subject: [PATCH] Rename Page.setInPageCallback into Page.addBinding (#236) This seems to be a much better name which is actually used for a similar purposes in chromium/v8. --- docs/api.md | 88 ++++++++++++++++++++++++++++++++++++++-------------- lib/Page.js | 26 ++++++++-------- test/test.js | 16 +++++----- 3 files changed, 86 insertions(+), 44 deletions(-) diff --git a/docs/api.md b/docs/api.md index ac3e7e8a..9b5c9fc5 100644 --- a/docs/api.md +++ b/docs/api.md @@ -25,6 +25,7 @@ + [event: 'requestfailed'](#event-requestfailed) + [event: 'requestfinished'](#event-requestfinished) + [event: 'response'](#event-response) + + [page.addBinding(name, puppeteerFunction)](#pageaddbindingname-puppeteerfunction) + [page.addScriptTag(url)](#pageaddscripttagurl) + [page.click(selector[, options])](#pageclickselector-options) + [page.close()](#pageclose) @@ -48,7 +49,6 @@ + [page.screenshot([options])](#pagescreenshotoptions) + [page.setContent(html)](#pagesetcontenthtml) + [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders) - + [page.setInPageCallback(name, callback)](#pagesetinpagecallbackname-callback) + [page.setRequestInterceptor(interceptor)](#pagesetrequestinterceptorinterceptor) + [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) + [page.setViewport(viewport)](#pagesetviewportviewport) @@ -313,6 +313,69 @@ Emitted when a request is successfully finished. Emitted when a [response] is received. + +#### page.addBinding(name, puppeteerFunction) +- `name` <[string]> Name of the binding on window object +- `puppeteerFunction` <[function]> Callback function which will be called in puppeteer's context. +- returns: <[Promise]> Promise which resolves with the result of `puppeteerFunction`. + +The method adds a function called `name` on `window` object. +When called, the function executes `puppeteerFunction` function in puppeteer context and returns +a promise that resolves with the puppeteer's result. + +If the `puppeteerFunction` returns a promise, it would be awaited. + +> **NOTE** All the bindings installed via the `page.addBinding` survive navigations. + +An example of adding `window.md5` binding to the page: +```js +const {Browser} = require('puppeteer'); +const browser = new Browser(); +const crypto = require('crypto'); + +browser.newPage().then(async page => { + page.on('console', console.log); + await page.setInPageCallback('md5', text => crypto.createHash('md5').update(text).digest('hex')); + await page.evaluate(async () => { + // use window.md5 to compute hashes + let myString = 'PUPPETEER'; + let myHash = await window.md5(myString); + console.log(`md5 of ${myString} is ${myHash}`); + }); + browser.close(); +}); +``` + +An example of adding `window.readfile` binding to the page: + +```js +const {Browser} = require('puppeteer'); +const browser = new Browser(); +const fs = require('fs'); + +browser.newPage().then(async page => { + page.on('console', console.log); + await page.setInPageCallback('readfile', async filePath => { + return new Promise((resolve, reject) => { + fs.readFile(filePath, 'utf8', (err, text) => { + if (err) + reject(err); + else + resolve(text); + }); + }); + }); + await page.evaluate(async () => { + // use window.readfile to read contents of a file + let content = await window.readfile('/etc/hosts'); + console.log(content); + }); + browser.close(); +}); + +``` + + #### page.addScriptTag(url) - `url` <[string]> Url of a script to be added - returns: <[Promise]> Promise which resolves as the script gets added and loads. @@ -554,27 +617,6 @@ The extra HTTP headers will be sent with every request the page initiates. > **NOTE** page.setExtraHTTPHeaders does not guarantee the order of headers in the outgoing requests. -#### page.setInPageCallback(name, callback) -- `name` <[string]> Name of the callback to be assigned on window object -- `callback` <[function]> Callback function which will be called in puppeteer's context. -- returns: <[Promise]> Promise which resolves when callback is successfully initialized - -The in-page callback allows page to asynchronously reach back to the Puppeteer. -An example of a page showing amount of CPU's: -```js -const os = require('os'); -const {Browser} = require('puppeteer'); -const browser = new Browser(); - -browser.newPage().then(async page => - await page.setInPageCallback('getCPUCount', () => os.cpus().length); - await page.evaluate(async () => { - alert(await window.getCPUCount()); - }); - browser.close(); -}); -``` - #### page.setRequestInterceptor(interceptor) - `interceptor` <[function]> Callback function which accepts a single argument of type <[InterceptedRequest]>. - returns: <[Promise]> Promise which resolves when request interceptor is successfully installed on the page. @@ -966,7 +1008,7 @@ Returns frame's name attribute as specified in the tag. If the name is empty, returns the id attribute instead. -Note: This value is calculated once when the frame is created, and will not update if the attribute is changed later. +> **NOTE** This value is calculated once when the frame is created, and will not update if the attribute is changed later. #### frame.parentFrame() - returns: <[Frame]> Returns parent frame, if any. Detached frames and main frames return `null`. diff --git a/lib/Page.js b/lib/Page.js index 12b91b4a..75b58ebd 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -65,7 +65,7 @@ class Page extends EventEmitter { this._emulationManager = new EmulationManager(client); this._tracing = new Tracing(client); /** @type {!Map} */ - this._inPageCallbacks = new Map(); + this._pageBindings = new Map(); this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._screenshotTaskQueue = screenshotTaskQueue; @@ -152,20 +152,20 @@ class Page extends EventEmitter { /** * @param {string} name - * @param {function(?)} callback + * @param {function(?)} puppeteerFunction */ - async setInPageCallback(name, callback) { - if (this._inPageCallbacks[name]) - throw new Error(`Failed to set in-page callback with name ${name}: window['${name}'] already exists!`); - this._inPageCallbacks[name] = callback; + async addBinding(name, puppeteerFunction) { + if (this._pageBindings[name]) + throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`); + this._pageBindings[name] = puppeteerFunction; - let expression = helper.evaluationString(inPageCallback, name); + let expression = helper.evaluationString(addPageBinding, name); await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: expression }); await this._client.send('Runtime.evaluate', { expression, returnByValue: true }); - function inPageCallback(callbackName) { - window[callbackName] = async(...args) => { - const me = window[callbackName]; + function addPageBinding(bindingName) { + window[bindingName] = async(...args) => { + const me = window[bindingName]; let callbacks = me['callbacks']; if (!callbacks) { callbacks = new Map(); @@ -175,7 +175,7 @@ class Page extends EventEmitter { me['lastSeq'] = seq; const promise = new Promise(fulfill => callbacks.set(seq, fulfill)); // eslint-disable-next-line no-console - console.debug('driver:InPageCallback', JSON.stringify({name: callbackName, seq, args})); + console.debug('driver:page-binding', JSON.stringify({name: bindingName, seq, args})); return promise; }; } @@ -206,9 +206,9 @@ class Page extends EventEmitter { } async _onConsoleAPI(event) { - if (event.type === 'debug' && event.args.length && event.args[0].value === 'driver:InPageCallback') { + if (event.type === 'debug' && event.args.length && event.args[0].value === 'driver:page-binding') { let {name, seq, args} = JSON.parse(event.args[1].value); - let result = await this._inPageCallbacks[name](...args); + let result = await this._pageBindings[name](...args); let expression = helper.evaluationString(deliverResult, name, seq, result); this._client.send('Runtime.evaluate', { expression }); diff --git a/test/test.js b/test/test.js index 07943405..811a6538 100644 --- a/test/test.js +++ b/test/test.js @@ -166,7 +166,7 @@ describe('Page', function() { })); it('should work from-inside inPageCallback', SX(async function() { // Setup inpage callback, which calls Page.evaluate - await page.setInPageCallback('callController', async function(a, b) { + await page.addBinding('callController', async function(a, b) { return await page.evaluate((a, b) => a * b, a, b); }); let result = await page.evaluate(async function() { @@ -713,34 +713,34 @@ describe('Page', function() { })); }); - describe('Page.setInPageCallback', function() { + describe('Page.addBinding', function() { it('should work', SX(async function() { - await page.setInPageCallback('callController', function(a, b) { + await page.addBinding('compute', function(a, b) { return a * b; }); let result = await page.evaluate(async function() { - return await callController(9, 4); + return await compute(9, 4); }); expect(result).toBe(36); })); it('should survive navigation', SX(async function() { - await page.setInPageCallback('callController', function(a, b) { + await page.addBinding('compute', function(a, b) { return a * b; }); await page.goto(EMPTY_PAGE); let result = await page.evaluate(async function() { - return await callController(9, 4); + return await compute(9, 4); }); expect(result).toBe(36); })); it('should await returned promise', SX(async function() { - await page.setInPageCallback('callController', function(a, b) { + await page.addBinding('compute', function(a, b) { return Promise.resolve(a * b); }); let result = await page.evaluate(async function() { - return await callController(3, 5); + return await compute(3, 5); }); expect(result).toBe(15); }));