From 8c548ed9e9916f41eeef80bf3a69305d7d57a639 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 10 Dec 2019 15:17:42 -0800 Subject: [PATCH] feat(webkit): implement page.exposeFunction (#195) --- src/page.ts | 6 +++--- src/webkit/FrameManager.ts | 16 ++++++++++++++-- test/evaluation.spec.js | 2 +- test/page.spec.js | 18 +++++++++--------- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/page.ts b/src/page.ts index fd7729446f..c718d8f88a 100644 --- a/src/page.ts +++ b/src/page.ts @@ -16,7 +16,6 @@ */ import { EventEmitter } from 'events'; -import * as console from './console'; import * as dom from './dom'; import * as frames from './frames'; import { assert, debugError, helper } from './helper'; @@ -28,6 +27,7 @@ import { TimeoutSettings } from './TimeoutSettings'; import * as types from './types'; import { Events } from './events'; import { BrowserContext } from './browserContext'; +import { ConsoleMessage, ConsoleMessageLocation } from './console'; export interface PageDelegate { readonly rawMouse: input.RawMouse; @@ -272,12 +272,12 @@ export class Page extends EventEmitter { } } - _addConsoleMessage(type: string, args: js.JSHandle[], location: console.ConsoleMessageLocation, text?: string) { + _addConsoleMessage(type: string, args: js.JSHandle[], location: ConsoleMessageLocation, text?: string) { if (!this.listenerCount(Events.Page.Console)) { args.forEach(arg => arg.dispose()); return; } - this.emit(Events.Page.Console, new console.ConsoleMessage(type, text, args, location)); + this.emit(Events.Page.Console, new ConsoleMessage(type, text, args, location)); } url(): string { diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index 8a5e489d6b..b86db45e0e 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -38,6 +38,7 @@ import * as input from '../input'; import * as types from '../types'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; +const BINDING_CALL_MESSAGE = '__playwright_binding_call__'; export const FrameManagerEvents = { FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'), @@ -391,11 +392,18 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate, async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) { const { type, level, text, parameters, url, line: lineNumber, column: columnNumber } = event.message; + if (level === 'debug' && parameters && parameters[0].value === BINDING_CALL_MESSAGE) { + const parsedObjectId = JSON.parse(parameters[1].objectId); + const context = this._contextIdToContext.get(parsedObjectId.injectedScriptId); + this._page._onBindingCalled(parameters[2].value, context); + return; + } let derivedType: string = type; if (type === 'log') derivedType = level; else if (type === 'timing') derivedType = 'timeEnd'; + const mainFrameContext = await this.mainFrame().executionContext(); const handles = (parameters || []).map(p => { let context: js.ExecutionContext | null = null; @@ -498,8 +506,12 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate, return this._go('Page.goForward', options); } - exposeBinding(name: string, bindingFunction: string): Promise { - throw new Error('Not implemented'); + async exposeBinding(name: string, bindingFunction: string): Promise { + const script = `self.${name} = (param) => console.debug('${BINDING_CALL_MESSAGE}', {}, param); ${bindingFunction}`; + this._bootstrapScripts.unshift(script); + const source = this._bootstrapScripts.join(';'); + await this._session.send('Page.setBootstrapScript', { source }); + await Promise.all(this.frames().map(frame => frame.evaluate(script).catch(debugError))); } async evaluateOnNewDocument(script: string): Promise { diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index 2230680330..753faf29c8 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -101,7 +101,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { await page.goto(server.EMPTY_PAGE); expect(await frameEvaluation).toBe(42); }); - it.skip(WEBKIT)('should work from-inside an exposed function', async({page, server}) => { + it('should work from-inside an exposed function', async({page, server}) => { // Setup inpage callback, which calls Page.evaluate await page.exposeFunction('callController', async function(a, b) { return await page.evaluate((a, b) => a * b, a, b); diff --git a/test/page.spec.js b/test/page.spec.js index bb2d14bf07..e7b0d3bfd6 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -396,7 +396,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF }); describe('Page.exposeFunction', function() { - it.skip(WEBKIT)('should work', async({page, server}) => { + it('should work', async({page, server}) => { await page.exposeFunction('compute', function(a, b) { return a * b; }); @@ -405,7 +405,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF }); expect(result).toBe(36); }); - it.skip(WEBKIT)('should throw exception in page context', async({page, server}) => { + it('should throw exception in page context', async({page, server}) => { await page.exposeFunction('woof', function() { throw new Error('WOOF WOOF'); }); @@ -419,7 +419,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF expect(message).toBe('WOOF WOOF'); expect(stack).toContain(__filename); }); - it.skip(WEBKIT)('should support throwing "null"', async({page, server}) => { + it('should support throwing "null"', async({page, server}) => { await page.exposeFunction('woof', function() { throw null; }); @@ -432,7 +432,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF }); expect(thrown).toBe(null); }); - it.skip(WEBKIT)('should be callable from-inside evaluateOnNewDocument', async({page, server}) => { + it('should be callable from-inside evaluateOnNewDocument', async({page, server}) => { let called = false; await page.exposeFunction('woof', function() { called = true; @@ -441,7 +441,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF await page.reload(); expect(called).toBe(true); }); - it.skip(WEBKIT)('should survive navigation', async({page, server}) => { + it('should survive navigation', async({page, server}) => { await page.exposeFunction('compute', function(a, b) { return a * b; }); @@ -452,7 +452,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF }); expect(result).toBe(36); }); - it.skip(WEBKIT)('should await returned promise', async({page, server}) => { + it('should await returned promise', async({page, server}) => { await page.exposeFunction('compute', function(a, b) { return Promise.resolve(a * b); }); @@ -462,7 +462,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF }); expect(result).toBe(15); }); - it.skip(WEBKIT)('should work on frames', async({page, server}) => { + it('should work on frames', async({page, server}) => { await page.exposeFunction('compute', function(a, b) { return Promise.resolve(a * b); }); @@ -474,7 +474,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF }); expect(result).toBe(15); }); - it.skip(WEBKIT)('should work on frames before navigation', async({page, server}) => { + it('should work on frames before navigation', async({page, server}) => { await page.goto(server.PREFIX + '/frames/nested-frames.html'); await page.exposeFunction('compute', function(a, b) { return Promise.resolve(a * b); @@ -486,7 +486,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF }); expect(result).toBe(15); }); - it.skip(WEBKIT)('should work with complex objects', async({page, server}) => { + it('should work with complex objects', async({page, server}) => { await page.exposeFunction('complexObject', function(a, b) { return {x: a.x + b.x}; });