mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore(chromium): move Page to common, implement PageDelegate (#184)
This commit is contained in:
parent
122837113b
commit
c323a3e50b
@ -21,11 +21,12 @@ import { Events } from './events';
|
|||||||
import { assert, helper } from '../helper';
|
import { assert, helper } from '../helper';
|
||||||
import { BrowserContext } from './BrowserContext';
|
import { BrowserContext } from './BrowserContext';
|
||||||
import { Connection, ConnectionEvents, CDPSession } from './Connection';
|
import { Connection, ConnectionEvents, CDPSession } from './Connection';
|
||||||
import { Page } from './Page';
|
import { Page } from '../page';
|
||||||
import { Target } from './Target';
|
import { Target } from './Target';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { Chromium } from './features/chromium';
|
import { Chromium } from './features/chromium';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
|
import { FrameManager } from './FrameManager';
|
||||||
|
|
||||||
export class Browser extends EventEmitter {
|
export class Browser extends EventEmitter {
|
||||||
private _ignoreHTTPSErrors: boolean;
|
private _ignoreHTTPSErrors: boolean;
|
||||||
@ -133,11 +134,11 @@ export class Browser extends EventEmitter {
|
|||||||
this.chromium.emit(Events.Chromium.TargetChanged, target);
|
this.chromium.emit(Events.Chromium.TargetChanged, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
async newPage(): Promise<Page> {
|
async newPage(): Promise<Page<Browser, BrowserContext>> {
|
||||||
return this._defaultContext.newPage();
|
return this._defaultContext.newPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createPageInContext(contextId: string | null): Promise<Page> {
|
async _createPageInContext(contextId: string | null): Promise<Page<Browser, BrowserContext>> {
|
||||||
const { targetId } = await this._client.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined });
|
const { targetId } = await this._client.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined });
|
||||||
const target = this._targets.get(targetId);
|
const target = this._targets.get(targetId);
|
||||||
assert(await target._initializedPromise, 'Failed to create target for page');
|
assert(await target._initializedPromise, 'Failed to create target for page');
|
||||||
@ -145,7 +146,7 @@ export class Browser extends EventEmitter {
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _closePage(page: Page) {
|
async _closePage(page: Page<Browser, BrowserContext>) {
|
||||||
await this._client.send('Target.closeTarget', { targetId: Target.fromPage(page)._targetId });
|
await this._client.send('Target.closeTarget', { targetId: Target.fromPage(page)._targetId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,14 +154,14 @@ export class Browser extends EventEmitter {
|
|||||||
return Array.from(this._targets.values()).filter(target => target._isInitialized);
|
return Array.from(this._targets.values()).filter(target => target._isInitialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _pages(context: BrowserContext): Promise<Page[]> {
|
async _pages(context: BrowserContext): Promise<Page<Browser, BrowserContext>[]> {
|
||||||
const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page');
|
const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page');
|
||||||
const pages = await Promise.all(targets.map(target => target.page()));
|
const pages = await Promise.all(targets.map(target => target.page()));
|
||||||
return pages.filter(page => !!page);
|
return pages.filter(page => !!page);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _activatePage(page: Page) {
|
async _activatePage(page: Page<Browser, BrowserContext>) {
|
||||||
await page._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId});
|
await (page._delegate as FrameManager)._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
|
async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
|
||||||
@ -189,7 +190,7 @@ export class Browser extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async pages(): Promise<Page[]> {
|
async pages(): Promise<Page<Browser, BrowserContext>[]> {
|
||||||
const contextPages = await Promise.all(this.browserContexts().map(context => context.pages()));
|
const contextPages = await Promise.all(this.browserContexts().map(context => context.pages()));
|
||||||
// Flatten array.
|
// Flatten array.
|
||||||
return contextPages.reduce((acc, x) => acc.concat(x), []);
|
return contextPages.reduce((acc, x) => acc.concat(x), []);
|
||||||
|
@ -20,7 +20,7 @@ import { filterCookies, NetworkCookie, rewriteCookies, SetNetworkCookieParam } f
|
|||||||
import { Browser } from './Browser';
|
import { Browser } from './Browser';
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { Permissions } from './features/permissions';
|
import { Permissions } from './features/permissions';
|
||||||
import { Page } from './Page';
|
import { Page } from '../page';
|
||||||
|
|
||||||
export class BrowserContext {
|
export class BrowserContext {
|
||||||
readonly permissions: Permissions;
|
readonly permissions: Permissions;
|
||||||
@ -34,7 +34,7 @@ export class BrowserContext {
|
|||||||
this.permissions = new Permissions(client, contextId);
|
this.permissions = new Permissions(client, contextId);
|
||||||
}
|
}
|
||||||
|
|
||||||
pages(): Promise<Page[]> {
|
pages(): Promise<Page<Browser, BrowserContext>[]> {
|
||||||
return this._browser._pages(this);
|
return this._browser._pages(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export class BrowserContext {
|
|||||||
return !!this._id;
|
return !!this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
newPage(): Promise<Page> {
|
newPage(): Promise<Page<Browser, BrowserContext>> {
|
||||||
return this._browser._createPageInContext(this._id);
|
return this._browser._createPageInContext(this._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,18 +21,30 @@ import * as frames from '../frames';
|
|||||||
import { assert, debugError } from '../helper';
|
import { assert, debugError } from '../helper';
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { TimeoutSettings } from '../TimeoutSettings';
|
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
|
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
|
||||||
import { DOMWorldDelegate } from './JSHandle';
|
import { DOMWorldDelegate } from './JSHandle';
|
||||||
import { LifecycleWatcher } from './LifecycleWatcher';
|
import { LifecycleWatcher } from './LifecycleWatcher';
|
||||||
import { NetworkManager } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||||
import { Page } from './Page';
|
import { Page } from '../page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { Events } from './events';
|
import { Events as CommonEvents } from '../events';
|
||||||
import { toConsoleMessageLocation, exceptionToError, releaseObject } from './protocolHelper';
|
import { toConsoleMessageLocation, exceptionToError, releaseObject } from './protocolHelper';
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import * as console from '../console';
|
import * as console from '../console';
|
||||||
|
import { PageDelegate } from '../page';
|
||||||
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||||
|
import { CRScreenshotDelegate } from './Screenshotter';
|
||||||
|
import { Accessibility } from './features/accessibility';
|
||||||
|
import { Coverage } from './features/coverage';
|
||||||
|
import { PDF } from './features/pdf';
|
||||||
|
import { Workers } from './features/workers';
|
||||||
|
import { Overrides } from './features/overrides';
|
||||||
|
import { Interception } from './features/interception';
|
||||||
|
import { Browser } from './Browser';
|
||||||
|
import { BrowserContext } from './BrowserContext';
|
||||||
|
import * as types from '../types';
|
||||||
|
import * as input from '../input';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
||||||
@ -51,26 +63,41 @@ type FrameData = {
|
|||||||
lifecycleEvents: Set<string>,
|
lifecycleEvents: Set<string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
|
||||||
_client: CDPSession;
|
_client: CDPSession;
|
||||||
private _page: Page;
|
private _page: Page<Browser, BrowserContext>;
|
||||||
private _networkManager: NetworkManager;
|
private _networkManager: NetworkManager;
|
||||||
_timeoutSettings: TimeoutSettings;
|
|
||||||
private _frames = new Map<string, frames.Frame>();
|
private _frames = new Map<string, frames.Frame>();
|
||||||
private _contextIdToContext = new Map<number, js.ExecutionContext>();
|
private _contextIdToContext = new Map<number, js.ExecutionContext>();
|
||||||
private _isolatedWorlds = new Set<string>();
|
private _isolatedWorlds = new Set<string>();
|
||||||
private _mainFrame: frames.Frame;
|
private _mainFrame: frames.Frame;
|
||||||
|
rawMouse: RawMouseImpl;
|
||||||
|
rawKeyboard: RawKeyboardImpl;
|
||||||
|
screenshotterDelegate: CRScreenshotDelegate;
|
||||||
|
|
||||||
constructor(client: CDPSession, page: Page, ignoreHTTPSErrors: boolean, timeoutSettings: TimeoutSettings) {
|
constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) {
|
||||||
super();
|
super();
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this._page = page;
|
this.rawKeyboard = new RawKeyboardImpl(client);
|
||||||
|
this.rawMouse = new RawMouseImpl(client);
|
||||||
|
this.screenshotterDelegate = new CRScreenshotDelegate(client);
|
||||||
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
|
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this);
|
||||||
this._timeoutSettings = timeoutSettings;
|
this._page = new Page(this, browserContext, ignoreHTTPSErrors);
|
||||||
|
(this._page as any).accessibility = new Accessibility(client);
|
||||||
|
(this._page as any).coverage = new Coverage(client);
|
||||||
|
(this._page as any).pdf = new PDF(client);
|
||||||
|
(this._page as any).workers = new Workers(client, this._page._addConsoleMessage.bind(this._page), error => this._page.emit(CommonEvents.Page.PageError, error));
|
||||||
|
(this._page as any).overrides = new Overrides(client);
|
||||||
|
(this._page as any).interception = new Interception(this._networkManager);
|
||||||
|
|
||||||
|
this._networkManager.on(NetworkManagerEvents.Request, event => this._page.emit(CommonEvents.Page.Request, event));
|
||||||
|
this._networkManager.on(NetworkManagerEvents.Response, event => this._page.emit(CommonEvents.Page.Response, event));
|
||||||
|
this._networkManager.on(NetworkManagerEvents.RequestFailed, event => this._page.emit(CommonEvents.Page.RequestFailed, event));
|
||||||
|
this._networkManager.on(NetworkManagerEvents.RequestFinished, event => this._page.emit(CommonEvents.Page.RequestFinished, event));
|
||||||
|
|
||||||
this._client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
|
this._client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
|
||||||
this._client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
|
this._client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
|
||||||
this._client.on('Page.domContentEventFired', event => page.emit(Events.Page.DOMContentLoaded));
|
this._client.on('Page.domContentEventFired', event => this._page.emit(CommonEvents.Page.DOMContentLoaded));
|
||||||
this._client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event));
|
this._client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event));
|
||||||
this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
|
this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
|
||||||
this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId));
|
this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId));
|
||||||
@ -78,7 +105,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
this._client.on('Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId));
|
this._client.on('Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId));
|
||||||
this._client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
|
this._client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
|
||||||
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event));
|
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event));
|
||||||
this._client.on('Page.loadEventFired', event => page.emit(Events.Page.Load));
|
this._client.on('Page.loadEventFired', event => this._page.emit(CommonEvents.Page.Load));
|
||||||
this._client.on('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url));
|
this._client.on('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url));
|
||||||
this._client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
|
this._client.on('Runtime.bindingCalled', event => this._onBindingCalled(event));
|
||||||
this._client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
|
this._client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
|
||||||
@ -119,7 +146,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
const {
|
const {
|
||||||
referer = this._networkManager.extraHTTPHeaders()['referer'],
|
referer = this._networkManager.extraHTTPHeaders()['referer'],
|
||||||
waitUntil = ['load'],
|
waitUntil = ['load'],
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);
|
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);
|
||||||
@ -157,7 +184,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
assertNoLegacyNavigationOptions(options);
|
assertNoLegacyNavigationOptions(options);
|
||||||
const {
|
const {
|
||||||
waitUntil = ['load'],
|
waitUntil = ['load'],
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||||
} = options;
|
} = options;
|
||||||
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);
|
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);
|
||||||
const error = await Promise.race([
|
const error = await Promise.race([
|
||||||
@ -174,7 +201,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) {
|
async setFrameContent(frame: frames.Frame, html: string, options: frames.NavigateOptions = {}) {
|
||||||
const {
|
const {
|
||||||
waitUntil = ['load'],
|
waitUntil = ['load'],
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||||
} = options;
|
} = options;
|
||||||
const context = await frame._utilityContext();
|
const context = await frame._utilityContext();
|
||||||
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
|
// We rely upon the fact that document.open() will reset frame lifecycle with "init"
|
||||||
@ -228,7 +255,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
this._handleFrameTree(child);
|
this._handleFrameTree(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
page(): Page {
|
page(): Page<Browser, BrowserContext> {
|
||||||
return this._page;
|
return this._page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +276,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
return;
|
return;
|
||||||
assert(parentFrameId);
|
assert(parentFrameId);
|
||||||
const parentFrame = this._frames.get(parentFrameId);
|
const parentFrame = this._frames.get(parentFrameId);
|
||||||
const frame = new frames.Frame(this, this._timeoutSettings, parentFrame);
|
const frame = new frames.Frame(this, this._page._timeoutSettings, parentFrame);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: frameId,
|
id: frameId,
|
||||||
loaderId: '',
|
loaderId: '',
|
||||||
@ -258,6 +285,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
frame[frameDataSymbol] = data;
|
frame[frameDataSymbol] = data;
|
||||||
this._frames.set(frameId, frame);
|
this._frames.set(frameId, frame);
|
||||||
this.emit(FrameManagerEvents.FrameAttached, frame);
|
this.emit(FrameManagerEvents.FrameAttached, frame);
|
||||||
|
this._page.emit(CommonEvents.Page.FrameAttached, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFrameNavigated(framePayload: Protocol.Page.Frame) {
|
_onFrameNavigated(framePayload: Protocol.Page.Frame) {
|
||||||
@ -280,7 +308,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
data.id = framePayload.id;
|
data.id = framePayload.id;
|
||||||
} else {
|
} else {
|
||||||
// Initial main frame navigation.
|
// Initial main frame navigation.
|
||||||
frame = new frames.Frame(this, this._timeoutSettings, null);
|
frame = new frames.Frame(this, this._page._timeoutSettings, null);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: framePayload.id,
|
id: framePayload.id,
|
||||||
loaderId: '',
|
loaderId: '',
|
||||||
@ -296,6 +324,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
frame._navigated(framePayload.url, framePayload.name);
|
frame._navigated(framePayload.url, framePayload.name);
|
||||||
|
|
||||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||||
|
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _ensureIsolatedWorld(name: string) {
|
async _ensureIsolatedWorld(name: string) {
|
||||||
@ -320,6 +349,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
frame._navigated(url, frame.name());
|
frame._navigated(url, frame.name());
|
||||||
this.emit(FrameManagerEvents.FrameNavigatedWithinDocument, frame);
|
this.emit(FrameManagerEvents.FrameNavigatedWithinDocument, frame);
|
||||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||||
|
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFrameDetached(frameId: string) {
|
_onFrameDetached(frameId: string) {
|
||||||
@ -371,6 +401,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
frame._detach();
|
frame._detach();
|
||||||
this._frames.delete(this._frameData(frame).id);
|
this._frames.delete(this._frameData(frame).id);
|
||||||
this.emit(FrameManagerEvents.FrameDetached, frame);
|
this.emit(FrameManagerEvents.FrameDetached, frame);
|
||||||
|
this._page.emit(CommonEvents.Page.FrameDetached, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) {
|
async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) {
|
||||||
@ -395,7 +426,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
|
this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _exposeBinding(name: string, bindingFunction: string) {
|
async exposeBinding(name: string, bindingFunction: string) {
|
||||||
await this._client.send('Runtime.addBinding', {name: name});
|
await this._client.send('Runtime.addBinding', {name: name});
|
||||||
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: bindingFunction});
|
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: bindingFunction});
|
||||||
await Promise.all(this.frames().map(frame => frame.evaluate(bindingFunction).catch(debugError)));
|
await Promise.all(this.frames().map(frame => frame.evaluate(bindingFunction).catch(debugError)));
|
||||||
@ -407,7 +438,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onDialog(event : Protocol.Page.javascriptDialogOpeningPayload) {
|
_onDialog(event : Protocol.Page.javascriptDialogOpeningPayload) {
|
||||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
this._page.emit(CommonEvents.Page.Dialog, new dialog.Dialog(
|
||||||
event.type as dialog.DialogType,
|
event.type as dialog.DialogType,
|
||||||
event.message,
|
event.message,
|
||||||
async (accept: boolean, promptText?: string) => {
|
async (accept: boolean, promptText?: string) => {
|
||||||
@ -417,7 +448,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
|
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
|
||||||
this._page.emit(Events.Page.PageError, exceptionToError(exceptionDetails));
|
this._page.emit(CommonEvents.Page.PageError, exceptionToError(exceptionDetails));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTargetCrashed() {
|
_onTargetCrashed() {
|
||||||
@ -429,7 +460,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
if (args)
|
if (args)
|
||||||
args.map(arg => releaseObject(this._client, arg));
|
args.map(arg => releaseObject(this._client, arg));
|
||||||
if (source !== 'worker')
|
if (source !== 'worker')
|
||||||
this._page.emit(Events.Page.Console, new console.ConsoleMessage(level, text, [], {url, lineNumber}));
|
this._page.emit(CommonEvents.Page.Console, new console.ConsoleMessage(level, text, [], {url, lineNumber}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
|
async _onFileChooserOpened(event: Protocol.Page.fileChooserOpenedPayload) {
|
||||||
@ -438,6 +469,88 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
const handle = await (utilityWorld.delegate as DOMWorldDelegate).adoptBackendNodeId(event.backendNodeId, utilityWorld);
|
const handle = await (utilityWorld.delegate as DOMWorldDelegate).adoptBackendNodeId(event.backendNodeId, utilityWorld);
|
||||||
this._page._onFileChooserOpened(handle);
|
this._page._onFileChooserOpened(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void> {
|
||||||
|
return this._networkManager.setExtraHTTPHeaders(extraHTTPHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserAgent(userAgent: string): Promise<void> {
|
||||||
|
return this._networkManager.setUserAgent(userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
|
||||||
|
await this._client.send('Emulation.setScriptExecutionDisabled', { value: !enabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBypassCSP(enabled: boolean): Promise<void> {
|
||||||
|
await this._client.send('Page.setBypassCSP', { enabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
async setViewport(viewport: types.Viewport): Promise<void> {
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
isMobile = false,
|
||||||
|
deviceScaleFactor = 1,
|
||||||
|
hasTouch = false,
|
||||||
|
isLandscape = false,
|
||||||
|
} = viewport;
|
||||||
|
const screenOrientation: Protocol.Emulation.ScreenOrientation = isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
|
||||||
|
await Promise.all([
|
||||||
|
this._client.send('Emulation.setDeviceMetricsOverride', { mobile: isMobile, width, height, deviceScaleFactor, screenOrientation }),
|
||||||
|
this._client.send('Emulation.setTouchEmulationEnabled', {
|
||||||
|
enabled: hasTouch
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void> {
|
||||||
|
const features = mediaColorScheme ? [{ name: 'prefers-color-scheme', value: mediaColorScheme }] : [];
|
||||||
|
await this._client.send('Emulation.setEmulatedMedia', { media: mediaType || '', features });
|
||||||
|
}
|
||||||
|
|
||||||
|
setCacheEnabled(enabled: boolean): Promise<void> {
|
||||||
|
return this._networkManager.setCacheEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
|
const [response] = await Promise.all([
|
||||||
|
this._page.waitForNavigation(options),
|
||||||
|
this._client.send('Page.reload')
|
||||||
|
]);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _go(delta: number, options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
|
const history = await this._client.send('Page.getNavigationHistory');
|
||||||
|
const entry = history.entries[history.currentIndex + delta];
|
||||||
|
if (!entry)
|
||||||
|
return null;
|
||||||
|
const [response] = await Promise.all([
|
||||||
|
this._page.waitForNavigation(options),
|
||||||
|
this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}),
|
||||||
|
]);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
|
return this._go(-1, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
goForward(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
|
return this._go(+1, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async evaluateOnNewDocument(source: string): Promise<void> {
|
||||||
|
await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
|
||||||
|
}
|
||||||
|
|
||||||
|
async closePage(runBeforeUnload: boolean): Promise<void> {
|
||||||
|
if (runBeforeUnload)
|
||||||
|
await this._client.send('Page.close');
|
||||||
|
else
|
||||||
|
await this._page.browser()._closePage(this._page);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertNoLegacyNavigationOptions(options) {
|
function assertNoLegacyNavigationOptions(options) {
|
||||||
|
@ -50,7 +50,7 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isJavascriptEnabled(): boolean {
|
isJavascriptEnabled(): boolean {
|
||||||
return this._frameManager.page()._javascriptEnabled;
|
return this._frameManager.page()._state.javascriptEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
isElement(remoteObject: any): boolean {
|
isElement(remoteObject: any): boolean {
|
||||||
|
@ -19,11 +19,12 @@ import * as types from '../types';
|
|||||||
import { Browser } from './Browser';
|
import { Browser } from './Browser';
|
||||||
import { BrowserContext } from './BrowserContext';
|
import { BrowserContext } from './BrowserContext';
|
||||||
import { CDPSession, CDPSessionEvents } from './Connection';
|
import { CDPSession, CDPSessionEvents } from './Connection';
|
||||||
import { Events } from './events';
|
import { Events as CommonEvents } from '../events';
|
||||||
import { Worker } from './features/workers';
|
import { Worker } from './features/workers';
|
||||||
import { Page } from './Page';
|
import { Page } from '../page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { debugError } from '../helper';
|
import { debugError } from '../helper';
|
||||||
|
import { FrameManager } from './FrameManager';
|
||||||
|
|
||||||
const targetSymbol = Symbol('target');
|
const targetSymbol = Symbol('target');
|
||||||
|
|
||||||
@ -34,14 +35,14 @@ export class Target {
|
|||||||
private _sessionFactory: () => Promise<CDPSession>;
|
private _sessionFactory: () => Promise<CDPSession>;
|
||||||
private _ignoreHTTPSErrors: boolean;
|
private _ignoreHTTPSErrors: boolean;
|
||||||
private _defaultViewport: types.Viewport;
|
private _defaultViewport: types.Viewport;
|
||||||
private _pagePromise: Promise<Page> | null = null;
|
private _pagePromise: Promise<Page<Browser, BrowserContext>> | null = null;
|
||||||
private _page: Page | null = null;
|
private _page: Page<Browser, BrowserContext> | null = null;
|
||||||
private _workerPromise: Promise<Worker> | null = null;
|
private _workerPromise: Promise<Worker> | null = null;
|
||||||
_initializedPromise: Promise<boolean>;
|
_initializedPromise: Promise<boolean>;
|
||||||
_initializedCallback: (value?: unknown) => void;
|
_initializedCallback: (value?: unknown) => void;
|
||||||
_isInitialized: boolean;
|
_isInitialized: boolean;
|
||||||
|
|
||||||
static fromPage(page: Page): Target {
|
static fromPage(page: Page<Browser, BrowserContext>): Target {
|
||||||
return (page as any)[targetSymbol];
|
return (page as any)[targetSymbol];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +65,10 @@ export class Target {
|
|||||||
if (!opener || !opener._pagePromise || this.type() !== 'page')
|
if (!opener || !opener._pagePromise || this.type() !== 'page')
|
||||||
return true;
|
return true;
|
||||||
const openerPage = await opener._pagePromise;
|
const openerPage = await opener._pagePromise;
|
||||||
if (!openerPage.listenerCount(Events.Page.Popup))
|
if (!openerPage.listenerCount(CommonEvents.Page.Popup))
|
||||||
return true;
|
return true;
|
||||||
const popupPage = await this.page();
|
const popupPage = await this.page();
|
||||||
openerPage.emit(Events.Page.Popup, popupPage);
|
openerPage.emit(CommonEvents.Page.Popup, popupPage);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== '';
|
this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== '';
|
||||||
@ -80,10 +81,11 @@ export class Target {
|
|||||||
this._page._didClose();
|
this._page._didClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async page(): Promise<Page | null> {
|
async page(): Promise<Page<Browser, BrowserContext> | null> {
|
||||||
if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) {
|
if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) {
|
||||||
this._pagePromise = this._sessionFactory().then(async client => {
|
this._pagePromise = this._sessionFactory().then(async client => {
|
||||||
const page = new Page(client, this._browserContext, this._ignoreHTTPSErrors);
|
const frameManager = new FrameManager(client, this._browserContext, this._ignoreHTTPSErrors);
|
||||||
|
const page = frameManager.page();
|
||||||
this._page = page;
|
this._page = page;
|
||||||
page[targetSymbol] = this;
|
page[targetSymbol] = this;
|
||||||
client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect());
|
client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect());
|
||||||
@ -93,7 +95,7 @@ export class Target {
|
|||||||
client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(debugError);
|
client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(debugError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await page._frameManager.initialize();
|
await frameManager.initialize();
|
||||||
await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
|
await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
|
||||||
if (this._defaultViewport)
|
if (this._defaultViewport)
|
||||||
await page.setViewport(this._defaultViewport);
|
await page.setViewport(this._defaultViewport);
|
||||||
|
@ -21,7 +21,7 @@ export { Overrides } from './features/overrides';
|
|||||||
export { PDF } from './features/pdf';
|
export { PDF } from './features/pdf';
|
||||||
export { Permissions } from './features/permissions';
|
export { Permissions } from './features/permissions';
|
||||||
export { Worker, Workers } from './features/workers';
|
export { Worker, Workers } from './features/workers';
|
||||||
export { Page } from './Page';
|
export { Page } from '../page';
|
||||||
export { Playwright } from './Playwright';
|
export { Playwright } from './Playwright';
|
||||||
export { Target } from './Target';
|
export { Target } from './Target';
|
||||||
|
|
||||||
|
@ -16,26 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const Events = {
|
export const Events = {
|
||||||
Page: {
|
|
||||||
Close: 'close',
|
|
||||||
Console: 'console',
|
|
||||||
Dialog: 'dialog',
|
|
||||||
FileChooser: 'filechooser',
|
|
||||||
DOMContentLoaded: 'domcontentloaded',
|
|
||||||
// Can't use just 'error' due to node.js special treatment of error events.
|
|
||||||
// @see https://nodejs.org/api/events.html#events_error_events
|
|
||||||
PageError: 'pageerror',
|
|
||||||
Request: 'request',
|
|
||||||
Response: 'response',
|
|
||||||
RequestFailed: 'requestfailed',
|
|
||||||
RequestFinished: 'requestfinished',
|
|
||||||
FrameAttached: 'frameattached',
|
|
||||||
FrameDetached: 'framedetached',
|
|
||||||
FrameNavigated: 'framenavigated',
|
|
||||||
Load: 'load',
|
|
||||||
Popup: 'popup',
|
|
||||||
},
|
|
||||||
|
|
||||||
Browser: {
|
Browser: {
|
||||||
Disconnected: 'disconnected'
|
Disconnected: 'disconnected'
|
||||||
},
|
},
|
||||||
|
@ -19,10 +19,11 @@ import { assert } from '../../helper';
|
|||||||
import { Browser } from '../Browser';
|
import { Browser } from '../Browser';
|
||||||
import { BrowserContext } from '../BrowserContext';
|
import { BrowserContext } from '../BrowserContext';
|
||||||
import { CDPSession, Connection } from '../Connection';
|
import { CDPSession, Connection } from '../Connection';
|
||||||
import { Page } from '../Page';
|
import { Page } from '../../page';
|
||||||
import { readProtocolStream } from '../protocolHelper';
|
import { readProtocolStream } from '../protocolHelper';
|
||||||
import { Target } from '../Target';
|
import { Target } from '../Target';
|
||||||
import { Worker } from './workers';
|
import { Worker } from './workers';
|
||||||
|
import { FrameManager } from '../FrameManager';
|
||||||
|
|
||||||
export class Chromium extends EventEmitter {
|
export class Chromium extends EventEmitter {
|
||||||
private _connection: Connection;
|
private _connection: Connection;
|
||||||
@ -47,9 +48,9 @@ export class Chromium extends EventEmitter {
|
|||||||
return target._worker();
|
return target._worker();
|
||||||
}
|
}
|
||||||
|
|
||||||
async startTracing(page: Page | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
|
async startTracing(page: Page<Browser, BrowserContext> | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
|
||||||
assert(!this._recording, 'Cannot start recording trace while already recording trace.');
|
assert(!this._recording, 'Cannot start recording trace while already recording trace.');
|
||||||
this._tracingClient = page ? page._client : this._client;
|
this._tracingClient = page ? (page._delegate as FrameManager)._client : this._client;
|
||||||
|
|
||||||
const defaultCategories = [
|
const defaultCategories = [
|
||||||
'-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline',
|
'-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline',
|
||||||
@ -91,7 +92,7 @@ export class Chromium extends EventEmitter {
|
|||||||
return context ? targets.filter(t => t.browserContext() === context) : targets;
|
return context ? targets.filter(t => t.browserContext() === context) : targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageTarget(page: Page): Target {
|
pageTarget(page: Page<Browser, BrowserContext>): Target {
|
||||||
return Target.fromPage(page);
|
return Target.fromPage(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
38
src/events.ts
Normal file
38
src/events.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Google Inc. All rights reserved.
|
||||||
|
* Modifications copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const Events = {
|
||||||
|
Page: {
|
||||||
|
Close: 'close',
|
||||||
|
Console: 'console',
|
||||||
|
Dialog: 'dialog',
|
||||||
|
FileChooser: 'filechooser',
|
||||||
|
DOMContentLoaded: 'domcontentloaded',
|
||||||
|
// Can't use just 'error' due to node.js special treatment of error events.
|
||||||
|
// @see https://nodejs.org/api/events.html#events_error_events
|
||||||
|
PageError: 'pageerror',
|
||||||
|
Request: 'request',
|
||||||
|
Response: 'response',
|
||||||
|
RequestFailed: 'requestfailed',
|
||||||
|
RequestFinished: 'requestfinished',
|
||||||
|
FrameAttached: 'frameattached',
|
||||||
|
FrameDetached: 'framedetached',
|
||||||
|
FrameNavigated: 'framenavigated',
|
||||||
|
Load: 'load',
|
||||||
|
Popup: 'popup',
|
||||||
|
},
|
||||||
|
};
|
@ -113,8 +113,8 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async emulateMedia(options: {
|
async emulateMedia(options: {
|
||||||
type?: ''|'screen'|'print',
|
type?: input.MediaType,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
|
colorScheme?: input.MediaColorScheme }) {
|
||||||
assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
|
assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
|
||||||
assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
||||||
await this._session.send('Page.setEmulatedMedia', options);
|
await this._session.send('Page.setEmulatedMedia', options);
|
||||||
|
@ -379,5 +379,7 @@ export type FilePayload = {
|
|||||||
data: string
|
data: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mediaTypes = new Set(['screen', 'print']);
|
export type MediaType = 'screen' | 'print';
|
||||||
export const mediaColorSchemes = new Set(['dark', 'light', 'no-preference']);
|
export const mediaTypes: Set<MediaType> = new Set(['screen', 'print']);
|
||||||
|
export type MediaColorScheme = 'dark' | 'light' | 'no-preference';
|
||||||
|
export const mediaColorSchemes: Set<MediaColorScheme> = new Set(['dark', 'light', 'no-preference']);
|
||||||
|
@ -16,86 +16,97 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import * as console from '../console';
|
import * as console from './console';
|
||||||
import * as dom from '../dom';
|
import * as dom from './dom';
|
||||||
import * as frames from '../frames';
|
import * as frames from './frames';
|
||||||
import { assert, debugError, helper } from '../helper';
|
import { assert, debugError, helper } from './helper';
|
||||||
import * as input from '../input';
|
import * as input from './input';
|
||||||
import { ClickOptions, mediaColorSchemes, mediaTypes, MultiClickOptions, PointerActionOptions, SelectOption } from '../input';
|
import * as js from './javascript';
|
||||||
import * as js from '../javascript';
|
import * as network from './network';
|
||||||
import * as network from '../network';
|
import { Screenshotter, ScreenshotterDelegate } from './screenshotter';
|
||||||
import { Screenshotter } from '../screenshotter';
|
import { TimeoutSettings } from './TimeoutSettings';
|
||||||
import { TimeoutSettings } from '../TimeoutSettings';
|
import * as types from './types';
|
||||||
import * as types from '../types';
|
|
||||||
import { Browser } from './Browser';
|
|
||||||
import { BrowserContext } from './BrowserContext';
|
|
||||||
import { CDPSession } from './Connection';
|
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { Accessibility } from './features/accessibility';
|
|
||||||
import { Coverage } from './features/coverage';
|
|
||||||
import { Interception } from './features/interception';
|
|
||||||
import { Overrides } from './features/overrides';
|
|
||||||
import { PDF } from './features/pdf';
|
|
||||||
import { Workers } from './features/workers';
|
|
||||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
|
||||||
import { RawKeyboardImpl, RawMouseImpl } from './Input';
|
|
||||||
import { NetworkManagerEvents } from './NetworkManager';
|
|
||||||
import { CRScreenshotDelegate } from './Screenshotter';
|
|
||||||
import { Protocol } from './protocol';
|
|
||||||
|
|
||||||
export class Page extends EventEmitter {
|
export interface PageDelegate {
|
||||||
|
readonly rawMouse: input.RawMouse;
|
||||||
|
readonly rawKeyboard: input.RawKeyboard;
|
||||||
|
readonly screenshotterDelegate: ScreenshotterDelegate;
|
||||||
|
mainFrame(): frames.Frame;
|
||||||
|
frames(): frames.Frame[];
|
||||||
|
reload(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||||
|
goBack(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||||
|
goForward(options?: frames.NavigateOptions): Promise<network.Response | null>;
|
||||||
|
exposeBinding(name: string, bindingFunction: string): Promise<void>;
|
||||||
|
evaluateOnNewDocument(source: string): Promise<void>;
|
||||||
|
closePage(runBeforeUnload: boolean): Promise<void>;
|
||||||
|
|
||||||
|
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>;
|
||||||
|
setUserAgent(userAgent: string): Promise<void>;
|
||||||
|
setJavaScriptEnabled(enabled: boolean): Promise<void>;
|
||||||
|
setBypassCSP(enabled: boolean): Promise<void>;
|
||||||
|
setViewport(viewport: types.Viewport): Promise<void>;
|
||||||
|
setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void>;
|
||||||
|
setCacheEnabled(enabled: boolean): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BrowserContextInterface<Browser> {
|
||||||
|
browser(): Browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageState = {
|
||||||
|
viewport: types.Viewport | null;
|
||||||
|
userAgent: string | null;
|
||||||
|
mediaType: input.MediaType | null;
|
||||||
|
mediaColorScheme: input.MediaColorScheme | null;
|
||||||
|
javascriptEnabled: boolean | null;
|
||||||
|
extraHTTPHeaders: network.Headers | null;
|
||||||
|
bypassCSP: boolean | null;
|
||||||
|
cacheEnabled: boolean | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FileChooser = {
|
||||||
|
element: dom.ElementHandle,
|
||||||
|
multiple: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Page<Browser, BrowserContext extends BrowserContextInterface<Browser>> extends EventEmitter {
|
||||||
private _closed = false;
|
private _closed = false;
|
||||||
private _closedCallback: () => void;
|
private _closedCallback: () => void;
|
||||||
private _closedPromise: Promise<void>;
|
private _closedPromise: Promise<void>;
|
||||||
private _disconnected = false;
|
private _disconnected = false;
|
||||||
private _disconnectedCallback: (e: Error) => void;
|
private _disconnectedCallback: (e: Error) => void;
|
||||||
private _disconnectedPromise: Promise<Error>;
|
private _disconnectedPromise: Promise<Error>;
|
||||||
_client: CDPSession;
|
|
||||||
private _browserContext: BrowserContext;
|
private _browserContext: BrowserContext;
|
||||||
readonly keyboard: input.Keyboard;
|
readonly keyboard: input.Keyboard;
|
||||||
readonly mouse: input.Mouse;
|
readonly mouse: input.Mouse;
|
||||||
private _timeoutSettings: TimeoutSettings;
|
readonly _timeoutSettings: TimeoutSettings;
|
||||||
_frameManager: FrameManager;
|
readonly _delegate: PageDelegate;
|
||||||
readonly accessibility: Accessibility;
|
readonly _state: PageState;
|
||||||
readonly coverage: Coverage;
|
|
||||||
readonly overrides: Overrides;
|
|
||||||
readonly interception: Interception;
|
|
||||||
readonly pdf: PDF;
|
|
||||||
readonly workers: Workers;
|
|
||||||
private _pageBindings = new Map<string, Function>();
|
private _pageBindings = new Map<string, Function>();
|
||||||
_javascriptEnabled = true;
|
readonly _screenshotter: Screenshotter;
|
||||||
private _viewport: types.Viewport | null = null;
|
|
||||||
_screenshotter: Screenshotter;
|
|
||||||
private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
|
private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
|
||||||
private _emulatedMediaType: string | undefined;
|
|
||||||
|
|
||||||
constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) {
|
constructor(delegate: PageDelegate, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) {
|
||||||
super();
|
super();
|
||||||
this._client = client;
|
this._delegate = delegate;
|
||||||
this._closedPromise = new Promise(f => this._closedCallback = f);
|
this._closedPromise = new Promise(f => this._closedCallback = f);
|
||||||
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
|
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
|
||||||
this._browserContext = browserContext;
|
this._browserContext = browserContext;
|
||||||
this.keyboard = new input.Keyboard(new RawKeyboardImpl(client));
|
this._state = {
|
||||||
this.mouse = new input.Mouse(new RawMouseImpl(client), this.keyboard);
|
viewport: null,
|
||||||
|
userAgent: null,
|
||||||
|
mediaType: null,
|
||||||
|
mediaColorScheme: null,
|
||||||
|
javascriptEnabled: null,
|
||||||
|
extraHTTPHeaders: null,
|
||||||
|
bypassCSP: null,
|
||||||
|
cacheEnabled: null,
|
||||||
|
};
|
||||||
|
this.keyboard = new input.Keyboard(delegate.rawKeyboard);
|
||||||
|
this.mouse = new input.Mouse(delegate.rawMouse, this.keyboard);
|
||||||
this._timeoutSettings = new TimeoutSettings();
|
this._timeoutSettings = new TimeoutSettings();
|
||||||
this.accessibility = new Accessibility(client);
|
this._screenshotter = new Screenshotter(this, delegate.screenshotterDelegate, browserContext.browser());
|
||||||
this._frameManager = new FrameManager(client, this, ignoreHTTPSErrors, this._timeoutSettings);
|
|
||||||
this.coverage = new Coverage(client);
|
|
||||||
this.pdf = new PDF(client);
|
|
||||||
this.workers = new Workers(client, this._addConsoleMessage.bind(this), error => this.emit(Events.Page.PageError, error));
|
|
||||||
this.overrides = new Overrides(client);
|
|
||||||
this.interception = new Interception(this._frameManager.networkManager());
|
|
||||||
this._screenshotter = new Screenshotter(this, new CRScreenshotDelegate(this._client), browserContext.browser());
|
|
||||||
|
|
||||||
this._frameManager.on(FrameManagerEvents.FrameAttached, event => this.emit(Events.Page.FrameAttached, event));
|
|
||||||
this._frameManager.on(FrameManagerEvents.FrameDetached, event => this.emit(Events.Page.FrameDetached, event));
|
|
||||||
this._frameManager.on(FrameManagerEvents.FrameNavigated, event => this.emit(Events.Page.FrameNavigated, event));
|
|
||||||
|
|
||||||
const networkManager = this._frameManager.networkManager();
|
|
||||||
networkManager.on(NetworkManagerEvents.Request, event => this.emit(Events.Page.Request, event));
|
|
||||||
networkManager.on(NetworkManagerEvents.Response, event => this.emit(Events.Page.Response, event));
|
|
||||||
networkManager.on(NetworkManagerEvents.RequestFailed, event => this.emit(Events.Page.RequestFailed, event));
|
|
||||||
networkManager.on(NetworkManagerEvents.RequestFinished, event => this.emit(Events.Page.RequestFinished, event));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_didClose() {
|
_didClose() {
|
||||||
@ -147,11 +158,11 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainFrame(): frames.Frame {
|
mainFrame(): frames.Frame {
|
||||||
return this._frameManager.mainFrame();
|
return this._delegate.mainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
frames(): frames.Frame[] {
|
frames(): frames.Frame[] {
|
||||||
return this._frameManager.frames();
|
return this._delegate.frames();
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultNavigationTimeout(timeout: number) {
|
setDefaultNavigationTimeout(timeout: number) {
|
||||||
@ -199,7 +210,7 @@ export class Page extends EventEmitter {
|
|||||||
if (this._pageBindings.has(name))
|
if (this._pageBindings.has(name))
|
||||||
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
||||||
this._pageBindings.set(name, playwrightFunction);
|
this._pageBindings.set(name, playwrightFunction);
|
||||||
await this._frameManager._exposeBinding(name, helper.evaluationString(addPageBinding, name));
|
await this._delegate.exposeBinding(name, helper.evaluationString(addPageBinding, name));
|
||||||
|
|
||||||
function addPageBinding(bindingName: string) {
|
function addPageBinding(bindingName: string) {
|
||||||
const binding = window[bindingName];
|
const binding = window[bindingName];
|
||||||
@ -219,12 +230,14 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExtraHTTPHeaders(headers: { [s: string]: string; }) {
|
setExtraHTTPHeaders(headers: network.Headers) {
|
||||||
return this._frameManager.networkManager().setExtraHTTPHeaders(headers);
|
this._state.extraHTTPHeaders = {...headers};
|
||||||
|
return this._delegate.setExtraHTTPHeaders(headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserAgent(userAgent: string) {
|
setUserAgent(userAgent: string) {
|
||||||
return this._frameManager.networkManager().setUserAgent(userAgent);
|
this._state.userAgent = userAgent;
|
||||||
|
return this._delegate.setUserAgent(userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onBindingCalled(payload: string, context: js.ExecutionContext) {
|
async _onBindingCalled(payload: string, context: js.ExecutionContext) {
|
||||||
@ -271,28 +284,24 @@ export class Page extends EventEmitter {
|
|||||||
return this.mainFrame().url();
|
return this.mainFrame().url();
|
||||||
}
|
}
|
||||||
|
|
||||||
async content(): Promise<string> {
|
content(): Promise<string> {
|
||||||
return await this.mainFrame().content();
|
return this.mainFrame().content();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setContent(html: string, options: { timeout?: number; waitUntil?: string | string[]; } | undefined) {
|
setContent(html: string, options?: frames.NavigateOptions): Promise<void> {
|
||||||
await this.mainFrame().setContent(html, options);
|
return this.mainFrame().setContent(html, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async goto(url: string, options: { referer?: string; timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
goto(url: string, options?: frames.GotoOptions): Promise<network.Response | null> {
|
||||||
return await this.mainFrame().goto(url, options);
|
return this.mainFrame().goto(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload(options: { timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
|
reload(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
const [response] = await Promise.all([
|
return this._delegate.reload(options);
|
||||||
this.waitForNavigation(options),
|
|
||||||
this._client.send('Page.reload')
|
|
||||||
]);
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForNavigation(options: { timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
|
waitForNavigation(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
return await this.mainFrame().waitForNavigation(options);
|
return this.mainFrame().waitForNavigation(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForRequest(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise<Request> {
|
async waitForRequest(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise<Request> {
|
||||||
@ -321,24 +330,12 @@ export class Page extends EventEmitter {
|
|||||||
}, timeout, this._disconnectedPromise);
|
}, timeout, this._disconnectedPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
async goBack(options: { timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
goBack(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
return this._go(-1, options);
|
return this._delegate.goBack(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async goForward(options: { timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
goForward(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||||
return this._go(+1, options);
|
return this._delegate.goForward(options);
|
||||||
}
|
|
||||||
|
|
||||||
async _go(delta, options: { timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
|
||||||
const history = await this._client.send('Page.getNavigationHistory');
|
|
||||||
const entry = history.entries[history.currentIndex + delta];
|
|
||||||
if (!entry)
|
|
||||||
return null;
|
|
||||||
const [response] = await Promise.all([
|
|
||||||
this.waitForNavigation(options),
|
|
||||||
this._client.send('Page.navigateToHistoryEntry', {entryId: entry.id}),
|
|
||||||
]);
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async emulate(options: { viewport: types.Viewport; userAgent: string; }) {
|
async emulate(options: { viewport: types.Viewport; userAgent: string; }) {
|
||||||
@ -349,52 +346,41 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setJavaScriptEnabled(enabled: boolean) {
|
async setJavaScriptEnabled(enabled: boolean) {
|
||||||
if (this._javascriptEnabled === enabled)
|
if (this._state.javascriptEnabled === enabled)
|
||||||
return;
|
return;
|
||||||
this._javascriptEnabled = enabled;
|
this._state.javascriptEnabled = enabled;
|
||||||
await this._client.send('Emulation.setScriptExecutionDisabled', { value: !enabled });
|
await this._delegate.setJavaScriptEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setBypassCSP(enabled: boolean) {
|
async setBypassCSP(enabled: boolean) {
|
||||||
await this._client.send('Page.setBypassCSP', { enabled });
|
if (this._state.bypassCSP === enabled)
|
||||||
|
return;
|
||||||
|
await this._delegate.setBypassCSP(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
async emulateMedia(options: {
|
async emulateMedia(options: { type?: input.MediaType, colorScheme?: input.MediaColorScheme }) {
|
||||||
type?: string,
|
assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
|
assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
||||||
assert(!options.type || mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
|
if (options.type !== undefined)
|
||||||
assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
this._state.mediaType = options.type;
|
||||||
const media = typeof options.type === 'undefined' ? this._emulatedMediaType : options.type;
|
if (options.colorScheme !== undefined)
|
||||||
const features = typeof options.colorScheme === 'undefined' ? [] : [{ name: 'prefers-color-scheme', value: options.colorScheme }];
|
this._state.mediaColorScheme = options.colorScheme;
|
||||||
await this._client.send('Emulation.setEmulatedMedia', { media: media || '', features });
|
await this._delegate.setEmulateMedia(this._state.mediaType, this._state.mediaColorScheme);
|
||||||
this._emulatedMediaType = options.type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setViewport(viewport: types.Viewport) {
|
async setViewport(viewport: types.Viewport) {
|
||||||
const {
|
const oldIsMobile = this._state.viewport ? !!this._state.viewport.isMobile : false;
|
||||||
width,
|
const oldHasTouch = this._state.viewport ? !!this._state.viewport.hasTouch : false;
|
||||||
height,
|
const newIsMobile = !!viewport.isMobile;
|
||||||
isMobile = false,
|
const newHasTouch = !!viewport.hasTouch;
|
||||||
deviceScaleFactor = 1,
|
this._state.viewport = { ...viewport };
|
||||||
hasTouch = false,
|
await this._delegate.setViewport(viewport);
|
||||||
isLandscape = false,
|
if (oldIsMobile !== newIsMobile || oldHasTouch !== newHasTouch)
|
||||||
} = viewport;
|
|
||||||
const screenOrientation: Protocol.Emulation.ScreenOrientation = isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' };
|
|
||||||
await Promise.all([
|
|
||||||
this._client.send('Emulation.setDeviceMetricsOverride', { mobile: isMobile, width, height, deviceScaleFactor, screenOrientation }),
|
|
||||||
this._client.send('Emulation.setTouchEmulationEnabled', {
|
|
||||||
enabled: hasTouch
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
const oldIsMobile = this._viewport ? !!this._viewport.isMobile : false;
|
|
||||||
const oldHasTouch = this._viewport ? !!this._viewport.hasTouch : false;
|
|
||||||
this._viewport = viewport;
|
|
||||||
if (oldIsMobile !== isMobile || oldHasTouch !== hasTouch)
|
|
||||||
await this.reload();
|
await this.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport(): types.Viewport | null {
|
viewport(): types.Viewport | null {
|
||||||
return this._viewport;
|
return this._state.viewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
||||||
@ -403,45 +389,45 @@ export class Page extends EventEmitter {
|
|||||||
|
|
||||||
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
|
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
|
||||||
const source = helper.evaluationString(pageFunction, ...args);
|
const source = helper.evaluationString(pageFunction, ...args);
|
||||||
await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
|
await this._delegate.evaluateOnNewDocument(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCacheEnabled(enabled: boolean = true) {
|
async setCacheEnabled(enabled: boolean = true) {
|
||||||
await this._frameManager.networkManager().setCacheEnabled(enabled);
|
if (this._state.cacheEnabled === enabled)
|
||||||
|
return;
|
||||||
|
this._state.cacheEnabled = enabled;
|
||||||
|
await this._delegate.setCacheEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
|
screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
|
||||||
return this._screenshotter.screenshotPage(options);
|
return this._screenshotter.screenshotPage(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async title(): Promise<string> {
|
title(): Promise<string> {
|
||||||
return this.mainFrame().title();
|
return this.mainFrame().title();
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(options: { runBeforeUnload: (boolean | undefined); } = {runBeforeUnload: undefined}) {
|
async close(options: { runBeforeUnload: (boolean | undefined); } = {runBeforeUnload: undefined}) {
|
||||||
assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
||||||
const runBeforeUnload = !!options.runBeforeUnload;
|
const runBeforeUnload = !!options.runBeforeUnload;
|
||||||
if (runBeforeUnload) {
|
await this._delegate.closePage(runBeforeUnload);
|
||||||
await this._client.send('Page.close');
|
if (!runBeforeUnload)
|
||||||
} else {
|
|
||||||
await this.browser()._closePage(this);
|
|
||||||
await this._closedPromise;
|
await this._closedPromise;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
isClosed(): boolean {
|
isClosed(): boolean {
|
||||||
return this._closed;
|
return this._closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
click(selector: string | types.Selector, options?: ClickOptions) {
|
click(selector: string | types.Selector, options?: input.ClickOptions) {
|
||||||
return this.mainFrame().click(selector, options);
|
return this.mainFrame().click(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
dblclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
dblclick(selector: string | types.Selector, options?: input.MultiClickOptions) {
|
||||||
return this.mainFrame().dblclick(selector, options);
|
return this.mainFrame().dblclick(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
tripleclick(selector: string | types.Selector, options?: input.MultiClickOptions) {
|
||||||
return this.mainFrame().tripleclick(selector, options);
|
return this.mainFrame().tripleclick(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,11 +439,11 @@ export class Page extends EventEmitter {
|
|||||||
return this.mainFrame().focus(selector);
|
return this.mainFrame().focus(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
hover(selector: string | types.Selector, options?: PointerActionOptions) {
|
hover(selector: string | types.Selector, options?: input.PointerActionOptions) {
|
||||||
return this.mainFrame().hover(selector, options);
|
return this.mainFrame().hover(selector, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
select(selector: string | types.Selector, ...values: (string | dom.ElementHandle | SelectOption)[]): Promise<string[]> {
|
select(selector: string | types.Selector, ...values: (string | dom.ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
||||||
return this.mainFrame().select(selector, ...values);
|
return this.mainFrame().select(selector, ...values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,8 +467,3 @@ export class Page extends EventEmitter {
|
|||||||
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
|
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileChooser = {
|
|
||||||
element: dom.ElementHandle,
|
|
||||||
multiple: boolean
|
|
||||||
};
|
|
@ -263,8 +263,8 @@ export class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async emulateMedia(options: {
|
async emulateMedia(options: {
|
||||||
type?: string | null,
|
type?: input.MediaType | null,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference' | null }) {
|
colorScheme?: input.MediaColorScheme | null }) {
|
||||||
assert(!options.type || mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
|
assert(!options.type || mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
|
||||||
assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
||||||
assert(!options.colorScheme, 'Media feature emulation is not supported');
|
assert(!options.colorScheme, 'Media feature emulation is not supported');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user