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();