Skip to content

Commit

Permalink
chore: remove WKPageProxy, use WKPage instead (#1256)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgozman authored Mar 6, 2020
1 parent 677ebf8 commit d114620
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 218 deletions.
69 changes: 34 additions & 35 deletions src/webkit/wkBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import * as types from '../types';
import { Protocol } from './protocol';
import { kPageProxyMessageReceived, PageProxyMessageReceivedPayload, WKConnection, WKSession } from './wkConnection';
import { WKPage } from './wkPage';
import { WKPageProxy } from './wkPageProxy';

const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15';

Expand All @@ -37,11 +36,11 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
readonly _browserSession: WKSession;
readonly _defaultContext: WKBrowserContext;
readonly _contexts = new Map<string, WKBrowserContext>();
readonly _pageProxies = new Map<string, WKPageProxy>();
readonly _wkPages = new Map<string, WKPage>();
private readonly _eventListeners: RegisteredListener[];

private _firstPageProxyCallback?: () => void;
private readonly _firstPageProxyPromise: Promise<void>;
private _firstPageCallback?: () => void;
private readonly _firstPagePromise: Promise<void>;

static async connect(transport: ConnectionTransport, slowMo: number = 0, attachToDefaultContext: boolean = false): Promise<WKBrowser> {
const browser = new WKBrowser(SlowMoTransport.wrap(transport, slowMo), attachToDefaultContext);
Expand All @@ -63,15 +62,15 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
helper.addEventListener(this._browserSession, kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)),
];

this._firstPageProxyPromise = new Promise<void>(resolve => this._firstPageProxyCallback = resolve);
this._firstPagePromise = new Promise<void>(resolve => this._firstPageCallback = resolve);
}

_onDisconnect() {
for (const context of this._contexts.values())
context._browserClosed();
for (const pageProxy of this._pageProxies.values())
pageProxy.dispose();
this._pageProxies.clear();
for (const wkPage of this._wkPages.values())
wkPage.dispose();
this._wkPages.clear();
this.emit(Events.Browser.Disconnected);
}

Expand All @@ -94,8 +93,8 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
}

async _waitForFirstPageTarget(): Promise<void> {
assert(!this._pageProxies.size);
return this._firstPageProxyPromise;
assert(!this._wkPages.size);
return this._firstPagePromise;
}

_onPageProxyCreated(event: Protocol.Browser.pageProxyCreatedPayload) {
Expand All @@ -116,16 +115,16 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
const pageProxySession = new WKSession(this._connection, pageProxyId, `The page has been closed.`, (message: any) => {
this._connection.rawSend({ ...message, pageProxyId });
});
const opener = pageProxyInfo.openerId ? this._pageProxies.get(pageProxyInfo.openerId) : undefined;
const pageProxy = new WKPageProxy(pageProxySession, context, opener || null);
this._pageProxies.set(pageProxyId, pageProxy);
const opener = pageProxyInfo.openerId ? this._wkPages.get(pageProxyInfo.openerId) : undefined;
const wkPage = new WKPage(context, pageProxySession, opener || null);
this._wkPages.set(pageProxyId, wkPage);

if (this._firstPageProxyCallback) {
this._firstPageProxyCallback();
this._firstPageProxyCallback = undefined;
if (this._firstPageCallback) {
this._firstPageCallback();
this._firstPageCallback = undefined;
}

const pageEvent = new PageEvent(pageProxy.pageOrError());
const pageEvent = new PageEvent(wkPage.pageOrError());
context.emit(Events.BrowserContext.Page, pageEvent);
if (!opener)
return;
Expand All @@ -137,26 +136,26 @@ export class WKBrowser extends platform.EventEmitter implements Browser {

_onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) {
const pageProxyId = event.pageProxyId;
const pageProxy = this._pageProxies.get(pageProxyId);
if (!pageProxy)
const wkPage = this._wkPages.get(pageProxyId);
if (!wkPage)
return;
pageProxy.didClose();
pageProxy.dispose();
this._pageProxies.delete(pageProxyId);
wkPage.didClose(false);
wkPage.dispose();
this._wkPages.delete(pageProxyId);
}

_onPageProxyMessageReceived(event: PageProxyMessageReceivedPayload) {
const pageProxy = this._pageProxies.get(event.pageProxyId);
if (!pageProxy)
const wkPage = this._wkPages.get(event.pageProxyId);
if (!wkPage)
return;
pageProxy.dispatchMessageToSession(event.message);
wkPage.dispatchMessageToSession(event.message);
}

_onProvisionalLoadFailed(event: Protocol.Browser.provisionalLoadFailedPayload) {
const pageProxy = this._pageProxies.get(event.pageProxyId);
if (!pageProxy)
const wkPage = this._wkPages.get(event.pageProxyId);
if (!wkPage)
return;
pageProxy.handleProvisionalLoadFailed(event);
wkPage.handleProvisionalLoadFailed(event);
}

isConnected(): boolean {
Expand Down Expand Up @@ -203,27 +202,27 @@ export class WKBrowserContext extends BrowserContextBase {

_existingPages(): Page[] {
const pages: Page[] = [];
for (const pageProxy of this._browser._pageProxies.values()) {
if (pageProxy._browserContext !== this)
for (const wkPage of this._browser._wkPages.values()) {
if (wkPage._browserContext !== this)
continue;
const page = pageProxy.existingPage();
const page = wkPage._initializedPage();
if (page)
pages.push(page);
}
return pages;
}

async pages(): Promise<Page[]> {
const pageProxies = Array.from(this._browser._pageProxies.values()).filter(proxy => proxy._browserContext === this);
const pages = await Promise.all(pageProxies.map(proxy => proxy.pageOrError()));
const wkPages = Array.from(this._browser._wkPages.values()).filter(wkPage => wkPage._browserContext === this);
const pages = await Promise.all(wkPages.map(wkPage => wkPage.pageOrError()));
return pages.filter(page => page instanceof Page && !page.isClosed()) as Page[];
}

async newPage(): Promise<Page> {
assertBrowserContextIsNotOwned(this);
const { pageProxyId } = await this._browser._browserSession.send('Browser.createPage', { browserContextId: this._browserContextId });
const pageProxy = this._browser._pageProxies.get(pageProxyId)!;
const result = await pageProxy.pageOrError();
const wkPage = this._browser._wkPages.get(pageProxyId)!;
const result = await wkPage.pageOrError();
if (result instanceof Page) {
if (result.isClosed())
throw new Error('Page has been closed.');
Expand Down
131 changes: 109 additions & 22 deletions src/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,36 @@ import * as accessibility from '../accessibility';
import * as platform from '../platform';
import { getAccessibilityTree } from './wkAccessibility';
import { WKProvisionalPage } from './wkProvisionalPage';
import { WKPageProxy } from './wkPageProxy';
import { WKBrowserContext } from './wkBrowser';

const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
const isPovisionalSymbol = Symbol('isPovisional');

export class WKPage implements PageDelegate {
readonly rawMouse: RawMouseImpl;
readonly rawKeyboard: RawKeyboardImpl;
_session: WKSession;
private _provisionalPage: WKProvisionalPage | null = null;
readonly _page: Page;
private readonly _pagePromise: Promise<Page | Error>;
private _pagePromiseCallback: (page: Page | Error) => void = () => {};
private readonly _pageProxySession: WKSession;
private readonly _opener: WKPageProxy | null;
private readonly _opener: WKPage | null;
private readonly _requestIdToRequest = new Map<string, WKInterceptableRequest>();
private readonly _workers: WKWorkers;
private readonly _contextIdToContext: Map<number, dom.FrameExecutionContext>;
private _mainFrameContextId?: number;
private _sessionListeners: RegisteredListener[] = [];
private _eventListeners: RegisteredListener[];
private readonly _evaluateOnNewDocumentSources: string[] = [];
private readonly _browserContext: WKBrowserContext;
readonly _browserContext: WKBrowserContext;
private _initialized = false;

constructor(browserContext: WKBrowserContext, pageProxySession: WKSession, opener: WKPageProxy | null) {
// TODO: we should be able to just use |this._session| and |this._provisionalPage|.
private readonly _sessions = new Map<string, WKSession>();

constructor(browserContext: WKBrowserContext, pageProxySession: WKSession, opener: WKPage | null) {
this._pageProxySession = pageProxySession;
this._opener = opener;
this.rawKeyboard = new RawKeyboardImpl(pageProxySession);
Expand All @@ -66,6 +73,17 @@ export class WKPage implements PageDelegate {
this._session = undefined as any as WKSession;
this._browserContext = browserContext;
this._page.on(Events.Page.FrameDetached, frame => this._removeContextsForFrame(frame, false));
this._eventListeners = [
helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),
helper.addEventListener(this._pageProxySession, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
helper.addEventListener(this._pageProxySession, 'Target.dispatchMessageFromTarget', this._onDispatchMessageFromTarget.bind(this)),
helper.addEventListener(this._pageProxySession, 'Target.didCommitProvisionalTarget', this._onDidCommitProvisionalTarget.bind(this)),
];
this._pagePromise = new Promise(f => this._pagePromiseCallback = f);
}

_initializedPage(): Page | undefined {
return this._initialized ? this._page : undefined;
}

private async _initializePageProxySession() {
Expand All @@ -90,14 +108,6 @@ export class WKPage implements PageDelegate {
this._workers.setSession(session);
}

async initialize(session: WKSession) {
this._setSession(session);
await Promise.all([
this._initializePageProxySession(),
this._initializeSession(this._session, ({frameTree}) => this._handleFrameTree(frameTree)),
]);
}

// This method is called for provisional targets as well. The session passed as the parameter
// may be different from the current session and may be destroyed without becoming current.
async _initializeSession(session: WKSession, resourceTreeHandler: (r: Protocol.Page.getResourceTreeReturnValue) => void) {
Expand Down Expand Up @@ -152,22 +162,28 @@ export class WKPage implements PageDelegate {
await Promise.all(promises);
}

initializeProvisionalPage(provisionalSession: WKSession): Promise<void> {
assert(!this._provisionalPage);
this._provisionalPage = new WKProvisionalPage(provisionalSession, this);
return this._provisionalPage.initializationPromise;
}

onProvisionalLoadCommitted(session: WKSession) {
private _onDidCommitProvisionalTarget(event: Protocol.Target.didCommitProvisionalTargetPayload) {
const { oldTargetId, newTargetId } = event;
const newSession = this._sessions.get(newTargetId);
assert(newSession, 'Unknown new target: ' + newTargetId);
const oldSession = this._sessions.get(oldTargetId);
assert(oldSession, 'Unknown old target: ' + oldTargetId);
oldSession.errorText = 'Target was swapped out.';
(newSession as any)[isPovisionalSymbol] = undefined;
assert(this._provisionalPage);
assert(this._provisionalPage._session === session);
assert(this._provisionalPage._session === newSession);
this._provisionalPage.commit();
this._provisionalPage.dispose();
this._provisionalPage = null;
this._setSession(session);
this._setSession(newSession);
}

onSessionDestroyed(session: WKSession, crashed: boolean) {
private _onTargetDestroyed(event: Protocol.Target.targetDestroyedPayload) {
const { targetId, crashed } = event;
const session = this._sessions.get(targetId);
assert(session, 'Unknown target destroyed: ' + targetId);
session.dispose();
this._sessions.delete(targetId);
if (this._provisionalPage && this._provisionalPage._session === session) {
this._provisionalPage.dispose();
this._provisionalPage = null;
Expand All @@ -186,13 +202,84 @@ export class WKPage implements PageDelegate {
}

dispose() {
this._pageProxySession.dispose();
helper.removeEventListeners(this._eventListeners);
for (const session of this._sessions.values())
session.dispose();
this._sessions.clear();
if (this._provisionalPage) {
this._provisionalPage.dispose();
this._provisionalPage = null;
}
this._page._didDisconnect();
}

dispatchMessageToSession(message: any) {
this._pageProxySession.dispatchMessage(message);
}

handleProvisionalLoadFailed(event: Protocol.Browser.provisionalLoadFailedPayload) {
if (!this._initialized || !this._provisionalPage)
return;
let errorText = event.error;
if (errorText.includes('cancelled'))
errorText += '; maybe frame was detached?';
this._page._frameManager.provisionalLoadFailed(this._page.mainFrame(), event.loaderId, errorText);
}

async pageOrError(): Promise<Page | Error> {
return this._pagePromise;
}

private async _onTargetCreated(event: Protocol.Target.targetCreatedPayload) {
const { targetInfo } = event;
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, `The ${targetInfo.type} has been closed.`, (message: any) => {
this._pageProxySession.send('Target.sendMessageToTarget', {
message: JSON.stringify(message), targetId: targetInfo.targetId
}).catch(e => {
session.dispatchMessage({ id: message.id, error: { message: e.message } });
});
});
assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
this._sessions.set(targetInfo.targetId, session);

if (!this._initialized) {
assert(!targetInfo.isProvisional);
let pageOrError: Page | Error;
try {
this._setSession(session);
await Promise.all([
this._initializePageProxySession(),
this._initializeSession(session, ({frameTree}) => this._handleFrameTree(frameTree)),
]);
pageOrError = this._page;
} catch (e) {
pageOrError = e;
}
if (targetInfo.isPaused)
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(debugError);
this._initialized = true;
this._pagePromiseCallback(pageOrError);
} else {
assert(targetInfo.isProvisional);
(session as any)[isPovisionalSymbol] = true;
assert(!this._provisionalPage);
this._provisionalPage = new WKProvisionalPage(session, this);
if (targetInfo.isPaused) {
this._provisionalPage.initializationPromise.then(() => {
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(debugError);
});
}
}
}

private _onDispatchMessageFromTarget(event: Protocol.Target.dispatchMessageFromTargetPayload) {
const { targetId, message } = event;
const session = this._sessions.get(targetId);
assert(session, 'Unknown target: ' + targetId);
session.dispatchMessage(JSON.parse(message));
}

private _addSessionListeners() {
this._sessionListeners = [
helper.addEventListener(this._session, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
Expand Down
Loading

0 comments on commit d114620

Please sign in to comment.