mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
refactor: adopt the rest of bidi/core
(#11836)
This commit is contained in:
parent
d3f00bf032
commit
e9f9f4c356
@ -19,22 +19,15 @@ import {BrowserContextEvent} from '../api/BrowserContext.js';
|
|||||||
import type {Page} from '../api/Page.js';
|
import type {Page} from '../api/Page.js';
|
||||||
import type {Target} from '../api/Target.js';
|
import type {Target} from '../api/Target.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
import type {Handler} from '../common/EventEmitter.js';
|
|
||||||
import {debugError} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import type {Viewport} from '../common/Viewport.js';
|
import type {Viewport} from '../common/Viewport.js';
|
||||||
|
|
||||||
import {BidiBrowserContext} from './BrowserContext.js';
|
import {BidiBrowserContext} from './BrowserContext.js';
|
||||||
import {BrowsingContext, BrowsingContextEvent} from './BrowsingContext.js';
|
|
||||||
import type {BidiConnection} from './Connection.js';
|
import type {BidiConnection} from './Connection.js';
|
||||||
import type {Browser as BrowserCore} from './core/Browser.js';
|
import type {Browser as BrowserCore} from './core/Browser.js';
|
||||||
import {Session} from './core/Session.js';
|
import {Session} from './core/Session.js';
|
||||||
import {UserContext} from './core/UserContext.js';
|
import type {UserContext} from './core/UserContext.js';
|
||||||
import {
|
import {BidiBrowserTarget} from './Target.js';
|
||||||
BiDiBrowserTarget,
|
|
||||||
BiDiBrowsingContextTarget,
|
|
||||||
BiDiPageTarget,
|
|
||||||
type BidiTarget,
|
|
||||||
} from './Target.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -89,7 +82,6 @@ export class BidiBrowser extends Browser {
|
|||||||
|
|
||||||
const browser = new BidiBrowser(session.browser, opts);
|
const browser = new BidiBrowser(session.browser, opts);
|
||||||
browser.#initialize();
|
browser.#initialize();
|
||||||
await browser.#getTree();
|
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,20 +89,8 @@ export class BidiBrowser extends Browser {
|
|||||||
#closeCallback?: BrowserCloseCallback;
|
#closeCallback?: BrowserCloseCallback;
|
||||||
#browserCore: BrowserCore;
|
#browserCore: BrowserCore;
|
||||||
#defaultViewport: Viewport | null;
|
#defaultViewport: Viewport | null;
|
||||||
#targets = new Map<string, BidiTarget>();
|
|
||||||
#browserContexts = new WeakMap<UserContext, BidiBrowserContext>();
|
#browserContexts = new WeakMap<UserContext, BidiBrowserContext>();
|
||||||
#browserTarget: BiDiBrowserTarget;
|
#target = new BidiBrowserTarget(this);
|
||||||
|
|
||||||
#connectionEventHandlers = new Map<
|
|
||||||
Bidi.BrowsingContextEvent['method'],
|
|
||||||
Handler<any>
|
|
||||||
>([
|
|
||||||
['browsingContext.contextCreated', this.#onContextCreated.bind(this)],
|
|
||||||
['browsingContext.contextDestroyed', this.#onContextDestroyed.bind(this)],
|
|
||||||
['browsingContext.domContentLoaded', this.#onContextDomLoaded.bind(this)],
|
|
||||||
['browsingContext.fragmentNavigated', this.#onContextNavigation.bind(this)],
|
|
||||||
['browsingContext.navigationStarted', this.#onContextNavigation.bind(this)],
|
|
||||||
]);
|
|
||||||
|
|
||||||
private constructor(browserCore: BrowserCore, opts: BidiBrowserOptions) {
|
private constructor(browserCore: BrowserCore, opts: BidiBrowserOptions) {
|
||||||
super();
|
super();
|
||||||
@ -118,13 +98,14 @@ export class BidiBrowser extends Browser {
|
|||||||
this.#closeCallback = opts.closeCallback;
|
this.#closeCallback = opts.closeCallback;
|
||||||
this.#browserCore = browserCore;
|
this.#browserCore = browserCore;
|
||||||
this.#defaultViewport = opts.defaultViewport;
|
this.#defaultViewport = opts.defaultViewport;
|
||||||
this.#browserTarget = new BiDiBrowserTarget(this);
|
|
||||||
for (const context of this.#browserCore.userContexts) {
|
|
||||||
this.#createBrowserContext(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#initialize() {
|
#initialize() {
|
||||||
|
// Initializing existing contexts.
|
||||||
|
for (const userContext of this.#browserCore.userContexts) {
|
||||||
|
this.#createBrowserContext(userContext);
|
||||||
|
}
|
||||||
|
|
||||||
this.#browserCore.once('disconnected', () => {
|
this.#browserCore.once('disconnected', () => {
|
||||||
this.emit(BrowserEvent.Disconnected, undefined);
|
this.emit(BrowserEvent.Disconnected, undefined);
|
||||||
});
|
});
|
||||||
@ -132,10 +113,6 @@ export class BidiBrowser extends Browser {
|
|||||||
this.#browserCore.dispose('Browser process exited.', true);
|
this.#browserCore.dispose('Browser process exited.', true);
|
||||||
this.connection.dispose();
|
this.connection.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const [eventName, handler] of this.#connectionEventHandlers) {
|
|
||||||
this.connection.on(eventName, handler);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get #browserName() {
|
get #browserName() {
|
||||||
@ -145,85 +122,31 @@ export class BidiBrowser extends Browser {
|
|||||||
return this.#browserCore.session.capabilities.browserVersion;
|
return this.#browserCore.session.capabilities.browserVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get cdpSupported(): boolean {
|
||||||
|
return !this.#browserName.toLocaleLowerCase().includes('firefox');
|
||||||
|
}
|
||||||
|
|
||||||
override userAgent(): never {
|
override userAgent(): never {
|
||||||
throw new UnsupportedOperation();
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
#createBrowserContext(userContext: UserContext) {
|
#createBrowserContext(userContext: UserContext) {
|
||||||
const browserContext = new BidiBrowserContext(this, userContext, {
|
const browserContext = BidiBrowserContext.from(this, userContext, {
|
||||||
defaultViewport: this.#defaultViewport,
|
defaultViewport: this.#defaultViewport,
|
||||||
});
|
});
|
||||||
this.#browserContexts.set(userContext, browserContext);
|
this.#browserContexts.set(userContext, browserContext);
|
||||||
return browserContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
#onContextDomLoaded(event: Bidi.BrowsingContext.Info) {
|
|
||||||
const target = this.#targets.get(event.context);
|
|
||||||
if (target) {
|
|
||||||
this.emit(BrowserEvent.TargetChanged, target);
|
|
||||||
target.browserContext().emit(BrowserContextEvent.TargetChanged, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onContextNavigation(event: Bidi.BrowsingContext.NavigationInfo) {
|
|
||||||
const target = this.#targets.get(event.context);
|
|
||||||
if (target) {
|
|
||||||
this.emit(BrowserEvent.TargetChanged, target);
|
|
||||||
target.browserContext().emit(BrowserContextEvent.TargetChanged, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onContextCreated(event: Bidi.BrowsingContext.ContextCreated['params']) {
|
|
||||||
const context = new BrowsingContext(
|
|
||||||
this.connection,
|
|
||||||
event,
|
|
||||||
this.#browserName
|
|
||||||
);
|
|
||||||
this.connection.registerBrowsingContexts(context);
|
|
||||||
const browserContext =
|
|
||||||
event.userContext === UserContext.DEFAULT
|
|
||||||
? this.defaultBrowserContext()
|
|
||||||
: this.browserContexts().find(browserContext => {
|
|
||||||
return browserContext.id === event.userContext;
|
|
||||||
});
|
|
||||||
if (!browserContext) {
|
|
||||||
throw new Error('Missing browser contexts');
|
|
||||||
}
|
|
||||||
const target = !context.parent
|
|
||||||
? new BiDiPageTarget(browserContext, context)
|
|
||||||
: new BiDiBrowsingContextTarget(browserContext, context);
|
|
||||||
this.#targets.set(event.context, target);
|
|
||||||
|
|
||||||
|
browserContext.on(BrowserContextEvent.TargetCreated, target => {
|
||||||
this.emit(BrowserEvent.TargetCreated, target);
|
this.emit(BrowserEvent.TargetCreated, target);
|
||||||
target.browserContext().emit(BrowserContextEvent.TargetCreated, target);
|
});
|
||||||
|
browserContext.on(BrowserContextEvent.TargetChanged, target => {
|
||||||
if (context.parent) {
|
this.emit(BrowserEvent.TargetChanged, target);
|
||||||
const topLevel = this.connection.getTopLevelContext(context.parent);
|
});
|
||||||
topLevel.emit(BrowsingContextEvent.Created, context);
|
browserContext.on(BrowserContextEvent.TargetDestroyed, target => {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async #getTree(): Promise<void> {
|
|
||||||
const {result} = await this.connection.send('browsingContext.getTree', {});
|
|
||||||
for (const context of result.contexts) {
|
|
||||||
this.#onContextCreated(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async #onContextDestroyed(
|
|
||||||
event: Bidi.BrowsingContext.ContextDestroyed['params']
|
|
||||||
) {
|
|
||||||
const context = this.connection.getBrowsingContext(event.context);
|
|
||||||
const topLevelContext = this.connection.getTopLevelContext(event.context);
|
|
||||||
topLevelContext.emit(BrowsingContextEvent.Destroyed, context);
|
|
||||||
const target = this.#targets.get(event.context);
|
|
||||||
const page = await target?.page();
|
|
||||||
await page?.close().catch(debugError);
|
|
||||||
this.#targets.delete(event.context);
|
|
||||||
if (target) {
|
|
||||||
this.emit(BrowserEvent.TargetDestroyed, target);
|
this.emit(BrowserEvent.TargetDestroyed, target);
|
||||||
target.browserContext().emit(BrowserContextEvent.TargetDestroyed, target);
|
});
|
||||||
}
|
|
||||||
|
return browserContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
get connection(): BidiConnection {
|
get connection(): BidiConnection {
|
||||||
@ -236,9 +159,6 @@ export class BidiBrowser extends Browser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async close(): Promise<void> {
|
override async close(): Promise<void> {
|
||||||
for (const [eventName, handler] of this.#connectionEventHandlers) {
|
|
||||||
this.connection.off(eventName, handler);
|
|
||||||
}
|
|
||||||
if (this.connection.closed) {
|
if (this.connection.closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -255,7 +175,7 @@ export class BidiBrowser extends Browser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override get connected(): boolean {
|
override get connected(): boolean {
|
||||||
return !this.#browserCore.disposed;
|
return !this.#browserCore.disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
override process(): ChildProcess | null {
|
override process(): ChildProcess | null {
|
||||||
@ -288,19 +208,16 @@ export class BidiBrowser extends Browser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override targets(): Target[] {
|
override targets(): Target[] {
|
||||||
return [this.#browserTarget, ...Array.from(this.#targets.values())];
|
return [
|
||||||
|
this.#target,
|
||||||
|
...this.browserContexts().flatMap(context => {
|
||||||
|
return context.targets();
|
||||||
|
}),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTargetById(id: string): BidiTarget {
|
override target(): BidiBrowserTarget {
|
||||||
const target = this.#targets.get(id);
|
return this.#target;
|
||||||
if (!target) {
|
|
||||||
throw new Error('Target not found');
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
override target(): Target {
|
|
||||||
return this.#browserTarget;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async disconnect(): Promise<void> {
|
override async disconnect(): Promise<void> {
|
||||||
|
@ -6,17 +6,20 @@
|
|||||||
|
|
||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
import {BrowserContext} from '../api/BrowserContext.js';
|
import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js';
|
||||||
import type {Page} from '../api/Page.js';
|
import {PageEvent, type Page} from '../api/Page.js';
|
||||||
import type {Target} from '../api/Target.js';
|
import type {Target} from '../api/Target.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
import {debugError} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import type {Viewport} from '../common/Viewport.js';
|
import type {Viewport} from '../common/Viewport.js';
|
||||||
|
|
||||||
import type {BidiBrowser} from './Browser.js';
|
import type {BidiBrowser} from './Browser.js';
|
||||||
import type {BidiConnection} from './Connection.js';
|
import type {BrowsingContext} from './core/BrowsingContext.js';
|
||||||
import {UserContext} from './core/UserContext.js';
|
import {UserContext} from './core/UserContext.js';
|
||||||
import type {BidiPage} from './Page.js';
|
import type {BidiFrame} from './Frame.js';
|
||||||
|
import {BidiPage} from './Page.js';
|
||||||
|
import {BidiPageTarget} from './Target.js';
|
||||||
|
import {BidiFrameTarget} from './Target.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -29,10 +32,25 @@ export interface BidiBrowserContextOptions {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiBrowserContext extends BrowserContext {
|
export class BidiBrowserContext extends BrowserContext {
|
||||||
#browser: BidiBrowser;
|
static from(
|
||||||
#connection: BidiConnection;
|
browser: BidiBrowser,
|
||||||
#defaultViewport: Viewport | null;
|
userContext: UserContext,
|
||||||
#userContext: UserContext;
|
options: BidiBrowserContextOptions
|
||||||
|
): BidiBrowserContext {
|
||||||
|
const context = new BidiBrowserContext(browser, userContext, options);
|
||||||
|
context.#initialize();
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #browser: BidiBrowser;
|
||||||
|
readonly #defaultViewport: Viewport | null;
|
||||||
|
// This is public because of cookies.
|
||||||
|
readonly userContext: UserContext;
|
||||||
|
readonly #pages = new WeakMap<BrowsingContext, BidiPage>();
|
||||||
|
readonly #targets = new Map<
|
||||||
|
BidiPage,
|
||||||
|
[BidiPageTarget, Map<BidiFrame, BidiFrameTarget>]
|
||||||
|
>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
browser: BidiBrowser,
|
browser: BidiBrowser,
|
||||||
@ -41,36 +59,78 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.#browser = browser;
|
this.#browser = browser;
|
||||||
this.#userContext = userContext;
|
this.userContext = userContext;
|
||||||
this.#connection = this.#browser.connection;
|
|
||||||
this.#defaultViewport = options.defaultViewport;
|
this.#defaultViewport = options.defaultViewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
override targets(): Target[] {
|
#initialize() {
|
||||||
return this.#browser.targets().filter(target => {
|
// Create targets for existing browsing contexts.
|
||||||
return target.browserContext() === this;
|
for (const browsingContext of this.userContext.browsingContexts) {
|
||||||
|
this.#createPage(browsingContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userContext.on('browsingcontext', ({browsingContext}) => {
|
||||||
|
this.#createPage(browsingContext);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get connection(): BidiConnection {
|
#createPage(browsingContext: BrowsingContext): BidiPage {
|
||||||
return this.#connection;
|
const page = BidiPage.from(this, browsingContext);
|
||||||
|
this.#pages.set(browsingContext, page);
|
||||||
|
page.on(PageEvent.Close, () => {
|
||||||
|
this.#pages.delete(browsingContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- Target stuff starts here --
|
||||||
|
const pageTarget = new BidiPageTarget(page);
|
||||||
|
const frameTargets = new Map();
|
||||||
|
this.#targets.set(page, [pageTarget, frameTargets]);
|
||||||
|
page.on(PageEvent.FrameAttached, frame => {
|
||||||
|
const bidiFrame = frame as BidiFrame;
|
||||||
|
const target = new BidiFrameTarget(bidiFrame);
|
||||||
|
frameTargets.set(bidiFrame, target);
|
||||||
|
this.emit(BrowserContextEvent.TargetCreated, target);
|
||||||
|
});
|
||||||
|
page.on(PageEvent.FrameNavigated, frame => {
|
||||||
|
const bidiFrame = frame as BidiFrame;
|
||||||
|
const target = frameTargets.get(bidiFrame);
|
||||||
|
// If there is no target, then this is the page's frame.
|
||||||
|
if (target === undefined) {
|
||||||
|
this.emit(BrowserContextEvent.TargetChanged, pageTarget);
|
||||||
|
} else {
|
||||||
|
this.emit(BrowserContextEvent.TargetChanged, target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
page.on(PageEvent.FrameDetached, frame => {
|
||||||
|
const bidiFrame = frame as BidiFrame;
|
||||||
|
const target = frameTargets.get(bidiFrame);
|
||||||
|
if (target === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frameTargets.delete(bidiFrame);
|
||||||
|
this.emit(BrowserContextEvent.TargetDestroyed, target);
|
||||||
|
});
|
||||||
|
page.on(PageEvent.Close, () => {
|
||||||
|
this.#targets.delete(page);
|
||||||
|
this.emit(BrowserContextEvent.TargetDestroyed, pageTarget);
|
||||||
|
});
|
||||||
|
this.emit(BrowserContextEvent.TargetCreated, pageTarget);
|
||||||
|
// -- Target stuff ends here --
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
override targets(): Target[] {
|
||||||
|
return [...this.#targets.values()].flatMap(([target, frames]) => {
|
||||||
|
return [target, ...frames.values()];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override async newPage(): Promise<Page> {
|
override async newPage(): Promise<Page> {
|
||||||
const {result} = await this.#connection.send('browsingContext.create', {
|
const context = await this.userContext.createBrowsingContext(
|
||||||
type: Bidi.BrowsingContext.CreateType.Tab,
|
Bidi.BrowsingContext.CreateType.Tab
|
||||||
userContext: this.#userContext.id,
|
);
|
||||||
});
|
const page = this.#pages.get(context)!;
|
||||||
const target = this.#browser._getTargetById(result.context);
|
|
||||||
|
|
||||||
// TODO: once BiDi has some concept matching BrowserContext, the newly
|
|
||||||
// created contexts should get automatically assigned to the right
|
|
||||||
// BrowserContext. For now, we assume that only explicitly created pages go
|
|
||||||
// to the current BrowserContext. Otherwise, the contexts get assigned to
|
|
||||||
// the default BrowserContext by the Browser.
|
|
||||||
target._setBrowserContext(this);
|
|
||||||
|
|
||||||
const page = await target.page();
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
throw new Error('Page is not found');
|
throw new Error('Page is not found');
|
||||||
}
|
}
|
||||||
@ -91,7 +151,7 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.#userContext.remove();
|
await this.userContext.remove();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugError(error);
|
debugError(error);
|
||||||
}
|
}
|
||||||
@ -102,18 +162,13 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async pages(): Promise<BidiPage[]> {
|
override async pages(): Promise<BidiPage[]> {
|
||||||
const results = await Promise.all(
|
return [...this.userContext.browsingContexts].map(context => {
|
||||||
[...this.targets()].map(t => {
|
return this.#pages.get(context)!;
|
||||||
return t.page();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return results.filter((p): p is BidiPage => {
|
|
||||||
return p !== null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override isIncognito(): boolean {
|
override isIncognito(): boolean {
|
||||||
return this.#userContext.id !== UserContext.DEFAULT;
|
return this.userContext.id !== UserContext.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
override overridePermissions(): never {
|
override overridePermissions(): never {
|
||||||
@ -125,9 +180,9 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override get id(): string | undefined {
|
override get id(): string | undefined {
|
||||||
if (this.#userContext.id === UserContext.DEFAULT) {
|
if (this.userContext.id === UserContext.DEFAULT) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this.#userContext.id;
|
return this.userContext.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2024 Google Inc.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|
||||||
import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js';
|
|
||||||
|
|
||||||
import type {CDPSession} from '../api/CDPSession.js';
|
|
||||||
import type {EventType} from '../common/EventEmitter.js';
|
|
||||||
import {debugError} from '../common/util.js';
|
|
||||||
|
|
||||||
import {BidiCdpSession} from './CDPSession.js';
|
|
||||||
import type {BidiConnection} from './Connection.js';
|
|
||||||
import {BidiRealm} from './Realm.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal events that the BrowsingContext class emits.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
||||||
export namespace BrowsingContextEvent {
|
|
||||||
/**
|
|
||||||
* Emitted on the top-level context, when a descendant context is created.
|
|
||||||
*/
|
|
||||||
export const Created = Symbol('BrowsingContext.created');
|
|
||||||
/**
|
|
||||||
* Emitted on the top-level context, when a descendant context or the
|
|
||||||
* top-level context itself is destroyed.
|
|
||||||
*/
|
|
||||||
export const Destroyed = Symbol('BrowsingContext.destroyed');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export interface BrowsingContextEvents extends Record<EventType, unknown> {
|
|
||||||
[BrowsingContextEvent.Created]: BrowsingContext;
|
|
||||||
[BrowsingContextEvent.Destroyed]: BrowsingContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class BrowsingContext extends BidiRealm {
|
|
||||||
#id: string;
|
|
||||||
#url: string;
|
|
||||||
#cdpSession: CDPSession;
|
|
||||||
#parent?: string | null;
|
|
||||||
#browserName = '';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
connection: BidiConnection,
|
|
||||||
info: Bidi.BrowsingContext.Info,
|
|
||||||
browserName: string
|
|
||||||
) {
|
|
||||||
super(connection);
|
|
||||||
this.#id = info.context;
|
|
||||||
this.#url = info.url;
|
|
||||||
this.#parent = info.parent;
|
|
||||||
this.#browserName = browserName;
|
|
||||||
this.#cdpSession = new BidiCdpSession(this, undefined);
|
|
||||||
|
|
||||||
this.on('browsingContext.domContentLoaded', this.#updateUrl.bind(this));
|
|
||||||
this.on('browsingContext.fragmentNavigated', this.#updateUrl.bind(this));
|
|
||||||
this.on('browsingContext.load', this.#updateUrl.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsCdp(): boolean {
|
|
||||||
return !this.#browserName.toLowerCase().includes('firefox');
|
|
||||||
}
|
|
||||||
|
|
||||||
#updateUrl(info: Bidi.BrowsingContext.NavigationInfo) {
|
|
||||||
this.#url = info.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
createRealmForSandbox(): BidiRealm {
|
|
||||||
return new BidiRealm(this.connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
get url(): string {
|
|
||||||
return this.#url;
|
|
||||||
}
|
|
||||||
|
|
||||||
get id(): string {
|
|
||||||
return this.#id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get parent(): string | undefined | null {
|
|
||||||
return this.#parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
get cdpSession(): CDPSession {
|
|
||||||
return this.#cdpSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendCdpCommand<T extends keyof ProtocolMapping.Commands>(
|
|
||||||
method: T,
|
|
||||||
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
|
|
||||||
): Promise<ProtocolMapping.Commands[T]['returnType']> {
|
|
||||||
return await this.#cdpSession.send(method, ...paramArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.removeAllListeners();
|
|
||||||
this.connection.unregisterBrowsingContexts(this.#id);
|
|
||||||
void this.#cdpSession.detach().catch(debugError);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,6 @@
|
|||||||
* Copyright 2024 Google Inc.
|
* Copyright 2024 Google Inc.
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js';
|
import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js';
|
||||||
|
|
||||||
import {CDPSession} from '../api/CDPSession.js';
|
import {CDPSession} from '../api/CDPSession.js';
|
||||||
@ -11,44 +10,49 @@ import type {Connection as CdpConnection} from '../cdp/Connection.js';
|
|||||||
import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js';
|
import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js';
|
||||||
import {Deferred} from '../util/Deferred.js';
|
import {Deferred} from '../util/Deferred.js';
|
||||||
|
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
import type {BidiConnection} from './Connection.js';
|
||||||
|
import type {BidiFrame} from './Frame.js';
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const cdpSessions = new Map<string, BidiCdpSession>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiCdpSession extends CDPSession {
|
export class BidiCdpSession extends CDPSession {
|
||||||
#context: BrowsingContext;
|
static sessions = new Map<string, BidiCdpSession>();
|
||||||
#sessionId = Deferred.create<string>();
|
|
||||||
#detached = false;
|
|
||||||
|
|
||||||
constructor(context: BrowsingContext, sessionId?: string) {
|
#detached = false;
|
||||||
|
readonly #connection: BidiConnection | undefined = undefined;
|
||||||
|
readonly #sessionId = Deferred.create<string>();
|
||||||
|
readonly frame: BidiFrame;
|
||||||
|
|
||||||
|
constructor(frame: BidiFrame, sessionId?: string) {
|
||||||
super();
|
super();
|
||||||
this.#context = context;
|
this.frame = frame;
|
||||||
if (!this.#context.supportsCdp()) {
|
if (!this.frame.page().browser().cdpSupported) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connection = this.frame.page().browser().connection;
|
||||||
|
this.#connection = connection;
|
||||||
|
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
this.#sessionId.resolve(sessionId);
|
this.#sessionId.resolve(sessionId);
|
||||||
cdpSessions.set(sessionId, this);
|
BidiCdpSession.sessions.set(sessionId, this);
|
||||||
} else {
|
} else {
|
||||||
context.connection
|
(async () => {
|
||||||
.send('cdp.getSession', {
|
try {
|
||||||
context: context.id,
|
const session = await connection.send('cdp.getSession', {
|
||||||
})
|
context: frame._id,
|
||||||
.then(session => {
|
|
||||||
this.#sessionId.resolve(session.result.session!);
|
|
||||||
cdpSessions.set(session.result.session!, this);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.#sessionId.reject(err);
|
|
||||||
});
|
});
|
||||||
|
this.#sessionId.resolve(session.result.session!);
|
||||||
|
BidiCdpSession.sessions.set(session.result.session!, this);
|
||||||
|
} catch (error) {
|
||||||
|
this.#sessionId.reject(error as Error);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: We never throw #sessionId.
|
||||||
|
BidiCdpSession.sessions.set(this.#sessionId.value() as string, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
override connection(): CdpConnection | undefined {
|
override connection(): CdpConnection | undefined {
|
||||||
@ -59,7 +63,7 @@ export class BidiCdpSession extends CDPSession {
|
|||||||
method: T,
|
method: T,
|
||||||
params?: ProtocolMapping.Commands[T]['paramsType'][0]
|
params?: ProtocolMapping.Commands[T]['paramsType'][0]
|
||||||
): Promise<ProtocolMapping.Commands[T]['returnType']> {
|
): Promise<ProtocolMapping.Commands[T]['returnType']> {
|
||||||
if (!this.#context.supportsCdp()) {
|
if (this.#connection === undefined) {
|
||||||
throw new UnsupportedOperation(
|
throw new UnsupportedOperation(
|
||||||
'CDP support is required for this feature. The current browser does not support CDP.'
|
'CDP support is required for this feature. The current browser does not support CDP.'
|
||||||
);
|
);
|
||||||
@ -70,7 +74,7 @@ export class BidiCdpSession extends CDPSession {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const session = await this.#sessionId.valueOrThrow();
|
const session = await this.#sessionId.valueOrThrow();
|
||||||
const {result} = await this.#context.connection.send('cdp.sendCommand', {
|
const {result} = await this.#connection.send('cdp.sendCommand', {
|
||||||
method: method,
|
method: method,
|
||||||
params: params,
|
params: params,
|
||||||
session,
|
session,
|
||||||
@ -79,17 +83,21 @@ export class BidiCdpSession extends CDPSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async detach(): Promise<void> {
|
override async detach(): Promise<void> {
|
||||||
cdpSessions.delete(this.id());
|
if (this.#connection === undefined || this.#detached) {
|
||||||
if (!this.#detached && this.#context.supportsCdp()) {
|
return;
|
||||||
await this.#context.cdpSession.send('Target.detachFromTarget', {
|
}
|
||||||
|
try {
|
||||||
|
await this.frame.client.send('Target.detachFromTarget', {
|
||||||
sessionId: this.id(),
|
sessionId: this.id(),
|
||||||
});
|
});
|
||||||
}
|
} finally {
|
||||||
|
BidiCdpSession.sessions.delete(this.id());
|
||||||
this.#detached = true;
|
this.#detached = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override id(): string {
|
override id(): string {
|
||||||
const val = this.#sessionId.value();
|
const value = this.#sessionId.value();
|
||||||
return val instanceof Error || val === undefined ? '' : val;
|
return typeof value === 'string' ? value : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,10 @@ import {EventEmitter} from '../common/EventEmitter.js';
|
|||||||
import {debugError} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import {assert} from '../util/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
|
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
import {BidiCdpSession} from './CDPSession.js';
|
||||||
import {cdpSessions} from './CDPSession.js';
|
|
||||||
import type {
|
import type {
|
||||||
BidiEvents,
|
|
||||||
Commands as BidiCommands,
|
Commands as BidiCommands,
|
||||||
|
BidiEvents,
|
||||||
Connection,
|
Connection,
|
||||||
} from './core/Connection.js';
|
} from './core/Connection.js';
|
||||||
|
|
||||||
@ -52,7 +51,6 @@ export class BidiConnection
|
|||||||
#timeout? = 0;
|
#timeout? = 0;
|
||||||
#closed = false;
|
#closed = false;
|
||||||
#callbacks = new CallbackRegistry();
|
#callbacks = new CallbackRegistry();
|
||||||
#browsingContexts = new Map<string, BrowsingContext>();
|
|
||||||
#emitters: Array<EventEmitter<any>> = [];
|
#emitters: Array<EventEmitter<any>> = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -138,12 +136,11 @@ export class BidiConnection
|
|||||||
return;
|
return;
|
||||||
case 'event':
|
case 'event':
|
||||||
if (isCdpEvent(object)) {
|
if (isCdpEvent(object)) {
|
||||||
cdpSessions
|
BidiCdpSession.sessions
|
||||||
.get(object.params.session)
|
.get(object.params.session)
|
||||||
?.emit(object.params.event, object.params.params);
|
?.emit(object.params.event, object.params.params);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#maybeEmitOnContext(object);
|
|
||||||
// SAFETY: We know the method and parameter still match here.
|
// SAFETY: We know the method and parameter still match here.
|
||||||
this.emit(
|
this.emit(
|
||||||
object.method,
|
object.method,
|
||||||
@ -164,52 +161,6 @@ export class BidiConnection
|
|||||||
debugError(object);
|
debugError(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
#maybeEmitOnContext(event: Bidi.ChromiumBidi.Event) {
|
|
||||||
let context: BrowsingContext | undefined;
|
|
||||||
// Context specific events
|
|
||||||
if ('context' in event.params && event.params.context !== null) {
|
|
||||||
context = this.#browsingContexts.get(event.params.context);
|
|
||||||
// `log.entryAdded` specific context
|
|
||||||
} else if (
|
|
||||||
'source' in event.params &&
|
|
||||||
event.params.source.context !== undefined
|
|
||||||
) {
|
|
||||||
context = this.#browsingContexts.get(event.params.source.context);
|
|
||||||
}
|
|
||||||
context?.emit(event.method, event.params);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerBrowsingContexts(context: BrowsingContext): void {
|
|
||||||
this.#browsingContexts.set(context.id, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBrowsingContext(contextId: string): BrowsingContext {
|
|
||||||
const currentContext = this.#browsingContexts.get(contextId);
|
|
||||||
if (!currentContext) {
|
|
||||||
throw new Error(`BrowsingContext ${contextId} does not exist.`);
|
|
||||||
}
|
|
||||||
return currentContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTopLevelContext(contextId: string): BrowsingContext {
|
|
||||||
let currentContext = this.#browsingContexts.get(contextId);
|
|
||||||
if (!currentContext) {
|
|
||||||
throw new Error(`BrowsingContext ${contextId} does not exist.`);
|
|
||||||
}
|
|
||||||
while (currentContext.parent) {
|
|
||||||
contextId = currentContext.parent;
|
|
||||||
currentContext = this.#browsingContexts.get(contextId);
|
|
||||||
if (!currentContext) {
|
|
||||||
throw new Error(`BrowsingContext ${contextId} does not exist.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
unregisterBrowsingContexts(id: string): void {
|
|
||||||
this.#browsingContexts.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unbinds the connection, but keeps the transport open. Useful when the transport will
|
* Unbinds the connection, but keeps the transport open. Useful when the transport will
|
||||||
* be reused by other connection e.g. with different protocol.
|
* be reused by other connection e.g. with different protocol.
|
||||||
@ -224,7 +175,6 @@ export class BidiConnection
|
|||||||
this.#transport.onmessage = () => {};
|
this.#transport.onmessage = () => {};
|
||||||
this.#transport.onclose = () => {};
|
this.#transport.onclose = () => {};
|
||||||
|
|
||||||
this.#browsingContexts.clear();
|
|
||||||
this.#callbacks.clear();
|
this.#callbacks.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,40 +4,26 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|
||||||
|
|
||||||
import {Dialog} from '../api/Dialog.js';
|
import {Dialog} from '../api/Dialog.js';
|
||||||
|
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
import type {UserPrompt} from './core/UserPrompt.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class BidiDialog extends Dialog {
|
export class BidiDialog extends Dialog {
|
||||||
#context: BrowsingContext;
|
static from(prompt: UserPrompt): BidiDialog {
|
||||||
|
return new BidiDialog(prompt);
|
||||||
/**
|
}
|
||||||
* @internal
|
|
||||||
*/
|
#prompt: UserPrompt;
|
||||||
constructor(
|
private constructor(prompt: UserPrompt) {
|
||||||
context: BrowsingContext,
|
super(prompt.info.type, prompt.info.message, prompt.info.defaultValue);
|
||||||
type: Bidi.BrowsingContext.UserPromptOpenedParameters['type'],
|
this.#prompt = prompt;
|
||||||
message: string,
|
|
||||||
defaultValue?: string
|
|
||||||
) {
|
|
||||||
super(type, message, defaultValue);
|
|
||||||
this.#context = context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
override async handle(options: {
|
override async handle(options: {
|
||||||
accept: boolean;
|
accept: boolean;
|
||||||
text?: string;
|
text?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await this.#context.connection.send('browsingContext.handleUserPrompt', {
|
await this.#prompt.handle({
|
||||||
context: this.#context.id,
|
|
||||||
accept: options.accept,
|
accept: options.accept,
|
||||||
userText: options.text,
|
userText: options.text,
|
||||||
});
|
});
|
||||||
|
@ -6,14 +6,13 @@
|
|||||||
|
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
import {type AutofillData, ElementHandle} from '../api/ElementHandle.js';
|
import {ElementHandle, type AutofillData} from '../api/ElementHandle.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
import {throwIfDisposed} from '../util/decorators.js';
|
import {throwIfDisposed} from '../util/decorators.js';
|
||||||
|
|
||||||
import type {BidiFrame} from './Frame.js';
|
import type {BidiFrame} from './Frame.js';
|
||||||
import {BidiJSHandle} from './JSHandle.js';
|
import {BidiJSHandle} from './JSHandle.js';
|
||||||
import type {BidiRealm} from './Realm.js';
|
import type {BidiFrameRealm} from './Realm.js';
|
||||||
import type {Sandbox} from './Sandbox.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -21,28 +20,28 @@ import type {Sandbox} from './Sandbox.js';
|
|||||||
export class BidiElementHandle<
|
export class BidiElementHandle<
|
||||||
ElementType extends Node = Element,
|
ElementType extends Node = Element,
|
||||||
> extends ElementHandle<ElementType> {
|
> extends ElementHandle<ElementType> {
|
||||||
declare handle: BidiJSHandle<ElementType>;
|
static from<ElementType extends Node = Element>(
|
||||||
|
value: Bidi.Script.RemoteValue,
|
||||||
constructor(sandbox: Sandbox, remoteValue: Bidi.Script.RemoteValue) {
|
realm: BidiFrameRealm
|
||||||
super(new BidiJSHandle(sandbox, remoteValue));
|
): BidiElementHandle<ElementType> {
|
||||||
|
return new BidiElementHandle(value, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
override get realm(): Sandbox {
|
declare handle: BidiJSHandle<ElementType>;
|
||||||
return this.handle.realm;
|
|
||||||
|
constructor(value: Bidi.Script.RemoteValue, realm: BidiFrameRealm) {
|
||||||
|
super(BidiJSHandle.from(value, realm));
|
||||||
|
}
|
||||||
|
|
||||||
|
override get realm(): BidiFrameRealm {
|
||||||
|
// SAFETY: See the super call in the constructor.
|
||||||
|
return this.handle.realm as BidiFrameRealm;
|
||||||
}
|
}
|
||||||
|
|
||||||
override get frame(): BidiFrame {
|
override get frame(): BidiFrame {
|
||||||
return this.realm.environment;
|
return this.realm.environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
context(): BidiRealm {
|
|
||||||
return this.handle.context();
|
|
||||||
}
|
|
||||||
|
|
||||||
get isPrimitiveValue(): boolean {
|
|
||||||
return this.handle.isPrimitiveValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteValue(): Bidi.Script.RemoteValue {
|
remoteValue(): Bidi.Script.RemoteValue {
|
||||||
return this.handle.remoteValue();
|
return this.handle.remoteValue();
|
||||||
}
|
}
|
||||||
@ -76,7 +75,14 @@ export class BidiElementHandle<
|
|||||||
})) as BidiJSHandle;
|
})) as BidiJSHandle;
|
||||||
const value = handle.remoteValue();
|
const value = handle.remoteValue();
|
||||||
if (value.type === 'window') {
|
if (value.type === 'window') {
|
||||||
return this.frame.page().frame(value.value.context);
|
return (
|
||||||
|
this.frame
|
||||||
|
.page()
|
||||||
|
.frames()
|
||||||
|
.find(frame => {
|
||||||
|
return frame._id === value.value.context;
|
||||||
|
}) ?? null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2023 Google Inc.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
import type {Viewport} from '../common/Viewport.js';
|
|
||||||
|
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class EmulationManager {
|
|
||||||
#browsingContext: BrowsingContext;
|
|
||||||
|
|
||||||
constructor(browsingContext: BrowsingContext) {
|
|
||||||
this.#browsingContext = browsingContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
async emulateViewport(viewport: Viewport): Promise<void> {
|
|
||||||
await this.#browsingContext.connection.send('browsingContext.setViewport', {
|
|
||||||
context: this.#browsingContext.id,
|
|
||||||
viewport:
|
|
||||||
viewport.width && viewport.height
|
|
||||||
? {
|
|
||||||
width: viewport.width,
|
|
||||||
height: viewport.height,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
devicePixelRatio: viewport.deviceScaleFactor
|
|
||||||
? viewport.deviceScaleFactor
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ import {assert} from '../util/assert.js';
|
|||||||
import {Deferred} from '../util/Deferred.js';
|
import {Deferred} from '../util/Deferred.js';
|
||||||
import {interpolateFunction, stringifyFunction} from '../util/Function.js';
|
import {interpolateFunction, stringifyFunction} from '../util/Function.js';
|
||||||
|
|
||||||
import type {BidiConnection} from './Connection.js';
|
import type {Connection} from './core/Connection.js';
|
||||||
import {BidiDeserializer} from './Deserializer.js';
|
import {BidiDeserializer} from './Deserializer.js';
|
||||||
import type {BidiFrame} from './Frame.js';
|
import type {BidiFrame} from './Frame.js';
|
||||||
import {BidiSerializer} from './Serializer.js';
|
import {BidiSerializer} from './Serializer.js';
|
||||||
@ -207,8 +207,8 @@ export class ExposeableFunction<Args extends unknown[], Ret> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get #connection(): BidiConnection {
|
get #connection(): Connection {
|
||||||
return this.#frame.context().connection;
|
return this.#frame.page().browser().connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
get #channelArguments() {
|
get #channelArguments() {
|
||||||
|
@ -4,17 +4,17 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
|
import type {Observable} from '../../third_party/rxjs/rxjs.js';
|
||||||
import {
|
import {
|
||||||
|
combineLatest,
|
||||||
first,
|
first,
|
||||||
firstValueFrom,
|
firstValueFrom,
|
||||||
forkJoin,
|
|
||||||
from,
|
|
||||||
map,
|
map,
|
||||||
merge,
|
of,
|
||||||
raceWith,
|
raceWith,
|
||||||
zip,
|
switchMap,
|
||||||
} from '../../third_party/rxjs/rxjs.js';
|
} from '../../third_party/rxjs/rxjs.js';
|
||||||
import type {CDPSession} from '../api/CDPSession.js';
|
import type {CDPSession} from '../api/CDPSession.js';
|
||||||
import type {ElementHandle} from '../api/ElementHandle.js';
|
import type {ElementHandle} from '../api/ElementHandle.js';
|
||||||
@ -25,85 +25,205 @@ import {
|
|||||||
type WaitForOptions,
|
type WaitForOptions,
|
||||||
} from '../api/Frame.js';
|
} from '../api/Frame.js';
|
||||||
import type {WaitForSelectorOptions} from '../api/Page.js';
|
import type {WaitForSelectorOptions} from '../api/Page.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {PageEvent} from '../api/Page.js';
|
||||||
|
import {
|
||||||
|
ConsoleMessage,
|
||||||
|
type ConsoleMessageLocation,
|
||||||
|
} from '../common/ConsoleMessage.js';
|
||||||
|
import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js';
|
||||||
import type {TimeoutSettings} from '../common/TimeoutSettings.js';
|
import type {TimeoutSettings} from '../common/TimeoutSettings.js';
|
||||||
import type {Awaitable, NodeFor} from '../common/types.js';
|
import type {Awaitable, NodeFor} from '../common/types.js';
|
||||||
import {
|
import {debugError, fromEmitterEvent, timeout} from '../common/util.js';
|
||||||
fromEmitterEvent,
|
|
||||||
NETWORK_IDLE_TIME,
|
|
||||||
timeout,
|
|
||||||
UTILITY_WORLD_NAME,
|
|
||||||
} from '../common/util.js';
|
|
||||||
import {Deferred} from '../util/Deferred.js';
|
|
||||||
import {disposeSymbol} from '../util/disposable.js';
|
|
||||||
|
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
import {BidiCdpSession} from './CDPSession.js';
|
||||||
|
import type {BrowsingContext} from './core/BrowsingContext.js';
|
||||||
|
import {BidiDeserializer} from './Deserializer.js';
|
||||||
|
import {BidiDialog} from './Dialog.js';
|
||||||
import {ExposeableFunction} from './ExposedFunction.js';
|
import {ExposeableFunction} from './ExposedFunction.js';
|
||||||
|
import {BidiHTTPRequest, requests} from './HTTPRequest.js';
|
||||||
import type {BidiHTTPResponse} from './HTTPResponse.js';
|
import type {BidiHTTPResponse} from './HTTPResponse.js';
|
||||||
import {
|
import {BidiJSHandle} from './JSHandle.js';
|
||||||
getBiDiLifecycleEvent,
|
|
||||||
getBiDiReadinessState,
|
|
||||||
rewriteNavigationError,
|
|
||||||
} from './lifecycle.js';
|
|
||||||
import type {BidiPage} from './Page.js';
|
import type {BidiPage} from './Page.js';
|
||||||
import {
|
import type {BidiRealm} from './Realm.js';
|
||||||
MAIN_SANDBOX,
|
import {BidiFrameRealm} from './Realm.js';
|
||||||
PUPPETEER_SANDBOX,
|
import {rewriteNavigationError} from './util.js';
|
||||||
Sandbox,
|
|
||||||
type SandboxChart,
|
|
||||||
} from './Sandbox.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puppeteer's Frame class could be viewed as a BiDi BrowsingContext implementation
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class BidiFrame extends Frame {
|
export class BidiFrame extends Frame {
|
||||||
#page: BidiPage;
|
static from(
|
||||||
#context: BrowsingContext;
|
parent: BidiPage | BidiFrame,
|
||||||
#timeoutSettings: TimeoutSettings;
|
browsingContext: BrowsingContext
|
||||||
#abortDeferred = Deferred.create<never>();
|
): BidiFrame {
|
||||||
#disposed = false;
|
const frame = new BidiFrame(parent, browsingContext);
|
||||||
sandboxes: SandboxChart;
|
frame.#initialize();
|
||||||
override _id: string;
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
readonly #parent: BidiPage | BidiFrame;
|
||||||
page: BidiPage,
|
readonly browsingContext: BrowsingContext;
|
||||||
context: BrowsingContext,
|
readonly #frames = new WeakMap<BrowsingContext, BidiFrame>();
|
||||||
timeoutSettings: TimeoutSettings,
|
readonly realms: {default: BidiFrameRealm; internal: BidiFrameRealm};
|
||||||
parentId?: string | null
|
|
||||||
|
override readonly _id: string;
|
||||||
|
override readonly client: BidiCdpSession;
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
parent: BidiPage | BidiFrame,
|
||||||
|
browsingContext: BrowsingContext
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.#page = page;
|
this.#parent = parent;
|
||||||
this.#context = context;
|
this.browsingContext = browsingContext;
|
||||||
this.#timeoutSettings = timeoutSettings;
|
|
||||||
this._id = this.#context.id;
|
|
||||||
this._parentId = parentId ?? undefined;
|
|
||||||
|
|
||||||
this.sandboxes = {
|
this._id = browsingContext.id;
|
||||||
[MAIN_SANDBOX]: new Sandbox(undefined, this, context, timeoutSettings),
|
this.client = new BidiCdpSession(this);
|
||||||
[PUPPETEER_SANDBOX]: new Sandbox(
|
this.realms = {
|
||||||
UTILITY_WORLD_NAME,
|
default: BidiFrameRealm.from(this.browsingContext.defaultRealm, this),
|
||||||
this,
|
internal: BidiFrameRealm.from(
|
||||||
context.createRealmForSandbox(),
|
this.browsingContext.createWindowRealm(
|
||||||
timeoutSettings
|
`__puppeteer_internal_${Math.ceil(Math.random() * 10000)}`
|
||||||
|
),
|
||||||
|
this
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
override get client(): CDPSession {
|
#initialize(): void {
|
||||||
return this.context().cdpSession;
|
for (const browsingContext of this.browsingContext.children) {
|
||||||
|
this.#createFrameTarget(browsingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
override mainRealm(): Sandbox {
|
this.browsingContext.on('browsingcontext', ({browsingContext}) => {
|
||||||
return this.sandboxes[MAIN_SANDBOX];
|
this.#createFrameTarget(browsingContext);
|
||||||
|
});
|
||||||
|
this.browsingContext.on('closed', () => {
|
||||||
|
for (const session of BidiCdpSession.sessions.values()) {
|
||||||
|
if (session.frame === this) {
|
||||||
|
void session.detach().catch(debugError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.page().emit(PageEvent.FrameDetached, this);
|
||||||
|
this.removeAllListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.browsingContext.on('request', ({request}) => {
|
||||||
|
const httpRequest = BidiHTTPRequest.from(request, this);
|
||||||
|
request.once('success', () => {
|
||||||
|
// SAFETY: BidiHTTPRequest will create this before here.
|
||||||
|
this.page().emit(PageEvent.RequestFinished, httpRequest);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.once('error', () => {
|
||||||
|
this.page().emit(PageEvent.RequestFailed, httpRequest);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.browsingContext.on('navigation', ({navigation}) => {
|
||||||
|
navigation.once('fragment', () => {
|
||||||
|
this.page().emit(PageEvent.FrameNavigated, this);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.browsingContext.on('load', () => {
|
||||||
|
this.page().emit(PageEvent.Load, undefined);
|
||||||
|
});
|
||||||
|
this.browsingContext.on('DOMContentLoaded', () => {
|
||||||
|
this._hasStartedLoading = true;
|
||||||
|
this.page().emit(PageEvent.DOMContentLoaded, undefined);
|
||||||
|
this.page().emit(PageEvent.FrameNavigated, this);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.browsingContext.on('userprompt', ({userPrompt}) => {
|
||||||
|
this.page().emit(PageEvent.Dialog, BidiDialog.from(userPrompt));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.browsingContext.on('log', ({entry}) => {
|
||||||
|
if (this._id !== entry.source.context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isConsoleLogEntry(entry)) {
|
||||||
|
const args = entry.args.map(arg => {
|
||||||
|
return this.mainRealm().createHandle(arg);
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = args
|
||||||
|
.reduce((value, arg) => {
|
||||||
|
const parsedValue =
|
||||||
|
arg instanceof BidiJSHandle && arg.isPrimitiveValue
|
||||||
|
? BidiDeserializer.deserialize(arg.remoteValue())
|
||||||
|
: arg.toString();
|
||||||
|
return `${value} ${parsedValue}`;
|
||||||
|
}, '')
|
||||||
|
.slice(1);
|
||||||
|
|
||||||
|
this.page().emit(
|
||||||
|
PageEvent.Console,
|
||||||
|
new ConsoleMessage(
|
||||||
|
entry.method as any,
|
||||||
|
text,
|
||||||
|
args,
|
||||||
|
getStackTraceLocations(entry.stackTrace)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (isJavaScriptLogEntry(entry)) {
|
||||||
|
const error = new Error(entry.text ?? '');
|
||||||
|
|
||||||
|
const messageHeight = error.message.split('\n').length;
|
||||||
|
const messageLines = error.stack!.split('\n').splice(0, messageHeight);
|
||||||
|
|
||||||
|
const stackLines = [];
|
||||||
|
if (entry.stackTrace) {
|
||||||
|
for (const frame of entry.stackTrace.callFrames) {
|
||||||
|
// Note we need to add `1` because the values are 0-indexed.
|
||||||
|
stackLines.push(
|
||||||
|
` at ${frame.functionName || '<anonymous>'} (${frame.url}:${
|
||||||
|
frame.lineNumber + 1
|
||||||
|
}:${frame.columnNumber + 1})`
|
||||||
|
);
|
||||||
|
if (stackLines.length >= Error.stackTraceLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override isolatedRealm(): Sandbox {
|
error.stack = [...messageLines, ...stackLines].join('\n');
|
||||||
return this.sandboxes[PUPPETEER_SANDBOX];
|
this.page().emit(PageEvent.PageError, error);
|
||||||
|
} else {
|
||||||
|
debugError(
|
||||||
|
`Unhandled LogEntry with type "${entry.type}", text "${entry.text}" and level "${entry.level}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#createFrameTarget(browsingContext: BrowsingContext) {
|
||||||
|
const frame = BidiFrame.from(this, browsingContext);
|
||||||
|
this.#frames.set(browsingContext, frame);
|
||||||
|
this.page().emit(PageEvent.FrameAttached, frame);
|
||||||
|
|
||||||
|
browsingContext.on('closed', () => {
|
||||||
|
this.#frames.delete(browsingContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
get timeoutSettings(): TimeoutSettings {
|
||||||
|
return this.page()._timeoutSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
override mainRealm(): BidiRealm {
|
||||||
|
return this.realms.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
override isolatedRealm(): BidiRealm {
|
||||||
|
return this.realms.internal;
|
||||||
}
|
}
|
||||||
|
|
||||||
override page(): BidiPage {
|
override page(): BidiPage {
|
||||||
return this.#page;
|
let parent = this.#parent;
|
||||||
|
while (parent instanceof BidiFrame) {
|
||||||
|
parent = parent.#parent;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
override isOOPFrame(): never {
|
override isOOPFrame(): never {
|
||||||
@ -111,15 +231,20 @@ export class BidiFrame extends Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override url(): string {
|
override url(): string {
|
||||||
return this.#context.url;
|
return this.browsingContext.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
override parentFrame(): BidiFrame | null {
|
override parentFrame(): BidiFrame | null {
|
||||||
return this.#page.frame(this._parentId ?? '');
|
if (this.#parent instanceof BidiFrame) {
|
||||||
|
return this.#parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
override childFrames(): BidiFrame[] {
|
override childFrames(): BidiFrame[] {
|
||||||
return this.#page.childFrames(this.#context.id);
|
return [...this.browsingContext.children].map(child => {
|
||||||
|
return this.#frames.get(child)!;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@throwIfDetached
|
@throwIfDetached
|
||||||
@ -127,40 +252,16 @@ export class BidiFrame extends Frame {
|
|||||||
url: string,
|
url: string,
|
||||||
options: GoToOptions = {}
|
options: GoToOptions = {}
|
||||||
): Promise<BidiHTTPResponse | null> {
|
): Promise<BidiHTTPResponse | null> {
|
||||||
const {
|
const [response] = await Promise.all([
|
||||||
waitUntil = 'load',
|
this.waitForNavigation(options),
|
||||||
timeout: ms = this.#timeoutSettings.navigationTimeout(),
|
this.browsingContext.navigate(url),
|
||||||
} = options;
|
]).catch(
|
||||||
|
rewriteNavigationError(
|
||||||
const [readiness, networkIdle] = getBiDiReadinessState(waitUntil);
|
|
||||||
|
|
||||||
const result$ = zip(
|
|
||||||
from(
|
|
||||||
this.#context.connection.send('browsingContext.navigate', {
|
|
||||||
context: this.#context.id,
|
|
||||||
url,
|
url,
|
||||||
wait: readiness,
|
options.timeout ?? this.timeoutSettings.navigationTimeout()
|
||||||
})
|
)
|
||||||
),
|
|
||||||
...(networkIdle !== null
|
|
||||||
? [
|
|
||||||
this.#page.waitForNetworkIdle$({
|
|
||||||
timeout: ms,
|
|
||||||
concurrency: networkIdle === 'networkidle2' ? 2 : 0,
|
|
||||||
idleTime: NETWORK_IDLE_TIME,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
).pipe(
|
|
||||||
map(([{result}]) => {
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow())),
|
|
||||||
rewriteNavigationError(url, ms)
|
|
||||||
);
|
);
|
||||||
|
return response;
|
||||||
const result = await firstValueFrom(result$);
|
|
||||||
return this.#page.getNavigationResponse(result.navigation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@throwIfDetached
|
@throwIfDetached
|
||||||
@ -168,95 +269,58 @@ export class BidiFrame extends Frame {
|
|||||||
html: string,
|
html: string,
|
||||||
options: WaitForOptions = {}
|
options: WaitForOptions = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const {
|
await Promise.all([
|
||||||
waitUntil = 'load',
|
this.setFrameContent(html),
|
||||||
timeout: ms = this.#timeoutSettings.navigationTimeout(),
|
firstValueFrom(
|
||||||
} = options;
|
combineLatest([
|
||||||
|
this.#waitForLoad$(options),
|
||||||
const [waitEvent, networkIdle] = getBiDiLifecycleEvent(waitUntil);
|
this.#waitForNetworkIdle$(options),
|
||||||
|
])
|
||||||
const result$ = zip(
|
|
||||||
forkJoin([
|
|
||||||
fromEmitterEvent(this.#context, waitEvent).pipe(first()),
|
|
||||||
from(this.setFrameContent(html)),
|
|
||||||
]).pipe(
|
|
||||||
map(() => {
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
),
|
),
|
||||||
...(networkIdle !== null
|
]);
|
||||||
? [
|
|
||||||
this.#page.waitForNetworkIdle$({
|
|
||||||
timeout: ms,
|
|
||||||
concurrency: networkIdle === 'networkidle2' ? 2 : 0,
|
|
||||||
idleTime: NETWORK_IDLE_TIME,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
).pipe(
|
|
||||||
raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow())),
|
|
||||||
rewriteNavigationError('setContent', ms)
|
|
||||||
);
|
|
||||||
|
|
||||||
await firstValueFrom(result$);
|
|
||||||
}
|
|
||||||
|
|
||||||
context(): BrowsingContext {
|
|
||||||
return this.#context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@throwIfDetached
|
@throwIfDetached
|
||||||
override async waitForNavigation(
|
override async waitForNavigation(
|
||||||
options: WaitForOptions = {}
|
options: WaitForOptions = {}
|
||||||
): Promise<BidiHTTPResponse | null> {
|
): Promise<BidiHTTPResponse | null> {
|
||||||
const {
|
const {timeout: ms = this.timeoutSettings.navigationTimeout()} = options;
|
||||||
waitUntil = 'load',
|
|
||||||
timeout: ms = this.#timeoutSettings.navigationTimeout(),
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const [waitUntilEvent, networkIdle] = getBiDiLifecycleEvent(waitUntil);
|
return await firstValueFrom(
|
||||||
|
combineLatest([
|
||||||
const navigation$ = merge(
|
fromEmitterEvent(this.browsingContext, 'navigation').pipe(
|
||||||
forkJoin([
|
switchMap(({navigation}) => {
|
||||||
fromEmitterEvent(
|
return this.#waitForLoad$(options).pipe(
|
||||||
this.#context,
|
raceWith(fromEmitterEvent(navigation, 'fragment')),
|
||||||
Bidi.ChromiumBidi.BrowsingContext.EventNames.NavigationStarted
|
map(() => {
|
||||||
).pipe(first()),
|
return navigation;
|
||||||
fromEmitterEvent(this.#context, waitUntilEvent).pipe(first()),
|
|
||||||
]),
|
|
||||||
fromEmitterEvent(
|
|
||||||
this.#context,
|
|
||||||
Bidi.ChromiumBidi.BrowsingContext.EventNames.FragmentNavigated
|
|
||||||
)
|
|
||||||
).pipe(
|
|
||||||
map(result => {
|
|
||||||
if (Array.isArray(result)) {
|
|
||||||
return {result: result[1]};
|
|
||||||
}
|
|
||||||
return {result};
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
})
|
||||||
const result$ = zip(
|
),
|
||||||
navigation$,
|
this.#waitForNetworkIdle$(options),
|
||||||
...(networkIdle !== null
|
]).pipe(
|
||||||
? [
|
map(([navigation]) => {
|
||||||
this.#page.waitForNetworkIdle$({
|
const request = navigation.request;
|
||||||
timeout: ms,
|
if (!request) {
|
||||||
concurrency: networkIdle === 'networkidle2' ? 2 : 0,
|
return null;
|
||||||
idleTime: NETWORK_IDLE_TIME,
|
}
|
||||||
|
const httpRequest = requests.get(request)!;
|
||||||
|
const lastRedirect = httpRequest.redirectChain().at(-1);
|
||||||
|
return (
|
||||||
|
lastRedirect !== undefined ? lastRedirect : httpRequest
|
||||||
|
).response();
|
||||||
}),
|
}),
|
||||||
]
|
raceWith(
|
||||||
: [])
|
timeout(ms),
|
||||||
).pipe(
|
fromEmitterEvent(this.browsingContext, 'closed').pipe(
|
||||||
map(([{result}]) => {
|
map(() => {
|
||||||
return result;
|
throw new TargetCloseError('Frame detached.');
|
||||||
}),
|
})
|
||||||
raceWith(timeout(ms), from(this.#abortDeferred.valueOrThrow()))
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await firstValueFrom(result$);
|
|
||||||
return this.#page.getNavigationResponse(result.navigation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override waitForDevicePrompt(): never {
|
override waitForDevicePrompt(): never {
|
||||||
@ -264,18 +328,7 @@ export class BidiFrame extends Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override get detached(): boolean {
|
override get detached(): boolean {
|
||||||
return this.#disposed;
|
return this.browsingContext.closed;
|
||||||
}
|
|
||||||
|
|
||||||
[disposeSymbol](): void {
|
|
||||||
if (this.#disposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#disposed = true;
|
|
||||||
this.#abortDeferred.reject(new Error('Frame detached'));
|
|
||||||
this.#context.dispose();
|
|
||||||
this.sandboxes[MAIN_SANDBOX][disposeSymbol]();
|
|
||||||
this.sandboxes[PUPPETEER_SANDBOX][disposeSymbol]();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#exposedFunctions = new Map<string, ExposeableFunction<never[], unknown>>();
|
#exposedFunctions = new Map<string, ExposeableFunction<never[], unknown>>();
|
||||||
@ -310,4 +363,115 @@ export class BidiFrame extends Frame {
|
|||||||
|
|
||||||
return super.waitForSelector(selector, options);
|
return super.waitForSelector(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createCDPSession(): Promise<CDPSession> {
|
||||||
|
const {sessionId} = await this.client.send('Target.attachToTarget', {
|
||||||
|
targetId: this._id,
|
||||||
|
flatten: true,
|
||||||
|
});
|
||||||
|
return new BidiCdpSession(this, sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@throwIfDetached
|
||||||
|
#waitForLoad$(options: WaitForOptions = {}): Observable<void> {
|
||||||
|
let {waitUntil = 'load'} = options;
|
||||||
|
const {timeout: ms = this.timeoutSettings.navigationTimeout()} = options;
|
||||||
|
|
||||||
|
if (!Array.isArray(waitUntil)) {
|
||||||
|
waitUntil = [waitUntil];
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = new Set<'load' | 'DOMContentLoaded'>();
|
||||||
|
for (const lifecycleEvent of waitUntil) {
|
||||||
|
switch (lifecycleEvent) {
|
||||||
|
case 'load': {
|
||||||
|
events.add('load');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'domcontentloaded': {
|
||||||
|
events.add('DOMContentLoaded');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (events.size === 0) {
|
||||||
|
return of(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineLatest(
|
||||||
|
[...events].map(event => {
|
||||||
|
return fromEmitterEvent(this.browsingContext, event);
|
||||||
|
})
|
||||||
|
).pipe(
|
||||||
|
map(() => {}),
|
||||||
|
first(),
|
||||||
|
raceWith(
|
||||||
|
timeout(ms),
|
||||||
|
fromEmitterEvent(this.browsingContext, 'closed').pipe(
|
||||||
|
map(() => {
|
||||||
|
throw new Error('Frame detached.');
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@throwIfDetached
|
||||||
|
#waitForNetworkIdle$(options: WaitForOptions = {}): Observable<void> {
|
||||||
|
let {waitUntil = 'load'} = options;
|
||||||
|
if (!Array.isArray(waitUntil)) {
|
||||||
|
waitUntil = [waitUntil];
|
||||||
|
}
|
||||||
|
|
||||||
|
let concurrency = Infinity;
|
||||||
|
for (const event of waitUntil) {
|
||||||
|
switch (event) {
|
||||||
|
case 'networkidle0': {
|
||||||
|
concurrency = Math.min(0, concurrency);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'networkidle2': {
|
||||||
|
concurrency = Math.min(2, concurrency);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (concurrency === Infinity) {
|
||||||
|
return of(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.page().waitForNetworkIdle$({
|
||||||
|
idleTime: 500,
|
||||||
|
timeout: options.timeout ?? this.timeoutSettings.timeout(),
|
||||||
|
concurrency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConsoleLogEntry(
|
||||||
|
event: Bidi.Log.Entry
|
||||||
|
): event is Bidi.Log.ConsoleLogEntry {
|
||||||
|
return event.type === 'console';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isJavaScriptLogEntry(
|
||||||
|
event: Bidi.Log.Entry
|
||||||
|
): event is Bidi.Log.JavascriptLogEntry {
|
||||||
|
return event.type === 'javascript';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStackTraceLocations(
|
||||||
|
stackTrace?: Bidi.Script.StackTrace
|
||||||
|
): ConsoleMessageLocation[] {
|
||||||
|
const stackTraceLocations: ConsoleMessageLocation[] = [];
|
||||||
|
if (stackTrace) {
|
||||||
|
for (const callFrame of stackTrace.callFrames) {
|
||||||
|
stackTraceLocations.push({
|
||||||
|
url: callFrame.url,
|
||||||
|
lineNumber: callFrame.lineNumber,
|
||||||
|
columnNumber: callFrame.columnNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stackTraceLocations;
|
||||||
}
|
}
|
||||||
|
@ -5,107 +5,126 @@
|
|||||||
*/
|
*/
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
import type {Frame} from '../api/Frame.js';
|
import type {CDPSession} from '../api/CDPSession.js';
|
||||||
import type {
|
import type {
|
||||||
ContinueRequestOverrides,
|
ContinueRequestOverrides,
|
||||||
ResponseForRequest,
|
ResponseForRequest,
|
||||||
} from '../api/HTTPRequest.js';
|
} from '../api/HTTPRequest.js';
|
||||||
import {HTTPRequest, type ResourceType} from '../api/HTTPRequest.js';
|
import {HTTPRequest, type ResourceType} from '../api/HTTPRequest.js';
|
||||||
|
import {PageEvent} from '../api/Page.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
|
|
||||||
import type {BidiHTTPResponse} from './HTTPResponse.js';
|
import type {Request} from './core/Request.js';
|
||||||
|
import type {BidiFrame} from './Frame.js';
|
||||||
|
import {BidiHTTPResponse} from './HTTPResponse.js';
|
||||||
|
|
||||||
|
export const requests = new WeakMap<Request, BidiHTTPRequest>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiHTTPRequest extends HTTPRequest {
|
export class BidiHTTPRequest extends HTTPRequest {
|
||||||
override id: string;
|
static from(
|
||||||
override _response: BidiHTTPResponse | null = null;
|
bidiRequest: Request,
|
||||||
override _redirectChain: BidiHTTPRequest[];
|
frame: BidiFrame | undefined
|
||||||
_navigationId: string | null;
|
): BidiHTTPRequest {
|
||||||
|
const request = new BidiHTTPRequest(bidiRequest, frame);
|
||||||
|
request.#initialize();
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
#url: string;
|
#redirect: BidiHTTPRequest | undefined;
|
||||||
#resourceType: ResourceType;
|
#response: BidiHTTPResponse | null = null;
|
||||||
|
override readonly id: string;
|
||||||
|
readonly #frame: BidiFrame | undefined;
|
||||||
|
readonly #request: Request;
|
||||||
|
|
||||||
#method: string;
|
private constructor(request: Request, frame: BidiFrame | undefined) {
|
||||||
#postData?: string;
|
|
||||||
#headers: Record<string, string> = {};
|
|
||||||
#initiator: Bidi.Network.Initiator;
|
|
||||||
#frame: Frame | null;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
event: Bidi.Network.BeforeRequestSentParameters,
|
|
||||||
frame: Frame | null,
|
|
||||||
redirectChain: BidiHTTPRequest[] = []
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
|
requests.set(request, this);
|
||||||
|
|
||||||
this.#url = event.request.url;
|
this.#request = request;
|
||||||
this.#resourceType = event.initiator.type.toLowerCase() as ResourceType;
|
|
||||||
this.#method = event.request.method;
|
|
||||||
this.#postData = undefined;
|
|
||||||
this.#initiator = event.initiator;
|
|
||||||
this.#frame = frame;
|
this.#frame = frame;
|
||||||
|
this.id = request.id;
|
||||||
this.id = event.request.request;
|
|
||||||
this._redirectChain = redirectChain;
|
|
||||||
this._navigationId = event.navigation;
|
|
||||||
|
|
||||||
for (const header of event.request.headers) {
|
|
||||||
// TODO: How to handle Binary Headers
|
|
||||||
// https://w3c.github.io/webdriver-bidi/#type-network-Header
|
|
||||||
if (header.value.type === 'string') {
|
|
||||||
this.#headers[header.name.toLowerCase()] = header.value.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override get client(): never {
|
override get client(): CDPSession {
|
||||||
throw new UnsupportedOperation();
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#initialize() {
|
||||||
|
this.#request.on('redirect', request => {
|
||||||
|
this.#redirect = BidiHTTPRequest.from(request, this.#frame);
|
||||||
|
});
|
||||||
|
this.#request.once('success', data => {
|
||||||
|
this.#response = BidiHTTPResponse.from(data, this);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#frame?.page().emit(PageEvent.Request, this);
|
||||||
|
}
|
||||||
|
|
||||||
override url(): string {
|
override url(): string {
|
||||||
return this.#url;
|
return this.#request.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
override resourceType(): ResourceType {
|
override resourceType(): ResourceType {
|
||||||
return this.#resourceType;
|
return this.initiator().type.toLowerCase() as ResourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
override method(): string {
|
override method(): string {
|
||||||
return this.#method;
|
return this.#request.method;
|
||||||
}
|
}
|
||||||
|
|
||||||
override postData(): string | undefined {
|
override postData(): string | undefined {
|
||||||
return this.#postData;
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
override hasPostData(): boolean {
|
override hasPostData(): boolean {
|
||||||
return this.#postData !== undefined;
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
override async fetchPostData(): Promise<string | undefined> {
|
override async fetchPostData(): Promise<string | undefined> {
|
||||||
return this.#postData;
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
override headers(): Record<string, string> {
|
override headers(): Record<string, string> {
|
||||||
return this.#headers;
|
const headers: Record<string, string> = {};
|
||||||
|
for (const header of this.#request.headers) {
|
||||||
|
headers[header.name.toLowerCase()] = header.value.value;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
override response(): BidiHTTPResponse | null {
|
override response(): BidiHTTPResponse | null {
|
||||||
return this._response;
|
return this.#response;
|
||||||
|
}
|
||||||
|
|
||||||
|
override failure(): {errorText: string} | null {
|
||||||
|
if (this.#request.error === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {errorText: this.#request.error};
|
||||||
}
|
}
|
||||||
|
|
||||||
override isNavigationRequest(): boolean {
|
override isNavigationRequest(): boolean {
|
||||||
return Boolean(this._navigationId);
|
return this.#request.navigation !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
override initiator(): Bidi.Network.Initiator {
|
override initiator(): Bidi.Network.Initiator {
|
||||||
return this.#initiator;
|
return this.#request.initiator;
|
||||||
}
|
}
|
||||||
|
|
||||||
override redirectChain(): BidiHTTPRequest[] {
|
override redirectChain(): BidiHTTPRequest[] {
|
||||||
return this._redirectChain.slice();
|
if (this.#redirect === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const redirects = [this.#redirect];
|
||||||
|
for (const redirect of redirects) {
|
||||||
|
if (redirect.#redirect !== undefined) {
|
||||||
|
redirects.push(redirect.#redirect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirects;
|
||||||
}
|
}
|
||||||
|
|
||||||
override enqueueInterceptAction(
|
override enqueueInterceptAction(
|
||||||
@ -115,8 +134,8 @@ export class BidiHTTPRequest extends HTTPRequest {
|
|||||||
void pendingHandler();
|
void pendingHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
override frame(): Frame | null {
|
override frame(): BidiFrame | null {
|
||||||
return this.#frame;
|
return this.#frame ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
override continueRequestOverrides(): never {
|
override continueRequestOverrides(): never {
|
||||||
@ -157,8 +176,4 @@ export class BidiHTTPRequest extends HTTPRequest {
|
|||||||
): never {
|
): never {
|
||||||
throw new UnsupportedOperation();
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
override failure(): never {
|
|
||||||
throw new UnsupportedOperation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,10 @@ import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|||||||
import type Protocol from 'devtools-protocol';
|
import type Protocol from 'devtools-protocol';
|
||||||
|
|
||||||
import type {Frame} from '../api/Frame.js';
|
import type {Frame} from '../api/Frame.js';
|
||||||
import {
|
import {HTTPResponse, type RemoteAddress} from '../api/HTTPResponse.js';
|
||||||
HTTPResponse as HTTPResponse,
|
import {PageEvent} from '../api/Page.js';
|
||||||
type RemoteAddress,
|
|
||||||
} from '../api/HTTPResponse.js';
|
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
|
import {invokeAtMostOnceForArguments} from '../util/decorators.js';
|
||||||
|
|
||||||
import type {BidiHTTPRequest} from './HTTPRequest.js';
|
import type {BidiHTTPRequest} from './HTTPRequest.js';
|
||||||
|
|
||||||
@ -19,62 +18,62 @@ import type {BidiHTTPRequest} from './HTTPRequest.js';
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiHTTPResponse extends HTTPResponse {
|
export class BidiHTTPResponse extends HTTPResponse {
|
||||||
#request: BidiHTTPRequest;
|
static from(
|
||||||
#remoteAddress: RemoteAddress;
|
data: Bidi.Network.ResponseData,
|
||||||
#status: number;
|
request: BidiHTTPRequest
|
||||||
#statusText: string;
|
): BidiHTTPResponse {
|
||||||
#url: string;
|
const response = new BidiHTTPResponse(data, request);
|
||||||
#fromCache: boolean;
|
response.#initialize();
|
||||||
#headers: Record<string, string> = {};
|
return response;
|
||||||
#timings: Record<string, string> | null;
|
}
|
||||||
|
|
||||||
constructor(
|
#data: Bidi.Network.ResponseData;
|
||||||
request: BidiHTTPRequest,
|
#request: BidiHTTPRequest;
|
||||||
{response}: Bidi.Network.ResponseCompletedParameters
|
|
||||||
|
private constructor(
|
||||||
|
data: Bidi.Network.ResponseData,
|
||||||
|
request: BidiHTTPRequest
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
this.#data = data;
|
||||||
this.#request = request;
|
this.#request = request;
|
||||||
|
}
|
||||||
|
|
||||||
this.#remoteAddress = {
|
#initialize() {
|
||||||
|
this.#request.frame()?.page().emit(PageEvent.Response, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@invokeAtMostOnceForArguments
|
||||||
|
override remoteAddress(): RemoteAddress {
|
||||||
|
return {
|
||||||
ip: '',
|
ip: '',
|
||||||
port: -1,
|
port: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#url = response.url;
|
|
||||||
this.#fromCache = response.fromCache;
|
|
||||||
this.#status = response.status;
|
|
||||||
this.#statusText = response.statusText;
|
|
||||||
// TODO: File and issue with BiDi spec
|
|
||||||
this.#timings = null;
|
|
||||||
|
|
||||||
// TODO: Removed once the Firefox implementation is compliant with https://w3c.github.io/webdriver-bidi/#get-the-response-data.
|
|
||||||
for (const header of response.headers || []) {
|
|
||||||
// TODO: How to handle Binary Headers
|
|
||||||
// https://w3c.github.io/webdriver-bidi/#type-network-Header
|
|
||||||
if (header.value.type === 'string') {
|
|
||||||
this.#headers[header.name.toLowerCase()] = header.value.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override remoteAddress(): RemoteAddress {
|
|
||||||
return this.#remoteAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override url(): string {
|
override url(): string {
|
||||||
return this.#url;
|
return this.#data.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
override status(): number {
|
override status(): number {
|
||||||
return this.#status;
|
return this.#data.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
override statusText(): string {
|
override statusText(): string {
|
||||||
return this.#statusText;
|
return this.#data.statusText;
|
||||||
}
|
}
|
||||||
|
|
||||||
override headers(): Record<string, string> {
|
override headers(): Record<string, string> {
|
||||||
return this.#headers;
|
const headers: Record<string, string> = {};
|
||||||
|
// TODO: Remove once the Firefox implementation is compliant with https://w3c.github.io/webdriver-bidi/#get-the-response-data.
|
||||||
|
for (const header of this.#data.headers || []) {
|
||||||
|
// TODO: How to handle Binary Headers
|
||||||
|
// https://w3c.github.io/webdriver-bidi/#type-network-Header
|
||||||
|
if (header.value.type === 'string') {
|
||||||
|
headers[header.name.toLowerCase()] = header.value.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
override request(): BidiHTTPRequest {
|
override request(): BidiHTTPRequest {
|
||||||
@ -82,11 +81,12 @@ export class BidiHTTPResponse extends HTTPResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fromCache(): boolean {
|
override fromCache(): boolean {
|
||||||
return this.#fromCache;
|
return this.#data.fromCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
override timing(): Protocol.Network.ResourceTiming | null {
|
override timing(): Protocol.Network.ResourceTiming | null {
|
||||||
return this.#timings as any;
|
// TODO: File and issue with BiDi spec
|
||||||
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
override frame(): Frame | null {
|
override frame(): Frame | null {
|
||||||
|
@ -12,9 +12,9 @@ import {
|
|||||||
Mouse,
|
Mouse,
|
||||||
MouseButton,
|
MouseButton,
|
||||||
Touchscreen,
|
Touchscreen,
|
||||||
|
type KeyboardTypeOptions,
|
||||||
type KeyDownOptions,
|
type KeyDownOptions,
|
||||||
type KeyPressOptions,
|
type KeyPressOptions,
|
||||||
type KeyboardTypeOptions,
|
|
||||||
type MouseClickOptions,
|
type MouseClickOptions,
|
||||||
type MouseMoveOptions,
|
type MouseMoveOptions,
|
||||||
type MouseOptions,
|
type MouseOptions,
|
||||||
@ -23,7 +23,6 @@ import {
|
|||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
import type {KeyInput} from '../common/USKeyboardLayout.js';
|
import type {KeyInput} from '../common/USKeyboardLayout.js';
|
||||||
|
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
|
||||||
import type {BidiPage} from './Page.js';
|
import type {BidiPage} from './Page.js';
|
||||||
|
|
||||||
const enum InputId {
|
const enum InputId {
|
||||||
@ -288,9 +287,7 @@ export class BidiKeyboard extends Keyboard {
|
|||||||
key: KeyInput,
|
key: KeyInput,
|
||||||
_options?: Readonly<KeyDownOptions>
|
_options?: Readonly<KeyDownOptions>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.#page.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#page.mainFrame()._id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Key,
|
type: SourceActionsType.Key,
|
||||||
id: InputId.Keyboard,
|
id: InputId.Keyboard,
|
||||||
@ -301,14 +298,11 @@ export class BidiKeyboard extends Keyboard {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async up(key: KeyInput): Promise<void> {
|
override async up(key: KeyInput): Promise<void> {
|
||||||
await this.#page.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#page.mainFrame()._id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Key,
|
type: SourceActionsType.Key,
|
||||||
id: InputId.Keyboard,
|
id: InputId.Keyboard,
|
||||||
@ -319,8 +313,7 @@ export class BidiKeyboard extends Keyboard {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async press(
|
override async press(
|
||||||
@ -344,16 +337,13 @@ export class BidiKeyboard extends Keyboard {
|
|||||||
type: ActionType.KeyUp,
|
type: ActionType.KeyUp,
|
||||||
value: getBidiKeyValue(key),
|
value: getBidiKeyValue(key),
|
||||||
});
|
});
|
||||||
await this.#page.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#page.mainFrame()._id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Key,
|
type: SourceActionsType.Key,
|
||||||
id: InputId.Keyboard,
|
id: InputId.Keyboard,
|
||||||
actions,
|
actions,
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async type(
|
override async type(
|
||||||
@ -396,16 +386,13 @@ export class BidiKeyboard extends Keyboard {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.#page.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#page.mainFrame()._id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Key,
|
type: SourceActionsType.Key,
|
||||||
id: InputId.Keyboard,
|
id: InputId.Keyboard,
|
||||||
actions,
|
actions,
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async sendCharacter(char: string): Promise<void> {
|
override async sendCharacter(char: string): Promise<void> {
|
||||||
@ -460,19 +447,17 @@ const getBidiButton = (button: MouseButton) => {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiMouse extends Mouse {
|
export class BidiMouse extends Mouse {
|
||||||
#context: BrowsingContext;
|
#page: BidiPage;
|
||||||
#lastMovePoint: Point = {x: 0, y: 0};
|
#lastMovePoint: Point = {x: 0, y: 0};
|
||||||
|
|
||||||
constructor(context: BrowsingContext) {
|
constructor(page: BidiPage) {
|
||||||
super();
|
super();
|
||||||
this.#context = context;
|
this.#page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async reset(): Promise<void> {
|
override async reset(): Promise<void> {
|
||||||
this.#lastMovePoint = {x: 0, y: 0};
|
this.#lastMovePoint = {x: 0, y: 0};
|
||||||
await this.#context.connection.send('input.releaseActions', {
|
await this.#page.mainFrame().browsingContext.releaseActions();
|
||||||
context: this.#context.id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async move(
|
override async move(
|
||||||
@ -502,22 +487,17 @@ export class BidiMouse extends Mouse {
|
|||||||
});
|
});
|
||||||
// https://w3c.github.io/webdriver-bidi/#command-input-performActions:~:text=input.PointerMoveAction%20%3D%20%7B%0A%20%20type%3A%20%22pointerMove%22%2C%0A%20%20x%3A%20js%2Dint%2C
|
// https://w3c.github.io/webdriver-bidi/#command-input-performActions:~:text=input.PointerMoveAction%20%3D%20%7B%0A%20%20type%3A%20%22pointerMove%22%2C%0A%20%20x%3A%20js%2Dint%2C
|
||||||
this.#lastMovePoint = to;
|
this.#lastMovePoint = to;
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Pointer,
|
type: SourceActionsType.Pointer,
|
||||||
id: InputId.Mouse,
|
id: InputId.Mouse,
|
||||||
actions,
|
actions,
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async down(options: Readonly<MouseOptions> = {}): Promise<void> {
|
override async down(options: Readonly<MouseOptions> = {}): Promise<void> {
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Pointer,
|
type: SourceActionsType.Pointer,
|
||||||
id: InputId.Mouse,
|
id: InputId.Mouse,
|
||||||
@ -528,14 +508,11 @@ export class BidiMouse extends Mouse {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async up(options: Readonly<MouseOptions> = {}): Promise<void> {
|
override async up(options: Readonly<MouseOptions> = {}): Promise<void> {
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Pointer,
|
type: SourceActionsType.Pointer,
|
||||||
id: InputId.Mouse,
|
id: InputId.Mouse,
|
||||||
@ -546,8 +523,7 @@ export class BidiMouse extends Mouse {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async click(
|
override async click(
|
||||||
@ -582,24 +558,19 @@ export class BidiMouse extends Mouse {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
actions.push(pointerUpAction);
|
actions.push(pointerUpAction);
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Pointer,
|
type: SourceActionsType.Pointer,
|
||||||
id: InputId.Mouse,
|
id: InputId.Mouse,
|
||||||
actions,
|
actions,
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async wheel(
|
override async wheel(
|
||||||
options: Readonly<MouseWheelOptions> = {}
|
options: Readonly<MouseWheelOptions> = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Wheel,
|
type: SourceActionsType.Wheel,
|
||||||
id: InputId.Wheel,
|
id: InputId.Wheel,
|
||||||
@ -615,8 +586,7 @@ export class BidiMouse extends Mouse {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override drag(): never {
|
override drag(): never {
|
||||||
@ -644,11 +614,11 @@ export class BidiMouse extends Mouse {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiTouchscreen extends Touchscreen {
|
export class BidiTouchscreen extends Touchscreen {
|
||||||
#context: BrowsingContext;
|
#page: BidiPage;
|
||||||
|
|
||||||
constructor(context: BrowsingContext) {
|
constructor(page: BidiPage) {
|
||||||
super();
|
super();
|
||||||
this.#context = context;
|
this.#page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async touchStart(
|
override async touchStart(
|
||||||
@ -656,9 +626,7 @@ export class BidiTouchscreen extends Touchscreen {
|
|||||||
y: number,
|
y: number,
|
||||||
options: BidiTouchMoveOptions = {}
|
options: BidiTouchMoveOptions = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Pointer,
|
type: SourceActionsType.Pointer,
|
||||||
id: InputId.Finger,
|
id: InputId.Finger,
|
||||||
@ -678,8 +646,7 @@ export class BidiTouchscreen extends Touchscreen {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async touchMove(
|
override async touchMove(
|
||||||
@ -687,9 +654,7 @@ export class BidiTouchscreen extends Touchscreen {
|
|||||||
y: number,
|
y: number,
|
||||||
options: BidiTouchMoveOptions = {}
|
options: BidiTouchMoveOptions = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Pointer,
|
type: SourceActionsType.Pointer,
|
||||||
id: InputId.Finger,
|
id: InputId.Finger,
|
||||||
@ -705,14 +670,11 @@ export class BidiTouchscreen extends Touchscreen {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async touchEnd(): Promise<void> {
|
override async touchEnd(): Promise<void> {
|
||||||
await this.#context.connection.send('input.performActions', {
|
await this.#page.mainFrame().browsingContext.performActions([
|
||||||
context: this.#context.id,
|
|
||||||
actions: [
|
|
||||||
{
|
{
|
||||||
type: SourceActionsType.Pointer,
|
type: SourceActionsType.Pointer,
|
||||||
id: InputId.Finger,
|
id: InputId.Finger,
|
||||||
@ -726,7 +688,6 @@ export class BidiTouchscreen extends Touchscreen {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,28 +12,28 @@ import {UnsupportedOperation} from '../common/Errors.js';
|
|||||||
|
|
||||||
import {BidiDeserializer} from './Deserializer.js';
|
import {BidiDeserializer} from './Deserializer.js';
|
||||||
import type {BidiRealm} from './Realm.js';
|
import type {BidiRealm} from './Realm.js';
|
||||||
import type {Sandbox} from './Sandbox.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiJSHandle<T = unknown> extends JSHandle<T> {
|
export class BidiJSHandle<T = unknown> extends JSHandle<T> {
|
||||||
#disposed = false;
|
static from<T>(
|
||||||
readonly #sandbox: Sandbox;
|
value: Bidi.Script.RemoteValue,
|
||||||
|
realm: BidiRealm
|
||||||
|
): BidiJSHandle<T> {
|
||||||
|
return new BidiJSHandle(value, realm);
|
||||||
|
}
|
||||||
|
|
||||||
readonly #remoteValue: Bidi.Script.RemoteValue;
|
readonly #remoteValue: Bidi.Script.RemoteValue;
|
||||||
|
|
||||||
constructor(sandbox: Sandbox, remoteValue: Bidi.Script.RemoteValue) {
|
override readonly realm: BidiRealm;
|
||||||
|
|
||||||
|
#disposed = false;
|
||||||
|
|
||||||
|
constructor(value: Bidi.Script.RemoteValue, realm: BidiRealm) {
|
||||||
super();
|
super();
|
||||||
this.#sandbox = sandbox;
|
this.#remoteValue = value;
|
||||||
this.#remoteValue = remoteValue;
|
this.realm = realm;
|
||||||
}
|
|
||||||
|
|
||||||
context(): BidiRealm {
|
|
||||||
return this.realm.environment.context();
|
|
||||||
}
|
|
||||||
|
|
||||||
override get realm(): Sandbox {
|
|
||||||
return this.#sandbox;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override get disposed(): boolean {
|
override get disposed(): boolean {
|
||||||
@ -55,7 +55,7 @@ export class BidiJSHandle<T = unknown> extends JSHandle<T> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#disposed = true;
|
this.#disposed = true;
|
||||||
await this.context().destroyHandles([this]);
|
await this.realm.destroyHandles([this]);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPrimitiveValue(): boolean {
|
get isPrimitiveValue(): boolean {
|
||||||
|
@ -1,155 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2023 Google Inc.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|
||||||
|
|
||||||
import {EventEmitter, EventSubscription} from '../common/EventEmitter.js';
|
|
||||||
import {
|
|
||||||
NetworkManagerEvent,
|
|
||||||
type NetworkManagerEvents,
|
|
||||||
} from '../common/NetworkManagerEvents.js';
|
|
||||||
import {DisposableStack} from '../util/disposable.js';
|
|
||||||
|
|
||||||
import type {BidiConnection} from './Connection.js';
|
|
||||||
import type {BidiFrame} from './Frame.js';
|
|
||||||
import {BidiHTTPRequest} from './HTTPRequest.js';
|
|
||||||
import {BidiHTTPResponse} from './HTTPResponse.js';
|
|
||||||
import type {BidiPage} from './Page.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class BidiNetworkManager extends EventEmitter<NetworkManagerEvents> {
|
|
||||||
#connection: BidiConnection;
|
|
||||||
#page: BidiPage;
|
|
||||||
#subscriptions = new DisposableStack();
|
|
||||||
|
|
||||||
#requestMap = new Map<string, BidiHTTPRequest>();
|
|
||||||
#navigationMap = new Map<string, BidiHTTPResponse>();
|
|
||||||
|
|
||||||
constructor(connection: BidiConnection, page: BidiPage) {
|
|
||||||
super();
|
|
||||||
this.#connection = connection;
|
|
||||||
this.#page = page;
|
|
||||||
|
|
||||||
// TODO: Subscribe to the Frame individually
|
|
||||||
this.#subscriptions.use(
|
|
||||||
new EventSubscription(
|
|
||||||
this.#connection,
|
|
||||||
'network.beforeRequestSent',
|
|
||||||
this.#onBeforeRequestSent.bind(this)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.#subscriptions.use(
|
|
||||||
new EventSubscription(
|
|
||||||
this.#connection,
|
|
||||||
'network.responseStarted',
|
|
||||||
this.#onResponseStarted.bind(this)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.#subscriptions.use(
|
|
||||||
new EventSubscription(
|
|
||||||
this.#connection,
|
|
||||||
'network.responseCompleted',
|
|
||||||
this.#onResponseCompleted.bind(this)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.#subscriptions.use(
|
|
||||||
new EventSubscription(
|
|
||||||
this.#connection,
|
|
||||||
'network.fetchError',
|
|
||||||
this.#onFetchError.bind(this)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#onBeforeRequestSent(event: Bidi.Network.BeforeRequestSentParameters): void {
|
|
||||||
const frame = this.#page.frame(event.context ?? '');
|
|
||||||
if (!frame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const request = this.#requestMap.get(event.request.request);
|
|
||||||
let upsertRequest: BidiHTTPRequest;
|
|
||||||
if (request) {
|
|
||||||
request._redirectChain.push(request);
|
|
||||||
upsertRequest = new BidiHTTPRequest(event, frame, request._redirectChain);
|
|
||||||
} else {
|
|
||||||
upsertRequest = new BidiHTTPRequest(event, frame, []);
|
|
||||||
}
|
|
||||||
this.#requestMap.set(event.request.request, upsertRequest);
|
|
||||||
this.emit(NetworkManagerEvent.Request, upsertRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#onResponseStarted(_event: Bidi.Network.ResponseStartedParameters) {}
|
|
||||||
|
|
||||||
#onResponseCompleted(event: Bidi.Network.ResponseCompletedParameters): void {
|
|
||||||
const request = this.#requestMap.get(event.request.request);
|
|
||||||
if (!request) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = new BidiHTTPResponse(request, event);
|
|
||||||
request._response = response;
|
|
||||||
if (event.navigation) {
|
|
||||||
this.#navigationMap.set(event.navigation, response);
|
|
||||||
}
|
|
||||||
if (response.fromCache()) {
|
|
||||||
this.emit(NetworkManagerEvent.RequestServedFromCache, request);
|
|
||||||
}
|
|
||||||
this.emit(NetworkManagerEvent.Response, response);
|
|
||||||
this.emit(NetworkManagerEvent.RequestFinished, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
#onFetchError(event: Bidi.Network.FetchErrorParameters) {
|
|
||||||
const request = this.#requestMap.get(event.request.request);
|
|
||||||
if (!request) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
request._failureText = event.errorText;
|
|
||||||
this.emit(NetworkManagerEvent.RequestFailed, request);
|
|
||||||
this.#requestMap.delete(event.request.request);
|
|
||||||
}
|
|
||||||
|
|
||||||
getNavigationResponse(navigationId?: string | null): BidiHTTPResponse | null {
|
|
||||||
if (!navigationId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const response = this.#navigationMap.get(navigationId);
|
|
||||||
|
|
||||||
return response ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
inFlightRequestsCount(): number {
|
|
||||||
let inFlightRequestCounter = 0;
|
|
||||||
for (const request of this.#requestMap.values()) {
|
|
||||||
if (!request.response() || request._failureText) {
|
|
||||||
inFlightRequestCounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inFlightRequestCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMapAfterFrameDispose(frame: BidiFrame): void {
|
|
||||||
for (const [id, request] of this.#requestMap.entries()) {
|
|
||||||
if (request.frame() === frame) {
|
|
||||||
this.#requestMap.delete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [id, response] of this.#navigationMap.entries()) {
|
|
||||||
if (response.frame() === frame) {
|
|
||||||
this.#navigationMap.delete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.removeAllListeners();
|
|
||||||
this.#requestMap.clear();
|
|
||||||
this.#navigationMap.clear();
|
|
||||||
this.#subscriptions.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,201 +7,93 @@
|
|||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
import type Protocol from 'devtools-protocol';
|
import type Protocol from 'devtools-protocol';
|
||||||
|
|
||||||
import {
|
import {firstValueFrom, from, raceWith} from '../../third_party/rxjs/rxjs.js';
|
||||||
firstValueFrom,
|
|
||||||
from,
|
|
||||||
map,
|
|
||||||
raceWith,
|
|
||||||
zip,
|
|
||||||
} from '../../third_party/rxjs/rxjs.js';
|
|
||||||
import type {CDPSession} from '../api/CDPSession.js';
|
import type {CDPSession} from '../api/CDPSession.js';
|
||||||
import type {BoundingBox} from '../api/ElementHandle.js';
|
import type {BoundingBox} from '../api/ElementHandle.js';
|
||||||
import type {WaitForOptions} from '../api/Frame.js';
|
import type {WaitForOptions} from '../api/Frame.js';
|
||||||
import type {HTTPResponse} from '../api/HTTPResponse.js';
|
import type {HTTPResponse} from '../api/HTTPResponse.js';
|
||||||
|
import type {MediaFeature, GeolocationOptions} from '../api/Page.js';
|
||||||
import {
|
import {
|
||||||
Page,
|
Page,
|
||||||
PageEvent,
|
PageEvent,
|
||||||
type GeolocationOptions,
|
|
||||||
type MediaFeature,
|
|
||||||
type NewDocumentScriptEvaluation,
|
type NewDocumentScriptEvaluation,
|
||||||
type ScreenshotOptions,
|
type ScreenshotOptions,
|
||||||
} from '../api/Page.js';
|
} from '../api/Page.js';
|
||||||
import {Accessibility} from '../cdp/Accessibility.js';
|
import {Accessibility} from '../cdp/Accessibility.js';
|
||||||
import {Coverage} from '../cdp/Coverage.js';
|
import {Coverage} from '../cdp/Coverage.js';
|
||||||
import {EmulationManager as CdpEmulationManager} from '../cdp/EmulationManager.js';
|
import {EmulationManager} from '../cdp/EmulationManager.js';
|
||||||
import {FrameTree} from '../cdp/FrameTree.js';
|
|
||||||
import {Tracing} from '../cdp/Tracing.js';
|
import {Tracing} from '../cdp/Tracing.js';
|
||||||
import type {ConsoleMessageType} from '../common/ConsoleMessage.js';
|
import type {Cookie, CookieParam, CookieSameSite} from '../common/Cookie.js';
|
||||||
import {
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
ConsoleMessage,
|
|
||||||
type ConsoleMessageLocation,
|
|
||||||
} from '../common/ConsoleMessage.js';
|
|
||||||
import type {Cookie, CookieSameSite, CookieParam} from '../common/Cookie.js';
|
|
||||||
import {TargetCloseError, UnsupportedOperation} from '../common/Errors.js';
|
|
||||||
import type {Handler} from '../common/EventEmitter.js';
|
|
||||||
import {NetworkManagerEvent} from '../common/NetworkManagerEvents.js';
|
|
||||||
import type {PDFOptions} from '../common/PDFOptions.js';
|
import type {PDFOptions} from '../common/PDFOptions.js';
|
||||||
import type {Awaitable} from '../common/types.js';
|
import type {Awaitable} from '../common/types.js';
|
||||||
import {
|
import {evaluationString, parsePDFOptions, timeout} from '../common/util.js';
|
||||||
debugError,
|
|
||||||
evaluationString,
|
|
||||||
NETWORK_IDLE_TIME,
|
|
||||||
parsePDFOptions,
|
|
||||||
timeout,
|
|
||||||
validateDialogType,
|
|
||||||
} from '../common/util.js';
|
|
||||||
import type {Viewport} from '../common/Viewport.js';
|
import type {Viewport} from '../common/Viewport.js';
|
||||||
import {assert} from '../util/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {Deferred} from '../util/Deferred.js';
|
|
||||||
import {disposeSymbol} from '../util/disposable.js';
|
|
||||||
import {isErrorLike} from '../util/ErrorLike.js';
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
|
|
||||||
import type {BidiBrowser} from './Browser.js';
|
import type {BidiBrowser} from './Browser.js';
|
||||||
import type {BidiBrowserContext} from './BrowserContext.js';
|
import type {BidiBrowserContext} from './BrowserContext.js';
|
||||||
import {BrowsingContextEvent, type BrowsingContext} from './BrowsingContext.js';
|
import type {BidiCdpSession} from './CDPSession.js';
|
||||||
import {BidiCdpSession} from './CDPSession.js';
|
import type {BrowsingContext} from './core/BrowsingContext.js';
|
||||||
import type {BidiConnection} from './Connection.js';
|
|
||||||
import {BidiDeserializer} from './Deserializer.js';
|
|
||||||
import {BidiDialog} from './Dialog.js';
|
|
||||||
import {BidiElementHandle} from './ElementHandle.js';
|
import {BidiElementHandle} from './ElementHandle.js';
|
||||||
import {EmulationManager} from './EmulationManager.js';
|
|
||||||
import {BidiFrame} from './Frame.js';
|
import {BidiFrame} from './Frame.js';
|
||||||
import type {BidiHTTPRequest} from './HTTPRequest.js';
|
|
||||||
import type {BidiHTTPResponse} from './HTTPResponse.js';
|
import type {BidiHTTPResponse} from './HTTPResponse.js';
|
||||||
import {BidiKeyboard, BidiMouse, BidiTouchscreen} from './Input.js';
|
import {BidiKeyboard, BidiMouse, BidiTouchscreen} from './Input.js';
|
||||||
import type {BidiJSHandle} from './JSHandle.js';
|
import type {BidiJSHandle} from './JSHandle.js';
|
||||||
import {getBiDiReadinessState, rewriteNavigationError} from './lifecycle.js';
|
import {rewriteNavigationError} from './util.js';
|
||||||
import {BidiNetworkManager} from './NetworkManager.js';
|
|
||||||
import {createBidiHandle} from './Realm.js';
|
|
||||||
import type {BiDiPageTarget} from './Target.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BidiPage extends Page {
|
export class BidiPage extends Page {
|
||||||
#accessibility: Accessibility;
|
static from(
|
||||||
#connection: BidiConnection;
|
browserContext: BidiBrowserContext,
|
||||||
#frameTree = new FrameTree<BidiFrame>();
|
browsingContext: BrowsingContext
|
||||||
#networkManager: BidiNetworkManager;
|
): BidiPage {
|
||||||
#viewport: Viewport | null = null;
|
const page = new BidiPage(browserContext, browsingContext);
|
||||||
#closedDeferred = Deferred.create<never, TargetCloseError>();
|
page.#initialize();
|
||||||
#subscribedEvents = new Map<Bidi.Event['method'], Handler<any>>([
|
return page;
|
||||||
['log.entryAdded', this.#onLogEntryAdded.bind(this)],
|
|
||||||
['browsingContext.load', this.#onFrameLoaded.bind(this)],
|
|
||||||
[
|
|
||||||
'browsingContext.fragmentNavigated',
|
|
||||||
this.#onFrameFragmentNavigated.bind(this),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'browsingContext.domContentLoaded',
|
|
||||||
this.#onFrameDOMContentLoaded.bind(this),
|
|
||||||
],
|
|
||||||
['browsingContext.userPromptOpened', this.#onDialog.bind(this)],
|
|
||||||
]);
|
|
||||||
readonly #networkManagerEvents = [
|
|
||||||
[
|
|
||||||
NetworkManagerEvent.Request,
|
|
||||||
(request: BidiHTTPRequest) => {
|
|
||||||
this.emit(PageEvent.Request, request);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
NetworkManagerEvent.RequestServedFromCache,
|
|
||||||
(request: BidiHTTPRequest) => {
|
|
||||||
this.emit(PageEvent.RequestServedFromCache, request);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
NetworkManagerEvent.RequestFailed,
|
|
||||||
(request: BidiHTTPRequest) => {
|
|
||||||
this.emit(PageEvent.RequestFailed, request);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
NetworkManagerEvent.RequestFinished,
|
|
||||||
(request: BidiHTTPRequest) => {
|
|
||||||
this.emit(PageEvent.RequestFinished, request);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
NetworkManagerEvent.Response,
|
|
||||||
(response: BidiHTTPResponse) => {
|
|
||||||
this.emit(PageEvent.Response, response);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
readonly #browsingContextEvents = new Map<symbol, Handler<any>>([
|
|
||||||
[BrowsingContextEvent.Created, this.#onContextCreated.bind(this)],
|
|
||||||
[BrowsingContextEvent.Destroyed, this.#onContextDestroyed.bind(this)],
|
|
||||||
]);
|
|
||||||
#tracing: Tracing;
|
|
||||||
#coverage: Coverage;
|
|
||||||
#cdpEmulationManager: CdpEmulationManager;
|
|
||||||
#emulationManager: EmulationManager;
|
|
||||||
#mouse: BidiMouse;
|
|
||||||
#touchscreen: BidiTouchscreen;
|
|
||||||
#keyboard: BidiKeyboard;
|
|
||||||
#browsingContext: BrowsingContext;
|
|
||||||
#browserContext: BidiBrowserContext;
|
|
||||||
|
|
||||||
_client(): CDPSession {
|
|
||||||
return this.mainFrame().context().cdpSession;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
readonly #browserContext: BidiBrowserContext;
|
||||||
browsingContext: BrowsingContext,
|
readonly #frame: BidiFrame;
|
||||||
browserContext: BidiBrowserContext
|
#viewport: Viewport | null = null;
|
||||||
|
|
||||||
|
readonly keyboard: BidiKeyboard;
|
||||||
|
readonly mouse: BidiMouse;
|
||||||
|
readonly touchscreen: BidiTouchscreen;
|
||||||
|
readonly accessibility: Accessibility;
|
||||||
|
readonly tracing: Tracing;
|
||||||
|
readonly coverage: Coverage;
|
||||||
|
readonly #cdpEmulationManager: EmulationManager;
|
||||||
|
|
||||||
|
_client(): BidiCdpSession {
|
||||||
|
return this.#frame.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
browserContext: BidiBrowserContext,
|
||||||
|
browsingContext: BrowsingContext
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.#browsingContext = browsingContext;
|
|
||||||
this.#browserContext = browserContext;
|
this.#browserContext = browserContext;
|
||||||
this.#connection = browsingContext.connection;
|
this.#frame = BidiFrame.from(this, browsingContext);
|
||||||
|
|
||||||
for (const [event, subscriber] of this.#browsingContextEvents) {
|
this.#cdpEmulationManager = new EmulationManager(this.#frame.client);
|
||||||
this.#browsingContext.on(event, subscriber);
|
this.accessibility = new Accessibility(this.#frame.client);
|
||||||
|
this.tracing = new Tracing(this.#frame.client);
|
||||||
|
this.coverage = new Coverage(this.#frame.client);
|
||||||
|
this.keyboard = new BidiKeyboard(this);
|
||||||
|
this.mouse = new BidiMouse(this);
|
||||||
|
this.touchscreen = new BidiTouchscreen(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#networkManager = new BidiNetworkManager(this.#connection, this);
|
#initialize() {
|
||||||
|
this.#frame.browsingContext.on('closed', () => {
|
||||||
for (const [event, subscriber] of this.#subscribedEvents) {
|
this.emit(PageEvent.Close, undefined);
|
||||||
this.#connection.on(event, subscriber);
|
this.removeAllListeners();
|
||||||
}
|
});
|
||||||
|
|
||||||
for (const [event, subscriber] of this.#networkManagerEvents) {
|
|
||||||
// TODO: remove any
|
|
||||||
this.#networkManager.on(event, subscriber as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
const frame = new BidiFrame(
|
|
||||||
this,
|
|
||||||
this.#browsingContext,
|
|
||||||
this._timeoutSettings,
|
|
||||||
this.#browsingContext.parent
|
|
||||||
);
|
|
||||||
this.#frameTree.addFrame(frame);
|
|
||||||
this.emit(PageEvent.FrameAttached, frame);
|
|
||||||
|
|
||||||
// TODO: https://github.com/w3c/webdriver-bidi/issues/443
|
|
||||||
this.#accessibility = new Accessibility(
|
|
||||||
this.mainFrame().context().cdpSession
|
|
||||||
);
|
|
||||||
this.#tracing = new Tracing(this.mainFrame().context().cdpSession);
|
|
||||||
this.#coverage = new Coverage(this.mainFrame().context().cdpSession);
|
|
||||||
this.#cdpEmulationManager = new CdpEmulationManager(
|
|
||||||
this.mainFrame().context().cdpSession
|
|
||||||
);
|
|
||||||
this.#emulationManager = new EmulationManager(browsingContext);
|
|
||||||
this.#mouse = new BidiMouse(this.mainFrame().context());
|
|
||||||
this.#touchscreen = new BidiTouchscreen(this.mainFrame().context());
|
|
||||||
this.#keyboard = new BidiKeyboard(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
get connection(): BidiConnection {
|
|
||||||
return this.#connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async setUserAgent(
|
override async setUserAgent(
|
||||||
@ -228,46 +120,15 @@ export class BidiPage extends Page {
|
|||||||
prototypeHandle.id,
|
prototypeHandle.id,
|
||||||
'Prototype JSHandle must not be referencing primitive value'
|
'Prototype JSHandle must not be referencing primitive value'
|
||||||
);
|
);
|
||||||
const response = await this.mainFrame().client.send(
|
const response = await this.#frame.client.send('Runtime.queryObjects', {
|
||||||
'Runtime.queryObjects',
|
|
||||||
{
|
|
||||||
prototypeObjectId: prototypeHandle.id,
|
prototypeObjectId: prototypeHandle.id,
|
||||||
}
|
});
|
||||||
);
|
return this.#frame.mainRealm().createHandle({
|
||||||
return createBidiHandle(this.mainFrame().mainRealm(), {
|
|
||||||
type: 'array',
|
type: 'array',
|
||||||
handle: response.objects.objectId,
|
handle: response.objects.objectId,
|
||||||
}) as BidiJSHandle<Prototype[]>;
|
}) as BidiJSHandle<Prototype[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setBrowserContext(browserContext: BidiBrowserContext): void {
|
|
||||||
this.#browserContext = browserContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get accessibility(): Accessibility {
|
|
||||||
return this.#accessibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get tracing(): Tracing {
|
|
||||||
return this.#tracing;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get coverage(): Coverage {
|
|
||||||
return this.#coverage;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get mouse(): BidiMouse {
|
|
||||||
return this.#mouse;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get touchscreen(): BidiTouchscreen {
|
|
||||||
return this.#touchscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
override get keyboard(): BidiKeyboard {
|
|
||||||
return this.#keyboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
override browser(): BidiBrowser {
|
override browser(): BidiBrowser {
|
||||||
return this.browserContext().browser();
|
return this.browserContext().browser();
|
||||||
}
|
}
|
||||||
@ -277,14 +138,9 @@ export class BidiPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override mainFrame(): BidiFrame {
|
override mainFrame(): BidiFrame {
|
||||||
const mainFrame = this.#frameTree.getMainFrame();
|
return this.#frame;
|
||||||
assert(mainFrame, 'Requesting main frame too early!');
|
|
||||||
return mainFrame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
async focusedFrame(): Promise<BidiFrame> {
|
async focusedFrame(): Promise<BidiFrame> {
|
||||||
using frame = await this.mainFrame()
|
using frame = await this.mainFrame()
|
||||||
.isolatedRealm()
|
.isolatedRealm()
|
||||||
@ -304,216 +160,38 @@ export class BidiPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override frames(): BidiFrame[] {
|
override frames(): BidiFrame[] {
|
||||||
return Array.from(this.#frameTree.frames());
|
const frames = [this.#frame];
|
||||||
|
for (const frame of frames) {
|
||||||
|
frames.push(...frame.childFrames());
|
||||||
}
|
}
|
||||||
|
return frames;
|
||||||
frame(frameId?: string): BidiFrame | null {
|
|
||||||
return this.#frameTree.getById(frameId ?? '') || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
childFrames(frameId: string): BidiFrame[] {
|
|
||||||
return this.#frameTree.childFrames(frameId);
|
|
||||||
}
|
|
||||||
|
|
||||||
#onFrameLoaded(info: Bidi.BrowsingContext.NavigationInfo): void {
|
|
||||||
const frame = this.frame(info.context);
|
|
||||||
if (frame && this.mainFrame() === frame) {
|
|
||||||
this.emit(PageEvent.Load, undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onFrameFragmentNavigated(info: Bidi.BrowsingContext.NavigationInfo): void {
|
|
||||||
const frame = this.frame(info.context);
|
|
||||||
if (frame) {
|
|
||||||
this.emit(PageEvent.FrameNavigated, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onFrameDOMContentLoaded(info: Bidi.BrowsingContext.NavigationInfo): void {
|
|
||||||
const frame = this.frame(info.context);
|
|
||||||
if (frame) {
|
|
||||||
frame._hasStartedLoading = true;
|
|
||||||
if (this.mainFrame() === frame) {
|
|
||||||
this.emit(PageEvent.DOMContentLoaded, undefined);
|
|
||||||
}
|
|
||||||
this.emit(PageEvent.FrameNavigated, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onContextCreated(context: BrowsingContext): void {
|
|
||||||
if (
|
|
||||||
!this.frame(context.id) &&
|
|
||||||
(this.frame(context.parent ?? '') || !this.#frameTree.getMainFrame())
|
|
||||||
) {
|
|
||||||
const frame = new BidiFrame(
|
|
||||||
this,
|
|
||||||
context,
|
|
||||||
this._timeoutSettings,
|
|
||||||
context.parent
|
|
||||||
);
|
|
||||||
this.#frameTree.addFrame(frame);
|
|
||||||
if (frame !== this.mainFrame()) {
|
|
||||||
this.emit(PageEvent.FrameAttached, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onContextDestroyed(context: BrowsingContext): void {
|
|
||||||
const frame = this.frame(context.id);
|
|
||||||
|
|
||||||
if (frame) {
|
|
||||||
if (frame === this.mainFrame()) {
|
|
||||||
this.emit(PageEvent.Close, undefined);
|
|
||||||
}
|
|
||||||
this.#removeFramesRecursively(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#removeFramesRecursively(frame: BidiFrame): void {
|
|
||||||
for (const child of frame.childFrames()) {
|
|
||||||
this.#removeFramesRecursively(child);
|
|
||||||
}
|
|
||||||
frame[disposeSymbol]();
|
|
||||||
this.#networkManager.clearMapAfterFrameDispose(frame);
|
|
||||||
this.#frameTree.removeFrame(frame);
|
|
||||||
this.emit(PageEvent.FrameDetached, frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
#onLogEntryAdded(event: Bidi.Log.Entry): void {
|
|
||||||
const frame = this.frame(event.source.context);
|
|
||||||
if (!frame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isConsoleLogEntry(event)) {
|
|
||||||
const args = event.args.map(arg => {
|
|
||||||
return createBidiHandle(frame.mainRealm(), arg);
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = args
|
|
||||||
.reduce((value, arg) => {
|
|
||||||
const parsedValue = arg.isPrimitiveValue
|
|
||||||
? BidiDeserializer.deserialize(arg.remoteValue())
|
|
||||||
: arg.toString();
|
|
||||||
return `${value} ${parsedValue}`;
|
|
||||||
}, '')
|
|
||||||
.slice(1);
|
|
||||||
|
|
||||||
this.emit(
|
|
||||||
PageEvent.Console,
|
|
||||||
new ConsoleMessage(
|
|
||||||
event.method as ConsoleMessageType,
|
|
||||||
text,
|
|
||||||
args,
|
|
||||||
getStackTraceLocations(event.stackTrace)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (isJavaScriptLogEntry(event)) {
|
|
||||||
const error = new Error(event.text ?? '');
|
|
||||||
|
|
||||||
const messageHeight = error.message.split('\n').length;
|
|
||||||
const messageLines = error.stack!.split('\n').splice(0, messageHeight);
|
|
||||||
|
|
||||||
const stackLines = [];
|
|
||||||
if (event.stackTrace) {
|
|
||||||
for (const frame of event.stackTrace.callFrames) {
|
|
||||||
// Note we need to add `1` because the values are 0-indexed.
|
|
||||||
stackLines.push(
|
|
||||||
` at ${frame.functionName || '<anonymous>'} (${frame.url}:${
|
|
||||||
frame.lineNumber + 1
|
|
||||||
}:${frame.columnNumber + 1})`
|
|
||||||
);
|
|
||||||
if (stackLines.length >= Error.stackTraceLimit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error.stack = [...messageLines, ...stackLines].join('\n');
|
|
||||||
this.emit(PageEvent.PageError, error);
|
|
||||||
} else {
|
|
||||||
debugError(
|
|
||||||
`Unhandled LogEntry with type "${event.type}", text "${event.text}" and level "${event.level}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onDialog(event: Bidi.BrowsingContext.UserPromptOpenedParameters): void {
|
|
||||||
const frame = this.frame(event.context);
|
|
||||||
if (!frame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const type = validateDialogType(event.type);
|
|
||||||
|
|
||||||
const dialog = new BidiDialog(
|
|
||||||
frame.context(),
|
|
||||||
type,
|
|
||||||
event.message,
|
|
||||||
event.defaultValue
|
|
||||||
);
|
|
||||||
this.emit(PageEvent.Dialog, dialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
getNavigationResponse(id?: string | null): BidiHTTPResponse | null {
|
|
||||||
return this.#networkManager.getNavigationResponse(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override isClosed(): boolean {
|
override isClosed(): boolean {
|
||||||
return this.#closedDeferred.finished();
|
return this.#frame.detached;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async close(options?: {runBeforeUnload?: boolean}): Promise<void> {
|
override async close(options?: {runBeforeUnload?: boolean}): Promise<void> {
|
||||||
if (this.#closedDeferred.finished()) {
|
try {
|
||||||
|
await this.#frame.browsingContext.close(options?.runBeforeUnload);
|
||||||
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#closedDeferred.reject(new TargetCloseError('Page closed!'));
|
|
||||||
this.#networkManager.dispose();
|
|
||||||
|
|
||||||
await this.#connection.send('browsingContext.close', {
|
|
||||||
context: this.mainFrame()._id,
|
|
||||||
promptUnload: options?.runBeforeUnload ?? false,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.emit(PageEvent.Close, undefined);
|
|
||||||
this.removeAllListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async reload(
|
override async reload(
|
||||||
options: WaitForOptions = {}
|
options: WaitForOptions = {}
|
||||||
): Promise<BidiHTTPResponse | null> {
|
): Promise<BidiHTTPResponse | null> {
|
||||||
const {
|
const [response] = await Promise.all([
|
||||||
waitUntil = 'load',
|
this.#frame.waitForNavigation(options),
|
||||||
timeout: ms = this._timeoutSettings.navigationTimeout(),
|
this.#frame.browsingContext.reload(),
|
||||||
} = options;
|
]).catch(
|
||||||
|
rewriteNavigationError(
|
||||||
const [readiness, networkIdle] = getBiDiReadinessState(waitUntil);
|
this.url(),
|
||||||
|
options.timeout ?? this._timeoutSettings.navigationTimeout()
|
||||||
const result$ = zip(
|
)
|
||||||
from(
|
|
||||||
this.#connection.send('browsingContext.reload', {
|
|
||||||
context: this.mainFrame()._id,
|
|
||||||
wait: readiness,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
...(networkIdle !== null
|
|
||||||
? [
|
|
||||||
this.waitForNetworkIdle$({
|
|
||||||
timeout: ms,
|
|
||||||
concurrency: networkIdle === 'networkidle2' ? 2 : 0,
|
|
||||||
idleTime: NETWORK_IDLE_TIME,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
).pipe(
|
|
||||||
map(([{result}]) => {
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
raceWith(timeout(ms), from(this.#closedDeferred.valueOrThrow())),
|
|
||||||
rewriteNavigationError(this.url(), ms)
|
|
||||||
);
|
);
|
||||||
|
return response;
|
||||||
const result = await firstValueFrom(result$);
|
|
||||||
return this.getNavigationResponse(result.navigation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override setDefaultNavigationTimeout(timeout: number): void {
|
override setDefaultNavigationTimeout(timeout: number): void {
|
||||||
@ -572,8 +250,19 @@ export class BidiPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async setViewport(viewport: Viewport): Promise<void> {
|
override async setViewport(viewport: Viewport): Promise<void> {
|
||||||
if (!this.#browsingContext.supportsCdp()) {
|
if (!this.browser().cdpSupported) {
|
||||||
await this.#emulationManager.emulateViewport(viewport);
|
await this.#frame.browsingContext.setViewport({
|
||||||
|
viewport:
|
||||||
|
viewport.width && viewport.height
|
||||||
|
? {
|
||||||
|
width: viewport.width,
|
||||||
|
height: viewport.height,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
devicePixelRatio: viewport.deviceScaleFactor
|
||||||
|
? viewport.deviceScaleFactor
|
||||||
|
: null,
|
||||||
|
});
|
||||||
this.#viewport = viewport;
|
this.#viewport = viewport;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -603,10 +292,9 @@ export class BidiPage extends Page {
|
|||||||
preferCSSPageSize,
|
preferCSSPageSize,
|
||||||
} = parsePDFOptions(options, 'cm');
|
} = parsePDFOptions(options, 'cm');
|
||||||
const pageRanges = ranges ? ranges.split(', ') : [];
|
const pageRanges = ranges ? ranges.split(', ') : [];
|
||||||
const {result} = await firstValueFrom(
|
const data = await firstValueFrom(
|
||||||
from(
|
from(
|
||||||
this.#connection.send('browsingContext.print', {
|
this.#frame.browsingContext.print({
|
||||||
context: this.mainFrame()._id,
|
|
||||||
background,
|
background,
|
||||||
margin,
|
margin,
|
||||||
orientation: landscape ? 'landscape' : 'portrait',
|
orientation: landscape ? 'landscape' : 'portrait',
|
||||||
@ -621,7 +309,7 @@ export class BidiPage extends Page {
|
|||||||
).pipe(raceWith(timeout(ms)))
|
).pipe(raceWith(timeout(ms)))
|
||||||
);
|
);
|
||||||
|
|
||||||
const buffer = Buffer.from(result.data, 'base64');
|
const buffer = Buffer.from(data, 'base64');
|
||||||
|
|
||||||
await this._maybeWriteBufferToFile(path, buffer);
|
await this._maybeWriteBufferToFile(path, buffer);
|
||||||
|
|
||||||
@ -687,10 +375,7 @@ export class BidiPage extends Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const data = await this.#frame.browsingContext.captureScreenshot({
|
||||||
result: {data},
|
|
||||||
} = await this.#connection.send('browsingContext.captureScreenshot', {
|
|
||||||
context: this.mainFrame()._id,
|
|
||||||
origin: captureBeyondViewport ? 'document' : 'viewport',
|
origin: captureBeyondViewport ? 'document' : 'viewport',
|
||||||
format: {
|
format: {
|
||||||
type: `image/${type}`,
|
type: `image/${type}`,
|
||||||
@ -702,19 +387,11 @@ export class BidiPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async createCDPSession(): Promise<CDPSession> {
|
override async createCDPSession(): Promise<CDPSession> {
|
||||||
const {sessionId} = await this.mainFrame()
|
return await this.#frame.createCDPSession();
|
||||||
.context()
|
|
||||||
.cdpSession.send('Target.attachToTarget', {
|
|
||||||
targetId: this.mainFrame()._id,
|
|
||||||
flatten: true,
|
|
||||||
});
|
|
||||||
return new BidiCdpSession(this.mainFrame().context(), sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async bringToFront(): Promise<void> {
|
override async bringToFront(): Promise<void> {
|
||||||
await this.#connection.send('browsingContext.activate', {
|
await this.#frame.browsingContext.activate();
|
||||||
context: this.mainFrame()._id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async evaluateOnNewDocument<
|
override async evaluateOnNewDocument<
|
||||||
@ -725,20 +402,16 @@ export class BidiPage extends Page {
|
|||||||
...args: Params
|
...args: Params
|
||||||
): Promise<NewDocumentScriptEvaluation> {
|
): Promise<NewDocumentScriptEvaluation> {
|
||||||
const expression = evaluationExpression(pageFunction, ...args);
|
const expression = evaluationExpression(pageFunction, ...args);
|
||||||
const {result} = await this.#connection.send('script.addPreloadScript', {
|
const script =
|
||||||
functionDeclaration: expression,
|
await this.#frame.browsingContext.addPreloadScript(expression);
|
||||||
contexts: [this.mainFrame()._id],
|
|
||||||
});
|
|
||||||
|
|
||||||
return {identifier: result.script};
|
return {identifier: script};
|
||||||
}
|
}
|
||||||
|
|
||||||
override async removeScriptToEvaluateOnNewDocument(
|
override async removeScriptToEvaluateOnNewDocument(
|
||||||
id: string
|
id: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.#connection.send('script.removePreloadScript', {
|
await this.#frame.browsingContext.removePreloadScript(id);
|
||||||
script: id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async exposeFunction<Args extends unknown[], Ret>(
|
override async exposeFunction<Args extends unknown[], Ret>(
|
||||||
@ -769,13 +442,8 @@ export class BidiPage extends Page {
|
|||||||
return new URL(url);
|
return new URL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
const bidiCookies = await this.#connection.send('storage.getCookies', {
|
const cookies = await this.#frame.browsingContext.getCookies();
|
||||||
partition: {
|
return cookies
|
||||||
type: 'context',
|
|
||||||
context: this.mainFrame()._id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return bidiCookies.result.cookies
|
|
||||||
.map(cookie => {
|
.map(cookie => {
|
||||||
return bidiToPuppeteerCookie(cookie);
|
return bidiToPuppeteerCookie(cookie);
|
||||||
})
|
})
|
||||||
@ -790,7 +458,7 @@ export class BidiPage extends Page {
|
|||||||
throw new UnsupportedOperation();
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
override target(): BiDiPageTarget {
|
override target(): never {
|
||||||
throw new UnsupportedOperation();
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -876,22 +544,14 @@ export class BidiPage extends Page {
|
|||||||
// TODO: delete cookie before setting them.
|
// TODO: delete cookie before setting them.
|
||||||
// await this.deleteCookie(bidiCookie);
|
// await this.deleteCookie(bidiCookie);
|
||||||
|
|
||||||
const partition: Bidi.Storage.PartitionDescriptor =
|
if (cookie.partitionKey !== undefined) {
|
||||||
cookie.partitionKey !== undefined
|
await this.browserContext().userContext.setCookie(
|
||||||
? {
|
bidiCookie,
|
||||||
type: 'storageKey',
|
cookie.partitionKey
|
||||||
sourceOrigin: cookie.partitionKey,
|
);
|
||||||
userContext: this.#browserContext.id,
|
} else {
|
||||||
|
await this.#frame.browsingContext.setCookie(bidiCookie);
|
||||||
}
|
}
|
||||||
: {
|
|
||||||
type: 'context',
|
|
||||||
context: this.mainFrame()._id,
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.#connection.send('storage.setCookie', {
|
|
||||||
cookie: bidiCookie,
|
|
||||||
partition,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -925,7 +585,7 @@ export class BidiPage extends Page {
|
|||||||
override async goForward(
|
override async goForward(
|
||||||
options: WaitForOptions = {}
|
options: WaitForOptions = {}
|
||||||
): Promise<HTTPResponse | null> {
|
): Promise<HTTPResponse | null> {
|
||||||
return await this.#go(+1, options);
|
return await this.#go(1, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #go(
|
async #go(
|
||||||
@ -933,22 +593,19 @@ export class BidiPage extends Page {
|
|||||||
options: WaitForOptions
|
options: WaitForOptions
|
||||||
): Promise<HTTPResponse | null> {
|
): Promise<HTTPResponse | null> {
|
||||||
try {
|
try {
|
||||||
const result = await Promise.all([
|
const [response] = await Promise.all([
|
||||||
this.waitForNavigation(options),
|
this.waitForNavigation(options),
|
||||||
this.#connection.send('browsingContext.traverseHistory', {
|
this.#frame.browsingContext.traverseHistory(delta),
|
||||||
delta,
|
|
||||||
context: this.mainFrame()._id,
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
return result[0];
|
return response;
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
// TODO: waitForNavigation should be cancelled if an error happens.
|
// TODO: waitForNavigation should be cancelled if an error happens.
|
||||||
if (isErrorLike(err)) {
|
if (isErrorLike(error)) {
|
||||||
if (err.message.includes('no such history entry')) {
|
if (error.message.includes('no such history entry')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw err;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,34 +614,6 @@ export class BidiPage extends Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isConsoleLogEntry(
|
|
||||||
event: Bidi.Log.Entry
|
|
||||||
): event is Bidi.Log.ConsoleLogEntry {
|
|
||||||
return event.type === 'console';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isJavaScriptLogEntry(
|
|
||||||
event: Bidi.Log.Entry
|
|
||||||
): event is Bidi.Log.JavascriptLogEntry {
|
|
||||||
return event.type === 'javascript';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStackTraceLocations(
|
|
||||||
stackTrace?: Bidi.Script.StackTrace
|
|
||||||
): ConsoleMessageLocation[] {
|
|
||||||
const stackTraceLocations: ConsoleMessageLocation[] = [];
|
|
||||||
if (stackTrace) {
|
|
||||||
for (const callFrame of stackTrace.callFrames) {
|
|
||||||
stackTraceLocations.push({
|
|
||||||
url: callFrame.url,
|
|
||||||
lineNumber: callFrame.lineNumber,
|
|
||||||
columnNumber: callFrame.columnNumber,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stackTraceLocations;
|
|
||||||
}
|
|
||||||
|
|
||||||
function evaluationExpression(fun: Function | string, ...args: unknown[]) {
|
function evaluationExpression(fun: Function | string, ...args: unknown[]) {
|
||||||
return `() => {${evaluationString(fun, ...args)}}`;
|
return `() => {${evaluationString(fun, ...args)}}`;
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
import {EventEmitter, type EventType} from '../common/EventEmitter.js';
|
import type {JSHandle} from '../api/JSHandle.js';
|
||||||
|
import {Realm} from '../api/Realm.js';
|
||||||
|
import {LazyArg} from '../common/LazyArg.js';
|
||||||
import {scriptInjector} from '../common/ScriptInjector.js';
|
import {scriptInjector} from '../common/ScriptInjector.js';
|
||||||
|
import type {TimeoutSettings} from '../common/TimeoutSettings.js';
|
||||||
import type {EvaluateFunc, HandleFor} from '../common/types.js';
|
import type {EvaluateFunc, HandleFor} from '../common/types.js';
|
||||||
import {
|
import {
|
||||||
debugError,
|
debugError,
|
||||||
@ -17,69 +20,33 @@ import {
|
|||||||
SOURCE_URL_REGEX,
|
SOURCE_URL_REGEX,
|
||||||
} from '../common/util.js';
|
} from '../common/util.js';
|
||||||
import type PuppeteerUtil from '../injected/injected.js';
|
import type PuppeteerUtil from '../injected/injected.js';
|
||||||
import {disposeSymbol} from '../util/disposable.js';
|
|
||||||
import {stringifyFunction} from '../util/Function.js';
|
import {stringifyFunction} from '../util/Function.js';
|
||||||
|
|
||||||
import type {BidiConnection} from './Connection.js';
|
import type {Realm as BidiRealmCore} from './core/Realm.js';
|
||||||
|
import type {WindowRealm} from './core/Realm.js';
|
||||||
import {BidiDeserializer} from './Deserializer.js';
|
import {BidiDeserializer} from './Deserializer.js';
|
||||||
import {BidiElementHandle} from './ElementHandle.js';
|
import {BidiElementHandle} from './ElementHandle.js';
|
||||||
|
import type {BidiFrame} from './Frame.js';
|
||||||
import {BidiJSHandle} from './JSHandle.js';
|
import {BidiJSHandle} from './JSHandle.js';
|
||||||
import type {Sandbox} from './Sandbox.js';
|
import {BidiSerializer} from './Serializer.js';
|
||||||
import {createEvaluationError} from './util.js';
|
import {createEvaluationError} from './util.js';
|
||||||
|
|
||||||
/**
|
export abstract class BidiRealm extends Realm {
|
||||||
* @internal
|
realm: BidiRealmCore;
|
||||||
*/
|
|
||||||
export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|
||||||
readonly connection: BidiConnection;
|
|
||||||
|
|
||||||
#id!: string;
|
constructor(realm: BidiRealmCore, timeoutSettings: TimeoutSettings) {
|
||||||
#sandbox!: Sandbox;
|
super(timeoutSettings);
|
||||||
|
this.realm = realm;
|
||||||
constructor(connection: BidiConnection) {
|
|
||||||
super();
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get target(): Bidi.Script.Target {
|
protected initialize(): void {
|
||||||
return {
|
this.realm.on('destroyed', ({reason}) => {
|
||||||
context: this.#sandbox.environment._id,
|
this.taskManager.terminateAll(new Error(reason));
|
||||||
sandbox: this.#sandbox.name,
|
});
|
||||||
};
|
this.realm.on('updated', () => {
|
||||||
}
|
|
||||||
|
|
||||||
handleRealmDestroyed = async (
|
|
||||||
params: Bidi.Script.RealmDestroyed['params']
|
|
||||||
): Promise<void> => {
|
|
||||||
if (params.realm === this.#id) {
|
|
||||||
// Note: The Realm is destroyed, so in theory the handle should be as
|
|
||||||
// well.
|
|
||||||
this.internalPuppeteerUtil = undefined;
|
this.internalPuppeteerUtil = undefined;
|
||||||
this.#sandbox.environment.clearDocumentHandle();
|
void this.taskManager.rerunAll();
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
handleRealmCreated = (params: Bidi.Script.RealmCreated['params']): void => {
|
|
||||||
if (
|
|
||||||
params.type === 'window' &&
|
|
||||||
params.context === this.#sandbox.environment._id &&
|
|
||||||
params.sandbox === this.#sandbox.name
|
|
||||||
) {
|
|
||||||
this.#id = params.realm;
|
|
||||||
void this.#sandbox.taskManager.rerunAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setSandbox(sandbox: Sandbox): void {
|
|
||||||
this.#sandbox = sandbox;
|
|
||||||
this.connection.on(
|
|
||||||
Bidi.ChromiumBidi.Script.EventNames.RealmCreated,
|
|
||||||
this.handleRealmCreated
|
|
||||||
);
|
|
||||||
this.connection.on(
|
|
||||||
Bidi.ChromiumBidi.Script.EventNames.RealmDestroyed,
|
|
||||||
this.handleRealmDestroyed
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internalPuppeteerUtil?: Promise<BidiJSHandle<PuppeteerUtil>>;
|
protected internalPuppeteerUtil?: Promise<BidiJSHandle<PuppeteerUtil>>;
|
||||||
@ -100,7 +67,7 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|||||||
return this.internalPuppeteerUtil as Promise<BidiJSHandle<PuppeteerUtil>>;
|
return this.internalPuppeteerUtil as Promise<BidiJSHandle<PuppeteerUtil>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateHandle<
|
override async evaluateHandle<
|
||||||
Params extends unknown[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
||||||
>(
|
>(
|
||||||
@ -110,7 +77,7 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|||||||
return await this.#evaluate(false, pageFunction, ...args);
|
return await this.#evaluate(false, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluate<
|
override async evaluate<
|
||||||
Params extends unknown[],
|
Params extends unknown[],
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
||||||
>(
|
>(
|
||||||
@ -149,8 +116,6 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|||||||
PuppeteerURL.INTERNAL_URL
|
PuppeteerURL.INTERNAL_URL
|
||||||
);
|
);
|
||||||
|
|
||||||
const sandbox = this.#sandbox;
|
|
||||||
|
|
||||||
let responsePromise;
|
let responsePromise;
|
||||||
const resultOwnership = returnByValue
|
const resultOwnership = returnByValue
|
||||||
? Bidi.Script.ResultOwnership.None
|
? Bidi.Script.ResultOwnership.None
|
||||||
@ -166,11 +131,8 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|||||||
? pageFunction
|
? pageFunction
|
||||||
: `${pageFunction}\n${sourceUrlComment}\n`;
|
: `${pageFunction}\n${sourceUrlComment}\n`;
|
||||||
|
|
||||||
responsePromise = this.connection.send('script.evaluate', {
|
responsePromise = this.realm.evaluate(expression, true, {
|
||||||
expression,
|
|
||||||
target: this.target,
|
|
||||||
resultOwnership,
|
resultOwnership,
|
||||||
awaitPromise: true,
|
|
||||||
userActivation: true,
|
userActivation: true,
|
||||||
serializationOptions,
|
serializationOptions,
|
||||||
});
|
});
|
||||||
@ -179,24 +141,25 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|||||||
functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration)
|
functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration)
|
||||||
? functionDeclaration
|
? functionDeclaration
|
||||||
: `${functionDeclaration}\n${sourceUrlComment}\n`;
|
: `${functionDeclaration}\n${sourceUrlComment}\n`;
|
||||||
responsePromise = this.connection.send('script.callFunction', {
|
responsePromise = this.realm.callFunction(
|
||||||
functionDeclaration,
|
functionDeclaration,
|
||||||
|
/* awaitPromise= */ true,
|
||||||
|
{
|
||||||
arguments: args.length
|
arguments: args.length
|
||||||
? await Promise.all(
|
? await Promise.all(
|
||||||
args.map(arg => {
|
args.map(arg => {
|
||||||
return sandbox.serialize(arg);
|
return this.serialize(arg);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
: [],
|
: [],
|
||||||
target: this.target,
|
|
||||||
resultOwnership,
|
resultOwnership,
|
||||||
awaitPromise: true,
|
|
||||||
userActivation: true,
|
userActivation: true,
|
||||||
serializationOptions,
|
serializationOptions,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {result} = await responsePromise;
|
const result = await responsePromise;
|
||||||
|
|
||||||
if ('type' in result && result.type === 'exception') {
|
if ('type' in result && result.type === 'exception') {
|
||||||
throw createEvaluationError(result.exceptionDetails);
|
throw createEvaluationError(result.exceptionDetails);
|
||||||
@ -204,7 +167,49 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|||||||
|
|
||||||
return returnByValue
|
return returnByValue
|
||||||
? BidiDeserializer.deserialize(result.result)
|
? BidiDeserializer.deserialize(result.result)
|
||||||
: createBidiHandle(sandbox, result.result);
|
: this.createHandle(result.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
createHandle(
|
||||||
|
result: Bidi.Script.RemoteValue
|
||||||
|
): BidiJSHandle<unknown> | BidiElementHandle<Node> {
|
||||||
|
if (
|
||||||
|
(result.type === 'node' || result.type === 'window') &&
|
||||||
|
this instanceof BidiFrameRealm
|
||||||
|
) {
|
||||||
|
return BidiElementHandle.from(result, this);
|
||||||
|
}
|
||||||
|
return BidiJSHandle.from(result, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async serialize(arg: unknown): Promise<Bidi.Script.LocalValue> {
|
||||||
|
if (arg instanceof LazyArg) {
|
||||||
|
arg = await arg.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg instanceof BidiJSHandle || arg instanceof BidiElementHandle) {
|
||||||
|
if (arg.realm !== this) {
|
||||||
|
if (
|
||||||
|
!(arg.realm instanceof BidiFrameRealm) ||
|
||||||
|
!(this instanceof BidiFrameRealm)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Trying to evaluate JSHandle from different global types. Usually this means you're using a handle from a worker in a page or vice versa."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (arg.realm.environment !== this.environment) {
|
||||||
|
throw new Error(
|
||||||
|
"Trying to evaluate JSHandle from different frames. Usually this means you're using a handle from a page on a different page."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arg.disposed) {
|
||||||
|
throw new Error('JSHandle is disposed!');
|
||||||
|
}
|
||||||
|
return arg.remoteValue() as Bidi.Script.RemoteReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BidiSerializer.serialize(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroyHandles(handles: Array<BidiJSHandle<unknown>>): Promise<void> {
|
async destroyHandles(handles: Array<BidiJSHandle<unknown>>): Promise<void> {
|
||||||
@ -215,43 +220,80 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
|
|||||||
.filter((id): id is string => {
|
.filter((id): id is string => {
|
||||||
return id !== undefined;
|
return id !== undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (handleIds.length === 0) {
|
if (handleIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.connection
|
await this.realm.disown(handleIds).catch(error => {
|
||||||
.send('script.disown', {
|
|
||||||
target: this.target,
|
|
||||||
handles: handleIds,
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Exceptions might happen in case of a page been navigated or closed.
|
// Exceptions might happen in case of a page been navigated or closed.
|
||||||
// Swallow these since they are harmless and we don't leak anything in this case.
|
// Swallow these since they are harmless and we don't leak anything in this case.
|
||||||
debugError(error);
|
debugError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[disposeSymbol](): void {
|
override async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||||
this.connection.off(
|
return (await this.evaluateHandle(node => {
|
||||||
Bidi.ChromiumBidi.Script.EventNames.RealmCreated,
|
return node;
|
||||||
this.handleRealmCreated
|
}, handle)) as unknown as T;
|
||||||
);
|
}
|
||||||
this.connection.off(
|
|
||||||
Bidi.ChromiumBidi.Script.EventNames.RealmDestroyed,
|
override async transferHandle<T extends JSHandle<Node>>(
|
||||||
this.handleRealmDestroyed
|
handle: T
|
||||||
);
|
): Promise<T> {
|
||||||
|
if (handle.realm === this) {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
const transferredHandle = this.adoptHandle(handle);
|
||||||
|
await handle.dispose();
|
||||||
|
return await transferredHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class BidiFrameRealm extends BidiRealm {
|
||||||
* @internal
|
static from(realm: WindowRealm, frame: BidiFrame): BidiFrameRealm {
|
||||||
*/
|
const frameRealm = new BidiFrameRealm(realm, frame);
|
||||||
export function createBidiHandle(
|
frameRealm.#initialize();
|
||||||
sandbox: Sandbox,
|
return frameRealm;
|
||||||
result: Bidi.Script.RemoteValue
|
}
|
||||||
): BidiJSHandle<unknown> | BidiElementHandle<Node> {
|
declare readonly realm: WindowRealm;
|
||||||
if (result.type === 'node' || result.type === 'window') {
|
|
||||||
return new BidiElementHandle(sandbox, result);
|
readonly #frame: BidiFrame;
|
||||||
|
|
||||||
|
private constructor(realm: WindowRealm, frame: BidiFrame) {
|
||||||
|
super(realm, frame.timeoutSettings);
|
||||||
|
this.#frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
#initialize() {
|
||||||
|
// This should run first.
|
||||||
|
this.realm.on('updated', () => {
|
||||||
|
this.environment.clearDocumentHandle();
|
||||||
|
});
|
||||||
|
|
||||||
|
super.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
get sandbox(): string | undefined {
|
||||||
|
return this.realm.sandbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
override get environment(): BidiFrame {
|
||||||
|
return this.#frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async adoptBackendNode(
|
||||||
|
backendNodeId?: number | undefined
|
||||||
|
): Promise<JSHandle<Node>> {
|
||||||
|
const {object} = await this.#frame.client.send('DOM.resolveNode', {
|
||||||
|
backendNodeId,
|
||||||
|
});
|
||||||
|
return BidiElementHandle.from(
|
||||||
|
{
|
||||||
|
handle: object.objectId,
|
||||||
|
type: 'node',
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return new BidiJSHandle(sandbox, result);
|
|
||||||
}
|
}
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2023 Google Inc.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|
||||||
|
|
||||||
import type {JSHandle} from '../api/JSHandle.js';
|
|
||||||
import {Realm} from '../api/Realm.js';
|
|
||||||
import {LazyArg} from '../common/LazyArg.js';
|
|
||||||
import type {TimeoutSettings} from '../common/TimeoutSettings.js';
|
|
||||||
import type {EvaluateFunc, HandleFor} from '../common/types.js';
|
|
||||||
import {withSourcePuppeteerURLIfNone} from '../common/util.js';
|
|
||||||
|
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
|
||||||
import {BidiElementHandle} from './ElementHandle.js';
|
|
||||||
import type {BidiFrame} from './Frame.js';
|
|
||||||
import {BidiJSHandle} from './JSHandle.js';
|
|
||||||
import type {BidiRealm as BidiRealm} from './Realm.js';
|
|
||||||
import {BidiSerializer} from './Serializer.js';
|
|
||||||
/**
|
|
||||||
* A unique key for {@link SandboxChart} to denote the default world.
|
|
||||||
* Realms are automatically created in the default sandbox.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export const MAIN_SANDBOX = Symbol('mainSandbox');
|
|
||||||
/**
|
|
||||||
* A unique key for {@link SandboxChart} to denote the puppeteer sandbox.
|
|
||||||
* This world contains all puppeteer-internal bindings/code.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export const PUPPETEER_SANDBOX = Symbol('puppeteerSandbox');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export interface SandboxChart {
|
|
||||||
[key: string]: Sandbox;
|
|
||||||
[MAIN_SANDBOX]: Sandbox;
|
|
||||||
[PUPPETEER_SANDBOX]: Sandbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class Sandbox extends Realm {
|
|
||||||
readonly name: string | undefined;
|
|
||||||
readonly realm: BidiRealm;
|
|
||||||
#frame: BidiFrame;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string | undefined,
|
|
||||||
frame: BidiFrame,
|
|
||||||
// TODO: We should split the Realm and BrowsingContext
|
|
||||||
realm: BidiRealm | BrowsingContext,
|
|
||||||
timeoutSettings: TimeoutSettings
|
|
||||||
) {
|
|
||||||
super(timeoutSettings);
|
|
||||||
this.name = name;
|
|
||||||
this.realm = realm;
|
|
||||||
this.#frame = frame;
|
|
||||||
this.realm.setSandbox(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
override get environment(): BidiFrame {
|
|
||||||
return this.#frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
async evaluateHandle<
|
|
||||||
Params extends unknown[],
|
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
|
||||||
>(
|
|
||||||
pageFunction: Func | string,
|
|
||||||
...args: Params
|
|
||||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
|
||||||
pageFunction = withSourcePuppeteerURLIfNone(
|
|
||||||
this.evaluateHandle.name,
|
|
||||||
pageFunction
|
|
||||||
);
|
|
||||||
return await this.realm.evaluateHandle(pageFunction, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async evaluate<
|
|
||||||
Params extends unknown[],
|
|
||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
|
||||||
>(
|
|
||||||
pageFunction: Func | string,
|
|
||||||
...args: Params
|
|
||||||
): Promise<Awaited<ReturnType<Func>>> {
|
|
||||||
pageFunction = withSourcePuppeteerURLIfNone(
|
|
||||||
this.evaluate.name,
|
|
||||||
pageFunction
|
|
||||||
);
|
|
||||||
return await this.realm.evaluate(pageFunction, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
|
||||||
return (await this.evaluateHandle(node => {
|
|
||||||
return node;
|
|
||||||
}, handle)) as unknown as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
|
||||||
if (handle.realm === this) {
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
const transferredHandle = await this.evaluateHandle(node => {
|
|
||||||
return node;
|
|
||||||
}, handle);
|
|
||||||
await handle.dispose();
|
|
||||||
return transferredHandle as unknown as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
override async adoptBackendNode(
|
|
||||||
backendNodeId?: number
|
|
||||||
): Promise<JSHandle<Node>> {
|
|
||||||
const {object} = await this.environment.client.send('DOM.resolveNode', {
|
|
||||||
backendNodeId: backendNodeId,
|
|
||||||
});
|
|
||||||
return new BidiElementHandle(this, {
|
|
||||||
handle: object.objectId,
|
|
||||||
type: 'node',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async serialize(arg: unknown): Promise<Bidi.Script.LocalValue> {
|
|
||||||
if (arg instanceof LazyArg) {
|
|
||||||
arg = await arg.get(this.realm);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line rulesdir/use-using -- We want this to continue living.
|
|
||||||
const objectHandle =
|
|
||||||
arg && (arg instanceof BidiJSHandle || arg instanceof BidiElementHandle)
|
|
||||||
? arg
|
|
||||||
: null;
|
|
||||||
if (objectHandle) {
|
|
||||||
if (
|
|
||||||
objectHandle.realm.environment.context() !== this.environment.context()
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
'JSHandles can be evaluated only in the context they were created!'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (objectHandle.disposed) {
|
|
||||||
throw new Error('JSHandle is disposed!');
|
|
||||||
}
|
|
||||||
return objectHandle.remoteValue() as Bidi.Script.RemoteReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BidiSerializer.serialize(arg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,57 +4,19 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {CDPSession} from '../api/CDPSession.js';
|
|
||||||
import type {Page} from '../api/Page.js';
|
|
||||||
import {Target, TargetType} from '../api/Target.js';
|
import {Target, TargetType} from '../api/Target.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
|
import type {CDPSession} from '../puppeteer-core.js';
|
||||||
|
|
||||||
import type {BidiBrowser} from './Browser.js';
|
import type {BidiBrowser} from './Browser.js';
|
||||||
import type {BidiBrowserContext} from './BrowserContext.js';
|
import type {BidiBrowserContext} from './BrowserContext.js';
|
||||||
import type {BrowsingContext} from './BrowsingContext.js';
|
import type {BidiFrame} from './Frame.js';
|
||||||
import {BidiCdpSession} from './CDPSession.js';
|
|
||||||
import {BidiPage} from './Page.js';
|
import {BidiPage} from './Page.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export abstract class BidiTarget extends Target {
|
export class BidiBrowserTarget extends Target {
|
||||||
protected _browserContext: BidiBrowserContext;
|
|
||||||
|
|
||||||
constructor(browserContext: BidiBrowserContext) {
|
|
||||||
super();
|
|
||||||
this._browserContext = browserContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setBrowserContext(browserContext: BidiBrowserContext): void {
|
|
||||||
this._browserContext = browserContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
override asPage(): Promise<Page> {
|
|
||||||
throw new UnsupportedOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
override browser(): BidiBrowser {
|
|
||||||
return this._browserContext.browser();
|
|
||||||
}
|
|
||||||
|
|
||||||
override browserContext(): BidiBrowserContext {
|
|
||||||
return this._browserContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
override opener(): never {
|
|
||||||
throw new UnsupportedOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
override createCDPSession(): Promise<CDPSession> {
|
|
||||||
throw new UnsupportedOperation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class BiDiBrowserTarget extends Target {
|
|
||||||
#browser: BidiBrowser;
|
#browser: BidiBrowser;
|
||||||
|
|
||||||
constructor(browser: BidiBrowser) {
|
constructor(browser: BidiBrowser) {
|
||||||
@ -62,91 +24,109 @@ export class BiDiBrowserTarget extends Target {
|
|||||||
this.#browser = browser;
|
this.#browser = browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override asPage(): Promise<BidiPage> {
|
||||||
|
throw new UnsupportedOperation();
|
||||||
|
}
|
||||||
override url(): string {
|
override url(): string {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
override type(): TargetType {
|
|
||||||
return TargetType.BROWSER;
|
|
||||||
}
|
|
||||||
|
|
||||||
override asPage(): Promise<Page> {
|
|
||||||
throw new UnsupportedOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
override browser(): BidiBrowser {
|
|
||||||
return this.#browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
override browserContext(): BidiBrowserContext {
|
|
||||||
return this.#browser.defaultBrowserContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
override opener(): never {
|
|
||||||
throw new UnsupportedOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
override createCDPSession(): Promise<CDPSession> {
|
override createCDPSession(): Promise<CDPSession> {
|
||||||
throw new UnsupportedOperation();
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class BiDiBrowsingContextTarget extends BidiTarget {
|
|
||||||
protected _browsingContext: BrowsingContext;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
browserContext: BidiBrowserContext,
|
|
||||||
browsingContext: BrowsingContext
|
|
||||||
) {
|
|
||||||
super(browserContext);
|
|
||||||
|
|
||||||
this._browsingContext = browsingContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
override url(): string {
|
|
||||||
return this._browsingContext.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
override async createCDPSession(): Promise<CDPSession> {
|
|
||||||
const {sessionId} = await this._browsingContext.cdpSession.send(
|
|
||||||
'Target.attachToTarget',
|
|
||||||
{
|
|
||||||
targetId: this._browsingContext.id,
|
|
||||||
flatten: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return new BidiCdpSession(this._browsingContext, sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
override type(): TargetType {
|
override type(): TargetType {
|
||||||
return TargetType.PAGE;
|
return TargetType.BROWSER;
|
||||||
|
}
|
||||||
|
override browser(): BidiBrowser {
|
||||||
|
return this.#browser;
|
||||||
|
}
|
||||||
|
override browserContext(): BidiBrowserContext {
|
||||||
|
return this.#browser.defaultBrowserContext();
|
||||||
|
}
|
||||||
|
override opener(): Target | undefined {
|
||||||
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export class BiDiPageTarget extends BiDiBrowsingContextTarget {
|
export class BidiPageTarget extends Target {
|
||||||
#page: BidiPage;
|
#page: BidiPage;
|
||||||
|
|
||||||
constructor(
|
constructor(page: BidiPage) {
|
||||||
browserContext: BidiBrowserContext,
|
super();
|
||||||
browsingContext: BrowsingContext
|
this.#page = page;
|
||||||
) {
|
|
||||||
super(browserContext, browsingContext);
|
|
||||||
|
|
||||||
this.#page = new BidiPage(browsingContext, browserContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async page(): Promise<BidiPage> {
|
override async page(): Promise<BidiPage> {
|
||||||
return this.#page;
|
return this.#page;
|
||||||
}
|
}
|
||||||
|
override async asPage(): Promise<BidiPage> {
|
||||||
|
return BidiPage.from(
|
||||||
|
this.browserContext(),
|
||||||
|
this.#page.mainFrame().browsingContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
override url(): string {
|
||||||
|
return this.#page.url();
|
||||||
|
}
|
||||||
|
override createCDPSession(): Promise<CDPSession> {
|
||||||
|
return this.#page.createCDPSession();
|
||||||
|
}
|
||||||
|
override type(): TargetType {
|
||||||
|
return TargetType.PAGE;
|
||||||
|
}
|
||||||
|
override browser(): BidiBrowser {
|
||||||
|
return this.browserContext().browser();
|
||||||
|
}
|
||||||
|
override browserContext(): BidiBrowserContext {
|
||||||
|
return this.#page.browserContext();
|
||||||
|
}
|
||||||
|
override opener(): Target | undefined {
|
||||||
|
throw new UnsupportedOperation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override _setBrowserContext(browserContext: BidiBrowserContext): void {
|
/**
|
||||||
super._setBrowserContext(browserContext);
|
* @internal
|
||||||
this.#page._setBrowserContext(browserContext);
|
*/
|
||||||
|
export class BidiFrameTarget extends Target {
|
||||||
|
#frame: BidiFrame;
|
||||||
|
#page: BidiPage | undefined;
|
||||||
|
|
||||||
|
constructor(frame: BidiFrame) {
|
||||||
|
super();
|
||||||
|
this.#frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async page(): Promise<BidiPage> {
|
||||||
|
if (this.#page === undefined) {
|
||||||
|
this.#page = BidiPage.from(
|
||||||
|
this.browserContext(),
|
||||||
|
this.#frame.browsingContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.#page;
|
||||||
|
}
|
||||||
|
override async asPage(): Promise<BidiPage> {
|
||||||
|
return BidiPage.from(this.browserContext(), this.#frame.browsingContext);
|
||||||
|
}
|
||||||
|
override url(): string {
|
||||||
|
return this.#frame.url();
|
||||||
|
}
|
||||||
|
override createCDPSession(): Promise<CDPSession> {
|
||||||
|
return this.#frame.createCDPSession();
|
||||||
|
}
|
||||||
|
override type(): TargetType {
|
||||||
|
return TargetType.PAGE;
|
||||||
|
}
|
||||||
|
override browser(): BidiBrowser {
|
||||||
|
return this.browserContext().browser();
|
||||||
|
}
|
||||||
|
override browserContext(): BidiBrowserContext {
|
||||||
|
return this.#frame.page().browserContext();
|
||||||
|
}
|
||||||
|
override opener(): Target | undefined {
|
||||||
|
throw new UnsupportedOperation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
export * from './BidiOverCdp.js';
|
export * from './BidiOverCdp.js';
|
||||||
export * from './Browser.js';
|
export * from './Browser.js';
|
||||||
export * from './BrowserContext.js';
|
export * from './BrowserContext.js';
|
||||||
export * from './BrowsingContext.js';
|
|
||||||
export * from './Connection.js';
|
export * from './Connection.js';
|
||||||
export * from './ElementHandle.js';
|
export * from './ElementHandle.js';
|
||||||
export * from './Frame.js';
|
export * from './Frame.js';
|
||||||
@ -15,8 +14,5 @@ export * from './HTTPRequest.js';
|
|||||||
export * from './HTTPResponse.js';
|
export * from './HTTPResponse.js';
|
||||||
export * from './Input.js';
|
export * from './Input.js';
|
||||||
export * from './JSHandle.js';
|
export * from './JSHandle.js';
|
||||||
export * from './NetworkManager.js';
|
|
||||||
export * from './Page.js';
|
export * from './Page.js';
|
||||||
export * from './Realm.js';
|
export * from './Realm.js';
|
||||||
export * from './Sandbox.js';
|
|
||||||
export * from './Target.js';
|
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright 2023 Google Inc.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ObservableInput,
|
|
||||||
ObservedValueOf,
|
|
||||||
OperatorFunction,
|
|
||||||
} from '../../third_party/rxjs/rxjs.js';
|
|
||||||
import {catchError} from '../../third_party/rxjs/rxjs.js';
|
|
||||||
import type {PuppeteerLifeCycleEvent} from '../cdp/LifecycleWatcher.js';
|
|
||||||
import {ProtocolError, TimeoutError} from '../common/Errors.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export type BiDiNetworkIdle = Extract<
|
|
||||||
PuppeteerLifeCycleEvent,
|
|
||||||
'networkidle0' | 'networkidle2'
|
|
||||||
> | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export function getBiDiLifeCycles(
|
|
||||||
event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]
|
|
||||||
): [
|
|
||||||
Extract<PuppeteerLifeCycleEvent, 'load' | 'domcontentloaded'>,
|
|
||||||
BiDiNetworkIdle,
|
|
||||||
] {
|
|
||||||
if (Array.isArray(event)) {
|
|
||||||
const pageLifeCycle = event.some(lifeCycle => {
|
|
||||||
return lifeCycle !== 'domcontentloaded';
|
|
||||||
})
|
|
||||||
? 'load'
|
|
||||||
: 'domcontentloaded';
|
|
||||||
|
|
||||||
const networkLifeCycle = event.reduce((acc, lifeCycle) => {
|
|
||||||
if (lifeCycle === 'networkidle0') {
|
|
||||||
return lifeCycle;
|
|
||||||
} else if (acc !== 'networkidle0' && lifeCycle === 'networkidle2') {
|
|
||||||
return lifeCycle;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, null as BiDiNetworkIdle);
|
|
||||||
|
|
||||||
return [pageLifeCycle, networkLifeCycle];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event === 'networkidle0' || event === 'networkidle2') {
|
|
||||||
return ['load', event];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [event, null];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export const lifeCycleToReadinessState = new Map<
|
|
||||||
PuppeteerLifeCycleEvent,
|
|
||||||
Bidi.BrowsingContext.ReadinessState
|
|
||||||
>([
|
|
||||||
['load', Bidi.BrowsingContext.ReadinessState.Complete],
|
|
||||||
['domcontentloaded', Bidi.BrowsingContext.ReadinessState.Interactive],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function getBiDiReadinessState(
|
|
||||||
event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]
|
|
||||||
): [Bidi.BrowsingContext.ReadinessState, BiDiNetworkIdle] {
|
|
||||||
const lifeCycles = getBiDiLifeCycles(event);
|
|
||||||
const readiness = lifeCycleToReadinessState.get(lifeCycles[0])!;
|
|
||||||
return [readiness, lifeCycles[1]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export const lifeCycleToSubscribedEvent = new Map<
|
|
||||||
PuppeteerLifeCycleEvent,
|
|
||||||
'browsingContext.load' | 'browsingContext.domContentLoaded'
|
|
||||||
>([
|
|
||||||
['load', 'browsingContext.load'],
|
|
||||||
['domcontentloaded', 'browsingContext.domContentLoaded'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export function getBiDiLifecycleEvent(
|
|
||||||
event: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]
|
|
||||||
): [
|
|
||||||
'browsingContext.load' | 'browsingContext.domContentLoaded',
|
|
||||||
BiDiNetworkIdle,
|
|
||||||
] {
|
|
||||||
const lifeCycles = getBiDiLifeCycles(event);
|
|
||||||
const bidiEvent = lifeCycleToSubscribedEvent.get(lifeCycles[0])!;
|
|
||||||
return [bidiEvent, lifeCycles[1]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export function rewriteNavigationError<T, R extends ObservableInput<T>>(
|
|
||||||
message: string,
|
|
||||||
ms: number
|
|
||||||
): OperatorFunction<T, T | ObservedValueOf<R>> {
|
|
||||||
return catchError<T, R>(error => {
|
|
||||||
if (error instanceof ProtocolError) {
|
|
||||||
error.message += ` at ${message}`;
|
|
||||||
} else if (error instanceof TimeoutError) {
|
|
||||||
error.message = `Navigation timeout of ${ms} ms exceeded`;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
|
import {ProtocolError, TimeoutError} from '../common/Errors.js';
|
||||||
import {PuppeteerURL} from '../common/util.js';
|
import {PuppeteerURL} from '../common/util.js';
|
||||||
|
|
||||||
import {BidiDeserializer} from './Deserializer.js';
|
import {BidiDeserializer} from './Deserializer.js';
|
||||||
@ -56,3 +57,20 @@ export function createEvaluationError(
|
|||||||
error.stack = [details.text, ...stackLines].join('\n');
|
error.stack = [details.text, ...stackLines].join('\n');
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function rewriteNavigationError(
|
||||||
|
message: string,
|
||||||
|
ms: number
|
||||||
|
): (error: unknown) => never {
|
||||||
|
return error => {
|
||||||
|
if (error instanceof ProtocolError) {
|
||||||
|
error.message += ` at ${message}`;
|
||||||
|
} else if (error instanceof TimeoutError) {
|
||||||
|
error.message = `Navigation timeout of ${ms} ms exceeded`;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -6,8 +6,10 @@
|
|||||||
export {
|
export {
|
||||||
bufferCount,
|
bufferCount,
|
||||||
catchError,
|
catchError,
|
||||||
|
combineLatest,
|
||||||
concat,
|
concat,
|
||||||
concatMap,
|
concatMap,
|
||||||
|
debounceTime,
|
||||||
defaultIfEmpty,
|
defaultIfEmpty,
|
||||||
defer,
|
defer,
|
||||||
delay,
|
delay,
|
||||||
@ -22,7 +24,6 @@ export {
|
|||||||
ignoreElements,
|
ignoreElements,
|
||||||
lastValueFrom,
|
lastValueFrom,
|
||||||
map,
|
map,
|
||||||
ReplaySubject,
|
|
||||||
merge,
|
merge,
|
||||||
mergeMap,
|
mergeMap,
|
||||||
mergeScan,
|
mergeScan,
|
||||||
@ -33,6 +34,7 @@ export {
|
|||||||
pipe,
|
pipe,
|
||||||
race,
|
race,
|
||||||
raceWith,
|
raceWith,
|
||||||
|
ReplaySubject,
|
||||||
retry,
|
retry,
|
||||||
startWith,
|
startWith,
|
||||||
switchMap,
|
switchMap,
|
||||||
|
@ -537,7 +537,7 @@
|
|||||||
"testIdPattern": "[coverage.spec] Coverage specs JSCoverage should ignore pptr internal scripts if reportAnonymousScripts is true",
|
"testIdPattern": "[coverage.spec] Coverage specs JSCoverage should ignore pptr internal scripts if reportAnonymousScripts is true",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[devtools.spec] DevTools should expose DevTools as a page",
|
"testIdPattern": "[devtools.spec] DevTools should expose DevTools as a page",
|
||||||
@ -579,7 +579,7 @@
|
|||||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should return Point data",
|
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should return Point data",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should not work if the click box is not visible due to the iframe",
|
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.clickablePoint should not work if the click box is not visible due to the iframe",
|
||||||
@ -670,7 +670,7 @@
|
|||||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should support lazy frames",
|
"testIdPattern": "[frame.spec] Frame specs Frame Management should support lazy frames",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome"],
|
"parameters": ["chrome"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[headful.spec] *",
|
"testIdPattern": "[headful.spec] *",
|
||||||
@ -712,7 +712,7 @@
|
|||||||
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should use the same JS wrappers",
|
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should use the same JS wrappers",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter in iframe",
|
"testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter in iframe",
|
||||||
@ -820,7 +820,8 @@
|
|||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when navigating to bad SSL",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when navigating to bad SSL",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox"],
|
"parameters": ["firefox"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/657"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should send referer",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should send referer",
|
||||||
@ -846,12 +847,6 @@
|
|||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"testIdPattern": "[network.spec] network raw network headers Cross-origin set-cookie",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["webDriverBiDi"],
|
|
||||||
"expectations": ["FAIL", "PASS"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testIdPattern": "[network.spec] network Request.initiator should return the initiator",
|
"testIdPattern": "[network.spec] network Request.initiator should return the initiator",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -870,6 +865,13 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[network.spec] network Request.postData should be |undefined| when there is no post data",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"],
|
||||||
|
"comment": "Unsupported"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[network.spec] network Request.postData should work",
|
"testIdPattern": "[network.spec] network Request.postData should work",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -1218,7 +1220,7 @@
|
|||||||
"testIdPattern": "[target.spec] Target Browser.waitForTarget should timeout waiting for a non-existent target",
|
"testIdPattern": "[target.spec] Target Browser.waitForTarget should timeout waiting for a non-existent target",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[target.spec] Target should be able to use async waitForTarget",
|
"testIdPattern": "[target.spec] Target should be able to use async waitForTarget",
|
||||||
@ -1609,7 +1611,7 @@
|
|||||||
"testIdPattern": "[click.spec] Page.click should click on checkbox input and toggle",
|
"testIdPattern": "[click.spec] Page.click should click on checkbox input and toggle",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle",
|
"testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle",
|
||||||
@ -1783,13 +1785,13 @@
|
|||||||
"testIdPattern": "[coverage.spec] Coverage specs CSSCoverage should work with complicated usecases",
|
"testIdPattern": "[coverage.spec] Coverage specs CSSCoverage should work with complicated usecases",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[coverage.spec] Coverage specs JSCoverage should not ignore eval() scripts if reportAnonymousScripts is true",
|
"testIdPattern": "[coverage.spec] Coverage specs JSCoverage should not ignore eval() scripts if reportAnonymousScripts is true",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[debugInfo.spec] DebugInfo Browser.debugInfo should work",
|
"testIdPattern": "[debugInfo.spec] DebugInfo Browser.debugInfo should work",
|
||||||
@ -2125,7 +2127,7 @@
|
|||||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should await promise",
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should await promise",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture",
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture",
|
||||||
@ -2198,7 +2200,8 @@
|
|||||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should detach child frames on navigation",
|
"testIdPattern": "[frame.spec] Frame specs Frame Management should detach child frames on navigation",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["FAIL"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/659"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should report different frame instance when frame re-attaches",
|
"testIdPattern": "[frame.spec] Frame specs Frame Management should report different frame instance when frame re-attaches",
|
||||||
@ -2236,6 +2239,13 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[frame.spec] Frame specs Frame Management should send \"framenavigated\" when navigating on anchor URLs",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/657"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should send events when frames are manipulated dynamically",
|
"testIdPattern": "[frame.spec] Frame specs Frame Management should send events when frames are manipulated dynamically",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -2252,7 +2262,8 @@
|
|||||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should support framesets",
|
"testIdPattern": "[frame.spec] Frame specs Frame Management should support framesets",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["FAIL"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/659"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[frame.spec] Frame specs Frame Management should support lazy frames",
|
"testIdPattern": "[frame.spec] Frame specs Frame Management should support lazy frames",
|
||||||
@ -3015,7 +3026,7 @@
|
|||||||
"testIdPattern": "[navigation.spec] navigation Frame.goto should navigate subframes",
|
"testIdPattern": "[navigation.spec] navigation Frame.goto should navigate subframes",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Frame.goto should reject when frame detaches",
|
"testIdPattern": "[navigation.spec] navigation Frame.goto should reject when frame detaches",
|
||||||
@ -3059,11 +3070,33 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[navigation.spec] navigation Page.goBack should work with HistoryAPI",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "browsingContext.navigationStarted event not emitted for fragment navigation"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when main resources failed to load",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when main resources failed to load",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/657"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when navigating and show the url at the error message",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/657"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when navigating to bad SSL after redirects",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/657"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when server returns 204",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when server returns 204",
|
||||||
@ -3071,6 +3104,13 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[navigation.spec] navigation Page.goto should fail when server returns 204",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "https://github.com/w3c/webdriver-bidi/issues/657"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should navigate to dataURL and fire dataURL requests",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should navigate to dataURL and fire dataURL requests",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -3087,7 +3127,7 @@
|
|||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should navigate to dataURL and fire dataURL requests",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should navigate to dataURL and fire dataURL requests",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should navigate to empty page with networkidle0",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should navigate to empty page with networkidle0",
|
||||||
@ -3131,11 +3171,17 @@
|
|||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[navigation.spec] navigation Page.goto should return last response in redirect chain",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["PASS"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should return response when page changes its URL after load",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should return response when page changes its URL after load",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should send referer",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should send referer",
|
||||||
@ -3161,18 +3207,19 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should work when page calls history API in beforeunload",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
|
||||||
"expectations": ["FAIL"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should work with anchor navigation",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should work with anchor navigation",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome", "chrome-headless-shell"],
|
"parameters": ["chrome", "chrome-headless-shell"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[navigation.spec] navigation Page.goto should work with anchor navigation",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "browsingContext.navigationStarted event not emitted for fragment navigation"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.goto should work with subframes return 204",
|
"testIdPattern": "[navigation.spec] navigation Page.goto should work with subframes return 204",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -3209,6 +3256,13 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with DOM history.back()/history.forward()",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "browsingContext.navigationStarted event not emitted for fragment navigation"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.pushState()",
|
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.pushState()",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -3217,9 +3271,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.pushState()",
|
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.pushState()",
|
||||||
"platforms": ["linux"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "browsingContext.navigationStarted event not emitted for fragment navigation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.replaceState()",
|
"testIdPattern": "[navigation.spec] navigation Page.waitForNavigation should work with history.replaceState()",
|
||||||
@ -3268,7 +3323,7 @@
|
|||||||
"testIdPattern": "[network.spec] network Network Events Page.Events.RequestServedFromCache",
|
"testIdPattern": "[network.spec] network Network Events Page.Events.RequestServedFromCache",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[network.spec] network Network Events Page.Events.Response",
|
"testIdPattern": "[network.spec] network Network Events Page.Events.Response",
|
||||||
@ -3342,6 +3397,12 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[network.spec] network raw network headers Cross-origin set-cookie",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL", "PASS"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[network.spec] network raw network headers Same-origin set-cookie navigation",
|
"testIdPattern": "[network.spec] network raw network headers Same-origin set-cookie navigation",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -3388,7 +3449,7 @@
|
|||||||
"testIdPattern": "[network.spec] network Request.frame should work for subframe navigation request",
|
"testIdPattern": "[network.spec] network Request.frame should work for subframe navigation request",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[network.spec] network Request.initiator should return the initiator",
|
"testIdPattern": "[network.spec] network Request.initiator should return the initiator",
|
||||||
@ -3455,7 +3516,7 @@
|
|||||||
"testIdPattern": "[network.spec] network Response.fromCache should work",
|
"testIdPattern": "[network.spec] network Response.fromCache should work",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[network.spec] network Response.fromCache should work",
|
"testIdPattern": "[network.spec] network Response.fromCache should work",
|
||||||
@ -3529,12 +3590,6 @@
|
|||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"testIdPattern": "[oopif.spec] OOPIF should detect existing OOPIFs when Puppeteer connects to an existing page",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
|
||||||
"expectations": ["FAIL"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testIdPattern": "[oopif.spec] OOPIF should detect existing OOPIFs when Puppeteer connects to an existing page",
|
"testIdPattern": "[oopif.spec] OOPIF should detect existing OOPIFs when Puppeteer connects to an existing page",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -3597,28 +3652,18 @@
|
|||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[oopif.spec] OOPIF should report oopif frames",
|
"testIdPattern": "[oopif.spec] OOPIF should support frames within OOP iframes",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
|
||||||
"expectations": ["FAIL"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"testIdPattern": "[oopif.spec] OOPIF should report oopif frames",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "Fetch error"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames",
|
"testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["FAIL"],
|
||||||
},
|
"comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=187816"
|
||||||
{
|
|
||||||
"testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
|
||||||
"expectations": ["PASS"]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames",
|
"testIdPattern": "[oopif.spec] OOPIF should support lazy OOP frames",
|
||||||
@ -3678,7 +3723,8 @@
|
|||||||
"testIdPattern": "[oopif.spec] OOPIF should wait for inner OOPIFs",
|
"testIdPattern": "[oopif.spec] OOPIF should wait for inner OOPIFs",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["SKIP"],
|
||||||
|
"comment": "Fetch error"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[page.spec] Page Page.addScriptTag should throw when added with content to the CSP page",
|
"testIdPattern": "[page.spec] Page Page.addScriptTag should throw when added with content to the CSP page",
|
||||||
@ -4080,7 +4126,7 @@
|
|||||||
"testIdPattern": "[proxy.spec] request proxy should respect proxy bypass list",
|
"testIdPattern": "[proxy.spec] request proxy should respect proxy bypass list",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[proxy.spec] request proxy should respect proxy bypass list",
|
"testIdPattern": "[proxy.spec] request proxy should respect proxy bypass list",
|
||||||
@ -4224,7 +4270,7 @@
|
|||||||
"testIdPattern": "[target.spec] Target Browser.waitForTarget should wait for a target",
|
"testIdPattern": "[target.spec] Target Browser.waitForTarget should wait for a target",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[target.spec] Target Browser.waitForTarget should wait for a target",
|
"testIdPattern": "[target.spec] Target Browser.waitForTarget should wait for a target",
|
||||||
@ -4232,12 +4278,6 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"testIdPattern": "[target.spec] Target Browser.waitForTarget should wait for a target",
|
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
|
||||||
"parameters": ["firefox", "webDriverBiDi"],
|
|
||||||
"expectations": ["FAIL"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testIdPattern": "[target.spec] Target should be able to use async waitForTarget",
|
"testIdPattern": "[target.spec] Target should be able to use async waitForTarget",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -4332,7 +4372,7 @@
|
|||||||
"testIdPattern": "[target.spec] Target should not crash while redirecting if original request was missed",
|
"testIdPattern": "[target.spec] Target should not crash while redirecting if original request was missed",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["chrome", "webDriverBiDi"],
|
"parameters": ["chrome", "webDriverBiDi"],
|
||||||
"expectations": ["FAIL", "PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[target.spec] Target should not report uninitialized pages",
|
"testIdPattern": "[target.spec] Target should not report uninitialized pages",
|
||||||
|
@ -370,9 +370,10 @@ describe('AriaQueryHandler', () => {
|
|||||||
await detachFrame(page, 'frame1');
|
await detachFrame(page, 'frame1');
|
||||||
await waitPromise;
|
await waitPromise;
|
||||||
expect(waitError).toBeTruthy();
|
expect(waitError).toBeTruthy();
|
||||||
expect(waitError.message).toContain(
|
expect(waitError.message).atLeastOneToContain([
|
||||||
'waitForFunction failed: frame got detached.'
|
'waitForFunction failed: frame got detached.',
|
||||||
);
|
'Browsing context already closed.',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should survive cross-process navigation', async () => {
|
it('should survive cross-process navigation', async () => {
|
||||||
|
@ -408,9 +408,10 @@ describe('Evaluation specs', function () {
|
|||||||
return (error = error_);
|
return (error = error_);
|
||||||
});
|
});
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
expect(error.message).toContain(
|
expect(error.message).atLeastOneToContain([
|
||||||
'JSHandles can be evaluated only in the context they were created'
|
'JSHandles can be evaluated only in the context they were created',
|
||||||
);
|
"Trying to evaluate JSHandle from different frames. Usually this means you're using a handle from a page on a different page.",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
it('should simulate a user gesture', async () => {
|
it('should simulate a user gesture', async () => {
|
||||||
const {page} = await getTestState();
|
const {page} = await getTestState();
|
||||||
|
@ -102,6 +102,7 @@ describe('Launcher specs', function () {
|
|||||||
expect(message).atLeastOneToContain([
|
expect(message).atLeastOneToContain([
|
||||||
'Target closed',
|
'Target closed',
|
||||||
'Page closed!',
|
'Page closed!',
|
||||||
|
'Browser already closed',
|
||||||
]);
|
]);
|
||||||
expect(message).not.toContain('Timeout');
|
expect(message).not.toContain('Timeout');
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import type {BrowserContext} from 'puppeteer-core/internal/api/BrowserContext.js';
|
|
||||||
import type {CDPSession} from 'puppeteer-core/internal/api/CDPSession.js';
|
import type {CDPSession} from 'puppeteer-core/internal/api/CDPSession.js';
|
||||||
import {CDPSessionEvent} from 'puppeteer-core/internal/api/CDPSession.js';
|
import {CDPSessionEvent} from 'puppeteer-core/internal/api/CDPSession.js';
|
||||||
import type {CdpTarget} from 'puppeteer-core/internal/cdp/Target.js';
|
import type {Page} from 'puppeteer-core/internal/api/Page.js';
|
||||||
|
|
||||||
import {getTestState, launch} from './mocha-utils.js';
|
import {getTestState, launch} from './mocha-utils.js';
|
||||||
import {attachFrame, detachFrame, navigateFrame} from './utils.js';
|
import {attachFrame, detachFrame, navigateFrame} from './utils.js';
|
||||||
@ -266,24 +265,24 @@ describe('OOPIF', function () {
|
|||||||
await frame.waitForSelector('#clicked');
|
await frame.waitForSelector('#clicked');
|
||||||
});
|
});
|
||||||
it('should report oopif frames', async () => {
|
it('should report oopif frames', async () => {
|
||||||
const {server, page, context} = state;
|
const {server, page} = state;
|
||||||
|
|
||||||
const frame = page.waitForFrame(frame => {
|
const frame = page.waitForFrame(frame => {
|
||||||
return frame.url().endsWith('/oopif.html');
|
return frame.url().endsWith('/oopif.html');
|
||||||
});
|
});
|
||||||
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
||||||
await frame;
|
await frame;
|
||||||
expect(oopifs(context)).toHaveLength(1);
|
expect(await iframes(page)).toHaveLength(1);
|
||||||
expect(page.frames()).toHaveLength(2);
|
expect(page.frames()).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should wait for inner OOPIFs', async () => {
|
it('should wait for inner OOPIFs', async () => {
|
||||||
const {server, page, context} = state;
|
const {server, page} = state;
|
||||||
await page.goto(`http://mainframe:${server.PORT}/main-frame.html`);
|
await page.goto(`http://mainframe:${server.PORT}/main-frame.html`);
|
||||||
const frame2 = await page.waitForFrame(frame => {
|
const frame2 = await page.waitForFrame(frame => {
|
||||||
return frame.url().endsWith('inner-frame2.html');
|
return frame.url().endsWith('inner-frame2.html');
|
||||||
});
|
});
|
||||||
expect(oopifs(context)).toHaveLength(2);
|
expect(await iframes(page)).toHaveLength(2);
|
||||||
expect(
|
expect(
|
||||||
page.frames().filter(frame => {
|
page.frames().filter(frame => {
|
||||||
return frame.isOOPFrame();
|
return frame.isOOPFrame();
|
||||||
@ -297,7 +296,7 @@ describe('OOPIF', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should load oopif iframes with subresources and request interception', async () => {
|
it('should load oopif iframes with subresources and request interception', async () => {
|
||||||
const {server, page, context} = state;
|
const {server, page} = state;
|
||||||
|
|
||||||
const framePromise = page.waitForFrame(frame => {
|
const framePromise = page.waitForFrame(frame => {
|
||||||
return frame.url().endsWith('/oopif.html');
|
return frame.url().endsWith('/oopif.html');
|
||||||
@ -312,7 +311,7 @@ describe('OOPIF', function () {
|
|||||||
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
||||||
const frame = await framePromise;
|
const frame = await framePromise;
|
||||||
const request = await requestPromise;
|
const request = await requestPromise;
|
||||||
expect(oopifs(context)).toHaveLength(1);
|
expect(await iframes(page)).toHaveLength(1);
|
||||||
expect(request.frame()).toBe(frame);
|
expect(request.frame()).toBe(frame);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -394,14 +393,14 @@ describe('OOPIF', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => {
|
it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => {
|
||||||
const {server, puppeteer, page, context} = state;
|
const {server, puppeteer, page} = state;
|
||||||
|
|
||||||
const frame = page.waitForFrame(frame => {
|
const frame = page.waitForFrame(frame => {
|
||||||
return frame.url().endsWith('/oopif.html');
|
return frame.url().endsWith('/oopif.html');
|
||||||
});
|
});
|
||||||
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
await page.goto(server.PREFIX + '/dynamic-oopif.html');
|
||||||
await frame;
|
await frame;
|
||||||
expect(oopifs(context)).toHaveLength(1);
|
expect(await iframes(page)).toHaveLength(1);
|
||||||
expect(page.frames()).toHaveLength(2);
|
expect(page.frames()).toHaveLength(2);
|
||||||
|
|
||||||
const browserURL = 'http://127.0.0.1:21222';
|
const browserURL = 'http://127.0.0.1:21222';
|
||||||
@ -520,8 +519,13 @@ describe('OOPIF', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function oopifs(context: BrowserContext) {
|
async function iframes(page: Page) {
|
||||||
return context.targets().filter(target => {
|
const iframes = await Promise.all(
|
||||||
return (target as CdpTarget)._getTargetInfo().type === 'iframe';
|
page.frames().map(async frame => {
|
||||||
|
return await frame.frameElement();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return iframes.filter(frame => {
|
||||||
|
return frame !== null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,11 @@ describe('Page', function () {
|
|||||||
]);
|
]);
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
const message = results[i].message;
|
const message = results[i].message;
|
||||||
expect(message).atLeastOneToContain(['Target closed', 'Page closed!']);
|
expect(message).atLeastOneToContain([
|
||||||
|
'Target closed',
|
||||||
|
'Page closed!',
|
||||||
|
'Frame detached',
|
||||||
|
]);
|
||||||
expect(message).not.toContain('Timeout');
|
expect(message).not.toContain('Timeout');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -446,9 +446,10 @@ describe('waittask specs', function () {
|
|||||||
await detachFrame(page, 'frame1');
|
await detachFrame(page, 'frame1');
|
||||||
await waitPromise;
|
await waitPromise;
|
||||||
expect(waitError).toBeTruthy();
|
expect(waitError).toBeTruthy();
|
||||||
expect(waitError?.message).toContain(
|
expect(waitError?.message).atLeastOneToContain([
|
||||||
'waitForFunction failed: frame got detached.'
|
'waitForFunction failed: frame got detached.',
|
||||||
);
|
'Browsing context already closed.',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
it('should survive cross-process navigation', async () => {
|
it('should survive cross-process navigation', async () => {
|
||||||
const {page, server} = await getTestState();
|
const {page, server} = await getTestState();
|
||||||
@ -754,9 +755,10 @@ describe('waittask specs', function () {
|
|||||||
await detachFrame(page, 'frame1');
|
await detachFrame(page, 'frame1');
|
||||||
await waitPromise;
|
await waitPromise;
|
||||||
expect(waitError).toBeTruthy();
|
expect(waitError).toBeTruthy();
|
||||||
expect(waitError?.message).toContain(
|
expect(waitError?.message).atLeastOneToContain([
|
||||||
'waitForFunction failed: frame got detached.'
|
'waitForFunction failed: frame got detached.',
|
||||||
);
|
'Browsing context already closed.',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
it('hidden should wait for display: none', async () => {
|
it('hidden should wait for display: none', async () => {
|
||||||
const {page} = await getTestState();
|
const {page} = await getTestState();
|
||||||
|
Loading…
Reference in New Issue
Block a user