From 4d0dbbc517f388a3fe984ec569bc1bad28d91494 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com> Date: Fri, 2 Jun 2023 14:08:36 +0200 Subject: [PATCH] feat: add `page.removeExposedFunction` (#10297) --- docs/api/puppeteer.page.md | 1 + .../puppeteer.page.removeexposedfunction.md | 25 +++++++++++ packages/puppeteer-core/src/api/Page.ts | 9 ++++ packages/puppeteer-core/src/common/Page.ts | 42 +++++++++++++++++-- test/TestExpectations.json | 6 +++ test/src/page.spec.ts | 25 +++++++++++ 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 docs/api/puppeteer.page.removeexposedfunction.md diff --git a/docs/api/puppeteer.page.md b/docs/api/puppeteer.page.md index 84afbc8a..4b6b655a 100644 --- a/docs/api/puppeteer.page.md +++ b/docs/api/puppeteer.page.md @@ -126,6 +126,7 @@ page.off('request', logRequest); | [pdf(options)](./puppeteer.page.pdf.md) | | Generates a PDF of the page with the print CSS media type. | | [queryObjects(prototypeHandle)](./puppeteer.page.queryobjects.md) | | This method iterates the JavaScript heap and finds all objects with the given prototype. | | [reload(options)](./puppeteer.page.reload.md) | | | +| [removeExposedFunction(name)](./puppeteer.page.removeexposedfunction.md) | | The method removes a previously added function via $[Page.exposeFunction()](./puppeteer.page.exposefunction.md) called name from the page's window object. | | [removeScriptToEvaluateOnNewDocument(identifier)](./puppeteer.page.removescripttoevaluateonnewdocument.md) | | Removes script that injected into page by Page.evaluateOnNewDocument. | | [screenshot(options)](./puppeteer.page.screenshot.md) | | Captures screenshot of the current page. | | [screenshot(options)](./puppeteer.page.screenshot_1.md) | | | diff --git a/docs/api/puppeteer.page.removeexposedfunction.md b/docs/api/puppeteer.page.removeexposedfunction.md new file mode 100644 index 00000000..1d55fa30 --- /dev/null +++ b/docs/api/puppeteer.page.removeexposedfunction.md @@ -0,0 +1,25 @@ +--- +sidebar_label: Page.removeExposedFunction +--- + +# Page.removeExposedFunction() method + +The method removes a previously added function via $[Page.exposeFunction()](./puppeteer.page.exposefunction.md) called `name` from the page's `window` object. + +#### Signature: + +```typescript +class Page { + removeExposedFunction(name: string): Promise; +} +``` + +## Parameters + +| Parameter | Type | Description | +| --------- | ------ | ----------- | +| name | string | | + +**Returns:** + +Promise<void> diff --git a/packages/puppeteer-core/src/api/Page.ts b/packages/puppeteer-core/src/api/Page.ts index 2d4e9435..6a5854a8 100644 --- a/packages/puppeteer-core/src/api/Page.ts +++ b/packages/puppeteer-core/src/api/Page.ts @@ -1279,6 +1279,15 @@ export class Page extends EventEmitter { throw new Error('Not implemented'); } + /** + * The method removes a previously added function via ${@link Page.exposeFunction} + * called `name` from the page's `window` object. + */ + async removeExposedFunction(name: string): Promise; + async removeExposedFunction(): Promise { + throw new Error('Not implemented'); + } + /** * Provide credentials for `HTTP authentication`. * diff --git a/packages/puppeteer-core/src/common/Page.ts b/packages/puppeteer-core/src/common/Page.ts index c25767a1..7d63980a 100644 --- a/packages/puppeteer-core/src/common/Page.ts +++ b/packages/puppeteer-core/src/common/Page.ts @@ -147,6 +147,7 @@ export class CDPPage extends Page { #emulationManager: EmulationManager; #tracing: Tracing; #bindings = new Map(); + #exposedFunctions = new Map(); #coverage: Coverage; #javascriptEnabled = true; #viewport: Viewport | null; @@ -705,10 +706,16 @@ export class CDPPage extends Page { this.#bindings.set(name, binding); const expression = pageBindingInitString('exposedFun', name); - await this.#client.send('Runtime.addBinding', {name: name}); - await this.#client.send('Page.addScriptToEvaluateOnNewDocument', { - source: expression, - }); + await this.#client.send('Runtime.addBinding', {name}); + const {identifier} = await this.#client.send( + 'Page.addScriptToEvaluateOnNewDocument', + { + source: expression, + } + ); + + this.#exposedFunctions.set(name, identifier); + await Promise.all( this.frames().map(frame => { return frame.evaluate(expression).catch(debugError); @@ -716,6 +723,33 @@ export class CDPPage extends Page { ); } + override async removeExposedFunction(name: string): Promise { + const exposedFun = this.#exposedFunctions.get(name); + if (!exposedFun) { + throw new Error( + `Failed to remove page binding with name ${name}: window['${name}'] does not exists!` + ); + } + + await this.#client.send('Runtime.removeBinding', {name}); + await this.removeScriptToEvaluateOnNewDocument(exposedFun); + + await Promise.all( + this.frames().map(frame => { + return frame + .evaluate(name => { + // Removes the dangling Puppeteer binding wrapper. + // @ts-expect-error: In a different context. + globalThis[name] = undefined; + }, name) + .catch(debugError); + }) + ); + + this.#exposedFunctions.delete(name); + this.#bindings.delete(name); + } + override async authenticate(credentials: Credentials): Promise { return this.#frameManager.networkManager.authenticate(credentials); } diff --git a/test/TestExpectations.json b/test/TestExpectations.json index 22d165bc..064a535f 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -2039,6 +2039,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[page.spec] Page Page.removeExposedFunction should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["cdp", "firefox"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[page.spec] Page Page.select should work when re-defining top-level Event class", "platforms": ["darwin", "linux", "win32"], diff --git a/test/src/page.spec.ts b/test/src/page.spec.ts index 18606ccc..d4901d6f 100644 --- a/test/src/page.spec.ts +++ b/test/src/page.spec.ts @@ -1331,6 +1331,31 @@ describe('Page', function () { }); }); + describe('Page.removeExposedFunction', function () { + it('should work', async () => { + const {page} = getTestState(); + + await page.exposeFunction('compute', function (a: number, b: number) { + return a * b; + }); + const result = await page.evaluate(async function () { + return await (globalThis as any).compute(9, 4); + }); + expect(result).toBe(36); + await page.removeExposedFunction('compute'); + + let error: Error | null = null; + await page + .evaluate(async function () { + return (globalThis as any).compute(9, 4); + }) + .catch(_error => { + return (error = _error); + }); + expect(error).toBeTruthy(); + }); + }); + describe('Page.Events.PageError', function () { it('should fire', async () => { const {page, server} = getTestState();