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 {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 {Page} from './Page.js';
|
||||
@ -376,12 +378,35 @@ export class Browser extends EventEmitter {
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
waitForTarget(
|
||||
async waitForTarget(
|
||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||
options?: WaitForTargetOptions
|
||||
): Promise<Target>;
|
||||
waitForTarget(): Promise<Target> {
|
||||
throw new Error('Not implemented');
|
||||
options: WaitForTargetOptions = {}
|
||||
): Promise<Target> {
|
||||
const {timeout = 30000} = options;
|
||||
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,
|
||||
BrowserContextOptions,
|
||||
WEB_PERMISSION_TO_PROTOCOL_PERMISSION,
|
||||
WaitForTargetOptions,
|
||||
Permission,
|
||||
} from '../api/Browser.js';
|
||||
import {BrowserContext} from '../api/BrowserContext.js';
|
||||
import {Page} from '../api/Page.js';
|
||||
import {Target} from '../api/Target.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {Deferred} from '../util/Deferred.js';
|
||||
|
||||
import {ChromeTargetManager} from './ChromeTargetManager.js';
|
||||
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
|
||||
@ -49,7 +47,6 @@ import {
|
||||
} from './Target.js';
|
||||
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js';
|
||||
import {TaskQueue} from './TaskQueue.js';
|
||||
import {waitWithTimeout} from './util.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -487,56 +484,6 @@ export class CDPBrowser extends BrowserBase {
|
||||
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> {
|
||||
const version = await this.#getVersion();
|
||||
return version.product;
|
||||
@ -626,9 +573,9 @@ export class CDPBrowserContext extends BrowserContext {
|
||||
* that matches the `predicate` function.
|
||||
*/
|
||||
override waitForTarget(
|
||||
predicate: (x: CDPTarget) => boolean | Promise<boolean>,
|
||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||
options: {timeout?: number} = {}
|
||||
): Promise<CDPTarget> {
|
||||
): Promise<Target> {
|
||||
return this.#browser.waitForTarget(target => {
|
||||
return target.browserContext() === this && predicate(target);
|
||||
}, options);
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
} from '../../api/Browser.js';
|
||||
import {BrowserContext as BrowserContextBase} from '../../api/BrowserContext.js';
|
||||
import {Page} from '../../api/Page.js';
|
||||
import {Target} from '../../puppeteer-core.js';
|
||||
import {Viewport} from '../PuppeteerViewport.js';
|
||||
|
||||
import {BrowserContext} from './BrowserContext.js';
|
||||
@ -178,6 +179,12 @@ export class Browser extends BrowserBase {
|
||||
override newPage(): Promise<Page> {
|
||||
return this.#defaultContext.newPage();
|
||||
}
|
||||
|
||||
override targets(): Target[] {
|
||||
return this.browserContexts().flatMap(c => {
|
||||
return c.targets();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 {Page as PageBase} from '../../api/Page.js';
|
||||
import {Target} from '../../api/Target.js';
|
||||
import {Deferred} from '../../util/Deferred.js';
|
||||
import {Viewport} from '../PuppeteerViewport.js';
|
||||
|
||||
import {Browser} from './Browser.js';
|
||||
import {Connection} from './Connection.js';
|
||||
import {Page} from './Page.js';
|
||||
import {BiDiTarget} from './Target.js';
|
||||
import {debugError} from './utils.js';
|
||||
|
||||
interface BrowserContextOptions {
|
||||
@ -38,7 +40,7 @@ export class BrowserContext extends BrowserContextBase {
|
||||
#browser: Browser;
|
||||
#connection: Connection;
|
||||
#defaultViewport: Viewport | null;
|
||||
#pages = new Map<string, Page>();
|
||||
#targets = new Map<string, BiDiTarget>();
|
||||
#onContextDestroyedBind = this.#onContextDestroyed.bind(this);
|
||||
#init = Deferred.create<void>();
|
||||
#isDefault = false;
|
||||
@ -56,6 +58,21 @@ export class BrowserContext extends BrowserContextBase {
|
||||
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 {
|
||||
return this.#connection;
|
||||
}
|
||||
@ -72,7 +89,8 @@ export class BrowserContext extends BrowserContextBase {
|
||||
);
|
||||
for (const context of result.contexts) {
|
||||
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();
|
||||
} catch (err) {
|
||||
@ -83,11 +101,12 @@ export class BrowserContext extends BrowserContextBase {
|
||||
async #onContextDestroyed(
|
||||
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 => {
|
||||
debugError(error);
|
||||
});
|
||||
this.#pages.delete(event.context);
|
||||
this.#targets.delete(event.context);
|
||||
}
|
||||
|
||||
override async newPage(): Promise<PageBase> {
|
||||
@ -100,6 +119,7 @@ export class BrowserContext extends BrowserContextBase {
|
||||
context: result.context,
|
||||
children: [],
|
||||
});
|
||||
const target = new BiDiTarget(page.mainFrame().context(), page);
|
||||
if (this.#defaultViewport) {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
@ -120,12 +140,13 @@ export class BrowserContext extends BrowserContextBase {
|
||||
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 => {
|
||||
debugError(error);
|
||||
});
|
||||
}
|
||||
this.#pages.clear();
|
||||
this.#targets.clear();
|
||||
}
|
||||
|
||||
override browser(): Browser {
|
||||
@ -134,7 +155,14 @@ export class BrowserContext extends BrowserContextBase {
|
||||
|
||||
override async pages(): Promise<PageBase[]> {
|
||||
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 {
|
||||
|
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