mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: add BiDi support for SetContent (#9878)
This commit is contained in:
parent
9ccde6ebf5
commit
cb079378bb
@ -16,6 +16,7 @@
|
||||
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
|
||||
import {HTTPResponse} from '../../api/HTTPResponse.js';
|
||||
import {WaitForOptions} from '../../api/Page.js';
|
||||
import {assert} from '../../util/assert.js';
|
||||
import {stringifyFunction} from '../../util/Function.js';
|
||||
@ -24,7 +25,7 @@ import {EventEmitter} from '../EventEmitter.js';
|
||||
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
|
||||
import {TimeoutSettings} from '../TimeoutSettings.js';
|
||||
import {EvaluateFunc, HandleFor} from '../types.js';
|
||||
import {isString} from '../util.js';
|
||||
import {isString, waitWithTimeout} from '../util.js';
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
@ -34,7 +35,7 @@ import {BidiSerializer} from './Serializer.js';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
const puppeteerToReadinessState = new Map<
|
||||
const lifeCycleToReadinessState = new Map<
|
||||
PuppeteerLifeCycleEvent,
|
||||
Bidi.BrowsingContext.ReadinessState
|
||||
>([
|
||||
@ -42,6 +43,14 @@ const puppeteerToReadinessState = new Map<
|
||||
['domcontentloaded', 'interactive'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
const lifeCycleToSubscribedEvent = new Map<PuppeteerLifeCycleEvent, string>([
|
||||
['load', 'browsingContext.load'],
|
||||
['domcontentloaded', 'browsingContext.domContentLoaded'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -150,69 +159,102 @@ export class Context extends EventEmitter {
|
||||
referer?: string | undefined;
|
||||
referrerPolicy?: string | undefined;
|
||||
} = {}
|
||||
): Promise<null> {
|
||||
const {waitUntil = 'load'} = options;
|
||||
): Promise<HTTPResponse | null> {
|
||||
const {
|
||||
waitUntil = 'load',
|
||||
timeout = this._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
|
||||
const readinessState = lifeCycleToReadinessState.get(
|
||||
getWaitUntilSingle(waitUntil)
|
||||
) as Bidi.BrowsingContext.ReadinessState;
|
||||
|
||||
try {
|
||||
const response = await Promise.race([
|
||||
const response = await waitWithTimeout(
|
||||
this.connection.send('browsingContext.navigate', {
|
||||
url: url,
|
||||
context: this.id,
|
||||
wait: getWaitUntil(waitUntil),
|
||||
wait: readinessState,
|
||||
}),
|
||||
new Promise((_, reject) => {
|
||||
const timeout =
|
||||
options.timeout ?? this._timeoutSettings.navigationTimeout();
|
||||
if (!timeout) {
|
||||
return;
|
||||
}
|
||||
const error = new TimeoutError(
|
||||
'Navigation timeout of ' + timeout + ' ms exceeded'
|
||||
);
|
||||
return setTimeout(() => {
|
||||
return reject(error);
|
||||
}, timeout);
|
||||
}),
|
||||
]);
|
||||
this.#url = (response as Bidi.BrowsingContext.NavigateResult).result.url;
|
||||
'Navigation',
|
||||
timeout
|
||||
);
|
||||
this.#url = response.result.url;
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
if (error instanceof ProtocolError) {
|
||||
error.message += ` at ${url}`;
|
||||
} else if (error instanceof TimeoutError) {
|
||||
error.message = 'Navigation timeout of ' + timeout + ' ms exceeded';
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
function getWaitUntil(
|
||||
event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]
|
||||
): Bidi.BrowsingContext.ReadinessState {
|
||||
if (Array.isArray(event) && event.length > 1) {
|
||||
throw new Error('BiDi support only single `waitUntil` argument');
|
||||
}
|
||||
const waitUntilSingle = Array.isArray(event)
|
||||
? (event.find(lifecycle => {
|
||||
return lifecycle === 'domcontentloaded' || lifecycle === 'load';
|
||||
}) as PuppeteerLifeCycleEvent)
|
||||
: event;
|
||||
|
||||
if (
|
||||
waitUntilSingle === 'networkidle0' ||
|
||||
waitUntilSingle === 'networkidle2'
|
||||
) {
|
||||
throw new Error(`BiDi does not support 'waitUntil' ${waitUntilSingle}`);
|
||||
}
|
||||
|
||||
assert(waitUntilSingle, `Invalid waitUntil option ${waitUntilSingle}`);
|
||||
|
||||
return puppeteerToReadinessState.get(
|
||||
waitUntilSingle
|
||||
) as Bidi.BrowsingContext.ReadinessState;
|
||||
}
|
||||
}
|
||||
|
||||
url(): string {
|
||||
return this.#url;
|
||||
}
|
||||
|
||||
async setContent(
|
||||
html: string,
|
||||
options: WaitForOptions | undefined = {}
|
||||
): Promise<void> {
|
||||
const {
|
||||
waitUntil = 'load',
|
||||
timeout = this._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
|
||||
const waitUntilCommand = lifeCycleToSubscribedEvent.get(
|
||||
getWaitUntilSingle(waitUntil)
|
||||
) as string;
|
||||
|
||||
await Promise.all([
|
||||
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
|
||||
// lifecycle event. @see https://crrev.com/608658
|
||||
this.evaluate(html => {
|
||||
document.open();
|
||||
document.write(html);
|
||||
document.close();
|
||||
}, html),
|
||||
waitWithTimeout(
|
||||
new Promise<void>(resolve => {
|
||||
this.once(waitUntilCommand, () => {
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
waitUntilCommand,
|
||||
timeout
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function getWaitUntilSingle(
|
||||
event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]
|
||||
): Extract<PuppeteerLifeCycleEvent, 'load' | 'domcontentloaded'> {
|
||||
if (Array.isArray(event) && event.length > 1) {
|
||||
throw new Error('BiDi support only single `waitUntil` argument');
|
||||
}
|
||||
const waitUntilSingle = Array.isArray(event)
|
||||
? (event.find(lifecycle => {
|
||||
return lifecycle === 'domcontentloaded' || lifecycle === 'load';
|
||||
}) as PuppeteerLifeCycleEvent)
|
||||
: event;
|
||||
|
||||
if (
|
||||
waitUntilSingle === 'networkidle0' ||
|
||||
waitUntilSingle === 'networkidle2'
|
||||
) {
|
||||
throw new Error(`BiDi does not support 'waitUntil' ${waitUntilSingle}`);
|
||||
}
|
||||
|
||||
assert(waitUntilSingle, `Invalid waitUntil option ${waitUntilSingle}`);
|
||||
|
||||
return waitUntilSingle;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
WaitForOptions,
|
||||
} from '../../api/Page.js';
|
||||
import {ConsoleMessage, ConsoleMessageLocation} from '../ConsoleMessage.js';
|
||||
import {Handler} from '../EventEmitter.js';
|
||||
import {EvaluateFunc, HandleFor} from '../types.js';
|
||||
|
||||
import {Context, getBidiHandle} from './Context.js';
|
||||
@ -33,25 +34,26 @@ import {BidiSerializer} from './Serializer.js';
|
||||
*/
|
||||
export class Page extends PageBase {
|
||||
#context: Context;
|
||||
#subscribedEvents = [
|
||||
'log.entryAdded',
|
||||
'browsingContext.load',
|
||||
] as Bidi.Session.SubscribeParameters['events'];
|
||||
|
||||
#boundOnLogEntryAdded = this.#onLogEntryAdded.bind(this);
|
||||
#boundOnLoaded = this.#onLoad.bind(this);
|
||||
#subscribedEvents = new Map<string, Handler<any>>([
|
||||
['log.entryAdded', this.#onLogEntryAdded.bind(this)],
|
||||
['browsingContext.load', this.#onLoad.bind(this)],
|
||||
['browsingContext.domContentLoaded', this.#onDOMLoad.bind(this)],
|
||||
]) as Map<Bidi.Session.SubscribeParametersEvent, Handler>;
|
||||
|
||||
constructor(context: Context) {
|
||||
super();
|
||||
this.#context = context;
|
||||
|
||||
this.#context.connection.send('session.subscribe', {
|
||||
events: this.#subscribedEvents,
|
||||
events: [
|
||||
...this.#subscribedEvents.keys(),
|
||||
] as Bidi.Session.SubscribeParameters['events'],
|
||||
contexts: [this.#context.id],
|
||||
});
|
||||
|
||||
this.#context.on('log.entryAdded', this.#boundOnLogEntryAdded);
|
||||
this.#context.on('browsingContext.load', this.#boundOnLoaded);
|
||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
||||
this.#context.on(event, subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
#onLogEntryAdded(event: Bidi.Log.LogEntry): void {
|
||||
@ -95,9 +97,13 @@ export class Page extends PageBase {
|
||||
this.emit(PageEmittedEvents.Load);
|
||||
}
|
||||
|
||||
#onDOMLoad(_event: Bidi.BrowsingContext.NavigationInfo): void {
|
||||
this.emit(PageEmittedEvents.DOMContentLoaded);
|
||||
}
|
||||
|
||||
override async close(): Promise<void> {
|
||||
await this.#context.connection.send('session.unsubscribe', {
|
||||
events: this.#subscribedEvents,
|
||||
events: [...this.#subscribedEvents.keys()],
|
||||
contexts: [this.#context.id],
|
||||
});
|
||||
|
||||
@ -105,8 +111,9 @@ export class Page extends PageBase {
|
||||
context: this.#context.id,
|
||||
});
|
||||
|
||||
this.#context.off('log.entryAdded', this.#boundOnLogEntryAdded);
|
||||
this.#context.off('browsingContext.load', this.#boundOnLogEntryAdded);
|
||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
||||
this.#context.off(event, subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
override async evaluateHandle<
|
||||
@ -150,6 +157,26 @@ export class Page extends PageBase {
|
||||
override setDefaultTimeout(timeout: number): void {
|
||||
this.#context._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
override async setContent(
|
||||
html: string,
|
||||
options: WaitForOptions = {}
|
||||
): Promise<void> {
|
||||
await this.#context.setContent(html, options);
|
||||
}
|
||||
|
||||
override async content(): Promise<string> {
|
||||
return await this.evaluate(() => {
|
||||
let retVal = '';
|
||||
if (document.doctype) {
|
||||
retVal = new XMLSerializer().serializeToString(document.doctype);
|
||||
}
|
||||
if (document.documentElement) {
|
||||
retVal += document.documentElement.outerHTML;
|
||||
}
|
||||
return retVal;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function isConsoleLogEntry(
|
||||
|
@ -339,7 +339,7 @@ export async function waitWithTimeout<T>(
|
||||
const timeoutError = new TimeoutError(
|
||||
`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`
|
||||
);
|
||||
const timeoutPromise = new Promise<T>((_res, rej) => {
|
||||
const timeoutPromise = new Promise<never>((_, rej) => {
|
||||
return (reject = rej);
|
||||
});
|
||||
let timeoutTimer = null;
|
||||
|
@ -1802,8 +1802,8 @@
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.asElement should return ElementHandle for TextNodes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["FAIL", "TIMEOUT"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.toString should work for complicated objects",
|
||||
@ -1962,88 +1962,28 @@
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work",
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with both domcontentloaded and load",
|
||||
"testIdPattern": "[navigation.spec] navigation Page.goBack *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with clicking on anchor links",
|
||||
"testIdPattern": "[navigation.spec] navigation Frame.goto *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.pushState()",
|
||||
"testIdPattern": "[navigation.spec] navigation Frame.waitForNavigation *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.replaceState()",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with DOM history.back()/history.forward()",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work when subframe issues window.stop()",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL", "TIMEOUT"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.goBack should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.goBack should work with HistoryAPI",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Frame.goto should navigate subframes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Frame.goto should reject when frame detaches",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Frame.goto should return matching responses",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Frame.waitForNavigation should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Frame.waitForNavigation should fail when frame detaches",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[navigation.spec] navigation Page.reload should work",
|
||||
@ -2068,5 +2008,47 @@
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["firefox", "headless", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.url should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.Events.DOMContentLoaded *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.setContent *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.setContent should work with tricky content",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.setContent should work with accents",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.setContent should work with emojis",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.setContent should work with newline",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["chrome", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
}
|
||||
]
|
||||
|
@ -140,11 +140,10 @@ export const waitEvent = (
|
||||
}
|
||||
): Promise<any> => {
|
||||
return new Promise(fulfill => {
|
||||
emitter.on(eventName, function listener(event: any) {
|
||||
emitter.once(eventName, (event: any) => {
|
||||
if (!predicate(event)) {
|
||||
return;
|
||||
}
|
||||
emitter.off(eventName, listener);
|
||||
fulfill(event);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user