chore(webkit): remove most session usages from Page (#181)
These are moved to FrameManager, so that we can reuse Page between browsers.
This commit is contained in:
parent
f5cd742b79
commit
b3817aab2a
|
|
@ -30,6 +30,7 @@ import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
|||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { DOMWorldDelegate } from './JSHandle';
|
||||
import * as dialog from '../dialog';
|
||||
|
||||
export const FrameManagerEvents = {
|
||||
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
|
||||
|
|
@ -77,8 +78,17 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||
this._handleFrameTree(frameTree);
|
||||
await Promise.all([
|
||||
this._session.send('Runtime.enable'),
|
||||
this._session.send('Console.enable'),
|
||||
this._session.send('Dialog.enable'),
|
||||
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
|
||||
this._networkManager.initialize(),
|
||||
]);
|
||||
if (this._page._userAgent !== null)
|
||||
await this._session.send('Page.overrideUserAgent', { value: this._page._userAgent });
|
||||
if (this._page._emulatedMediaType !== undefined)
|
||||
await this._session.send('Page.setEmulatedMedia', { media: this._page._emulatedMediaType || '' });
|
||||
if (!this._page._javascriptEnabled)
|
||||
await this._session.send('Emulation.setJavaScriptEnabled', { enabled: this._page._javascriptEnabled });
|
||||
}
|
||||
|
||||
_addSessionListeners() {
|
||||
|
|
@ -88,17 +98,22 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||
helper.addEventListener(this._session, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
|
||||
helper.addEventListener(this._session, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
|
||||
helper.addEventListener(this._session, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
|
||||
helper.addEventListener(this._session, 'Page.loadEventFired', event => this._page.emit(Events.Page.Load)),
|
||||
helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
|
||||
helper.addEventListener(this._session, 'Page.domContentEventFired', event => this._page.emit(Events.Page.DOMContentLoaded)),
|
||||
helper.addEventListener(this._session, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
|
||||
helper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event))
|
||||
];
|
||||
}
|
||||
|
||||
_swapSessionOnNavigation(newSession) {
|
||||
async _swapSessionOnNavigation(newSession: TargetSession) {
|
||||
helper.removeEventListeners(this._sessionListeners);
|
||||
this.disconnectFromTarget();
|
||||
this._session = newSession;
|
||||
this._addSessionListeners();
|
||||
this._networkManager.setSession(newSession);
|
||||
this.emit(FrameManagerEvents.TargetSwappedOnNavigation);
|
||||
// this.initialize() will be called by page.
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
disconnectFromTarget() {
|
||||
|
|
@ -284,6 +299,55 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||
}, html);
|
||||
await watchDog.waitForNavigation();
|
||||
}
|
||||
|
||||
async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) {
|
||||
const { type, level, text, parameters, url, line: lineNumber, column: columnNumber } = event.message;
|
||||
let derivedType: string = type;
|
||||
if (type === 'log')
|
||||
derivedType = level;
|
||||
else if (type === 'timing')
|
||||
derivedType = 'timeEnd';
|
||||
const mainFrameContext = await this.mainFrame().executionContext();
|
||||
const handles = (parameters || []).map(p => {
|
||||
let context: js.ExecutionContext | null = null;
|
||||
if (p.objectId) {
|
||||
const objectId = JSON.parse(p.objectId);
|
||||
context = this._contextIdToContext.get(objectId.injectedScriptId);
|
||||
} else {
|
||||
context = mainFrameContext;
|
||||
}
|
||||
return context._createHandle(p);
|
||||
});
|
||||
this._page._addConsoleMessage(derivedType, handles, { url, lineNumber, columnNumber }, handles.length ? undefined : text);
|
||||
}
|
||||
|
||||
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
||||
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||
event.type as dialog.DialogType,
|
||||
event.message,
|
||||
async (accept: boolean, promptText?: string) => {
|
||||
await this._session.send('Dialog.handleJavaScriptDialog', { accept, promptText });
|
||||
},
|
||||
event.defaultPrompt));
|
||||
}
|
||||
|
||||
async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) {
|
||||
const context = await this.frame(event.frameId)._utilityContext();
|
||||
const handle = context._createHandle(event.element).asElement()!;
|
||||
this._page._onFileChooserOpened(handle);
|
||||
}
|
||||
|
||||
async setUserAgent(userAgent: string) {
|
||||
await this._session.send('Page.overrideUserAgent', { value: userAgent });
|
||||
}
|
||||
|
||||
async setEmulatedMedia(type?: string | null) {
|
||||
await this._session.send('Page.setEmulatedMedia', { media: type || '' });
|
||||
}
|
||||
|
||||
async setJavaScriptEnabled(enabled: boolean) {
|
||||
await this._session.send('Emulation.setJavaScriptEnabled', { enabled });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,10 +17,9 @@
|
|||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as console from '../console';
|
||||
import * as dialog from '../dialog';
|
||||
import * as dom from '../dom';
|
||||
import * as frames from '../frames';
|
||||
import { assert, helper, RegisteredListener } from '../helper';
|
||||
import { assert, helper } from '../helper';
|
||||
import * as input from '../input';
|
||||
import { ClickOptions, mediaColorSchemes, mediaTypes, MultiClickOptions } from '../input';
|
||||
import * as js from '../javascript';
|
||||
|
|
@ -29,7 +28,7 @@ import { Screenshotter } from '../screenshotter';
|
|||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import * as types from '../types';
|
||||
import { Browser, BrowserContext } from './Browser';
|
||||
import { TargetSession, TargetSessionEvents } from './Connection';
|
||||
import { TargetSession } from './Connection';
|
||||
import { Events } from './events';
|
||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||
import { RawKeyboardImpl, RawMouseImpl } from './Input';
|
||||
|
|
@ -41,6 +40,9 @@ export class Page extends EventEmitter {
|
|||
private _closed = false;
|
||||
private _closedCallback: () => void;
|
||||
private _closedPromise: Promise<void>;
|
||||
private _disconnected = false;
|
||||
private _disconnectedCallback: (e: Error) => void;
|
||||
private _disconnectedPromise: Promise<Error>;
|
||||
_session: TargetSession;
|
||||
private _browserContext: BrowserContext;
|
||||
private _keyboard: input.Keyboard;
|
||||
|
|
@ -49,18 +51,16 @@ export class Page extends EventEmitter {
|
|||
private _frameManager: FrameManager;
|
||||
private _bootstrapScripts: string[] = [];
|
||||
_javascriptEnabled = true;
|
||||
private _userAgent: string | null = null;
|
||||
_userAgent: string | null = null;
|
||||
_emulatedMediaType: string | undefined;
|
||||
private _viewport: types.Viewport | null = null;
|
||||
_screenshotter: Screenshotter;
|
||||
private _workers = new Map<string, Worker>();
|
||||
private _disconnectPromise: Promise<Error> | undefined;
|
||||
private _sessionListeners: RegisteredListener[] = [];
|
||||
private _emulatedMediaType: string | undefined;
|
||||
private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
|
||||
|
||||
constructor(session: TargetSession, browserContext: BrowserContext) {
|
||||
super();
|
||||
this._closedPromise = new Promise(f => this._closedCallback = f);
|
||||
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
|
||||
this._keyboard = new input.Keyboard(new RawKeyboardImpl(session));
|
||||
this._mouse = new input.Mouse(new RawMouseImpl(session), this._keyboard);
|
||||
this._timeoutSettings = new TimeoutSettings();
|
||||
|
|
@ -68,7 +68,7 @@ export class Page extends EventEmitter {
|
|||
|
||||
this._screenshotter = new Screenshotter(this, new WKScreenshotDelegate(session), browserContext.browser());
|
||||
|
||||
this._setSession(session);
|
||||
this._session = session;
|
||||
this._browserContext = browserContext;
|
||||
|
||||
this._frameManager.on(FrameManagerEvents.FrameAttached, event => this.emit(Events.Page.FrameAttached, event));
|
||||
|
|
@ -89,46 +89,20 @@ export class Page extends EventEmitter {
|
|||
this._closedCallback();
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
await Promise.all([
|
||||
this._frameManager.initialize(),
|
||||
this._session.send('Console.enable'),
|
||||
this._session.send('Dialog.enable'),
|
||||
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
|
||||
]);
|
||||
if (this._userAgent !== null)
|
||||
await this._session.send('Page.overrideUserAgent', { value: this._userAgent });
|
||||
if (this._emulatedMediaType !== undefined)
|
||||
await this._session.send('Page.setEmulatedMedia', { media: this._emulatedMediaType || '' });
|
||||
_didDisconnect() {
|
||||
assert(!this._disconnected, 'Page disconnected twice');
|
||||
this._disconnected = true;
|
||||
this._frameManager.disconnectFromTarget();
|
||||
this._disconnectedCallback(new Error('Target closed'));
|
||||
}
|
||||
|
||||
_setSession(newSession: TargetSession) {
|
||||
helper.removeEventListeners(this._sessionListeners);
|
||||
this._session = newSession;
|
||||
this._sessionListeners = [
|
||||
helper.addEventListener(this._session, TargetSessionEvents.Disconnected, () => this._frameManager.disconnectFromTarget()),
|
||||
helper.addEventListener(this._session, 'Page.loadEventFired', event => this.emit(Events.Page.Load)),
|
||||
helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
|
||||
helper.addEventListener(this._session, 'Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded)),
|
||||
helper.addEventListener(this._session, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
|
||||
helper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event))
|
||||
];
|
||||
}
|
||||
|
||||
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
||||
this.emit(Events.Page.Dialog, new dialog.Dialog(
|
||||
event.type as dialog.DialogType,
|
||||
event.message,
|
||||
async (accept: boolean, promptText?: string) => {
|
||||
await this._session.send('Dialog.handleJavaScriptDialog', { accept, promptText });
|
||||
},
|
||||
event.defaultPrompt));
|
||||
_initialize() {
|
||||
return this._frameManager.initialize();
|
||||
}
|
||||
|
||||
async _swapSessionOnNavigation(newSession: TargetSession) {
|
||||
this._setSession(newSession);
|
||||
this._frameManager._swapSessionOnNavigation(newSession);
|
||||
await this._initialize();
|
||||
this._session = newSession;
|
||||
await this._frameManager._swapSessionOnNavigation(newSession);
|
||||
}
|
||||
|
||||
browser(): Browser {
|
||||
|
|
@ -143,25 +117,12 @@ export class Page extends EventEmitter {
|
|||
this.emit('error', new Error('Page crashed!'));
|
||||
}
|
||||
|
||||
async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) {
|
||||
const { type, level, text, parameters, url, line: lineNumber, column: columnNumber } = event.message;
|
||||
let derivedType: string = type;
|
||||
if (type === 'log')
|
||||
derivedType = level;
|
||||
else if (type === 'timing')
|
||||
derivedType = 'timeEnd';
|
||||
const mainFrameContext = await this.mainFrame().executionContext();
|
||||
const handles = (parameters || []).map(p => {
|
||||
let context: js.ExecutionContext | null = null;
|
||||
if (p.objectId) {
|
||||
const objectId = JSON.parse(p.objectId);
|
||||
context = this._frameManager._contextIdToContext.get(objectId.injectedScriptId);
|
||||
} else {
|
||||
context = mainFrameContext;
|
||||
}
|
||||
return context._createHandle(p);
|
||||
});
|
||||
this.emit(Events.Page.Console, new console.ConsoleMessage(derivedType, handles.length ? undefined : text, handles, { url, lineNumber, columnNumber }));
|
||||
_addConsoleMessage(type: string, args: js.JSHandle[], location: console.ConsoleMessageLocation, text?: string) {
|
||||
if (!this.listenerCount(Events.Page.Console)) {
|
||||
args.forEach(arg => arg.dispose());
|
||||
return;
|
||||
}
|
||||
this.emit(Events.Page.Console, new console.ConsoleMessage(type, text, args, location));
|
||||
}
|
||||
|
||||
mainFrame(): frames.Frame {
|
||||
|
|
@ -176,11 +137,6 @@ export class Page extends EventEmitter {
|
|||
return this._frameManager.frames();
|
||||
}
|
||||
|
||||
workers(): Worker[] {
|
||||
return Array.from(this._workers.values());
|
||||
}
|
||||
|
||||
|
||||
setDefaultNavigationTimeout(timeout: number) {
|
||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||
}
|
||||
|
|
@ -228,7 +184,7 @@ export class Page extends EventEmitter {
|
|||
|
||||
async setUserAgent(userAgent: string) {
|
||||
this._userAgent = userAgent;
|
||||
await this._session.send('Page.overrideUserAgent', { value: userAgent });
|
||||
this._frameManager.setUserAgent(userAgent);
|
||||
}
|
||||
|
||||
url(): string {
|
||||
|
|
@ -279,12 +235,6 @@ export class Page extends EventEmitter {
|
|||
return await this._frameManager.mainFrame().waitForNavigation();
|
||||
}
|
||||
|
||||
_sessionClosePromise() {
|
||||
if (!this._disconnectPromise)
|
||||
this._disconnectPromise = new Promise(fulfill => this._session.once(TargetSessionEvents.Disconnected, () => fulfill(new Error('Target closed'))));
|
||||
return this._disconnectPromise;
|
||||
}
|
||||
|
||||
async waitForRequest(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise<Request> {
|
||||
const {
|
||||
timeout = this._timeoutSettings.timeout(),
|
||||
|
|
@ -295,7 +245,7 @@ export class Page extends EventEmitter {
|
|||
if (typeof urlOrPredicate === 'function')
|
||||
return !!(urlOrPredicate(request));
|
||||
return false;
|
||||
}, timeout, this._sessionClosePromise());
|
||||
}, timeout, this._disconnectedPromise);
|
||||
}
|
||||
|
||||
async waitForResponse(urlOrPredicate: (string | Function), options: { timeout?: number; } = {}): Promise<Response> {
|
||||
|
|
@ -308,7 +258,7 @@ export class Page extends EventEmitter {
|
|||
if (typeof urlOrPredicate === 'function')
|
||||
return !!(urlOrPredicate(response));
|
||||
return false;
|
||||
}, timeout, this._sessionClosePromise());
|
||||
}, timeout, this._disconnectedPromise);
|
||||
}
|
||||
|
||||
async emulate(options: { viewport: types.Viewport; userAgent: string; }) {
|
||||
|
|
@ -325,7 +275,7 @@ export class Page extends EventEmitter {
|
|||
assert(!options.colorScheme || mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
|
||||
assert(!options.colorScheme, 'Media feature emulation is not supported');
|
||||
this._emulatedMediaType = typeof options.type === 'undefined' ? this._emulatedMediaType : options.type;
|
||||
await this._session.send('Page.setEmulatedMedia', { media: this._emulatedMediaType || '' });
|
||||
this._frameManager.setEmulatedMedia(this._emulatedMediaType);
|
||||
}
|
||||
|
||||
async setViewport(viewport: types.Viewport) {
|
||||
|
|
@ -351,11 +301,11 @@ export class Page extends EventEmitter {
|
|||
await this._session.send('Page.setBootstrapScript', { source });
|
||||
}
|
||||
|
||||
async setJavaScriptEnabled(enabled: boolean) {
|
||||
setJavaScriptEnabled(enabled: boolean) {
|
||||
if (this._javascriptEnabled === enabled)
|
||||
return;
|
||||
this._javascriptEnabled = enabled;
|
||||
await this._session.send('Emulation.setJavaScriptEnabled', { enabled });
|
||||
return this._frameManager.setJavaScriptEnabled(enabled);
|
||||
}
|
||||
|
||||
async setCacheEnabled(enabled: boolean = true) {
|
||||
|
|
@ -371,6 +321,7 @@ export class Page extends EventEmitter {
|
|||
}
|
||||
|
||||
async close() {
|
||||
assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.');
|
||||
this.browser()._closePage(this);
|
||||
await this._closedPromise;
|
||||
}
|
||||
|
|
@ -392,11 +343,11 @@ export class Page extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) {
|
||||
if (!this._fileChooserInterceptors.size)
|
||||
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
||||
if (!this._fileChooserInterceptors.size) {
|
||||
await handle.dispose();
|
||||
return;
|
||||
const context = await this._frameManager.frame(event.frameId)._utilityContext();
|
||||
const handle = context._createHandle(event.element).asElement()!;
|
||||
}
|
||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||
this._fileChooserInterceptors.clear();
|
||||
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { RegisteredListener } from '../helper';
|
|||
import { Browser, BrowserContext } from './Browser';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { isSwappedOutError, TargetSession } from './Connection';
|
||||
import { isSwappedOutError, TargetSession, TargetSessionEvents } from './Connection';
|
||||
|
||||
const targetSymbol = Symbol('target');
|
||||
|
||||
|
|
@ -74,6 +74,11 @@ export class Target {
|
|||
if (this.browser()._defaultViewport)
|
||||
await page.setViewport(this.browser()._defaultViewport);
|
||||
(page as any)[targetSymbol] = this;
|
||||
session.once(TargetSessionEvents.Disconnected, () => {
|
||||
// Check that this target has not been swapped out.
|
||||
if ((page as any)[targetSymbol] === this)
|
||||
page._didDisconnect();
|
||||
});
|
||||
f(page);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue