mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: support targets for bidi (#10615)
This commit is contained in:
parent
1cf231de7f
commit
996d53fc65
@ -21,6 +21,8 @@ import {ChildProcess} from 'child_process';
|
|||||||
import {Protocol} from 'devtools-protocol';
|
import {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
import {EventEmitter} from '../common/EventEmitter.js';
|
import {EventEmitter} from '../common/EventEmitter.js';
|
||||||
|
import {waitWithTimeout} from '../common/util.js';
|
||||||
|
import {Deferred} from '../util/Deferred.js';
|
||||||
|
|
||||||
import type {BrowserContext} from './BrowserContext.js';
|
import type {BrowserContext} from './BrowserContext.js';
|
||||||
import type {Page} from './Page.js';
|
import type {Page} from './Page.js';
|
||||||
@ -376,12 +378,35 @@ export class Browser extends EventEmitter {
|
|||||||
* );
|
* );
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
waitForTarget(
|
async waitForTarget(
|
||||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
options?: WaitForTargetOptions
|
options: WaitForTargetOptions = {}
|
||||||
): Promise<Target>;
|
): Promise<Target> {
|
||||||
waitForTarget(): Promise<Target> {
|
const {timeout = 30000} = options;
|
||||||
throw new Error('Not implemented');
|
const targetDeferred = Deferred.create<Target | PromiseLike<Target>>();
|
||||||
|
|
||||||
|
this.on(BrowserEmittedEvents.TargetCreated, check);
|
||||||
|
this.on(BrowserEmittedEvents.TargetChanged, check);
|
||||||
|
try {
|
||||||
|
this.targets().forEach(check);
|
||||||
|
if (!timeout) {
|
||||||
|
return await targetDeferred.valueOrThrow();
|
||||||
|
}
|
||||||
|
return await waitWithTimeout(
|
||||||
|
targetDeferred.valueOrThrow(),
|
||||||
|
'target',
|
||||||
|
timeout
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.off(BrowserEmittedEvents.TargetCreated, check);
|
||||||
|
this.off(BrowserEmittedEvents.TargetChanged, check);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check(target: Target): Promise<void> {
|
||||||
|
if ((await predicate(target)) && !targetDeferred.resolved()) {
|
||||||
|
targetDeferred.resolve(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,14 +27,12 @@ import {
|
|||||||
BrowserContextEmittedEvents,
|
BrowserContextEmittedEvents,
|
||||||
BrowserContextOptions,
|
BrowserContextOptions,
|
||||||
WEB_PERMISSION_TO_PROTOCOL_PERMISSION,
|
WEB_PERMISSION_TO_PROTOCOL_PERMISSION,
|
||||||
WaitForTargetOptions,
|
|
||||||
Permission,
|
Permission,
|
||||||
} from '../api/Browser.js';
|
} from '../api/Browser.js';
|
||||||
import {BrowserContext} from '../api/BrowserContext.js';
|
import {BrowserContext} from '../api/BrowserContext.js';
|
||||||
import {Page} from '../api/Page.js';
|
import {Page} from '../api/Page.js';
|
||||||
import {Target} from '../api/Target.js';
|
import {Target} from '../api/Target.js';
|
||||||
import {assert} from '../util/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
import {Deferred} from '../util/Deferred.js';
|
|
||||||
|
|
||||||
import {ChromeTargetManager} from './ChromeTargetManager.js';
|
import {ChromeTargetManager} from './ChromeTargetManager.js';
|
||||||
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
|
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
|
||||||
@ -49,7 +47,6 @@ import {
|
|||||||
} from './Target.js';
|
} from './Target.js';
|
||||||
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js';
|
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js';
|
||||||
import {TaskQueue} from './TaskQueue.js';
|
import {TaskQueue} from './TaskQueue.js';
|
||||||
import {waitWithTimeout} from './util.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -487,56 +484,6 @@ export class CDPBrowser extends BrowserBase {
|
|||||||
return browserTarget;
|
return browserTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for a target in all browser contexts.
|
|
||||||
*
|
|
||||||
* @param predicate - A function to be run for every target.
|
|
||||||
* @returns The first target found that matches the `predicate` function.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* An example of finding a target for a page opened via `window.open`:
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* await page.evaluate(() => window.open('https://www.example.com/'));
|
|
||||||
* const newWindowTarget = await browser.waitForTarget(
|
|
||||||
* target => target.url() === 'https://www.example.com/'
|
|
||||||
* );
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
override async waitForTarget(
|
|
||||||
predicate: (x: CDPTarget) => boolean | Promise<boolean>,
|
|
||||||
options: WaitForTargetOptions = {}
|
|
||||||
): Promise<CDPTarget> {
|
|
||||||
const {timeout = 30000} = options;
|
|
||||||
const targetDeferred = Deferred.create<
|
|
||||||
CDPTarget | PromiseLike<CDPTarget>
|
|
||||||
>();
|
|
||||||
|
|
||||||
this.on(BrowserEmittedEvents.TargetCreated, check);
|
|
||||||
this.on(BrowserEmittedEvents.TargetChanged, check);
|
|
||||||
try {
|
|
||||||
this.targets().forEach(check);
|
|
||||||
if (!timeout) {
|
|
||||||
return await targetDeferred.valueOrThrow();
|
|
||||||
}
|
|
||||||
return await waitWithTimeout(
|
|
||||||
targetDeferred.valueOrThrow(),
|
|
||||||
'target',
|
|
||||||
timeout
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
this.off(BrowserEmittedEvents.TargetCreated, check);
|
|
||||||
this.off(BrowserEmittedEvents.TargetChanged, check);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function check(target: CDPTarget): Promise<void> {
|
|
||||||
if ((await predicate(target)) && !targetDeferred.resolved()) {
|
|
||||||
targetDeferred.resolve(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override async version(): Promise<string> {
|
override async version(): Promise<string> {
|
||||||
const version = await this.#getVersion();
|
const version = await this.#getVersion();
|
||||||
return version.product;
|
return version.product;
|
||||||
@ -626,9 +573,9 @@ export class CDPBrowserContext extends BrowserContext {
|
|||||||
* that matches the `predicate` function.
|
* that matches the `predicate` function.
|
||||||
*/
|
*/
|
||||||
override waitForTarget(
|
override waitForTarget(
|
||||||
predicate: (x: CDPTarget) => boolean | Promise<boolean>,
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
options: {timeout?: number} = {}
|
options: {timeout?: number} = {}
|
||||||
): Promise<CDPTarget> {
|
): Promise<Target> {
|
||||||
return this.#browser.waitForTarget(target => {
|
return this.#browser.waitForTarget(target => {
|
||||||
return target.browserContext() === this && predicate(target);
|
return target.browserContext() === this && predicate(target);
|
||||||
}, options);
|
}, options);
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
} from '../../api/Browser.js';
|
} from '../../api/Browser.js';
|
||||||
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
||||||
import {Page} from '../../api/Page.js';
|
import {Page} from '../../api/Page.js';
|
||||||
|
import {Target} from '../../puppeteer-core.js';
|
||||||
import {Viewport} from '../PuppeteerViewport.js';
|
import {Viewport} from '../PuppeteerViewport.js';
|
||||||
|
|
||||||
import {BrowserContext} from './BrowserContext.js';
|
import {BrowserContext} from './BrowserContext.js';
|
||||||
@ -178,6 +179,12 @@ export class Browser extends BrowserBase {
|
|||||||
override newPage(): Promise<Page> {
|
override newPage(): Promise<Page> {
|
||||||
return this.#defaultContext.newPage();
|
return this.#defaultContext.newPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override targets(): Target[] {
|
||||||
|
return this.browserContexts().flatMap(c => {
|
||||||
|
return c.targets();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
|
@ -18,12 +18,14 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|||||||
|
|
||||||
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
||||||
import {Page as PageBase} from '../../api/Page.js';
|
import {Page as PageBase} from '../../api/Page.js';
|
||||||
|
import {Target} from '../../api/Target.js';
|
||||||
import {Deferred} from '../../util/Deferred.js';
|
import {Deferred} from '../../util/Deferred.js';
|
||||||
import {Viewport} from '../PuppeteerViewport.js';
|
import {Viewport} from '../PuppeteerViewport.js';
|
||||||
|
|
||||||
import {Browser} from './Browser.js';
|
import {Browser} from './Browser.js';
|
||||||
import {Connection} from './Connection.js';
|
import {Connection} from './Connection.js';
|
||||||
import {Page} from './Page.js';
|
import {Page} from './Page.js';
|
||||||
|
import {BiDiTarget} from './Target.js';
|
||||||
import {debugError} from './utils.js';
|
import {debugError} from './utils.js';
|
||||||
|
|
||||||
interface BrowserContextOptions {
|
interface BrowserContextOptions {
|
||||||
@ -38,7 +40,7 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
#browser: Browser;
|
#browser: Browser;
|
||||||
#connection: Connection;
|
#connection: Connection;
|
||||||
#defaultViewport: Viewport | null;
|
#defaultViewport: Viewport | null;
|
||||||
#pages = new Map<string, Page>();
|
#targets = new Map<string, BiDiTarget>();
|
||||||
#onContextDestroyedBind = this.#onContextDestroyed.bind(this);
|
#onContextDestroyedBind = this.#onContextDestroyed.bind(this);
|
||||||
#init = Deferred.create<void>();
|
#init = Deferred.create<void>();
|
||||||
#isDefault = false;
|
#isDefault = false;
|
||||||
@ -56,6 +58,21 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
this.#getTree().catch(debugError);
|
this.#getTree().catch(debugError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override targets(): Target[] {
|
||||||
|
return this.#browser.targets().filter(target => {
|
||||||
|
return target.browserContext() === this;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override waitForTarget(
|
||||||
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
|
options: {timeout?: number} = {}
|
||||||
|
): Promise<Target> {
|
||||||
|
return this.#browser.waitForTarget(target => {
|
||||||
|
return target.browserContext() === this && predicate(target);
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
get connection(): Connection {
|
get connection(): Connection {
|
||||||
return this.#connection;
|
return this.#connection;
|
||||||
}
|
}
|
||||||
@ -72,7 +89,8 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
);
|
);
|
||||||
for (const context of result.contexts) {
|
for (const context of result.contexts) {
|
||||||
const page = new Page(this, context);
|
const page = new Page(this, context);
|
||||||
this.#pages.set(context.context, page);
|
const target = new BiDiTarget(page.mainFrame().context(), page);
|
||||||
|
this.#targets.set(context.context, target);
|
||||||
}
|
}
|
||||||
this.#init.resolve();
|
this.#init.resolve();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -83,11 +101,12 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
async #onContextDestroyed(
|
async #onContextDestroyed(
|
||||||
event: Bidi.BrowsingContext.ContextDestroyedEvent['params']
|
event: Bidi.BrowsingContext.ContextDestroyedEvent['params']
|
||||||
) {
|
) {
|
||||||
const page = this.#pages.get(event.context);
|
const target = this.#targets.get(event.context);
|
||||||
|
const page = await target?.page();
|
||||||
await page?.close().catch(error => {
|
await page?.close().catch(error => {
|
||||||
debugError(error);
|
debugError(error);
|
||||||
});
|
});
|
||||||
this.#pages.delete(event.context);
|
this.#targets.delete(event.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async newPage(): Promise<PageBase> {
|
override async newPage(): Promise<PageBase> {
|
||||||
@ -100,6 +119,7 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
context: result.context,
|
context: result.context,
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
|
const target = new BiDiTarget(page.mainFrame().context(), page);
|
||||||
if (this.#defaultViewport) {
|
if (this.#defaultViewport) {
|
||||||
try {
|
try {
|
||||||
await page.setViewport(this.#defaultViewport);
|
await page.setViewport(this.#defaultViewport);
|
||||||
@ -108,7 +128,7 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#pages.set(result.context, page);
|
this.#targets.set(result.context, target);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
@ -120,12 +140,13 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
throw new Error('Default context cannot be closed!');
|
throw new Error('Default context cannot be closed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const page of this.#pages.values()) {
|
for (const target of this.#targets.values()) {
|
||||||
|
const page = await target?.page();
|
||||||
await page?.close().catch(error => {
|
await page?.close().catch(error => {
|
||||||
debugError(error);
|
debugError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.#pages.clear();
|
this.#targets.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
override browser(): Browser {
|
override browser(): Browser {
|
||||||
@ -134,7 +155,14 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
|
|
||||||
override async pages(): Promise<PageBase[]> {
|
override async pages(): Promise<PageBase[]> {
|
||||||
await this.#init.valueOrThrow();
|
await this.#init.valueOrThrow();
|
||||||
return [...this.#pages.values()];
|
const results = await Promise.all(
|
||||||
|
[...this.#targets.values()].map(t => {
|
||||||
|
return t.page();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return results.filter((p): p is Page => {
|
||||||
|
return p !== null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override isIncognito(): boolean {
|
override isIncognito(): boolean {
|
||||||
|
94
packages/puppeteer-core/src/common/bidi/Target.ts
Normal file
94
packages/puppeteer-core/src/common/bidi/Target.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Target, TargetType} from '../../api/Target.js';
|
||||||
|
import {CDPSession} from '../Connection.js';
|
||||||
|
import type {WebWorker} from '../WebWorker.js';
|
||||||
|
|
||||||
|
import {Browser} from './Browser.js';
|
||||||
|
import {BrowserContext} from './BrowserContext.js';
|
||||||
|
import {BrowsingContext, CDPSessionWrapper} from './BrowsingContext.js';
|
||||||
|
import {Page} from './Page.js';
|
||||||
|
|
||||||
|
export class BiDiTarget extends Target {
|
||||||
|
#browsingContext: BrowsingContext;
|
||||||
|
#page: Page;
|
||||||
|
|
||||||
|
constructor(browsingContext: BrowsingContext, page: Page) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.#browsingContext = browsingContext;
|
||||||
|
this.#page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async worker(): Promise<WebWorker | null> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async page(): Promise<Page | null> {
|
||||||
|
return this.#page;
|
||||||
|
}
|
||||||
|
|
||||||
|
override url(): string {
|
||||||
|
return this.#browsingContext.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Chrome Devtools Protocol session attached to the target.
|
||||||
|
*/
|
||||||
|
override async createCDPSession(): Promise<CDPSession> {
|
||||||
|
const {sessionId} = await this.#browsingContext.cdpSession.send(
|
||||||
|
'Target.attachToTarget',
|
||||||
|
{
|
||||||
|
targetId: this.#page.mainFrame()._id,
|
||||||
|
flatten: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return new CDPSessionWrapper(this.#browsingContext, sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies what kind of target this is.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
*
|
||||||
|
* See {@link https://developer.chrome.com/extensions/background_pages | docs} for more info about background pages.
|
||||||
|
*/
|
||||||
|
override type(): TargetType {
|
||||||
|
return TargetType.PAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the browser the target belongs to.
|
||||||
|
*/
|
||||||
|
override browser(): Browser {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the browser context the target belongs to.
|
||||||
|
*/
|
||||||
|
override browserContext(): BrowserContext {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the target that opened this target. Top-level targets return `null`.
|
||||||
|
*/
|
||||||
|
override opener(): Target | undefined {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user