feat: allow handling other targets as pages internally (#8336)

Co-authored-by: jrandolf <101637635+jrandolf@users.noreply.github.com>
This commit is contained in:
Alex Rudenko 2022-05-13 08:04:46 +02:00 committed by GitHub
parent 0ae8936f5e
commit 3b66a2c47e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 16 deletions

View File

@ -54,6 +54,13 @@ export type TargetFilterCallback = (
target: Protocol.Target.TargetInfo target: Protocol.Target.TargetInfo
) => boolean; ) => boolean;
/**
* @internal
*/
export type IsPageTargetCallback = (
target: Protocol.Target.TargetInfo
) => boolean;
const WEB_PERMISSION_TO_PROTOCOL_PERMISSION = new Map< const WEB_PERMISSION_TO_PROTOCOL_PERMISSION = new Map<
Permission, Permission,
Protocol.Browser.PermissionType Protocol.Browser.PermissionType
@ -217,7 +224,8 @@ export class Browser extends EventEmitter {
defaultViewport?: Viewport | null, defaultViewport?: Viewport | null,
process?: ChildProcess, process?: ChildProcess,
closeCallback?: BrowserCloseCallback, closeCallback?: BrowserCloseCallback,
targetFilterCallback?: TargetFilterCallback targetFilterCallback?: TargetFilterCallback,
isPageTargetCallback?: IsPageTargetCallback
): Promise<Browser> { ): Promise<Browser> {
const browser = new Browser( const browser = new Browser(
connection, connection,
@ -226,7 +234,8 @@ export class Browser extends EventEmitter {
defaultViewport, defaultViewport,
process, process,
closeCallback, closeCallback,
targetFilterCallback targetFilterCallback,
isPageTargetCallback
); );
await connection.send('Target.setDiscoverTargets', { discover: true }); await connection.send('Target.setDiscoverTargets', { discover: true });
return browser; return browser;
@ -237,6 +246,7 @@ export class Browser extends EventEmitter {
private _connection: Connection; private _connection: Connection;
private _closeCallback: BrowserCloseCallback; private _closeCallback: BrowserCloseCallback;
private _targetFilterCallback: TargetFilterCallback; private _targetFilterCallback: TargetFilterCallback;
private _isPageTargetCallback: IsPageTargetCallback;
private _defaultContext: BrowserContext; private _defaultContext: BrowserContext;
private _contexts: Map<string, BrowserContext>; private _contexts: Map<string, BrowserContext>;
private _screenshotTaskQueue: TaskQueue; private _screenshotTaskQueue: TaskQueue;
@ -257,7 +267,8 @@ export class Browser extends EventEmitter {
defaultViewport?: Viewport | null, defaultViewport?: Viewport | null,
process?: ChildProcess, process?: ChildProcess,
closeCallback?: BrowserCloseCallback, closeCallback?: BrowserCloseCallback,
targetFilterCallback?: TargetFilterCallback targetFilterCallback?: TargetFilterCallback,
isPageTargetCallback?: IsPageTargetCallback
) { ) {
super(); super();
this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._ignoreHTTPSErrors = ignoreHTTPSErrors;
@ -267,6 +278,7 @@ export class Browser extends EventEmitter {
this._connection = connection; this._connection = connection;
this._closeCallback = closeCallback || function (): void {}; this._closeCallback = closeCallback || function (): void {};
this._targetFilterCallback = targetFilterCallback || ((): boolean => true); this._targetFilterCallback = targetFilterCallback || ((): boolean => true);
this._setIsPageTargetCallback(isPageTargetCallback);
this._defaultContext = new BrowserContext(this._connection, this); this._defaultContext = new BrowserContext(this._connection, this);
this._contexts = new Map(); this._contexts = new Map();
@ -299,6 +311,21 @@ export class Browser extends EventEmitter {
return this._process ?? null; return this._process ?? null;
} }
/**
* @internal
*/
_setIsPageTargetCallback(isPageTargetCallback?: IsPageTargetCallback): void {
this._isPageTargetCallback =
isPageTargetCallback ||
((target: Protocol.Target.TargetInfo): boolean => {
return (
target.type === 'page' ||
target.type === 'background_page' ||
target.type === 'webview'
);
});
}
/** /**
* Creates a new incognito browser context. This won't share cookies/cache with other * Creates a new incognito browser context. This won't share cookies/cache with other
* browser contexts. * browser contexts.
@ -392,7 +419,8 @@ export class Browser extends EventEmitter {
() => this._connection.createSession(targetInfo), () => this._connection.createSession(targetInfo),
this._ignoreHTTPSErrors, this._ignoreHTTPSErrors,
this._defaultViewport ?? null, this._defaultViewport ?? null,
this._screenshotTaskQueue this._screenshotTaskQueue,
this._isPageTargetCallback
); );
assert( assert(
!this._targets.has(event.targetInfo.targetId), !this._targets.has(event.targetInfo.targetId),

View File

@ -15,7 +15,11 @@
*/ */
import { ConnectionTransport } from './ConnectionTransport.js'; import { ConnectionTransport } from './ConnectionTransport.js';
import { Browser, TargetFilterCallback } from './Browser.js'; import {
Browser,
TargetFilterCallback,
IsPageTargetCallback,
} from './Browser.js';
import { assert } from './assert.js'; import { assert } from './assert.js';
import { debugError } from '../common/helper.js'; import { debugError } from '../common/helper.js';
import { Connection } from './Connection.js'; import { Connection } from './Connection.js';
@ -47,6 +51,10 @@ export interface BrowserConnectOptions {
* Callback to decide if Puppeteer should connect to a given target or not. * Callback to decide if Puppeteer should connect to a given target or not.
*/ */
targetFilter?: TargetFilterCallback; targetFilter?: TargetFilterCallback;
/**
* @internal
*/
isPageTarget?: IsPageTargetCallback;
} }
const getWebSocketTransportClass = async () => { const getWebSocketTransportClass = async () => {
@ -76,6 +84,7 @@ export const connectToBrowser = async (
transport, transport,
slowMo = 0, slowMo = 0,
targetFilter, targetFilter,
isPageTarget,
} = options; } = options;
assert( assert(
@ -110,7 +119,8 @@ export const connectToBrowser = async (
defaultViewport, defaultViewport,
null, null,
() => connection.send('Browser.close').catch(debugError), () => connection.send('Browser.close').catch(debugError),
targetFilter targetFilter,
isPageTarget
); );
}; };

View File

@ -17,7 +17,7 @@
import { Page, PageEmittedEvents } from './Page.js'; import { Page, PageEmittedEvents } from './Page.js';
import { WebWorker } from './WebWorker.js'; import { WebWorker } from './WebWorker.js';
import { CDPSession } from './Connection.js'; import { CDPSession } from './Connection.js';
import { Browser, BrowserContext } from './Browser.js'; import { Browser, BrowserContext, IsPageTargetCallback } from './Browser.js';
import { Viewport } from './PuppeteerViewport.js'; import { Viewport } from './PuppeteerViewport.js';
import { Protocol } from 'devtools-protocol'; import { Protocol } from 'devtools-protocol';
import { TaskQueue } from './TaskQueue.js'; import { TaskQueue } from './TaskQueue.js';
@ -59,6 +59,10 @@ export class Target {
* @internal * @internal
*/ */
_targetId: string; _targetId: string;
/**
* @internal
*/
_isPageTargetCallback: IsPageTargetCallback;
/** /**
* @internal * @internal
@ -69,7 +73,8 @@ export class Target {
sessionFactory: () => Promise<CDPSession>, sessionFactory: () => Promise<CDPSession>,
ignoreHTTPSErrors: boolean, ignoreHTTPSErrors: boolean,
defaultViewport: Viewport | null, defaultViewport: Viewport | null,
screenshotTaskQueue: TaskQueue screenshotTaskQueue: TaskQueue,
isPageTargetCallback: IsPageTargetCallback
) { ) {
this._targetInfo = targetInfo; this._targetInfo = targetInfo;
this._browserContext = browserContext; this._browserContext = browserContext;
@ -78,6 +83,7 @@ export class Target {
this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._defaultViewport = defaultViewport; this._defaultViewport = defaultViewport;
this._screenshotTaskQueue = screenshotTaskQueue; this._screenshotTaskQueue = screenshotTaskQueue;
this._isPageTargetCallback = isPageTargetCallback;
/** @type {?Promise<!Puppeteer.Page>} */ /** @type {?Promise<!Puppeteer.Page>} */
this._pagePromise = null; this._pagePromise = null;
/** @type {?Promise<!WebWorker>} */ /** @type {?Promise<!WebWorker>} */
@ -99,7 +105,8 @@ export class Target {
(fulfill) => (this._closedCallback = fulfill) (fulfill) => (this._closedCallback = fulfill)
); );
this._isInitialized = this._isInitialized =
this._targetInfo.type !== 'page' || this._targetInfo.url !== ''; !this._isPageTargetCallback(this._targetInfo) ||
this._targetInfo.url !== '';
if (this._isInitialized) this._initializedCallback(true); if (this._isInitialized) this._initializedCallback(true);
} }
@ -114,12 +121,7 @@ export class Target {
* If the target is not of type `"page"` or `"background_page"`, returns `null`. * If the target is not of type `"page"` or `"background_page"`, returns `null`.
*/ */
async page(): Promise<Page | null> { async page(): Promise<Page | null> {
if ( if (this._isPageTargetCallback(this._targetInfo) && !this._pagePromise) {
(this._targetInfo.type === 'page' ||
this._targetInfo.type === 'background_page' ||
this._targetInfo.type === 'webview') &&
!this._pagePromise
) {
this._pagePromise = this._sessionFactory().then((client) => this._pagePromise = this._sessionFactory().then((client) =>
Page.create( Page.create(
client, client,
@ -220,7 +222,8 @@ export class Target {
if ( if (
!this._isInitialized && !this._isInitialized &&
(this._targetInfo.type !== 'page' || this._targetInfo.url !== '') (!this._isPageTargetCallback(this._targetInfo) ||
this._targetInfo.url !== '')
) { ) {
this._isInitialized = true; this._isInitialized = true;
this._initializedCallback(true); this._initializedCallback(true);

View File

@ -43,6 +43,7 @@ describeChromeOnly('headful tests', function () {
let headlessOptions; let headlessOptions;
let extensionOptions; let extensionOptions;
let forcedOopifOptions; let forcedOopifOptions;
let devtoolsOptions;
const browsers = []; const browsers = [];
beforeEach(() => { beforeEach(() => {
@ -73,6 +74,11 @@ describeChromeOnly('headful tests', function () {
)}`, )}`,
], ],
}); });
devtoolsOptions = Object.assign({}, defaultBrowserOptions, {
headless: false,
devtools: true,
});
}); });
async function launchBrowser(puppeteer, options) { async function launchBrowser(puppeteer, options) {
@ -120,6 +126,28 @@ describeChromeOnly('headful tests', function () {
expect(await page.evaluate(() => globalThis.MAGIC)).toBe(42); expect(await page.evaluate(() => globalThis.MAGIC)).toBe(42);
await browserWithExtension.close(); await browserWithExtension.close();
}); });
it('target.page() should return a DevTools page if custom isPageTarget is provided', async function () {
const { puppeteer } = getTestState();
const originalBrowser = await launchBrowser(puppeteer, devtoolsOptions);
const browserWSEndpoint = originalBrowser.wsEndpoint();
const browser = await puppeteer.connect({
browserWSEndpoint,
isPageTarget: (target) => {
return (
target.type === 'other' && target.url.startsWith('devtools://')
);
},
});
const devtoolsPageTarget = await browser.waitForTarget(
(target) => target.type() === 'other'
);
console.log(devtoolsPageTarget);
const page = await devtoolsPageTarget.page();
expect(await page.evaluate(() => 2 * 3)).toBe(6);
await browser.close();
});
it('should have default url when launching browser', async function () { it('should have default url when launching browser', async function () {
const { puppeteer } = getTestState(); const { puppeteer } = getTestState();
const browser = await launchBrowser(puppeteer, extensionOptions); const browser = await launchBrowser(puppeteer, extensionOptions);