mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
fix: improve reliability of exposeFunction (#11600)
This commit is contained in:
parent
42b03a67d0
commit
b0c5392cb3
@ -96,6 +96,11 @@ export class CdpFrame extends Frame {
|
|||||||
updateClient(client: CDPSession, keepWorlds = false): void {
|
updateClient(client: CDPSession, keepWorlds = false): void {
|
||||||
this.#client = client;
|
this.#client = client;
|
||||||
if (!keepWorlds) {
|
if (!keepWorlds) {
|
||||||
|
// Clear the current contexts on previous world instances.
|
||||||
|
if (this.worlds) {
|
||||||
|
this.worlds[MAIN_WORLD].clearContext();
|
||||||
|
this.worlds[PUPPETEER_WORLD].clearContext();
|
||||||
|
}
|
||||||
this.worlds = {
|
this.worlds = {
|
||||||
[MAIN_WORLD]: new IsolatedWorld(
|
[MAIN_WORLD]: new IsolatedWorld(
|
||||||
this,
|
this,
|
||||||
|
@ -93,6 +93,8 @@ export class IsolatedWorld extends Realm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearContext(): void {
|
clearContext(): void {
|
||||||
|
// The message has to match the CDP message expected by the WaitTask class.
|
||||||
|
this.#context?.reject(new Error('Execution context was destroyed'));
|
||||||
this.#context = Deferred.create();
|
this.#context = Deferred.create();
|
||||||
if ('clearDocumentHandle' in this.#frameOrWorker) {
|
if ('clearDocumentHandle' in this.#frameOrWorker) {
|
||||||
this.#frameOrWorker.clearDocumentHandle();
|
this.#frameOrWorker.clearDocumentHandle();
|
||||||
|
@ -673,6 +673,9 @@ export class CdpPage extends Page {
|
|||||||
|
|
||||||
const expression = pageBindingInitString('exposedFun', name);
|
const expression = pageBindingInitString('exposedFun', name);
|
||||||
await this.#primaryTargetClient.send('Runtime.addBinding', {name});
|
await this.#primaryTargetClient.send('Runtime.addBinding', {name});
|
||||||
|
// TODO: investigate this as it appears to only apply to the main frame and
|
||||||
|
// local subframes instead of the entire frame tree (including future
|
||||||
|
// frame).
|
||||||
const {identifier} = await this.#primaryTargetClient.send(
|
const {identifier} = await this.#primaryTargetClient.send(
|
||||||
'Page.addScriptToEvaluateOnNewDocument',
|
'Page.addScriptToEvaluateOnNewDocument',
|
||||||
{
|
{
|
||||||
@ -684,6 +687,11 @@ export class CdpPage extends Page {
|
|||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.frames().map(frame => {
|
this.frames().map(frame => {
|
||||||
|
// If a frame has not started loading, it might never start. Rely on
|
||||||
|
// addScriptToEvaluateOnNewDocument in that case.
|
||||||
|
if (frame !== this.mainFrame() && !frame._hasStartedLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return frame.evaluate(expression).catch(debugError);
|
return frame.evaluate(expression).catch(debugError);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -702,6 +710,11 @@ export class CdpPage extends Page {
|
|||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.frames().map(frame => {
|
this.frames().map(frame => {
|
||||||
|
// If a frame has not started loading, it might never start. Rely on
|
||||||
|
// addScriptToEvaluateOnNewDocument in that case.
|
||||||
|
if (frame !== this.mainFrame() && !frame._hasStartedLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return frame
|
return frame
|
||||||
.evaluate(name => {
|
.evaluate(name => {
|
||||||
// Removes the dangling Puppeteer binding wrapper.
|
// Removes the dangling Puppeteer binding wrapper.
|
||||||
|
@ -365,6 +365,13 @@ export function addPageBinding(type: string, name: string): void {
|
|||||||
// @ts-expect-error: In a different context.
|
// @ts-expect-error: In a different context.
|
||||||
const callCdp = globalThis[name];
|
const callCdp = globalThis[name];
|
||||||
|
|
||||||
|
// Depending on the frame loading state either Runtime.evaluate or
|
||||||
|
// Page.addScriptToEvaluateOnNewDocument might succeed. Let's check that we
|
||||||
|
// don't re-wrap Puppeteer's binding.
|
||||||
|
if (callCdp[Symbol.toStringTag] === 'PuppeteerBinding') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We replace the CDP binding with a Puppeteer binding.
|
// We replace the CDP binding with a Puppeteer binding.
|
||||||
Object.assign(globalThis, {
|
Object.assign(globalThis, {
|
||||||
[name](...args: unknown[]): Promise<unknown> {
|
[name](...args: unknown[]): Promise<unknown> {
|
||||||
@ -404,6 +411,8 @@ export function addPageBinding(type: string, name: string): void {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// @ts-expect-error: In a different context.
|
||||||
|
globalThis[name][Symbol.toStringTag] = 'PuppeteerBinding';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1044,6 +1044,13 @@
|
|||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[page.spec] Page Page.exposeFunction should work with loading frames",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "Missing request interception"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[page.spec] Page Page.pdf can print to PDF with accessible",
|
"testIdPattern": "[page.spec] Page Page.pdf can print to PDF with accessible",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -3127,6 +3134,12 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[page.spec] Page Page.exposeFunction should work with loading frames",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["cdp", "firefox"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[page.spec] Page Page.metrics metrics event fired on console.timeStamp",
|
"testIdPattern": "[page.spec] Page Page.metrics metrics event fired on console.timeStamp",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
@ -21,6 +21,7 @@ import path from 'path';
|
|||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import {KnownDevices, TimeoutError} from 'puppeteer';
|
import {KnownDevices, TimeoutError} from 'puppeteer';
|
||||||
import {CDPSession} from 'puppeteer-core/internal/api/CDPSession.js';
|
import {CDPSession} from 'puppeteer-core/internal/api/CDPSession.js';
|
||||||
|
import type {HTTPRequest} from 'puppeteer-core/internal/api/HTTPRequest.js';
|
||||||
import type {Metrics, Page} from 'puppeteer-core/internal/api/Page.js';
|
import type {Metrics, Page} from 'puppeteer-core/internal/api/Page.js';
|
||||||
import type {CdpPage} from 'puppeteer-core/internal/cdp/Page.js';
|
import type {CdpPage} from 'puppeteer-core/internal/cdp/Page.js';
|
||||||
import type {ConsoleMessage} from 'puppeteer-core/internal/common/ConsoleMessage.js';
|
import type {ConsoleMessage} from 'puppeteer-core/internal/common/ConsoleMessage.js';
|
||||||
@ -1177,6 +1178,50 @@ describe('Page', function () {
|
|||||||
});
|
});
|
||||||
expect(result).toBe(15);
|
expect(result).toBe(15);
|
||||||
});
|
});
|
||||||
|
it('should work with loading frames', async () => {
|
||||||
|
// Tries to reproduce the scenario from
|
||||||
|
// https://github.com/puppeteer/puppeteer/issues/8106
|
||||||
|
const {page, server} = await getTestState();
|
||||||
|
|
||||||
|
await page.setRequestInterception(true);
|
||||||
|
let saveRequest: (value: HTTPRequest | PromiseLike<HTTPRequest>) => void;
|
||||||
|
const iframeRequest = new Promise<HTTPRequest>(resolve => {
|
||||||
|
saveRequest = resolve;
|
||||||
|
});
|
||||||
|
page.on('request', async req => {
|
||||||
|
if (req.url().endsWith('/frames/frame.html')) {
|
||||||
|
saveRequest(req);
|
||||||
|
} else {
|
||||||
|
await req.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let error: Error | undefined;
|
||||||
|
const navPromise = page
|
||||||
|
.goto(server.PREFIX + '/frames/one-frame.html', {
|
||||||
|
waitUntil: 'networkidle0',
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
error = err;
|
||||||
|
});
|
||||||
|
const req = await iframeRequest;
|
||||||
|
// Expose function while the frame is being loaded. Loading process is
|
||||||
|
// controlled by interception.
|
||||||
|
const exposePromise = page.exposeFunction(
|
||||||
|
'compute',
|
||||||
|
function (a: number, b: number) {
|
||||||
|
return Promise.resolve(a * b);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await Promise.all([req.continue(), exposePromise]);
|
||||||
|
await navPromise;
|
||||||
|
expect(error).toBeUndefined();
|
||||||
|
const frame = page.frames()[1]!;
|
||||||
|
const result = await frame.evaluate(async function () {
|
||||||
|
return (globalThis as any).compute(3, 5);
|
||||||
|
});
|
||||||
|
expect(result).toBe(15);
|
||||||
|
});
|
||||||
it('should work on frames before navigation', async () => {
|
it('should work on frames before navigation', async () => {
|
||||||
const {page, server} = await getTestState();
|
const {page, server} = await getTestState();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user