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

View File

@ -15,7 +15,11 @@
*/
import { ConnectionTransport } from './ConnectionTransport.js';
import { Browser, TargetFilterCallback } from './Browser.js';
import {
Browser,
TargetFilterCallback,
IsPageTargetCallback,
} from './Browser.js';
import { assert } from './assert.js';
import { debugError } from '../common/helper.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.
*/
targetFilter?: TargetFilterCallback;
/**
* @internal
*/
isPageTarget?: IsPageTargetCallback;
}
const getWebSocketTransportClass = async () => {
@ -76,6 +84,7 @@ export const connectToBrowser = async (
transport,
slowMo = 0,
targetFilter,
isPageTarget,
} = options;
assert(
@ -110,7 +119,8 @@ export const connectToBrowser = async (
defaultViewport,
null,
() => connection.send('Browser.close').catch(debugError),
targetFilter
targetFilter,
isPageTarget
);
};

View File

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

View File

@ -43,6 +43,7 @@ describeChromeOnly('headful tests', function () {
let headlessOptions;
let extensionOptions;
let forcedOopifOptions;
let devtoolsOptions;
const browsers = [];
beforeEach(() => {
@ -73,6 +74,11 @@ describeChromeOnly('headful tests', function () {
)}`,
],
});
devtoolsOptions = Object.assign({}, defaultBrowserOptions, {
headless: false,
devtools: true,
});
});
async function launchBrowser(puppeteer, options) {
@ -120,6 +126,28 @@ describeChromeOnly('headful tests', function () {
expect(await page.evaluate(() => globalThis.MAGIC)).toBe(42);
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 () {
const { puppeteer } = getTestState();
const browser = await launchBrowser(puppeteer, extensionOptions);