chore: get rid of <Browser> templating (#209)

This commit is contained in:
Dmitry Gozman 2019-12-11 07:18:43 -08:00 committed by Pavel Feldman
parent 57acdfd860
commit 58336d3eb9
11 changed files with 102 additions and 89 deletions

View file

@ -18,28 +18,41 @@
import { assert } from './helper'; import { assert } from './helper';
import { Page } from './page'; import { Page } from './page';
import * as network from './network'; import * as network from './network';
import * as childProcess from 'child_process';
export interface BrowserDelegate<Browser> { export interface BrowserInterface {
contextPages(): Promise<Page<Browser>[]>; browserContexts(): BrowserContext[];
createPageInContext(): Promise<Page<Browser>>; close(): Promise<void>;
createIncognitoBrowserContext(): Promise<BrowserContext>;
defaultBrowserContext(): BrowserContext;
newPage(): Promise<Page>;
pages(): Promise<Page[]>;
process(): childProcess.ChildProcess | null;
version(): Promise<string>;
userAgent(): Promise<string>;
}
export interface BrowserDelegate {
contextPages(): Promise<Page[]>;
createPageInContext(): Promise<Page>;
closeContext(): Promise<void>; closeContext(): Promise<void>;
getContextCookies(): Promise<network.NetworkCookie[]>; getContextCookies(): Promise<network.NetworkCookie[]>;
clearContextCookies(): Promise<void>; clearContextCookies(): Promise<void>;
setContextCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>; setContextCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>;
} }
export class BrowserContext<Browser> { export class BrowserContext {
private readonly _delegate: BrowserDelegate<Browser>; private readonly _delegate: BrowserDelegate;
private readonly _browser: Browser; private readonly _browser: BrowserInterface;
private readonly _isIncognito: boolean; private readonly _isIncognito: boolean;
constructor(delegate: BrowserDelegate<Browser>, browser: Browser, isIncognito: boolean) { constructor(delegate: BrowserDelegate, browser: BrowserInterface, isIncognito: boolean) {
this._delegate = delegate; this._delegate = delegate;
this._browser = browser; this._browser = browser;
this._isIncognito = isIncognito; this._isIncognito = isIncognito;
} }
async pages(): Promise<Page<Browser>[]> { async pages(): Promise<Page[]> {
return this._delegate.contextPages(); return this._delegate.contextPages();
} }
@ -47,11 +60,11 @@ export class BrowserContext<Browser> {
return this._isIncognito; return this._isIncognito;
} }
async newPage(): Promise<Page<Browser>> { async newPage(): Promise<Page> {
return this._delegate.createPageInContext(); return this._delegate.createPageInContext();
} }
browser(): Browser { browser(): BrowserInterface {
return this._browser; return this._browser;
} }

View file

@ -19,7 +19,7 @@ import * as childProcess from 'child_process';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { Events } from './events'; import { Events } from './events';
import { assert, helper } from '../helper'; import { assert, helper } from '../helper';
import { BrowserContext } from '../browserContext'; import { BrowserContext, BrowserInterface } 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';
@ -30,15 +30,15 @@ import { FrameManager } from './FrameManager';
import * as network from '../network'; import * as network from '../network';
import { Permissions } from './features/permissions'; import { Permissions } from './features/permissions';
export class Browser extends EventEmitter { export class Browser extends EventEmitter implements BrowserInterface {
private _ignoreHTTPSErrors: boolean; private _ignoreHTTPSErrors: boolean;
private _defaultViewport: types.Viewport; private _defaultViewport: types.Viewport;
private _process: childProcess.ChildProcess; private _process: childProcess.ChildProcess;
_connection: Connection; _connection: Connection;
_client: CDPSession; _client: CDPSession;
private _closeCallback: () => Promise<void>; private _closeCallback: () => Promise<void>;
private _defaultContext: BrowserContext<Browser>; private _defaultContext: BrowserContext;
private _contexts = new Map<string, BrowserContext<Browser>>(); private _contexts = new Map<string, BrowserContext>();
_targets = new Map<string, Target>(); _targets = new Map<string, Target>();
readonly chromium: Chromium; readonly chromium: Chromium;
@ -80,16 +80,16 @@ export class Browser extends EventEmitter {
this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
} }
_createBrowserContext(contextId: string | null): BrowserContext<Browser> { _createBrowserContext(contextId: string | null): BrowserContext {
const isIncognito = !!contextId; const isIncognito = !!contextId;
const context = new BrowserContext({ const context = new BrowserContext({
contextPages: async (): Promise<Page<Browser>[]> => { contextPages: async (): Promise<Page[]> => {
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);
}, },
createPageInContext: async (): Promise<Page<Browser>> => { createPageInContext: async (): Promise<Page> => {
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');
@ -127,18 +127,18 @@ export class Browser extends EventEmitter {
return this._process; return this._process;
} }
async createIncognitoBrowserContext(): Promise<BrowserContext<Browser>> { async createIncognitoBrowserContext(): Promise<BrowserContext> {
const {browserContextId} = await this._client.send('Target.createBrowserContext'); const {browserContextId} = await this._client.send('Target.createBrowserContext');
const context = this._createBrowserContext(browserContextId); const context = this._createBrowserContext(browserContextId);
this._contexts.set(browserContextId, context); this._contexts.set(browserContextId, context);
return context; return context;
} }
browserContexts(): BrowserContext<Browser>[] { browserContexts(): BrowserContext[] {
return [this._defaultContext, ...Array.from(this._contexts.values())]; return [this._defaultContext, ...Array.from(this._contexts.values())];
} }
defaultBrowserContext(): BrowserContext<Browser> { defaultBrowserContext(): BrowserContext {
return this._defaultContext; return this._defaultContext;
} }
@ -174,11 +174,11 @@ export class Browser extends EventEmitter {
this.chromium.emit(Events.Chromium.TargetChanged, target); this.chromium.emit(Events.Chromium.TargetChanged, target);
} }
async newPage(): Promise<Page<Browser>> { async newPage(): Promise<Page> {
return this._defaultContext.newPage(); return this._defaultContext.newPage();
} }
async _closePage(page: Page<Browser>) { async _closePage(page: Page) {
await this._client.send('Target.closeTarget', { targetId: Target.fromPage(page)._targetId }); await this._client.send('Target.closeTarget', { targetId: Target.fromPage(page)._targetId });
} }
@ -186,7 +186,7 @@ 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 _activatePage(page: Page<Browser>) { async _activatePage(page: Page) {
await (page._delegate as FrameManager)._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId}); await (page._delegate as FrameManager)._client.send('Target.activateTarget', {targetId: Target.fromPage(page)._targetId});
} }
@ -216,7 +216,7 @@ export class Browser extends EventEmitter {
} }
} }
async pages(): Promise<Page<Browser>[]> { async pages(): Promise<Page[]> {
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), []);

View file

@ -62,7 +62,7 @@ type FrameData = {
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate { export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
_client: CDPSession; _client: CDPSession;
private _page: Page<Browser>; private _page: Page;
private _networkManager: NetworkManager; private _networkManager: NetworkManager;
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>();
@ -72,7 +72,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
rawKeyboard: RawKeyboardImpl; rawKeyboard: RawKeyboardImpl;
screenshotterDelegate: CRScreenshotDelegate; screenshotterDelegate: CRScreenshotDelegate;
constructor(client: CDPSession, browserContext: BrowserContext<Browser>, ignoreHTTPSErrors: boolean) { constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) {
super(); super();
this._client = client; this._client = client;
this.rawKeyboard = new RawKeyboardImpl(client); this.rawKeyboard = new RawKeyboardImpl(client);
@ -251,7 +251,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
this._handleFrameTree(child); this._handleFrameTree(child);
} }
page(): Page<Browser> { page(): Page {
return this._page; return this._page;
} }
@ -540,7 +540,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
if (runBeforeUnload) if (runBeforeUnload)
await this._client.send('Page.close'); await this._client.send('Page.close');
else else
await this._page.browser()._closePage(this._page); await (this._page.browser() as Browser)._closePage(this._page);
} }
} }

View file

@ -30,25 +30,25 @@ const targetSymbol = Symbol('target');
export class Target { export class Target {
private _targetInfo: Protocol.Target.TargetInfo; private _targetInfo: Protocol.Target.TargetInfo;
private _browserContext: BrowserContext<Browser>; private _browserContext: BrowserContext;
_targetId: string; _targetId: string;
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<Browser>> | null = null; private _pagePromise: Promise<Page> | null = null;
private _page: Page<Browser> | null = null; private _page: Page | 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<Browser>): Target { static fromPage(page: Page): Target {
return (page as any)[targetSymbol]; return (page as any)[targetSymbol];
} }
constructor( constructor(
targetInfo: Protocol.Target.TargetInfo, targetInfo: Protocol.Target.TargetInfo,
browserContext: BrowserContext<Browser>, browserContext: BrowserContext,
sessionFactory: () => Promise<CDPSession>, sessionFactory: () => Promise<CDPSession>,
ignoreHTTPSErrors: boolean, ignoreHTTPSErrors: boolean,
defaultViewport: types.Viewport | null) { defaultViewport: types.Viewport | null) {
@ -81,7 +81,7 @@ export class Target {
this._page._didClose(); this._page._didClose();
} }
async page(): Promise<Page<Browser> | null> { async page(): Promise<Page | 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 frameManager = new FrameManager(client, this._browserContext, this._ignoreHTTPSErrors); const frameManager = new FrameManager(client, this._browserContext, this._ignoreHTTPSErrors);
@ -128,10 +128,10 @@ export class Target {
} }
browser(): Browser { browser(): Browser {
return this._browserContext.browser(); return this._browserContext.browser() as Browser;
} }
browserContext(): BrowserContext<Browser> { browserContext(): BrowserContext {
return this._browserContext; return this._browserContext;
} }

View file

@ -48,7 +48,7 @@ export class Chromium extends EventEmitter {
return target._worker(); return target._worker();
} }
async startTracing(page: Page<Browser> | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) { async startTracing(page: Page | 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._delegate as FrameManager)._client : this._client; this._tracingClient = page ? (page._delegate as FrameManager)._client : this._client;
@ -87,12 +87,12 @@ export class Chromium extends EventEmitter {
return contentPromise; return contentPromise;
} }
targets(context?: BrowserContext<Browser>): Target[] { targets(context?: BrowserContext): Target[] {
const targets = this._browser._allTargets(); const targets = this._browser._allTargets();
return context ? targets.filter(t => t.browserContext() === context) : targets; return context ? targets.filter(t => t.browserContext() === context) : targets;
} }
pageTarget(page: Page<Browser>): Target { pageTarget(page: Page): Target {
return Target.fromPage(page); return Target.fromPage(page);
} }

View file

@ -25,16 +25,16 @@ import { Page } from '../page';
import * as types from '../types'; import * as types from '../types';
import { FrameManager } from './FrameManager'; import { FrameManager } from './FrameManager';
import * as network from '../network'; import * as network from '../network';
import { BrowserContext } from '../browserContext'; import { BrowserContext, BrowserInterface } from '../browserContext';
export class Browser extends EventEmitter { export class Browser extends EventEmitter implements BrowserInterface {
private _connection: Connection; private _connection: Connection;
_defaultViewport: types.Viewport; _defaultViewport: types.Viewport;
private _process: import('child_process').ChildProcess; private _process: import('child_process').ChildProcess;
private _closeCallback: () => void; private _closeCallback: () => void;
_targets: Map<string, Target>; _targets: Map<string, Target>;
private _defaultContext: BrowserContext<Browser>; private _defaultContext: BrowserContext;
private _contexts: Map<string, BrowserContext<Browser>>; private _contexts: Map<string, BrowserContext>;
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
static async create(connection: Connection, defaultViewport: types.Viewport | null, process: import('child_process').ChildProcess | null, closeCallback: () => void) { static async create(connection: Connection, defaultViewport: types.Viewport | null, process: import('child_process').ChildProcess | null, closeCallback: () => void) {
@ -75,14 +75,14 @@ export class Browser extends EventEmitter {
return !this._connection._closed; return !this._connection._closed;
} }
async createIncognitoBrowserContext(): Promise<BrowserContext<Browser>> { async createIncognitoBrowserContext(): Promise<BrowserContext> {
const {browserContextId} = await this._connection.send('Target.createBrowserContext'); const {browserContextId} = await this._connection.send('Target.createBrowserContext');
const context = this._createBrowserContext(browserContextId); const context = this._createBrowserContext(browserContextId);
this._contexts.set(browserContextId, context); this._contexts.set(browserContextId, context);
return context; return context;
} }
browserContexts(): Array<BrowserContext<Browser>> { browserContexts(): Array<BrowserContext> {
return [this._defaultContext, ...Array.from(this._contexts.values())]; return [this._defaultContext, ...Array.from(this._contexts.values())];
} }
@ -128,7 +128,7 @@ export class Browser extends EventEmitter {
} }
} }
newPage(): Promise<Page<Browser>> { newPage(): Promise<Page> {
return this._defaultContext.newPage(); return this._defaultContext.newPage();
} }
@ -170,16 +170,16 @@ export class Browser extends EventEmitter {
this._closeCallback(); this._closeCallback();
} }
_createBrowserContext(browserContextId: string | null): BrowserContext<Browser> { _createBrowserContext(browserContextId: string | null): BrowserContext {
const isIncognito = !!browserContextId; const isIncognito = !!browserContextId;
const context = new BrowserContext({ const context = new BrowserContext({
contextPages: async (): Promise<Page<Browser>[]> => { contextPages: async (): Promise<Page[]> => {
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);
}, },
createPageInContext: async (): Promise<Page<Browser>> => { createPageInContext: async (): Promise<Page> => {
const {targetId} = await this._connection.send('Target.newPage', { const {targetId} = await this._connection.send('Target.newPage', {
browserContextId: browserContextId || undefined browserContextId: browserContextId || undefined
}); });
@ -215,17 +215,17 @@ export class Browser extends EventEmitter {
} }
export class Target { export class Target {
_pagePromise?: Promise<Page<Browser>>; _pagePromise?: Promise<Page>;
private _page: Page<Browser> | null = null; private _page: Page | null = null;
private _browser: Browser; private _browser: Browser;
_context: BrowserContext<Browser>; _context: BrowserContext;
private _connection: Connection; private _connection: Connection;
private _targetId: string; private _targetId: string;
private _type: 'page' | 'browser'; private _type: 'page' | 'browser';
_url: string; _url: string;
private _openerId: string; private _openerId: string;
constructor(connection: any, browser: Browser, context: BrowserContext<Browser>, targetId: string, type: 'page' | 'browser', url: string, openerId: string | undefined) { constructor(connection: any, browser: Browser, context: BrowserContext, targetId: string, type: 'page' | 'browser', url: string, openerId: string | undefined) {
this._browser = browser; this._browser = browser;
this._context = context; this._context = context;
this._connection = connection; this._connection = connection;
@ -252,11 +252,11 @@ export class Target {
return this._url; return this._url;
} }
browserContext(): BrowserContext<Browser> { browserContext(): BrowserContext {
return this._context; return this._context;
} }
page(): Promise<Page<Browser>> { page(): Promise<Page> {
if (this._type === 'page' && !this._pagePromise) { if (this._type === 'page' && !this._pagePromise) {
this._pagePromise = new Promise(async f => { this._pagePromise = new Promise(async f => {
const session = await this._connection.createSession(this._targetId); const session = await this._connection.createSession(this._targetId);

View file

@ -56,14 +56,14 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
readonly rawKeyboard: RawKeyboardImpl; readonly rawKeyboard: RawKeyboardImpl;
readonly screenshotterDelegate: FFScreenshotDelegate; readonly screenshotterDelegate: FFScreenshotDelegate;
readonly _session: JugglerSession; readonly _session: JugglerSession;
readonly _page: Page<Browser>; readonly _page: Page;
private readonly _networkManager: NetworkManager; private readonly _networkManager: NetworkManager;
private _mainFrame: frames.Frame; private _mainFrame: frames.Frame;
private readonly _frames: Map<string, frames.Frame>; private readonly _frames: Map<string, frames.Frame>;
private readonly _contextIdToContext: Map<string, js.ExecutionContext>; private readonly _contextIdToContext: Map<string, js.ExecutionContext>;
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
constructor(session: JugglerSession, browserContext: BrowserContext<Browser>) { constructor(session: JugglerSession, browserContext: BrowserContext) {
super(); super();
this._session = session; this._session = session;
this.rawKeyboard = new RawKeyboardImpl(session); this.rawKeyboard = new RawKeyboardImpl(session);

View file

@ -26,7 +26,7 @@ import { Screenshotter, ScreenshotterDelegate } from './screenshotter';
import { TimeoutSettings } from './TimeoutSettings'; import { TimeoutSettings } from './TimeoutSettings';
import * as types from './types'; import * as types from './types';
import { Events } from './events'; import { Events } from './events';
import { BrowserContext } from './browserContext'; import { BrowserContext, BrowserInterface } from './browserContext';
import { ConsoleMessage, ConsoleMessageLocation } from './console'; import { ConsoleMessage, ConsoleMessageLocation } from './console';
export interface PageDelegate { export interface PageDelegate {
@ -69,14 +69,14 @@ export type FileChooser = {
multiple: boolean multiple: boolean
}; };
export class Page<Browser> extends EventEmitter { export class Page 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;
readonly _disconnectedPromise: Promise<Error>; readonly _disconnectedPromise: Promise<Error>;
private _browserContext: BrowserContext<Browser>; private _browserContext: BrowserContext;
readonly keyboard: input.Keyboard; readonly keyboard: input.Keyboard;
readonly mouse: input.Mouse; readonly mouse: input.Mouse;
readonly _timeoutSettings: TimeoutSettings; readonly _timeoutSettings: TimeoutSettings;
@ -87,7 +87,7 @@ export class Page<Browser> extends EventEmitter {
private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>(); private _fileChooserInterceptors = new Set<(chooser: FileChooser) => void>();
readonly _lifecycleWatchers = new Set<frames.LifecycleWatcher>(); readonly _lifecycleWatchers = new Set<frames.LifecycleWatcher>();
constructor(delegate: PageDelegate, browserContext: BrowserContext<Browser>) { constructor(delegate: PageDelegate, browserContext: BrowserContext) {
super(); super();
this._delegate = delegate; this._delegate = delegate;
this._closedPromise = new Promise(f => this._closedCallback = f); this._closedPromise = new Promise(f => this._closedCallback = f);
@ -150,11 +150,11 @@ export class Page<Browser> extends EventEmitter {
}); });
} }
browser(): Browser { browser(): BrowserInterface {
return this._browserContext.browser(); return this._browserContext.browser();
} }
browserContext(): BrowserContext<Browser> { browserContext(): BrowserContext {
return this._browserContext; return this._browserContext;
} }

View file

@ -25,15 +25,15 @@ import { Target } from './Target';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as types from '../types'; import * as types from '../types';
import { Events } from '../events'; import { Events } from '../events';
import { BrowserContext } from '../browserContext'; import { BrowserContext, BrowserInterface } from '../browserContext';
export class Browser extends EventEmitter { export class Browser extends EventEmitter implements BrowserInterface {
readonly _defaultViewport: types.Viewport; readonly _defaultViewport: types.Viewport;
private readonly _process: childProcess.ChildProcess; private readonly _process: childProcess.ChildProcess;
readonly _connection: Connection; readonly _connection: Connection;
private _closeCallback: () => Promise<void>; private _closeCallback: () => Promise<void>;
private readonly _defaultContext: BrowserContext<Browser>; private readonly _defaultContext: BrowserContext;
private _contexts = new Map<string, BrowserContext<Browser>>(); private _contexts = new Map<string, BrowserContext>();
_targets = new Map<string, Target>(); _targets = new Map<string, Target>();
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
private _privateEvents = new EventEmitter(); private _privateEvents = new EventEmitter();
@ -86,25 +86,25 @@ export class Browser extends EventEmitter {
return this._process; return this._process;
} }
async createIncognitoBrowserContext(): Promise<BrowserContext<Browser>> { async createIncognitoBrowserContext(): Promise<BrowserContext> {
const {browserContextId} = await this._connection.send('Browser.createContext'); const {browserContextId} = await this._connection.send('Browser.createContext');
const context = this._createBrowserContext(browserContextId); const context = this._createBrowserContext(browserContextId);
this._contexts.set(browserContextId, context); this._contexts.set(browserContextId, context);
return context; return context;
} }
browserContexts(): BrowserContext<Browser>[] { browserContexts(): BrowserContext[] {
return [this._defaultContext, ...Array.from(this._contexts.values())]; return [this._defaultContext, ...Array.from(this._contexts.values())];
} }
defaultBrowserContext(): BrowserContext<Browser> { defaultBrowserContext(): BrowserContext {
return this._defaultContext; return this._defaultContext;
} }
async _disposeContext(browserContextId: string | null) { async _disposeContext(browserContextId: string | null) {
} }
async newPage(): Promise<Page<Browser>> { async newPage(): Promise<Page> {
return this._defaultContext.newPage(); return this._defaultContext.newPage();
} }
@ -136,7 +136,7 @@ export class Browser extends EventEmitter {
} }
} }
async pages(): Promise<Page<Browser>[]> { async pages(): Promise<Page[]> {
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), []);
@ -181,13 +181,13 @@ export class Browser extends EventEmitter {
target._didClose(); target._didClose();
} }
_closePage(page: Page<Browser>) { _closePage(page: Page) {
this._connection.send('Target.close', { this._connection.send('Target.close', {
targetId: Target.fromPage(page)._targetId targetId: Target.fromPage(page)._targetId
}).catch(debugError); }).catch(debugError);
} }
async _activatePage(page: Page<Browser>): Promise<void> { async _activatePage(page: Page): Promise<void> {
await this._connection.send('Target.activate', { targetId: Target.fromPage(page)._targetId }); await this._connection.send('Target.activate', { targetId: Target.fromPage(page)._targetId });
} }
@ -210,16 +210,16 @@ export class Browser extends EventEmitter {
await this._closeCallback.call(null); await this._closeCallback.call(null);
} }
_createBrowserContext(browserContextId: string | undefined): BrowserContext<Browser> { _createBrowserContext(browserContextId: string | undefined): BrowserContext {
const isIncognito = !!browserContextId; const isIncognito = !!browserContextId;
const context = new BrowserContext({ const context = new BrowserContext({
contextPages: async (): Promise<Page<Browser>[]> => { contextPages: async (): Promise<Page[]> => {
const targets = this.targets().filter(target => target._browserContext === context && target._type === 'page'); const targets = this.targets().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);
}, },
createPageInContext: async (): Promise<Page<Browser>> => { createPageInContext: async (): Promise<Page> => {
const { targetId } = await this._connection.send('Browser.createPage', { browserContextId }); const { targetId } = await this._connection.send('Browser.createPage', { browserContextId });
const target = this._targets.get(targetId); const target = this._targets.get(targetId);
return await target.page(); return await target.page();

View file

@ -59,7 +59,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
readonly rawKeyboard: RawKeyboardImpl; readonly rawKeyboard: RawKeyboardImpl;
readonly screenshotterDelegate: WKScreenshotDelegate; readonly screenshotterDelegate: WKScreenshotDelegate;
_session: TargetSession; _session: TargetSession;
readonly _page: Page<Browser>; readonly _page: Page;
private readonly _networkManager: NetworkManager; private readonly _networkManager: NetworkManager;
private readonly _frames: Map<string, frames.Frame>; private readonly _frames: Map<string, frames.Frame>;
private readonly _contextIdToContext: Map<number, js.ExecutionContext>; private readonly _contextIdToContext: Map<number, js.ExecutionContext>;
@ -68,7 +68,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
private _mainFrame: frames.Frame; private _mainFrame: frames.Frame;
private readonly _bootstrapScripts: string[] = []; private readonly _bootstrapScripts: string[] = [];
constructor(browserContext: BrowserContext<Browser>) { constructor(browserContext: BrowserContext) {
super(); super();
this.rawKeyboard = new RawKeyboardImpl(); this.rawKeyboard = new RawKeyboardImpl();
this.rawMouse = new RawMouseImpl(); this.rawMouse = new RawMouseImpl();
@ -197,7 +197,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
this._handleFrameTree(child); this._handleFrameTree(child);
} }
page(): Page<Browser> { page(): Page {
return this._page; return this._page;
} }
@ -524,6 +524,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
async closePage(runBeforeUnload: boolean): Promise<void> { async closePage(runBeforeUnload: boolean): Promise<void> {
if (runBeforeUnload) if (runBeforeUnload)
throw new Error('Not implemented'); throw new Error('Not implemented');
this._page.browser()._closePage(this._page); (this._page.browser() as Browser)._closePage(this._page);
} }
} }

View file

@ -25,18 +25,18 @@ import { FrameManager } from './FrameManager';
const targetSymbol = Symbol('target'); const targetSymbol = Symbol('target');
export class Target { export class Target {
readonly _browserContext: BrowserContext<Browser>; readonly _browserContext: BrowserContext;
readonly _targetId: string; readonly _targetId: string;
readonly _type: 'page' | 'service-worker' | 'worker'; readonly _type: 'page' | 'service-worker' | 'worker';
private readonly _session: TargetSession; private readonly _session: TargetSession;
private _pagePromise: Promise<Page<Browser>> | null = null; private _pagePromise: Promise<Page> | null = null;
_page: Page<Browser> | null = null; _page: Page | null = null;
static fromPage(page: Page<Browser>): Target { static fromPage(page: Page): Target {
return (page as any)[targetSymbol]; return (page as any)[targetSymbol];
} }
constructor(session: TargetSession, targetInfo: Protocol.Target.TargetInfo, browserContext: BrowserContext<Browser>) { constructor(session: TargetSession, targetInfo: Protocol.Target.TargetInfo, browserContext: BrowserContext) {
const {targetId, type} = targetInfo; const {targetId, type} = targetInfo;
this._session = session; this._session = session;
this._browserContext = browserContext; this._browserContext = browserContext;
@ -84,9 +84,9 @@ export class Target {
(this._page._delegate as FrameManager).setSession(this._session); (this._page._delegate as FrameManager).setSession(this._session);
} }
async page(): Promise<Page<Browser>> { async page(): Promise<Page> {
if (this._type === 'page' && !this._pagePromise) { if (this._type === 'page' && !this._pagePromise) {
const browser = this._browserContext.browser(); const browser = this._browserContext.browser() as Browser;
// Reference local page variable as _page may be // Reference local page variable as _page may be
// cleared on swap. // cleared on swap.
const frameManager = new FrameManager(this._browserContext); const frameManager = new FrameManager(this._browserContext);