chore: various cleanup (#266)

This commit is contained in:
Dmitry Gozman 2019-12-16 16:43:28 -08:00 committed by Andrey Lushnikov
parent f9f7d5c55a
commit 03e2336d49
12 changed files with 121 additions and 160 deletions

View File

@ -310,7 +310,7 @@
- [class: TimeoutError](#class-timeouterror) - [class: TimeoutError](#class-timeouterror)
- [class: Selector](#class-selector) - [class: Selector](#class-selector)
* [selector.selector](#selectorselector) * [selector.selector](#selectorselector)
* [selector.visible](#selectorvisible) * [selector.visibility](#selectorvisibility)
<!-- GEN:stop --> <!-- GEN:stop -->
### Overview ### Overview

View File

@ -15,11 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { EventEmitter } from 'events';
import * as dom from '../dom'; import * as dom from '../dom';
import * as frames from '../frames'; import * as frames from '../frames';
import { assert, debugError } from '../helper'; import { debugError, helper, RegisteredListener } from '../helper';
import * as js from '../javascript';
import * as network from '../network'; import * as network from '../network';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext'; import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
@ -45,17 +43,17 @@ import { ConsoleMessage } from '../console';
const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const UTILITY_WORLD_NAME = '__playwright_utility_world__';
export class FrameManager extends EventEmitter implements PageDelegate { export class FrameManager implements PageDelegate {
_client: CDPSession; _client: CDPSession;
private _page: Page; private _page: Page;
private _networkManager: NetworkManager; private _networkManager: NetworkManager;
private _contextIdToContext = new Map<number, js.ExecutionContext>(); private _contextIdToContext = new Map<number, dom.FrameExecutionContext>();
private _isolatedWorlds = new Set<string>(); private _isolatedWorlds = new Set<string>();
private _eventListeners: RegisteredListener[];
rawMouse: RawMouseImpl; rawMouse: RawMouseImpl;
rawKeyboard: RawKeyboardImpl; rawKeyboard: RawKeyboardImpl;
constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) { constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) {
super();
this._client = client; this._client = client;
this.rawKeyboard = new RawKeyboardImpl(client); this.rawKeyboard = new RawKeyboardImpl(client);
this.rawMouse = new RawMouseImpl(client); this.rawMouse = new RawMouseImpl(client);
@ -68,22 +66,24 @@ export class FrameManager extends EventEmitter implements PageDelegate {
(this._page as any).overrides = new Overrides(client); (this._page as any).overrides = new Overrides(client);
(this._page as any).interception = new Interception(this._networkManager); (this._page as any).interception = new Interception(this._networkManager);
this._client.on('Inspector.targetCrashed', event => this._onTargetCrashed()); this._eventListeners = [
this._client.on('Log.entryAdded', event => this._onLogEntryAdded(event)); helper.addEventListener(client, 'Inspector.targetCrashed', event => this._onTargetCrashed()),
this._client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event)); helper.addEventListener(client, 'Log.entryAdded', event => this._onLogEntryAdded(event)),
this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)); helper.addEventListener(client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId)); helper.addEventListener(client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)); helper.addEventListener(client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
this._client.on('Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)); helper.addEventListener(client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
this._client.on('Page.javascriptDialogOpening', event => this._onDialog(event)); helper.addEventListener(client, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event)); helper.addEventListener(client, 'Page.javascriptDialogOpening', event => this._onDialog(event)),
this._client.on('Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)); helper.addEventListener(client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)),
this._client.on('Runtime.bindingCalled', event => this._onBindingCalled(event)); helper.addEventListener(client, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
this._client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)); helper.addEventListener(client, 'Runtime.bindingCalled', event => this._onBindingCalled(event)),
this._client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)); helper.addEventListener(client, 'Runtime.consoleAPICalled', event => this._onConsoleAPI(event)),
this._client.on('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)); helper.addEventListener(client, 'Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)),
this._client.on('Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)); helper.addEventListener(client, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
this._client.on('Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()); helper.addEventListener(client, 'Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)),
helper.addEventListener(client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()),
];
} }
async initialize() { async initialize() {
@ -102,11 +102,9 @@ export class FrameManager extends EventEmitter implements PageDelegate {
} }
didClose() { didClose() {
// TODO: remove listeners. helper.removeEventListeners(this._eventListeners);
} this._networkManager.dispose();
this._page._didClose();
networkManager(): NetworkManager {
return this._networkManager;
} }
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> { async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> {
@ -243,21 +241,18 @@ export class FrameManager extends EventEmitter implements PageDelegate {
} }
_onExecutionContextCreated(contextPayload: Protocol.Runtime.ExecutionContextDescription) { _onExecutionContextCreated(contextPayload: Protocol.Runtime.ExecutionContextDescription) {
const frameId = contextPayload.auxData ? contextPayload.auxData.frameId : null; const frame = this._page._frameManager.frame(contextPayload.auxData ? contextPayload.auxData.frameId : null);
const frame = this._page._frameManager.frame(frameId); if (!frame)
return;
if (contextPayload.auxData && contextPayload.auxData.type === 'isolated') if (contextPayload.auxData && contextPayload.auxData.type === 'isolated')
this._isolatedWorlds.add(contextPayload.name); this._isolatedWorlds.add(contextPayload.name);
const delegate = new ExecutionContextDelegate(this._client, contextPayload); const delegate = new ExecutionContextDelegate(this._client, contextPayload);
if (frame) { const context = new dom.FrameExecutionContext(delegate, frame);
const context = new dom.FrameExecutionContext(delegate, frame); if (contextPayload.auxData && !!contextPayload.auxData.isDefault)
if (contextPayload.auxData && !!contextPayload.auxData.isDefault) frame._contextCreated('main', context);
frame._contextCreated('main', context); else if (contextPayload.name === UTILITY_WORLD_NAME)
else if (contextPayload.name === UTILITY_WORLD_NAME) frame._contextCreated('utility', context);
frame._contextCreated('utility', context); this._contextIdToContext.set(contextPayload.id, context);
this._contextIdToContext.set(contextPayload.id, context);
} else {
this._contextIdToContext.set(contextPayload.id, new js.ExecutionContext(delegate));
}
} }
_onExecutionContextDestroyed(executionContextId: number) { _onExecutionContextDestroyed(executionContextId: number) {
@ -265,8 +260,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
if (!context) if (!context)
return; return;
this._contextIdToContext.delete(executionContextId); this._contextIdToContext.delete(executionContextId);
if (context.frame()) context.frame()._contextDestroyed(context);
context.frame()._contextDestroyed(context as dom.FrameExecutionContext);
} }
_onExecutionContextsCleared() { _onExecutionContextsCleared() {
@ -274,12 +268,6 @@ export class FrameManager extends EventEmitter implements PageDelegate {
this._onExecutionContextDestroyed(contextId); this._onExecutionContextDestroyed(contextId);
} }
executionContextById(contextId: number): js.ExecutionContext {
const context = this._contextIdToContext.get(contextId);
assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
return context;
}
async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) { async _onConsoleAPI(event: Protocol.Runtime.consoleAPICalledPayload) {
if (event.executionContextId === 0) { if (event.executionContextId === 0) {
// DevTools protocol stores the last 1000 console messages. These // DevTools protocol stores the last 1000 console messages. These
@ -297,7 +285,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
// @see https://github.com/GoogleChrome/puppeteer/issues/3865 // @see https://github.com/GoogleChrome/puppeteer/issues/3865
return; return;
} }
const context = this.executionContextById(event.executionContextId); const context = this._contextIdToContext.get(event.executionContextId);
const values = event.args.map(arg => context._createHandle(arg)); const values = event.args.map(arg => context._createHandle(arg));
this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace)); this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
} }
@ -309,7 +297,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
} }
_onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) { _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
const context = this.executionContextById(event.executionContextId); const context = this._contextIdToContext.get(event.executionContextId);
this._page._onBindingCalled(event.payload, context); this._page._onBindingCalled(event.payload, context);
} }

View File

@ -17,7 +17,7 @@
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { Page } from '../page'; import { Page } from '../page';
import { assert, debugError, helper } from '../helper'; import { assert, debugError, helper, RegisteredListener } from '../helper';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as network from '../network'; import * as network from '../network';
import * as frames from '../frames'; import * as frames from '../frames';
@ -36,18 +36,21 @@ export class NetworkManager {
private _protocolRequestInterceptionEnabled = false; private _protocolRequestInterceptionEnabled = false;
private _userCacheDisabled = false; private _userCacheDisabled = false;
private _requestIdToInterceptionId = new Map<string, string>(); private _requestIdToInterceptionId = new Map<string, string>();
private _eventListeners: RegisteredListener[];
constructor(client: CDPSession, ignoreHTTPSErrors: boolean, page: Page) { constructor(client: CDPSession, ignoreHTTPSErrors: boolean, page: Page) {
this._client = client; this._client = client;
this._ignoreHTTPSErrors = ignoreHTTPSErrors; this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._page = page; this._page = page;
this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); this._eventListeners = [
this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); helper.addEventListener(client, 'Fetch.requestPaused', this._onRequestPaused.bind(this)),
this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)); helper.addEventListener(client, 'Fetch.authRequired', this._onAuthRequired.bind(this)),
this._client.on('Network.responseReceived', this._onResponseReceived.bind(this)); helper.addEventListener(client, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)),
this._client.on('Network.loadingFinished', this._onLoadingFinished.bind(this)); helper.addEventListener(client, 'Network.responseReceived', this._onResponseReceived.bind(this)),
this._client.on('Network.loadingFailed', this._onLoadingFailed.bind(this)); helper.addEventListener(client, 'Network.loadingFinished', this._onLoadingFinished.bind(this)),
helper.addEventListener(client, 'Network.loadingFailed', this._onLoadingFailed.bind(this)),
];
} }
async initialize() { async initialize() {
@ -56,6 +59,10 @@ export class NetworkManager {
await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true}); await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true});
} }
dispose() {
helper.removeEventListeners(this._eventListeners);
}
async authenticate(credentials: { username: string; password: string; } | null) { async authenticate(credentials: { username: string; password: string; } | null) {
this._credentials = credentials; this._credentials = credentials;
await this._updateProtocolRequestInterception(); await this._updateProtocolRequestInterception();

View File

@ -36,7 +36,7 @@ export class Target {
private _ignoreHTTPSErrors: boolean; private _ignoreHTTPSErrors: boolean;
private _defaultViewport: types.Viewport; private _defaultViewport: types.Viewport;
private _pagePromise: Promise<Page> | null = null; private _pagePromise: Promise<Page> | null = null;
private _page: Page | null = null; private _frameManager: FrameManager | 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;
@ -77,16 +77,15 @@ export class Target {
} }
_didClose() { _didClose() {
if (this._page) if (this._frameManager)
this._page._didClose(); this._frameManager.didClose();
} }
async page(): Promise<Page | 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); this._frameManager = new FrameManager(client, this._browserContext, this._ignoreHTTPSErrors);
const page = frameManager.page(); const page = this._frameManager.page();
this._page = page;
(page as any)[targetSymbol] = this; (page as any)[targetSymbol] = this;
client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect()); client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect());
client.on('Target.attachedToTarget', event => { client.on('Target.attachedToTarget', event => {
@ -95,7 +94,7 @@ export class Target {
client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(debugError); client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(debugError);
} }
}); });
await frameManager.initialize(); await this._frameManager.initialize();
await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}); await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
if (this._defaultViewport) if (this._defaultViewport)
await page.setViewport(this._defaultViewport); await page.setViewport(this._defaultViewport);

View File

@ -219,7 +219,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
export class Target { export class Target {
_pagePromise?: Promise<Page>; _pagePromise?: Promise<Page>;
private _page: Page | null = null; private _frameManager: FrameManager | null = null;
private _browser: Browser; private _browser: Browser;
_context: BrowserContext; _context: BrowserContext;
private _connection: Connection; private _connection: Connection;
@ -239,8 +239,8 @@ export class Target {
} }
_didClose() { _didClose() {
if (this._page) if (this._frameManager)
this._page._didClose(); this._frameManager.didClose();
} }
opener(): Target | null { opener(): Target | null {
@ -263,11 +263,10 @@ export class Target {
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);
const frameManager = new FrameManager(session, this._context); this._frameManager = new FrameManager(session, this._context);
const page = frameManager._page; const page = this._frameManager._page;
this._page = page;
session.once(JugglerSessionEvents.Disconnected, () => page._didDisconnect()); session.once(JugglerSessionEvents.Disconnected, () => page._didDisconnect());
await frameManager._initialize(); await this._frameManager._initialize();
if (this._browser._defaultViewport) if (this._browser._defaultViewport)
await page.setViewport(this._browser._defaultViewport); await page.setViewport(this._browser._defaultViewport);
f(page); f(page);

View File

@ -15,10 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { EventEmitter } from 'events';
import * as frames from '../frames'; import * as frames from '../frames';
import { assert, helper, RegisteredListener, debugError } from '../helper'; import { assert, helper, RegisteredListener, debugError } from '../helper';
import * as js from '../javascript';
import * as dom from '../dom'; import * as dom from '../dom';
import { JugglerSession } from './Connection'; import { JugglerSession } from './Connection';
import { ExecutionContextDelegate } from './ExecutionContext'; import { ExecutionContextDelegate } from './ExecutionContext';
@ -35,17 +33,16 @@ import { Accessibility } from './features/accessibility';
import * as network from '../network'; import * as network from '../network';
import * as types from '../types'; import * as types from '../types';
export class FrameManager extends EventEmitter implements PageDelegate { export class FrameManager implements PageDelegate {
readonly rawMouse: RawMouseImpl; readonly rawMouse: RawMouseImpl;
readonly rawKeyboard: RawKeyboardImpl; readonly rawKeyboard: RawKeyboardImpl;
readonly _session: JugglerSession; readonly _session: JugglerSession;
readonly _page: Page; readonly _page: Page;
private readonly _networkManager: NetworkManager; private readonly _networkManager: NetworkManager;
private readonly _contextIdToContext: Map<string, js.ExecutionContext>; private readonly _contextIdToContext: Map<string, dom.FrameExecutionContext>;
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
constructor(session: JugglerSession, browserContext: BrowserContext) { constructor(session: JugglerSession, browserContext: BrowserContext) {
super();
this._session = session; this._session = session;
this.rawKeyboard = new RawKeyboardImpl(session); this.rawKeyboard = new RawKeyboardImpl(session);
this.rawMouse = new RawMouseImpl(session); this.rawMouse = new RawMouseImpl(session);
@ -81,22 +78,15 @@ export class FrameManager extends EventEmitter implements PageDelegate {
]); ]);
} }
executionContextById(executionContextId) {
return this._contextIdToContext.get(executionContextId) || null;
}
_onExecutionContextCreated({executionContextId, auxData}) { _onExecutionContextCreated({executionContextId, auxData}) {
const frameId = auxData ? auxData.frameId : null; const frame = this._page._frameManager.frame(auxData ? auxData.frameId : null);
const frame = this._page._frameManager.frame(frameId); if (!frame)
return;
const delegate = new ExecutionContextDelegate(this._session, executionContextId); const delegate = new ExecutionContextDelegate(this._session, executionContextId);
if (frame) { const context = new dom.FrameExecutionContext(delegate, frame);
const context = new dom.FrameExecutionContext(delegate, frame); frame._contextCreated('main', context);
frame._contextCreated('main', context); frame._contextCreated('utility', context);
frame._contextCreated('utility', context); this._contextIdToContext.set(executionContextId, context);
this._contextIdToContext.set(executionContextId, context);
} else {
this._contextIdToContext.set(executionContextId, new js.ExecutionContext(delegate));
}
} }
_onExecutionContextDestroyed({executionContextId}) { _onExecutionContextDestroyed({executionContextId}) {
@ -104,14 +94,13 @@ export class FrameManager extends EventEmitter implements PageDelegate {
if (!context) if (!context)
return; return;
this._contextIdToContext.delete(executionContextId); this._contextIdToContext.delete(executionContextId);
if (context.frame()) context.frame()._contextDestroyed(context as dom.FrameExecutionContext);
context.frame()._contextDestroyed(context as dom.FrameExecutionContext);
} }
_onNavigationStarted(params) { _onNavigationStarted() {
} }
_onNavigationAborted(params) { _onNavigationAborted(params: Protocol.Page.navigationAbortedPayload) {
const frame = this._page._frameManager.frame(params.frameId); const frame = this._page._frameManager.frame(params.frameId);
for (const watcher of this._page._frameManager._lifecycleWatchers) for (const watcher of this._page._frameManager._lifecycleWatchers)
watcher._onAbortedNewDocumentNavigation(frame, params.navigationId, params.errorText); watcher._onAbortedNewDocumentNavigation(frame, params.navigationId, params.errorText);
@ -140,18 +129,18 @@ export class FrameManager extends EventEmitter implements PageDelegate {
this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded'); this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded');
} }
_onUncaughtError(params) { _onUncaughtError(params: Protocol.Page.uncaughtErrorPayload) {
const error = new Error(params.message); const error = new Error(params.message);
error.stack = params.stack; error.stack = params.stack;
this._page.emit(Events.Page.PageError, error); this._page.emit(Events.Page.PageError, error);
} }
_onConsole({type, args, executionContextId, location}) { _onConsole({type, args, executionContextId, location}) {
const context = this.executionContextById(executionContextId); const context = this._contextIdToContext.get(executionContextId);
this._page._addConsoleMessage(type, args.map(arg => context._createHandle(arg)), location); this._page._addConsoleMessage(type, args.map(arg => context._createHandle(arg)), location);
} }
_onDialogOpened(params) { _onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
this._page.emit(Events.Page.Dialog, new dialog.Dialog( this._page.emit(Events.Page.Dialog, new dialog.Dialog(
params.type as dialog.DialogType, params.type as dialog.DialogType,
params.message, params.message,
@ -162,12 +151,12 @@ export class FrameManager extends EventEmitter implements PageDelegate {
} }
_onBindingCalled(event: Protocol.Page.bindingCalledPayload) { _onBindingCalled(event: Protocol.Page.bindingCalledPayload) {
const context = this.executionContextById(event.executionContextId); const context = this._contextIdToContext.get(event.executionContextId);
this._page._onBindingCalled(event.payload, context); this._page._onBindingCalled(event.payload, context);
} }
async _onFileChooserOpened({executionContextId, element}) { async _onFileChooserOpened({executionContextId, element}) {
const context = this.executionContextById(executionContextId); const context = this._contextIdToContext.get(executionContextId);
const handle = context._createHandle(element).asElement()!; const handle = context._createHandle(element).asElement()!;
this._page._onFileChooserOpened(handle); this._page._onFileChooserOpened(handle);
} }
@ -181,6 +170,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
didClose() { didClose() {
helper.removeEventListeners(this._eventListeners); helper.removeEventListeners(this._eventListeners);
this._networkManager.dispose(); this._networkManager.dispose();
this._page._didClose();
} }
async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}) { async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}) {

View File

@ -30,8 +30,6 @@ import { launchProcess, waitForLine } from '../processLauncher';
const mkdtempAsync = util.promisify(fs.mkdtemp); const mkdtempAsync = util.promisify(fs.mkdtemp);
const writeFileAsync = util.promisify(fs.writeFile); const writeFileAsync = util.promisify(fs.writeFile);
const FIREFOX_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_firefox_profile-');
const DEFAULT_ARGS = [ const DEFAULT_ARGS = [
'-no-remote', '-no-remote',
'-foreground', '-foreground',

View File

@ -40,8 +40,6 @@ export interface PageDelegate {
exposeBinding(name: string, bindingFunction: string): Promise<void>; exposeBinding(name: string, bindingFunction: string): Promise<void>;
evaluateOnNewDocument(source: string): Promise<void>; evaluateOnNewDocument(source: string): Promise<void>;
closePage(runBeforeUnload: boolean): Promise<void>; closePage(runBeforeUnload: boolean): Promise<void>;
// TODO: reverse didClose call sequence.
didClose(): void;
navigateFrame(frame: frames.Frame, url: string, options?: frames.GotoOptions): Promise<network.Response | null>; navigateFrame(frame: frames.Frame, url: string, options?: frames.GotoOptions): Promise<network.Response | null>;
waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise<network.Response | null>; waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise<network.Response | null>;
@ -130,7 +128,6 @@ export class Page extends EventEmitter {
_didClose() { _didClose() {
assert(!this._closed, 'Page closed twice'); assert(!this._closed, 'Page closed twice');
this._closed = true; this._closed = true;
this._delegate.didClose();
this.emit(Events.Page.Close); this.emit(Events.Page.Close);
this._closedCallback(); this._closedCallback();
} }

View File

@ -175,7 +175,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
const opener = this._targets.get(targetInfo.openerId); const opener = this._targets.get(targetInfo.openerId);
if (!opener) if (!opener)
return; return;
const openerPage = opener._page; const openerPage = opener._frameManager ? opener._frameManager._page : null;
if (!openerPage || !openerPage.listenerCount(Events.Page.Popup)) if (!openerPage || !openerPage.listenerCount(Events.Page.Popup))
return; return;
target.page().then(page => openerPage.emit(Events.Page.Popup, page)); target.page().then(page => openerPage.emit(Events.Page.Popup, page));

View File

@ -15,10 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import * as EventEmitter from 'events';
import * as frames from '../frames'; import * as frames from '../frames';
import { assert, debugError, helper, RegisteredListener } from '../helper'; import { debugError, helper, RegisteredListener } from '../helper';
import * as js from '../javascript';
import * as dom from '../dom'; import * as dom from '../dom';
import * as network from '../network'; import * as network from '../network';
import { TargetSession } from './Connection'; import { TargetSession } from './Connection';
@ -39,19 +37,18 @@ import { PNG } from 'pngjs';
const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const BINDING_CALL_MESSAGE = '__playwright_binding_call__'; const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
export class FrameManager extends EventEmitter implements PageDelegate { export class FrameManager implements PageDelegate {
readonly rawMouse: RawMouseImpl; readonly rawMouse: RawMouseImpl;
readonly rawKeyboard: RawKeyboardImpl; readonly rawKeyboard: RawKeyboardImpl;
_session: TargetSession; _session: TargetSession;
readonly _page: Page; readonly _page: Page;
private readonly _networkManager: NetworkManager; private readonly _networkManager: NetworkManager;
private readonly _contextIdToContext: Map<number, js.ExecutionContext>; private readonly _contextIdToContext: Map<number, dom.FrameExecutionContext>;
private _isolatedWorlds: Set<string>; private _isolatedWorlds: Set<string>;
private _sessionListeners: RegisteredListener[] = []; private _sessionListeners: RegisteredListener[] = [];
private readonly _bootstrapScripts: string[] = []; private readonly _bootstrapScripts: string[] = [];
constructor(browserContext: BrowserContext) { constructor(browserContext: BrowserContext) {
super();
this.rawKeyboard = new RawKeyboardImpl(); this.rawKeyboard = new RawKeyboardImpl();
this.rawMouse = new RawMouseImpl(); this.rawMouse = new RawMouseImpl();
this._contextIdToContext = new Map(); this._contextIdToContext = new Map();
@ -102,7 +99,9 @@ export class FrameManager extends EventEmitter implements PageDelegate {
didClose() { didClose() {
helper.removeEventListeners(this._sessionListeners); helper.removeEventListeners(this._sessionListeners);
this._networkManager.dispose();
this.disconnectFromTarget(); this.disconnectFromTarget();
this._page._didClose();
} }
_addSessionListeners() { _addSessionListeners() {
@ -124,14 +123,9 @@ export class FrameManager extends EventEmitter implements PageDelegate {
disconnectFromTarget() { disconnectFromTarget() {
for (const context of this._contextIdToContext.values()) { for (const context of this._contextIdToContext.values()) {
(context._delegate as ExecutionContextDelegate)._dispose(); (context._delegate as ExecutionContextDelegate)._dispose();
if (context.frame()) context.frame()._contextDestroyed(context);
context.frame()._contextDestroyed(context as dom.FrameExecutionContext);
} }
// this._mainFrame = null; this._contextIdToContext.clear();
}
networkManager(): NetworkManager {
return this._networkManager;
} }
_onFrameStoppedLoading(frameId: string) { _onFrameStoppedLoading(frameId: string) {
@ -158,12 +152,11 @@ export class FrameManager extends EventEmitter implements PageDelegate {
_onFrameNavigated(framePayload: Protocol.Page.Frame, initial: boolean) { _onFrameNavigated(framePayload: Protocol.Page.Frame, initial: boolean) {
const frame = this._page._frameManager.frame(framePayload.id); const frame = this._page._frameManager.frame(framePayload.id);
for (const context of this._contextIdToContext.values()) { for (const [contextId, context] of this._contextIdToContext) {
if (context.frame() === frame) { if (context.frame() === frame) {
const delegate = context._delegate as ExecutionContextDelegate; (context._delegate as ExecutionContextDelegate)._dispose();
delegate._dispose(); this._contextIdToContext.delete(contextId);
this._contextIdToContext.delete(delegate._contextId); frame._contextDestroyed(context);
frame._contextDestroyed(context as dom.FrameExecutionContext);
} }
} }
// Append session id to avoid cross-process loaderId clash. // Append session id to avoid cross-process loaderId clash.
@ -182,29 +175,16 @@ export class FrameManager extends EventEmitter implements PageDelegate {
_onExecutionContextCreated(contextPayload : Protocol.Runtime.ExecutionContextDescription) { _onExecutionContextCreated(contextPayload : Protocol.Runtime.ExecutionContextDescription) {
if (this._contextIdToContext.has(contextPayload.id)) if (this._contextIdToContext.has(contextPayload.id))
return; return;
const frameId = contextPayload.frameId; const frame = this._page._frameManager.frame(contextPayload.frameId);
// If the frame was attached manually there is no navigation event.
// FIXME: support frameAttached event in WebKit protocol.
const frame = this._page._frameManager.frame(frameId);
if (!frame) if (!frame)
return; return;
const delegate = new ExecutionContextDelegate(this._session, contextPayload); const delegate = new ExecutionContextDelegate(this._session, contextPayload);
if (frame) { const context = new dom.FrameExecutionContext(delegate, frame);
const context = new dom.FrameExecutionContext(delegate, frame); if (contextPayload.isPageContext)
if (contextPayload.isPageContext) frame._contextCreated('main', context);
frame._contextCreated('main', context); else if (contextPayload.name === UTILITY_WORLD_NAME)
else if (contextPayload.name === UTILITY_WORLD_NAME) frame._contextCreated('utility', context);
frame._contextCreated('utility', context); this._contextIdToContext.set(contextPayload.id, context);
this._contextIdToContext.set(contextPayload.id, context);
} else {
this._contextIdToContext.set(contextPayload.id, new js.ExecutionContext(delegate));
}
}
executionContextById(contextId: number): js.ExecutionContext {
const context = this._contextIdToContext.get(contextId);
assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
return context;
} }
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> { async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}): Promise<network.Response | null> {
@ -279,7 +259,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
const mainFrameContext = await this._page.mainFrame().executionContext(); const mainFrameContext = await this._page.mainFrame().executionContext();
const handles = (parameters || []).map(p => { const handles = (parameters || []).map(p => {
let context: js.ExecutionContext | null = null; let context: dom.FrameExecutionContext | null = null;
if (p.objectId) { if (p.objectId) {
const objectId = JSON.parse(p.objectId); const objectId = JSON.parse(p.objectId);
context = this._contextIdToContext.get(objectId.injectedScriptId); context = this._contextIdToContext.get(objectId.injectedScriptId);

View File

@ -53,6 +53,10 @@ export class NetworkManager {
]); ]);
} }
dispose() {
helper.removeEventListeners(this._sessionListeners);
}
async setExtraHTTPHeaders(extraHTTPHeaders: { [s: string]: string; }) { async setExtraHTTPHeaders(extraHTTPHeaders: { [s: string]: string; }) {
this._extraHTTPHeaders = {}; this._extraHTTPHeaders = {};
for (const key of Object.keys(extraHTTPHeaders)) { for (const key of Object.keys(extraHTTPHeaders)) {

View File

@ -30,7 +30,7 @@ export class Target {
readonly _type: 'page' | 'service-worker' | 'worker'; readonly _type: 'page' | 'service-worker' | 'worker';
private readonly _session: TargetSession; private readonly _session: TargetSession;
private _pagePromise: Promise<Page> | null = null; private _pagePromise: Promise<Page> | null = null;
_page: Page | null = null; _frameManager: FrameManager | null = null;
static fromPage(page: Page): Target { static fromPage(page: Page): Target {
return (page as any)[targetSymbol]; return (page as any)[targetSymbol];
@ -47,17 +47,17 @@ export class Target {
} }
_didClose() { _didClose() {
if (this._page) if (this._frameManager)
this._page._didClose(); this._frameManager.didClose();
} }
async _initializeSession(session: TargetSession) { async _initializeSession(session: TargetSession) {
if (!this._page) if (!this._frameManager)
return; return;
await (this._page._delegate as FrameManager)._initializeSession(session).catch(e => { await this._frameManager._initializeSession(session).catch(e => {
// Swallow initialization errors due to newer target swap in, // Swallow initialization errors due to newer target swap in,
// since we will reinitialize again. // since we will reinitialize again.
if (this._page) if (this._frameManager)
throw e; throw e;
}); });
} }
@ -66,32 +66,31 @@ export class Target {
if (!oldTarget._pagePromise) if (!oldTarget._pagePromise)
return; return;
this._pagePromise = oldTarget._pagePromise; this._pagePromise = oldTarget._pagePromise;
this._page = oldTarget._page; this._frameManager = oldTarget._frameManager;
// Swapped out target should not be accessed by anyone. Reset page promise so that // Swapped out target should not be accessed by anyone. Reset page promise so that
// old target does not close the page on connection reset. // old target does not close the page on connection reset.
oldTarget._pagePromise = null; oldTarget._pagePromise = null;
oldTarget._page = null; oldTarget._frameManager = null;
this._adoptPage(); this._adoptPage();
} }
private _adoptPage() { private _adoptPage() {
(this._page as any)[targetSymbol] = this; (this._frameManager._page as any)[targetSymbol] = this;
this._session.once(TargetSessionEvents.Disconnected, () => { this._session.once(TargetSessionEvents.Disconnected, () => {
// Once swapped out, we reset _page and won't call _didDisconnect for old session. // Once swapped out, we reset _page and won't call _didDisconnect for old session.
if (this._page) if (this._frameManager)
this._page._didDisconnect(); this._frameManager._page._didDisconnect();
}); });
(this._page._delegate as FrameManager).setSession(this._session); this._frameManager.setSession(this._session);
} }
async page(): Promise<Page> { async page(): Promise<Page> {
if (this._type === 'page' && !this._pagePromise) { if (this._type === 'page' && !this._pagePromise) {
const browser = this._browserContext.browser() as Browser; const browser = this._browserContext.browser() as Browser;
// Reference local page variable as _page may be this._frameManager = new FrameManager(this._browserContext);
// Reference local page variable as |this._frameManager| may be
// cleared on swap. // cleared on swap.
const frameManager = new FrameManager(this._browserContext); const page = this._frameManager._page;
const page = frameManager._page;
this._page = page;
this._pagePromise = new Promise(async f => { this._pagePromise = new Promise(async f => {
this._adoptPage(); this._adoptPage();
await this._initializeSession(this._session); await this._initializeSession(this._session);