2020-04-28 12:26:37 +00:00
|
|
|
/**
|
|
|
|
* Copyright 2017 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.
|
|
|
|
*/
|
|
|
|
|
2022-06-22 13:25:44 +00:00
|
|
|
import {ChildProcess} from 'child_process';
|
|
|
|
import {Protocol} from 'devtools-protocol';
|
2022-08-17 12:39:41 +00:00
|
|
|
import {assert} from '../util/assert.js';
|
2022-07-21 18:50:46 +00:00
|
|
|
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
|
2022-06-22 13:25:44 +00:00
|
|
|
import {waitWithTimeout} from './util.js';
|
2022-09-21 06:10:50 +00:00
|
|
|
import {Page} from '../api/Page.js';
|
2022-06-22 13:25:44 +00:00
|
|
|
import {Viewport} from './PuppeteerViewport.js';
|
|
|
|
import {Target} from './Target.js';
|
|
|
|
import {TaskQueue} from './TaskQueue.js';
|
2022-07-21 18:50:46 +00:00
|
|
|
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js';
|
|
|
|
import {ChromeTargetManager} from './ChromeTargetManager.js';
|
|
|
|
import {FirefoxTargetManager} from './FirefoxTargetManager.js';
|
2022-09-14 14:40:58 +00:00
|
|
|
import {
|
|
|
|
Browser as BrowserBase,
|
|
|
|
BrowserCloseCallback,
|
|
|
|
TargetFilterCallback,
|
|
|
|
IsPageTargetCallback,
|
|
|
|
BrowserEmittedEvents,
|
|
|
|
BrowserContextEmittedEvents,
|
|
|
|
BrowserContextOptions,
|
|
|
|
WEB_PERMISSION_TO_PROTOCOL_PERMISSION,
|
|
|
|
WaitForTargetOptions,
|
2021-02-11 15:44:56 +00:00
|
|
|
Permission,
|
2022-09-14 14:40:58 +00:00
|
|
|
} from '../api/Browser.js';
|
2022-10-19 07:06:31 +00:00
|
|
|
import {BrowserContext} from '../api/BrowserContext.js';
|
2021-02-11 15:44:56 +00:00
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
2022-09-14 14:40:58 +00:00
|
|
|
* @internal
|
2020-06-23 05:19:15 +00:00
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
export class CDPBrowser extends BrowserBase {
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-06-13 09:16:25 +00:00
|
|
|
static async _create(
|
2022-07-21 18:50:46 +00:00
|
|
|
product: 'firefox' | 'chrome' | undefined,
|
2020-05-07 10:54:55 +00:00
|
|
|
connection: Connection,
|
|
|
|
contextIds: string[],
|
|
|
|
ignoreHTTPSErrors: boolean,
|
2021-03-24 11:14:06 +00:00
|
|
|
defaultViewport?: Viewport | null,
|
2020-05-07 10:54:55 +00:00
|
|
|
process?: ChildProcess,
|
2021-05-03 11:48:31 +00:00
|
|
|
closeCallback?: BrowserCloseCallback,
|
2022-05-13 06:04:46 +00:00
|
|
|
targetFilterCallback?: TargetFilterCallback,
|
|
|
|
isPageTargetCallback?: IsPageTargetCallback
|
2022-09-14 14:40:58 +00:00
|
|
|
): Promise<CDPBrowser> {
|
|
|
|
const browser = new CDPBrowser(
|
2022-07-21 18:50:46 +00:00
|
|
|
product,
|
2020-05-07 10:54:55 +00:00
|
|
|
connection,
|
|
|
|
contextIds,
|
|
|
|
ignoreHTTPSErrors,
|
|
|
|
defaultViewport,
|
|
|
|
process,
|
2021-05-03 11:48:31 +00:00
|
|
|
closeCallback,
|
2022-05-13 06:04:46 +00:00
|
|
|
targetFilterCallback,
|
|
|
|
isPageTargetCallback
|
2020-05-07 10:54:55 +00:00
|
|
|
);
|
2022-07-21 18:50:46 +00:00
|
|
|
await browser._attach();
|
2020-04-28 12:26:37 +00:00
|
|
|
return browser;
|
|
|
|
}
|
2022-06-13 09:16:25 +00:00
|
|
|
#ignoreHTTPSErrors: boolean;
|
|
|
|
#defaultViewport?: Viewport | null;
|
|
|
|
#process?: ChildProcess;
|
|
|
|
#connection: Connection;
|
|
|
|
#closeCallback: BrowserCloseCallback;
|
|
|
|
#targetFilterCallback: TargetFilterCallback;
|
|
|
|
#isPageTargetCallback!: IsPageTargetCallback;
|
2022-09-14 14:40:58 +00:00
|
|
|
#defaultContext: CDPBrowserContext;
|
|
|
|
#contexts: Map<string, CDPBrowserContext>;
|
2022-06-13 09:16:25 +00:00
|
|
|
#screenshotTaskQueue: TaskQueue;
|
2022-07-21 18:50:46 +00:00
|
|
|
#targetManager: TargetManager;
|
2022-06-13 09:16:25 +00:00
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override get _targets(): Map<string, Target> {
|
2022-07-21 18:50:46 +00:00
|
|
|
return this.#targetManager.getAvailableTargets();
|
2022-06-13 09:16:25 +00:00
|
|
|
}
|
2020-05-07 10:54:55 +00:00
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2020-05-07 10:54:55 +00:00
|
|
|
constructor(
|
2022-07-21 18:50:46 +00:00
|
|
|
product: 'chrome' | 'firefox' | undefined,
|
2020-05-07 10:54:55 +00:00
|
|
|
connection: Connection,
|
|
|
|
contextIds: string[],
|
|
|
|
ignoreHTTPSErrors: boolean,
|
2021-03-24 11:14:06 +00:00
|
|
|
defaultViewport?: Viewport | null,
|
2020-05-07 10:54:55 +00:00
|
|
|
process?: ChildProcess,
|
2021-05-03 11:48:31 +00:00
|
|
|
closeCallback?: BrowserCloseCallback,
|
2022-05-13 06:04:46 +00:00
|
|
|
targetFilterCallback?: TargetFilterCallback,
|
|
|
|
isPageTargetCallback?: IsPageTargetCallback
|
2020-05-07 10:54:55 +00:00
|
|
|
) {
|
|
|
|
super();
|
2022-07-21 18:50:46 +00:00
|
|
|
product = product || 'chrome';
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#ignoreHTTPSErrors = ignoreHTTPSErrors;
|
|
|
|
this.#defaultViewport = defaultViewport;
|
|
|
|
this.#process = process;
|
|
|
|
this.#screenshotTaskQueue = new TaskQueue();
|
|
|
|
this.#connection = connection;
|
|
|
|
this.#closeCallback = closeCallback || function (): void {};
|
2022-06-15 10:42:21 +00:00
|
|
|
this.#targetFilterCallback =
|
|
|
|
targetFilterCallback ||
|
|
|
|
((): boolean => {
|
|
|
|
return true;
|
|
|
|
});
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#setIsPageTargetCallback(isPageTargetCallback);
|
2022-07-21 18:50:46 +00:00
|
|
|
if (product === 'firefox') {
|
|
|
|
this.#targetManager = new FirefoxTargetManager(
|
|
|
|
connection,
|
|
|
|
this.#createTarget,
|
|
|
|
this.#targetFilterCallback
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.#targetManager = new ChromeTargetManager(
|
|
|
|
connection,
|
|
|
|
this.#createTarget,
|
|
|
|
this.#targetFilterCallback
|
|
|
|
);
|
|
|
|
}
|
2022-09-14 14:40:58 +00:00
|
|
|
this.#defaultContext = new CDPBrowserContext(this.#connection, this);
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#contexts = new Map();
|
2022-06-14 11:55:35 +00:00
|
|
|
for (const contextId of contextIds) {
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#contexts.set(
|
2020-05-07 10:54:55 +00:00
|
|
|
contextId,
|
2022-09-14 14:40:58 +00:00
|
|
|
new CDPBrowserContext(this.#connection, this, contextId)
|
2020-05-07 10:54:55 +00:00
|
|
|
);
|
2022-06-14 11:55:35 +00:00
|
|
|
}
|
2022-07-21 18:50:46 +00:00
|
|
|
}
|
2020-05-07 10:54:55 +00:00
|
|
|
|
2022-07-21 18:50:46 +00:00
|
|
|
#emitDisconnected = () => {
|
|
|
|
this.emit(BrowserEmittedEvents.Disconnected);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async _attach(): Promise<void> {
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#connection.on(
|
2022-07-21 18:50:46 +00:00
|
|
|
ConnectionEmittedEvents.Disconnected,
|
|
|
|
this.#emitDisconnected
|
2020-05-07 10:54:55 +00:00
|
|
|
);
|
2022-07-21 18:50:46 +00:00
|
|
|
this.#targetManager.on(
|
|
|
|
TargetManagerEmittedEvents.TargetAvailable,
|
|
|
|
this.#onAttachedToTarget
|
|
|
|
);
|
|
|
|
this.#targetManager.on(
|
|
|
|
TargetManagerEmittedEvents.TargetGone,
|
|
|
|
this.#onDetachedFromTarget
|
|
|
|
);
|
|
|
|
this.#targetManager.on(
|
|
|
|
TargetManagerEmittedEvents.TargetChanged,
|
|
|
|
this.#onTargetChanged
|
|
|
|
);
|
|
|
|
this.#targetManager.on(
|
|
|
|
TargetManagerEmittedEvents.TargetDiscovered,
|
|
|
|
this.#onTargetDiscovered
|
|
|
|
);
|
|
|
|
await this.#targetManager.initialize();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override _detach(): void {
|
2022-07-21 18:50:46 +00:00
|
|
|
this.#connection.off(
|
|
|
|
ConnectionEmittedEvents.Disconnected,
|
|
|
|
this.#emitDisconnected
|
|
|
|
);
|
|
|
|
this.#targetManager.off(
|
|
|
|
TargetManagerEmittedEvents.TargetAvailable,
|
|
|
|
this.#onAttachedToTarget
|
|
|
|
);
|
|
|
|
this.#targetManager.off(
|
|
|
|
TargetManagerEmittedEvents.TargetGone,
|
|
|
|
this.#onDetachedFromTarget
|
|
|
|
);
|
|
|
|
this.#targetManager.off(
|
|
|
|
TargetManagerEmittedEvents.TargetChanged,
|
|
|
|
this.#onTargetChanged
|
|
|
|
);
|
|
|
|
this.#targetManager.off(
|
|
|
|
TargetManagerEmittedEvents.TargetDiscovered,
|
|
|
|
this.#onTargetDiscovered
|
2020-05-07 10:54:55 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* The spawned browser process. Returns `null` if the browser instance was created with
|
|
|
|
* {@link Puppeteer.connect}.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override process(): ChildProcess | null {
|
2022-06-13 09:16:25 +00:00
|
|
|
return this.#process ?? null;
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2022-07-21 18:50:46 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
_targetManager(): TargetManager {
|
|
|
|
return this.#targetManager;
|
|
|
|
}
|
|
|
|
|
2022-06-13 09:16:25 +00:00
|
|
|
#setIsPageTargetCallback(isPageTargetCallback?: IsPageTargetCallback): void {
|
|
|
|
this.#isPageTargetCallback =
|
2022-05-13 06:04:46 +00:00
|
|
|
isPageTargetCallback ||
|
|
|
|
((target: Protocol.Target.TargetInfo): boolean => {
|
|
|
|
return (
|
|
|
|
target.type === 'page' ||
|
|
|
|
target.type === 'background_page' ||
|
|
|
|
target.type === 'webview'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-02 11:27:31 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override _getIsPageTargetCallback(): IsPageTargetCallback | undefined {
|
2022-06-13 09:16:25 +00:00
|
|
|
return this.#isPageTargetCallback;
|
2022-06-02 11:27:31 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* Creates a new incognito browser context. This won't share cookies/cache with other
|
|
|
|
* browser contexts.
|
|
|
|
*
|
|
|
|
* @example
|
2022-08-12 12:15:26 +00:00
|
|
|
*
|
2022-07-01 11:52:39 +00:00
|
|
|
* ```ts
|
2020-06-23 05:19:15 +00:00
|
|
|
* (async () => {
|
2022-08-12 12:15:26 +00:00
|
|
|
* const browser = await puppeteer.launch();
|
2020-06-23 05:19:15 +00:00
|
|
|
* // Create a new incognito browser context.
|
|
|
|
* const context = await browser.createIncognitoBrowserContext();
|
|
|
|
* // Create a new page in a pristine context.
|
|
|
|
* const page = await context.newPage();
|
|
|
|
* // Do stuff
|
|
|
|
* await page.goto('https://example.com');
|
|
|
|
* })();
|
|
|
|
* ```
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async createIncognitoBrowserContext(
|
2021-09-18 09:56:05 +00:00
|
|
|
options: BrowserContextOptions = {}
|
2022-09-14 14:40:58 +00:00
|
|
|
): Promise<CDPBrowserContext> {
|
2022-06-22 13:25:44 +00:00
|
|
|
const {proxyServer, proxyBypassList} = options;
|
2021-09-18 09:56:05 +00:00
|
|
|
|
2022-06-22 13:25:44 +00:00
|
|
|
const {browserContextId} = await this.#connection.send(
|
2021-09-18 09:56:05 +00:00
|
|
|
'Target.createBrowserContext',
|
|
|
|
{
|
|
|
|
proxyServer,
|
|
|
|
proxyBypassList: proxyBypassList && proxyBypassList.join(','),
|
|
|
|
}
|
2020-05-07 10:54:55 +00:00
|
|
|
);
|
2022-09-14 14:40:58 +00:00
|
|
|
const context = new CDPBrowserContext(
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#connection,
|
2020-05-07 10:54:55 +00:00
|
|
|
this,
|
|
|
|
browserContextId
|
|
|
|
);
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#contexts.set(browserContextId, context);
|
2020-05-07 10:54:55 +00:00
|
|
|
return context;
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* Returns an array of all open browser contexts. In a newly created browser, this will
|
|
|
|
* return a single instance of {@link BrowserContext}.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override browserContexts(): CDPBrowserContext[] {
|
2022-06-13 09:16:25 +00:00
|
|
|
return [this.#defaultContext, ...Array.from(this.#contexts.values())];
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* Returns the default browser context. The default browser context cannot be closed.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override defaultBrowserContext(): CDPBrowserContext {
|
2022-06-13 09:16:25 +00:00
|
|
|
return this.#defaultContext;
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-23 05:19:15 +00:00
|
|
|
* @internal
|
2020-04-28 12:26:37 +00:00
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async _disposeContext(contextId?: string): Promise<void> {
|
2022-01-20 13:15:23 +00:00
|
|
|
if (!contextId) {
|
|
|
|
return;
|
|
|
|
}
|
2022-06-13 09:16:25 +00:00
|
|
|
await this.#connection.send('Target.disposeBrowserContext', {
|
2022-01-20 13:15:23 +00:00
|
|
|
browserContextId: contextId,
|
2020-05-07 10:54:55 +00:00
|
|
|
});
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#contexts.delete(contextId);
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2022-07-21 18:50:46 +00:00
|
|
|
#createTarget = (
|
|
|
|
targetInfo: Protocol.Target.TargetInfo,
|
|
|
|
session?: CDPSession
|
|
|
|
) => {
|
2022-06-22 13:25:44 +00:00
|
|
|
const {browserContextId} = targetInfo;
|
2020-05-07 10:54:55 +00:00
|
|
|
const context =
|
2022-06-13 09:16:25 +00:00
|
|
|
browserContextId && this.#contexts.has(browserContextId)
|
|
|
|
? this.#contexts.get(browserContextId)
|
|
|
|
: this.#defaultContext;
|
2020-05-07 10:54:55 +00:00
|
|
|
|
2022-01-20 13:15:23 +00:00
|
|
|
if (!context) {
|
|
|
|
throw new Error('Missing browser context');
|
|
|
|
}
|
|
|
|
|
2022-07-21 18:50:46 +00:00
|
|
|
return new Target(
|
2020-05-07 10:54:55 +00:00
|
|
|
targetInfo,
|
2022-07-21 18:50:46 +00:00
|
|
|
session,
|
2020-05-07 10:54:55 +00:00
|
|
|
context,
|
2022-07-21 18:50:46 +00:00
|
|
|
this.#targetManager,
|
2022-08-16 10:56:13 +00:00
|
|
|
(isAutoAttachEmulated: boolean) => {
|
|
|
|
return this.#connection._createSession(
|
|
|
|
targetInfo,
|
|
|
|
isAutoAttachEmulated
|
|
|
|
);
|
2022-06-15 10:42:21 +00:00
|
|
|
},
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#ignoreHTTPSErrors,
|
|
|
|
this.#defaultViewport ?? null,
|
|
|
|
this.#screenshotTaskQueue,
|
|
|
|
this.#isPageTargetCallback
|
2020-05-07 10:54:55 +00:00
|
|
|
);
|
2022-07-21 18:50:46 +00:00
|
|
|
};
|
2020-05-07 10:54:55 +00:00
|
|
|
|
2022-07-21 18:50:46 +00:00
|
|
|
#onAttachedToTarget = async (target: Target) => {
|
2020-05-07 10:54:55 +00:00
|
|
|
if (await target._initializedPromise) {
|
2020-07-06 11:23:40 +00:00
|
|
|
this.emit(BrowserEmittedEvents.TargetCreated, target);
|
2022-07-21 18:50:46 +00:00
|
|
|
target
|
|
|
|
.browserContext()
|
|
|
|
.emit(BrowserContextEmittedEvents.TargetCreated, target);
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
2022-07-21 18:50:46 +00:00
|
|
|
};
|
2020-05-07 10:54:55 +00:00
|
|
|
|
2022-07-21 18:50:46 +00:00
|
|
|
#onDetachedFromTarget = async (target: Target): Promise<void> => {
|
2020-05-07 10:54:55 +00:00
|
|
|
target._initializedCallback(false);
|
|
|
|
target._closedCallback();
|
|
|
|
if (await target._initializedPromise) {
|
2020-07-06 11:23:40 +00:00
|
|
|
this.emit(BrowserEmittedEvents.TargetDestroyed, target);
|
2020-05-07 10:54:55 +00:00
|
|
|
target
|
|
|
|
.browserContext()
|
2020-07-07 09:21:14 +00:00
|
|
|
.emit(BrowserContextEmittedEvents.TargetDestroyed, target);
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
2022-07-21 18:50:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#onTargetChanged = ({
|
|
|
|
target,
|
|
|
|
targetInfo,
|
|
|
|
}: {
|
|
|
|
target: Target;
|
|
|
|
targetInfo: Protocol.Target.TargetInfo;
|
|
|
|
}): void => {
|
2020-05-07 10:54:55 +00:00
|
|
|
const previousURL = target.url();
|
|
|
|
const wasInitialized = target._isInitialized;
|
2022-07-21 18:50:46 +00:00
|
|
|
target._targetInfoChanged(targetInfo);
|
2020-05-07 10:54:55 +00:00
|
|
|
if (wasInitialized && previousURL !== target.url()) {
|
2020-07-06 11:23:40 +00:00
|
|
|
this.emit(BrowserEmittedEvents.TargetChanged, target);
|
2020-07-07 09:21:14 +00:00
|
|
|
target
|
|
|
|
.browserContext()
|
|
|
|
.emit(BrowserContextEmittedEvents.TargetChanged, target);
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
2022-07-21 18:50:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#onTargetDiscovered = (targetInfo: Protocol.Target.TargetInfo): void => {
|
|
|
|
this.emit('targetdiscovered', targetInfo);
|
|
|
|
};
|
2020-05-07 10:54:55 +00:00
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* The browser websocket endpoint which can be used as an argument to
|
|
|
|
* {@link Puppeteer.connect}.
|
|
|
|
*
|
|
|
|
* @returns The Browser websocket url.
|
|
|
|
*
|
|
|
|
* @remarks
|
|
|
|
*
|
|
|
|
* The format is `ws://${host}:${port}/devtools/browser/<id>`.
|
|
|
|
*
|
|
|
|
* You can find the `webSocketDebuggerUrl` from `http://${host}:${port}/json/version`.
|
|
|
|
* Learn more about the
|
|
|
|
* {@link https://chromedevtools.github.io/devtools-protocol | devtools protocol} and
|
|
|
|
* the {@link
|
|
|
|
* https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target
|
|
|
|
* | browser endpoint}.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override wsEndpoint(): string {
|
2022-06-13 09:16:25 +00:00
|
|
|
return this.#connection.url();
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
2021-05-24 13:14:51 +00:00
|
|
|
* Promise which resolves to a new {@link Page} object. The Page is created in
|
|
|
|
* a default browser context.
|
2020-06-23 05:19:15 +00:00
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async newPage(): Promise<Page> {
|
2022-06-13 09:16:25 +00:00
|
|
|
return this.#defaultContext.newPage();
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async _createPageInContext(contextId?: string): Promise<Page> {
|
2022-06-22 13:25:44 +00:00
|
|
|
const {targetId} = await this.#connection.send('Target.createTarget', {
|
2020-05-07 10:54:55 +00:00
|
|
|
url: 'about:blank',
|
|
|
|
browserContextId: contextId || undefined,
|
|
|
|
});
|
2022-07-21 18:50:46 +00:00
|
|
|
const target = this.#targetManager.getAvailableTargets().get(targetId);
|
2022-01-20 13:15:23 +00:00
|
|
|
if (!target) {
|
|
|
|
throw new Error(`Missing target for page (id = ${targetId})`);
|
|
|
|
}
|
|
|
|
const initialized = await target._initializedPromise;
|
|
|
|
if (!initialized) {
|
|
|
|
throw new Error(`Failed to create target for page (id = ${targetId})`);
|
|
|
|
}
|
2020-05-07 10:54:55 +00:00
|
|
|
const page = await target.page();
|
2022-01-20 13:15:23 +00:00
|
|
|
if (!page) {
|
|
|
|
throw new Error(
|
|
|
|
`Failed to create a page for context (id = ${contextId})`
|
|
|
|
);
|
|
|
|
}
|
2020-05-07 10:54:55 +00:00
|
|
|
return page;
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* All active targets inside the Browser. In case of multiple browser contexts, returns
|
|
|
|
* an array with all the targets in all browser contexts.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override targets(): Target[] {
|
2022-07-21 18:50:46 +00:00
|
|
|
return Array.from(
|
|
|
|
this.#targetManager.getAvailableTargets().values()
|
|
|
|
).filter(target => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return target._isInitialized;
|
|
|
|
});
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* The target associated with the browser.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override target(): Target {
|
2022-06-22 13:25:44 +00:00
|
|
|
const browserTarget = this.targets().find(target => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return target.type() === 'browser';
|
|
|
|
});
|
2022-01-20 13:15:23 +00:00
|
|
|
if (!browserTarget) {
|
|
|
|
throw new Error('Browser target is not found');
|
|
|
|
}
|
|
|
|
return browserTarget;
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-23 05:19:15 +00:00
|
|
|
* 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`:
|
2022-08-12 12:15:26 +00:00
|
|
|
*
|
2022-07-01 11:52:39 +00:00
|
|
|
* ```ts
|
2020-06-23 05:19:15 +00:00
|
|
|
* await page.evaluate(() => window.open('https://www.example.com/'));
|
2022-08-12 12:15:26 +00:00
|
|
|
* const newWindowTarget = await browser.waitForTarget(
|
|
|
|
* target => target.url() === 'https://www.example.com/'
|
|
|
|
* );
|
2020-06-23 05:19:15 +00:00
|
|
|
* ```
|
2020-04-28 12:26:37 +00:00
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async waitForTarget(
|
2022-02-18 11:05:29 +00:00
|
|
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
2020-06-23 05:19:15 +00:00
|
|
|
options: WaitForTargetOptions = {}
|
2020-05-07 10:54:55 +00:00
|
|
|
): Promise<Target> {
|
2022-06-22 13:25:44 +00:00
|
|
|
const {timeout = 30000} = options;
|
2021-06-24 08:30:21 +00:00
|
|
|
let resolve: (value: Target | PromiseLike<Target>) => void;
|
2022-05-31 14:34:16 +00:00
|
|
|
let isResolved = false;
|
2022-06-22 13:25:44 +00:00
|
|
|
const targetPromise = new Promise<Target>(x => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return (resolve = x);
|
|
|
|
});
|
2020-07-06 11:23:40 +00:00
|
|
|
this.on(BrowserEmittedEvents.TargetCreated, check);
|
|
|
|
this.on(BrowserEmittedEvents.TargetChanged, check);
|
2020-05-07 10:54:55 +00:00
|
|
|
try {
|
2022-08-10 13:49:59 +00:00
|
|
|
this.targets().forEach(check);
|
2022-06-14 11:55:35 +00:00
|
|
|
if (!timeout) {
|
|
|
|
return await targetPromise;
|
|
|
|
}
|
2022-06-14 11:16:21 +00:00
|
|
|
return await waitWithTimeout(targetPromise, 'target', timeout);
|
2020-05-07 10:54:55 +00:00
|
|
|
} finally {
|
2022-05-31 14:34:16 +00:00
|
|
|
this.off(BrowserEmittedEvents.TargetCreated, check);
|
|
|
|
this.off(BrowserEmittedEvents.TargetChanged, check);
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2022-02-18 11:05:29 +00:00
|
|
|
async function check(target: Target): Promise<void> {
|
2022-05-31 14:34:16 +00:00
|
|
|
if ((await predicate(target)) && !isResolved) {
|
|
|
|
isResolved = true;
|
|
|
|
resolve(target);
|
|
|
|
}
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* An array of all open pages inside the Browser.
|
|
|
|
*
|
|
|
|
* @remarks
|
|
|
|
*
|
|
|
|
* In case of multiple browser contexts, returns an array with all the pages in all
|
|
|
|
* browser contexts. Non-visible pages, such as `"background_page"`, will not be listed
|
|
|
|
* here. You can find them using {@link Target.page}.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async pages(): Promise<Page[]> {
|
2020-05-07 10:54:55 +00:00
|
|
|
const contextPages = await Promise.all(
|
2022-06-22 13:25:44 +00:00
|
|
|
this.browserContexts().map(context => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return context.pages();
|
|
|
|
})
|
2020-05-07 10:54:55 +00:00
|
|
|
);
|
|
|
|
// Flatten array.
|
2022-06-15 10:42:21 +00:00
|
|
|
return contextPages.reduce((acc, x) => {
|
|
|
|
return acc.concat(x);
|
|
|
|
}, []);
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* A string representing the browser name and version.
|
|
|
|
*
|
|
|
|
* @remarks
|
|
|
|
*
|
|
|
|
* For headless Chromium, this is similar to `HeadlessChrome/61.0.3153.0`. For
|
|
|
|
* non-headless, this is similar to `Chrome/61.0.3153.0`.
|
|
|
|
*
|
|
|
|
* The format of browser.version() might change with future releases of Chromium.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async version(): Promise<string> {
|
2022-06-13 09:16:25 +00:00
|
|
|
const version = await this.#getVersion();
|
2020-05-07 10:54:55 +00:00
|
|
|
return version.product;
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* The browser's original user agent. Pages can override the browser user agent with
|
|
|
|
* {@link Page.setUserAgent}.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async userAgent(): Promise<string> {
|
2022-06-13 09:16:25 +00:00
|
|
|
const version = await this.#getVersion();
|
2020-05-07 10:54:55 +00:00
|
|
|
return version.userAgent;
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
2022-09-14 14:40:58 +00:00
|
|
|
* Closes Chromium and all of its pages (if any were opened). The
|
|
|
|
* {@link CDPBrowser} object itself is considered to be disposed and cannot be
|
|
|
|
* used anymore.
|
2020-06-23 05:19:15 +00:00
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async close(): Promise<void> {
|
2022-06-13 09:16:25 +00:00
|
|
|
await this.#closeCallback.call(null);
|
2020-05-07 10:54:55 +00:00
|
|
|
this.disconnect();
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* Disconnects Puppeteer from the browser, but leaves the Chromium process running.
|
2022-09-14 14:40:58 +00:00
|
|
|
* After calling `disconnect`, the {@link CDPBrowser} object is considered disposed and
|
2020-06-23 05:19:15 +00:00
|
|
|
* cannot be used anymore.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override disconnect(): void {
|
2022-07-21 18:50:46 +00:00
|
|
|
this.#targetManager.dispose();
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#connection.dispose();
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:19:15 +00:00
|
|
|
/**
|
|
|
|
* Indicates that the browser is connected.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override isConnected(): boolean {
|
2022-06-13 09:16:25 +00:00
|
|
|
return !this.#connection._closed;
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 09:16:25 +00:00
|
|
|
#getVersion(): Promise<Protocol.Browser.GetVersionResponse> {
|
|
|
|
return this.#connection.send('Browser.getVersion');
|
2020-05-07 10:54:55 +00:00
|
|
|
}
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
2020-07-07 09:21:14 +00:00
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
2022-09-14 14:40:58 +00:00
|
|
|
* @internal
|
2020-06-23 05:21:01 +00:00
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
export class CDPBrowserContext extends BrowserContext {
|
2022-06-13 09:16:25 +00:00
|
|
|
#connection: Connection;
|
2022-09-14 14:40:58 +00:00
|
|
|
#browser: CDPBrowser;
|
2022-06-13 09:16:25 +00:00
|
|
|
#id?: string;
|
2020-04-28 12:26:37 +00:00
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
constructor(connection: Connection, browser: CDPBrowser, contextId?: string) {
|
2020-04-28 12:26:37 +00:00
|
|
|
super();
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#connection = connection;
|
|
|
|
this.#browser = browser;
|
|
|
|
this.#id = contextId;
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2022-10-19 08:30:57 +00:00
|
|
|
override get id(): string | undefined {
|
|
|
|
return this.#id;
|
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* An array of all active targets inside the browser context.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override targets(): Target[] {
|
2022-06-22 13:25:44 +00:00
|
|
|
return this.#browser.targets().filter(target => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return target.browserContext() === this;
|
|
|
|
});
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* This searches for a target in this specific browser context.
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* An example of finding a target for a page opened via `window.open`:
|
2022-08-12 12:15:26 +00:00
|
|
|
*
|
2022-07-01 11:52:39 +00:00
|
|
|
* ```ts
|
2020-06-23 05:21:01 +00:00
|
|
|
* await page.evaluate(() => window.open('https://www.example.com/'));
|
2022-08-12 12:15:26 +00:00
|
|
|
* const newWindowTarget = await browserContext.waitForTarget(
|
|
|
|
* target => target.url() === 'https://www.example.com/'
|
|
|
|
* );
|
2020-06-23 05:21:01 +00:00
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* @param predicate - A function to be run for every target
|
|
|
|
* @param options - An object of options. Accepts a timout,
|
|
|
|
* which is the maximum wait time in milliseconds.
|
|
|
|
* Pass `0` to disable the timeout. Defaults to 30 seconds.
|
|
|
|
* @returns Promise which resolves to the first target found
|
|
|
|
* that matches the `predicate` function.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override waitForTarget(
|
2022-02-18 11:05:29 +00:00
|
|
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
2022-06-22 13:25:44 +00:00
|
|
|
options: {timeout?: number} = {}
|
2020-05-07 10:54:55 +00:00
|
|
|
): Promise<Target> {
|
2022-06-22 13:25:44 +00:00
|
|
|
return this.#browser.waitForTarget(target => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return target.browserContext() === this && predicate(target);
|
|
|
|
}, options);
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* An array of all pages inside the browser context.
|
|
|
|
*
|
|
|
|
* @returns Promise which resolves to an array of all open pages.
|
|
|
|
* Non visible pages, such as `"background_page"`, will not be listed here.
|
|
|
|
* You can find them using {@link Target.page | the target page}.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async pages(): Promise<Page[]> {
|
2020-04-28 12:26:37 +00:00
|
|
|
const pages = await Promise.all(
|
2020-05-07 10:54:55 +00:00
|
|
|
this.targets()
|
2022-06-22 13:25:44 +00:00
|
|
|
.filter(target => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return (
|
2022-06-02 11:27:31 +00:00
|
|
|
target.type() === 'page' ||
|
|
|
|
(target.type() === 'other' &&
|
2022-06-13 09:16:25 +00:00
|
|
|
this.#browser._getIsPageTargetCallback()?.(
|
2022-06-02 11:27:31 +00:00
|
|
|
target._getTargetInfo()
|
|
|
|
))
|
2022-06-15 10:42:21 +00:00
|
|
|
);
|
|
|
|
})
|
2022-06-22 13:25:44 +00:00
|
|
|
.map(target => {
|
2022-06-15 10:42:21 +00:00
|
|
|
return target.page();
|
|
|
|
})
|
2020-04-28 12:26:37 +00:00
|
|
|
);
|
2022-06-15 10:42:21 +00:00
|
|
|
return pages.filter((page): page is Page => {
|
|
|
|
return !!page;
|
|
|
|
});
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* Returns whether BrowserContext is incognito.
|
|
|
|
* The default browser context is the only non-incognito browser context.
|
|
|
|
*
|
|
|
|
* @remarks
|
|
|
|
* The default browser context cannot be closed.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override isIncognito(): boolean {
|
2022-06-13 09:16:25 +00:00
|
|
|
return !!this.#id;
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* @example
|
2022-08-12 12:15:26 +00:00
|
|
|
*
|
2022-07-01 11:52:39 +00:00
|
|
|
* ```ts
|
2020-06-23 05:21:01 +00:00
|
|
|
* const context = browser.defaultBrowserContext();
|
2022-08-12 12:15:26 +00:00
|
|
|
* await context.overridePermissions('https://html5demos.com', [
|
|
|
|
* 'geolocation',
|
|
|
|
* ]);
|
2020-06-23 05:21:01 +00:00
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* @param origin - The origin to grant permissions to, e.g. "https://example.com".
|
|
|
|
* @param permissions - An array of permissions to grant.
|
|
|
|
* All permissions that are not listed here will be automatically denied.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async overridePermissions(
|
2020-05-07 10:54:55 +00:00
|
|
|
origin: string,
|
2021-02-11 15:44:56 +00:00
|
|
|
permissions: Permission[]
|
2020-05-07 10:54:55 +00:00
|
|
|
): Promise<void> {
|
2022-06-22 13:25:44 +00:00
|
|
|
const protocolPermissions = permissions.map(permission => {
|
2021-05-12 14:48:30 +00:00
|
|
|
const protocolPermission =
|
|
|
|
WEB_PERMISSION_TO_PROTOCOL_PERMISSION.get(permission);
|
2022-06-14 11:55:35 +00:00
|
|
|
if (!protocolPermission) {
|
2020-04-28 12:26:37 +00:00
|
|
|
throw new Error('Unknown permission: ' + permission);
|
2022-06-14 11:55:35 +00:00
|
|
|
}
|
2020-04-28 12:26:37 +00:00
|
|
|
return protocolPermission;
|
|
|
|
});
|
2022-06-13 09:16:25 +00:00
|
|
|
await this.#connection.send('Browser.grantPermissions', {
|
2020-05-07 10:54:55 +00:00
|
|
|
origin,
|
2022-06-13 09:16:25 +00:00
|
|
|
browserContextId: this.#id || undefined,
|
2020-10-01 06:24:47 +00:00
|
|
|
permissions: protocolPermissions,
|
2020-05-07 10:54:55 +00:00
|
|
|
});
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* Clears all permission overrides for the browser context.
|
|
|
|
*
|
|
|
|
* @example
|
2022-08-12 12:15:26 +00:00
|
|
|
*
|
2022-07-01 11:52:39 +00:00
|
|
|
* ```ts
|
2020-06-23 05:21:01 +00:00
|
|
|
* const context = browser.defaultBrowserContext();
|
|
|
|
* context.overridePermissions('https://example.com', ['clipboard-read']);
|
|
|
|
* // do stuff ..
|
|
|
|
* context.clearPermissionOverrides();
|
|
|
|
* ```
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async clearPermissionOverrides(): Promise<void> {
|
2022-06-13 09:16:25 +00:00
|
|
|
await this.#connection.send('Browser.resetPermissions', {
|
|
|
|
browserContextId: this.#id || undefined,
|
2020-05-07 10:54:55 +00:00
|
|
|
});
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* Creates a new page in the browser context.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override newPage(): Promise<Page> {
|
2022-06-13 09:16:25 +00:00
|
|
|
return this.#browser._createPageInContext(this.#id);
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* The browser this browser context belongs to.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override browser(): CDPBrowser {
|
2022-06-13 09:16:25 +00:00
|
|
|
return this.#browser;
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 05:21:01 +00:00
|
|
|
/**
|
|
|
|
* Closes the browser context. All the targets that belong to the browser context
|
|
|
|
* will be closed.
|
|
|
|
*
|
|
|
|
* @remarks
|
|
|
|
* Only incognito browser contexts can be closed.
|
|
|
|
*/
|
2022-09-14 14:40:58 +00:00
|
|
|
override async close(): Promise<void> {
|
2022-06-13 09:16:25 +00:00
|
|
|
assert(this.#id, 'Non-incognito profiles cannot be closed!');
|
|
|
|
await this.#browser._disposeContext(this.#id);
|
2020-04-28 12:26:37 +00:00
|
|
|
}
|
|
|
|
}
|