mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03: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 { BrowserContext } from './BrowserContext';
|
||||
import { Connection, ConnectionEvents, CDPSession } from './Connection';
|
||||
import { Page } from './Page';
|
||||
import { Page } from '../page';
|
||||
import { Target } from './Target';
|
||||
import { Protocol } from './protocol';
|
||||
import { Chromium } from './features/chromium';
|
||||
import * as types from '../types';
|
||||
import { FrameManager } from './FrameManager';
|
||||
|
||||
export class Browser extends EventEmitter {
|
||||
private _ignoreHTTPSErrors: boolean;
|
||||
@ -133,11 +134,11 @@ export class Browser extends EventEmitter {
|
||||
this.chromium.emit(Events.Chromium.TargetChanged, target);
|
||||
}
|
||||
|
||||
async newPage(): Promise<Page> {
|
||||
async newPage(): Promise<Page<Browser, BrowserContext>> {
|
||||
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 target = this._targets.get(targetId);
|
||||
assert(await target._initializedPromise, 'Failed to create target for page');
|
||||
@ -145,7 +146,7 @@ export class Browser extends EventEmitter {
|
||||
return page;
|
||||
}
|
||||
|
||||
async _closePage(page: Page) {
|
||||
async _closePage(page: Page<Browser, BrowserContext>) {
|
||||
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);
|
||||
}
|
||||
|
||||
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 pages = await Promise.all(targets.map(target => target.page()));
|
||||
return pages.filter(page => !!page);
|
||||
}
|
||||
|
||||
async _activatePage(page: Page) {
|
||||
await page._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId});
|
||||
async _activatePage(page: Page<Browser, BrowserContext>) {
|
||||
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> {
|
||||
@ -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()));
|
||||
// Flatten array.
|
||||
return contextPages.reduce((acc, x) => acc.concat(x), []);
|
||||
|
@ -20,7 +20,7 @@ import { filterCookies, NetworkCookie, rewriteCookies, SetNetworkCookieParam } f
|
||||
import { Browser } from './Browser';
|
||||
import { CDPSession } from './Connection';
|
||||
import { Permissions } from './features/permissions';
|
||||
import { Page } from './Page';
|
||||
import { Page } from '../page';
|
||||
|
||||
export class BrowserContext {
|
||||
readonly permissions: Permissions;
|
||||
@ -34,7 +34,7 @@ export class BrowserContext {
|
||||
this.permissions = new Permissions(client, contextId);
|
||||
}
|
||||
|
||||
pages(): Promise<Page[]> {
|
||||
pages(): Promise<Page<Browser, BrowserContext>[]> {
|
||||
return this._browser._pages(this);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ export class BrowserContext {
|
||||
return !!this._id;
|
||||
}
|
||||
|
||||
newPage(): Promise<Page> {
|
||||
newPage(): Promise<Page<Browser, BrowserContext>> {
|
||||
return this._browser._createPageInContext(this._id);
|
||||
}
|
||||
|
||||
|
@ -21,18 +21,30 @@ import * as frames from '../frames';
|
||||
import { assert, debugError } from '../helper';
|
||||
import * as js from '../javascript';
|
||||
import * as network from '../network';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { CDPSession } from './Connection';
|
||||
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { DOMWorldDelegate } from './JSHandle';
|
||||
import { LifecycleWatcher } from './LifecycleWatcher';
|
||||
import { NetworkManager } from './NetworkManager';
|
||||
import { Page } from './Page';
|
||||
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||
import { Page } from '../page';
|
||||
import { Protocol } from './protocol';
|
||||
import { Events } from './events';
|
||||
import { Events as CommonEvents } from '../events';
|
||||
import { toConsoleMessageLocation, exceptionToError, releaseObject } from './protocolHelper';
|
||||
import * as dialog from '../dialog';
|
||||
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__';
|
||||
|
||||
@ -51,26 +63,41 @@ type FrameData = {
|
||||
lifecycleEvents: Set<string>,
|
||||
};
|
||||
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
|
||||
_client: CDPSession;
|
||||
private _page: Page;
|
||||
private _page: Page<Browser, BrowserContext>;
|
||||
private _networkManager: NetworkManager;
|
||||
_timeoutSettings: TimeoutSettings;
|
||||
private _frames = new Map<string, frames.Frame>();
|
||||
private _contextIdToContext = new Map<number, js.ExecutionContext>();
|
||||
private _isolatedWorlds = new Set<string>();
|
||||
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();
|
||||
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._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('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.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
|
||||
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.javascriptDialogOpening', event => this._onDialog(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('Runtime.bindingCalled', event => this._onBindingCalled(event));
|
||||
this._client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event));
|
||||
@ -119,7 +146,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
const {
|
||||
referer = this._networkManager.extraHTTPHeaders()['referer'],
|
||||
waitUntil = ['load'],
|
||||
timeout = this._timeoutSettings.navigationTimeout(),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
|
||||
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);
|
||||
@ -157,7 +184,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
assertNoLegacyNavigationOptions(options);
|
||||
const {
|
||||
waitUntil = ['load'],
|
||||
timeout = this._timeoutSettings.navigationTimeout(),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const watcher = new LifecycleWatcher(this, frame, waitUntil, timeout);
|
||||
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 = {}) {
|
||||
const {
|
||||
waitUntil = ['load'],
|
||||
timeout = this._timeoutSettings.navigationTimeout(),
|
||||
timeout = this._page._timeoutSettings.navigationTimeout(),
|
||||
} = options;
|
||||
const context = await frame._utilityContext();
|
||||
// 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);
|
||||
}
|
||||
|
||||
page(): Page {
|
||||
page(): Page<Browser, BrowserContext> {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
@ -249,7 +276,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
return;
|
||||
assert(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 = {
|
||||
id: frameId,
|
||||
loaderId: '',
|
||||
@ -258,6 +285,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
frame[frameDataSymbol] = data;
|
||||
this._frames.set(frameId, frame);
|
||||
this.emit(FrameManagerEvents.FrameAttached, frame);
|
||||
this._page.emit(CommonEvents.Page.FrameAttached, frame);
|
||||
}
|
||||
|
||||
_onFrameNavigated(framePayload: Protocol.Page.Frame) {
|
||||
@ -280,7 +308,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
data.id = framePayload.id;
|
||||
} else {
|
||||
// Initial main frame navigation.
|
||||
frame = new frames.Frame(this, this._timeoutSettings, null);
|
||||
frame = new frames.Frame(this, this._page._timeoutSettings, null);
|
||||
const data: FrameData = {
|
||||
id: framePayload.id,
|
||||
loaderId: '',
|
||||
@ -296,6 +324,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
frame._navigated(framePayload.url, framePayload.name);
|
||||
|
||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
||||
}
|
||||
|
||||
async _ensureIsolatedWorld(name: string) {
|
||||
@ -320,6 +349,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
frame._navigated(url, frame.name());
|
||||
this.emit(FrameManagerEvents.FrameNavigatedWithinDocument, frame);
|
||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
||||
}
|
||||
|
||||
_onFrameDetached(frameId: string) {
|
||||
@ -371,6 +401,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
frame._detach();
|
||||
this._frames.delete(this._frameData(frame).id);
|
||||
this.emit(FrameManagerEvents.FrameDetached, frame);
|
||||
this._page.emit(CommonEvents.Page.FrameDetached, frame);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
async _exposeBinding(name: string, bindingFunction: string) {
|
||||
async exposeBinding(name: string, bindingFunction: string) {
|
||||
await this._client.send('Runtime.addBinding', {name: name});
|
||||
await this._client.send('Page.addScriptToEvaluateOnNewDocument', {source: bindingFunction});
|
||||
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) {
|
||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||
this._page.emit(CommonEvents.Page.Dialog, new dialog.Dialog(
|
||||
event.type as dialog.DialogType,
|
||||
event.message,
|
||||
async (accept: boolean, promptText?: string) => {
|
||||
@ -417,7 +448,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
}
|
||||
|
||||
_handleException(exceptionDetails: Protocol.Runtime.ExceptionDetails) {
|
||||
this._page.emit(Events.Page.PageError, exceptionToError(exceptionDetails));
|
||||
this._page.emit(CommonEvents.Page.PageError, exceptionToError(exceptionDetails));
|
||||
}
|
||||
|
||||
_onTargetCrashed() {
|
||||
@ -429,7 +460,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
if (args)
|
||||
args.map(arg => releaseObject(this._client, arg));
|
||||
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) {
|
||||
@ -438,6 +469,88 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
||||
const handle = await (utilityWorld.delegate as DOMWorldDelegate).adoptBackendNodeId(event.backendNodeId, utilityWorld);
|
||||
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) {
|
||||
|
@ -50,7 +50,7 @@ export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
||||
}
|
||||
|
||||
isJavascriptEnabled(): boolean {
|
||||
return this._frameManager.page()._javascriptEnabled;
|
||||
return this._frameManager.page()._state.javascriptEnabled;
|
||||
}
|
||||
|
||||
isElement(remoteObject: any): boolean {
|
||||
|
@ -19,11 +19,12 @@ import * as types from '../types';
|
||||
import { Browser } from './Browser';
|
||||
import { BrowserContext } from './BrowserContext';
|
||||
import { CDPSession, CDPSessionEvents } from './Connection';
|
||||
import { Events } from './events';
|
||||
import { Events as CommonEvents } from '../events';
|
||||
import { Worker } from './features/workers';
|
||||
import { Page } from './Page';
|
||||
import { Page } from '../page';
|
||||
import { Protocol } from './protocol';
|
||||
import { debugError } from '../helper';
|
||||
import { FrameManager } from './FrameManager';
|
||||
|
||||
const targetSymbol = Symbol('target');
|
||||
|
||||
@ -34,14 +35,14 @@ export class Target {
|
||||
private _sessionFactory: () => Promise<CDPSession>;
|
||||
private _ignoreHTTPSErrors: boolean;
|
||||
private _defaultViewport: types.Viewport;
|
||||
private _pagePromise: Promise<Page> | null = null;
|
||||
private _page: Page | null = null;
|
||||
private _pagePromise: Promise<Page<Browser, BrowserContext>> | null = null;
|
||||
private _page: Page<Browser, BrowserContext> | null = null;
|
||||
private _workerPromise: Promise<Worker> | null = null;
|
||||
_initializedPromise: Promise<boolean>;
|
||||
_initializedCallback: (value?: unknown) => void;
|
||||
_isInitialized: boolean;
|
||||
|
||||
static fromPage(page: Page): Target {
|
||||
static fromPage(page: Page<Browser, BrowserContext>): Target {
|
||||
return (page as any)[targetSymbol];
|
||||
}
|
||||
|
||||
@ -64,10 +65,10 @@ export class Target {
|
||||
if (!opener || !opener._pagePromise || this.type() !== 'page')
|
||||
return true;
|
||||
const openerPage = await opener._pagePromise;
|
||||
if (!openerPage.listenerCount(Events.Page.Popup))
|
||||
if (!openerPage.listenerCount(CommonEvents.Page.Popup))
|
||||
return true;
|
||||
const popupPage = await this.page();
|
||||
openerPage.emit(Events.Page.Popup, popupPage);
|
||||
openerPage.emit(CommonEvents.Page.Popup, popupPage);
|
||||
return true;
|
||||
});
|
||||
this._isInitialized = this._targetInfo.type !== 'page' || this._targetInfo.url !== '';
|
||||
@ -80,10 +81,11 @@ export class Target {
|
||||
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) {
|
||||
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;
|
||||
page[targetSymbol] = this;
|
||||
client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect());
|
||||
@ -93,7 +95,7 @@ export class Target {
|
||||
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});
|
||||
if (this._defaultViewport)
|
||||
await page.setViewport(this._defaultViewport);
|
||||
|
@ -21,7 +21,7 @@ export { Overrides } from './features/overrides';
|
||||
export { PDF } from './features/pdf';
|
||||
export { Permissions } from './features/permissions';
|
||||
export { Worker, Workers } from './features/workers';
|
||||
export { Page } from './Page';
|
||||
export { Page } from '../page';
|
||||
export { Playwright } from './Playwright';
|
||||
export { Target } from './Target';
|
||||
|
||||
|
@ -16,26 +16,6 @@
|
||||
*/
|
||||
|
||||
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: {
|
||||
Disconnected: 'disconnected'
|
||||
},
|
||||
|
@ -19,10 +19,11 @@ import { assert } from '../../helper';
|
||||
import { Browser } from '../Browser';
|
||||
import { BrowserContext } from '../BrowserContext';
|
||||
import { CDPSession, Connection } from '../Connection';
|
||||
import { Page } from '../Page';
|
||||
import { Page } from '../../page';
|
||||
import { readProtocolStream } from '../protocolHelper';
|
||||
import { Target } from '../Target';
|
||||
import { Worker } from './workers';
|
||||
import { FrameManager } from '../FrameManager';
|
||||
|
||||
export class Chromium extends EventEmitter {
|
||||
private _connection: Connection;
|
||||
@ -47,9 +48,9 @@ export class Chromium extends EventEmitter {
|
||||
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.');
|
||||
this._tracingClient = page ? page._client : this._client;
|
||||
this._tracingClient = page ? (page._delegate as FrameManager)._client : this._client;
|
||||
|
||||
const defaultCategories = [
|
||||
'-*', '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;
|
||||
}
|
||||
|
||||
pageTarget(page: Page): Target {
|
||||
pageTarget(page: Page<Browser, BrowserContext>): Target {
|
||||
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: {
|
||||
type?: ''|'screen'|'print',
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
|
||||
type?: input.MediaType,
|
||||
colorScheme?: input.MediaColorScheme }) {
|
||||
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);
|
||||
await this._session.send('Page.setEmulatedMedia', options);
|
||||
|
@ -379,5 +379,7 @@ export type FilePayload = {
|
||||
data: string
|
||||
};
|
||||
|
||||
export const mediaTypes = new Set(['screen', 'print']);
|
||||
export const mediaColorSchemes = new Set(['dark', 'light', 'no-preference']);
|
||||
export type MediaType = 'screen' | 'print';
|
||||
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 * as console from '../console';
|
||||
import * as dom from '../dom';
|
||||
import * as frames from '../frames';
|
||||
import { assert, debugError, helper } from '../helper';
|
||||
import * as input from '../input';
|
||||
import { ClickOptions, mediaColorSchemes, mediaTypes, MultiClickOptions, PointerActionOptions, SelectOption } from '../input';
|
||||
import * as js from '../javascript';
|
||||
import * as network from '../network';
|
||||
import { Screenshotter } from '../screenshotter';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import * as types from '../types';
|
||||
import { Browser } from './Browser';
|
||||
import { BrowserContext } from './BrowserContext';
|
||||
import { CDPSession } from './Connection';
|
||||
import * as console from './console';
|
||||
import * as dom from './dom';
|
||||
import * as frames from './frames';
|
||||
import { assert, debugError, helper } from './helper';
|
||||
import * as input from './input';
|
||||
import * as js from './javascript';
|
||||
import * as network from './network';
|
||||
import { Screenshotter, ScreenshotterDelegate } from './screenshotter';
|
||||
import { TimeoutSettings } from './TimeoutSettings';
|
||||
import * as types from './types';
|
||||
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 _closedCallback: () => void;
|
||||
private _closedPromise: Promise<void>;
|
||||
private _disconnected = false;
|
||||
private _disconnectedCallback: (e: Error) => void;
|
||||
private _disconnectedPromise: Promise<Error>;
|
||||
_client: CDPSession;
|
||||
private _browserContext: BrowserContext;
|
||||
readonly keyboard: input.Keyboard;
|
||||
readonly mouse: input.Mouse;
|
||||
private _timeoutSettings: TimeoutSettings;
|
||||
_frameManager: FrameManager;
|
||||
readonly accessibility: Accessibility;
|
||||
readonly coverage: Coverage;
|
||||
readonly overrides: Overrides;
|
||||
readonly interception: Interception;
|
||||
readonly pdf: PDF;
|
||||
readonly workers: Workers;
|
||||
readonly _timeoutSettings: TimeoutSettings;
|
||||
readonly _delegate: PageDelegate;
|
||||
readonly _state: PageState;
|
||||
private _pageBindings = new Map<string, Function>();
|
||||
_javascriptEnabled = true;
|
||||
private _viewport: types.Viewport | null = null;
|
||||
_screenshotter: Screenshotter;
|
||||
readonly _screenshotter: Screenshotter;
|
||||
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();
|
||||
this._client = client;
|
||||
this._delegate = delegate;
|
||||
this._closedPromise = new Promise(f => this._closedCallback = f);
|
||||
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
|
||||
this._browserContext = browserContext;
|
||||
this.keyboard = new input.Keyboard(new RawKeyboardImpl(client));
|
||||
this.mouse = new input.Mouse(new RawMouseImpl(client), this.keyboard);
|
||||
this._state = {
|
||||
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.accessibility = new Accessibility(client);
|
||||
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));
|
||||
this._screenshotter = new Screenshotter(this, delegate.screenshotterDelegate, browserContext.browser());
|
||||
}
|
||||
|
||||
_didClose() {
|
||||
@ -147,11 +158,11 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
mainFrame(): frames.Frame {
|
||||
return this._frameManager.mainFrame();
|
||||
return this._delegate.mainFrame();
|
||||
}
|
||||
|
||||
frames(): frames.Frame[] {
|
||||
return this._frameManager.frames();
|
||||
return this._delegate.frames();
|
||||
}
|
||||
|
||||
setDefaultNavigationTimeout(timeout: number) {
|
||||
@ -199,7 +210,7 @@ export class Page extends EventEmitter {
|
||||
if (this._pageBindings.has(name))
|
||||
throw new Error(`Failed to add page binding with name ${name}: window['${name}'] already exists!`);
|
||||
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) {
|
||||
const binding = window[bindingName];
|
||||
@ -219,12 +230,14 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: { [s: string]: string; }) {
|
||||
return this._frameManager.networkManager().setExtraHTTPHeaders(headers);
|
||||
setExtraHTTPHeaders(headers: network.Headers) {
|
||||
this._state.extraHTTPHeaders = {...headers};
|
||||
return this._delegate.setExtraHTTPHeaders(headers);
|
||||
}
|
||||
|
||||
async setUserAgent(userAgent: string) {
|
||||
return this._frameManager.networkManager().setUserAgent(userAgent);
|
||||
setUserAgent(userAgent: string) {
|
||||
this._state.userAgent = userAgent;
|
||||
return this._delegate.setUserAgent(userAgent);
|
||||
}
|
||||
|
||||
async _onBindingCalled(payload: string, context: js.ExecutionContext) {
|
||||
@ -271,28 +284,24 @@ export class Page extends EventEmitter {
|
||||
return this.mainFrame().url();
|
||||
}
|
||||
|
||||
async content(): Promise<string> {
|
||||
return await this.mainFrame().content();
|
||||
content(): Promise<string> {
|
||||
return this.mainFrame().content();
|
||||
}
|
||||
|
||||
async setContent(html: string, options: { timeout?: number; waitUntil?: string | string[]; } | undefined) {
|
||||
await this.mainFrame().setContent(html, options);
|
||||
setContent(html: string, options?: frames.NavigateOptions): Promise<void> {
|
||||
return this.mainFrame().setContent(html, options);
|
||||
}
|
||||
|
||||
async goto(url: string, options: { referer?: string; timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
||||
return await this.mainFrame().goto(url, options);
|
||||
goto(url: string, options?: frames.GotoOptions): Promise<network.Response | null> {
|
||||
return this.mainFrame().goto(url, options);
|
||||
}
|
||||
|
||||
async reload(options: { timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
|
||||
const [response] = await Promise.all([
|
||||
this.waitForNavigation(options),
|
||||
this._client.send('Page.reload')
|
||||
]);
|
||||
return response;
|
||||
reload(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||
return this._delegate.reload(options);
|
||||
}
|
||||
|
||||
async waitForNavigation(options: { timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
|
||||
return await this.mainFrame().waitForNavigation(options);
|
||||
waitForNavigation(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||
return this.mainFrame().waitForNavigation(options);
|
||||
}
|
||||
|
||||
async waitForRequest(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise<Request> {
|
||||
@ -321,24 +330,12 @@ export class Page extends EventEmitter {
|
||||
}, timeout, this._disconnectedPromise);
|
||||
}
|
||||
|
||||
async goBack(options: { timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
||||
return this._go(-1, options);
|
||||
goBack(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||
return this._delegate.goBack(options);
|
||||
}
|
||||
|
||||
async goForward(options: { timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
|
||||
return this._go(+1, 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;
|
||||
goForward(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
||||
return this._delegate.goForward(options);
|
||||
}
|
||||
|
||||
async emulate(options: { viewport: types.Viewport; userAgent: string; }) {
|
||||
@ -349,52 +346,41 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
async setJavaScriptEnabled(enabled: boolean) {
|
||||
if (this._javascriptEnabled === enabled)
|
||||
if (this._state.javascriptEnabled === enabled)
|
||||
return;
|
||||
this._javascriptEnabled = enabled;
|
||||
await this._client.send('Emulation.setScriptExecutionDisabled', { value: !enabled });
|
||||
this._state.javascriptEnabled = enabled;
|
||||
await this._delegate.setJavaScriptEnabled(enabled);
|
||||
}
|
||||
|
||||
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: {
|
||||
type?: string,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
|
||||
assert(!options.type || mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
|
||||
assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
||||
const media = typeof options.type === 'undefined' ? this._emulatedMediaType : options.type;
|
||||
const features = typeof options.colorScheme === 'undefined' ? [] : [{ name: 'prefers-color-scheme', value: options.colorScheme }];
|
||||
await this._client.send('Emulation.setEmulatedMedia', { media: media || '', features });
|
||||
this._emulatedMediaType = options.type;
|
||||
async emulateMedia(options: { type?: input.MediaType, colorScheme?: input.MediaColorScheme }) {
|
||||
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);
|
||||
if (options.type !== undefined)
|
||||
this._state.mediaType = options.type;
|
||||
if (options.colorScheme !== undefined)
|
||||
this._state.mediaColorScheme = options.colorScheme;
|
||||
await this._delegate.setEmulateMedia(this._state.mediaType, this._state.mediaColorScheme);
|
||||
}
|
||||
|
||||
async setViewport(viewport: types.Viewport) {
|
||||
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
|
||||
})
|
||||
]);
|
||||
const oldIsMobile = this._viewport ? !!this._viewport.isMobile : false;
|
||||
const oldHasTouch = this._viewport ? !!this._viewport.hasTouch : false;
|
||||
this._viewport = viewport;
|
||||
if (oldIsMobile !== isMobile || oldHasTouch !== hasTouch)
|
||||
const oldIsMobile = this._state.viewport ? !!this._state.viewport.isMobile : false;
|
||||
const oldHasTouch = this._state.viewport ? !!this._state.viewport.hasTouch : false;
|
||||
const newIsMobile = !!viewport.isMobile;
|
||||
const newHasTouch = !!viewport.hasTouch;
|
||||
this._state.viewport = { ...viewport };
|
||||
await this._delegate.setViewport(viewport);
|
||||
if (oldIsMobile !== newIsMobile || oldHasTouch !== newHasTouch)
|
||||
await this.reload();
|
||||
}
|
||||
|
||||
viewport(): types.Viewport | null {
|
||||
return this._viewport;
|
||||
return this._state.viewport;
|
||||
}
|
||||
|
||||
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
||||
@ -403,45 +389,45 @@ export class Page extends EventEmitter {
|
||||
|
||||
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
|
||||
const source = helper.evaluationString(pageFunction, ...args);
|
||||
await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
|
||||
await this._delegate.evaluateOnNewDocument(source);
|
||||
}
|
||||
|
||||
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> {
|
||||
return this._screenshotter.screenshotPage(options);
|
||||
}
|
||||
|
||||
async title(): Promise<string> {
|
||||
title(): Promise<string> {
|
||||
return this.mainFrame().title();
|
||||
}
|
||||
|
||||
async close(options: { runBeforeUnload: (boolean | undefined); } = {runBeforeUnload: undefined}) {
|
||||
assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
||||
const runBeforeUnload = !!options.runBeforeUnload;
|
||||
if (runBeforeUnload) {
|
||||
await this._client.send('Page.close');
|
||||
} else {
|
||||
await this.browser()._closePage(this);
|
||||
await this._delegate.closePage(runBeforeUnload);
|
||||
if (!runBeforeUnload)
|
||||
await this._closedPromise;
|
||||
}
|
||||
}
|
||||
|
||||
isClosed(): boolean {
|
||||
return this._closed;
|
||||
}
|
||||
|
||||
click(selector: string | types.Selector, options?: ClickOptions) {
|
||||
click(selector: string | types.Selector, options?: input.ClickOptions) {
|
||||
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);
|
||||
}
|
||||
|
||||
tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
||||
tripleclick(selector: string | types.Selector, options?: input.MultiClickOptions) {
|
||||
return this.mainFrame().tripleclick(selector, options);
|
||||
}
|
||||
|
||||
@ -453,11 +439,11 @@ export class Page extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -481,8 +467,3 @@ export class Page extends EventEmitter {
|
||||
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: {
|
||||
type?: string | null,
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference' | null }) {
|
||||
type?: input.MediaType | null,
|
||||
colorScheme?: input.MediaColorScheme | null }) {
|
||||
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, 'Media feature emulation is not supported');
|
||||
|
Loading…
Reference in New Issue
Block a user