Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: remove WKPageProxy, use WKPage instead #1256

Merged
merged 1 commit into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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