mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore(webkit): always attach to all pages, simplify initialization (#1139)
This commit is contained in:
parent
6b6a671754
commit
c6fde22b1f
@ -79,7 +79,7 @@ export class WebKit implements BrowserType {
|
||||
async launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext> {
|
||||
const { timeout = 30000 } = options || {};
|
||||
const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir);
|
||||
const browser = await WKBrowser.connect(transport!);
|
||||
const browser = await WKBrowser.connect(transport!, undefined, true);
|
||||
await helper.waitWithTimeout(browser._waitForFirstPageTarget(), 'first page', timeout);
|
||||
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
|
||||
const browserContext = browser._defaultContext;
|
||||
|
@ -34,6 +34,7 @@ const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) Appl
|
||||
|
||||
export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||
private readonly _connection: WKConnection;
|
||||
private readonly _attachToDefaultContext: boolean;
|
||||
readonly _browserSession: WKSession;
|
||||
readonly _defaultContext: BrowserContext;
|
||||
readonly _contexts = new Map<string, WKBrowserContext>();
|
||||
@ -43,14 +44,15 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||
private _firstPageProxyCallback?: () => void;
|
||||
private readonly _firstPageProxyPromise: Promise<void>;
|
||||
|
||||
static async connect(transport: ConnectionTransport, slowMo: number = 0): Promise<WKBrowser> {
|
||||
const browser = new WKBrowser(SlowMoTransport.wrap(transport, slowMo));
|
||||
static async connect(transport: ConnectionTransport, slowMo: number = 0, attachToDefaultContext: boolean = false): Promise<WKBrowser> {
|
||||
const browser = new WKBrowser(SlowMoTransport.wrap(transport, slowMo), attachToDefaultContext);
|
||||
return browser;
|
||||
}
|
||||
|
||||
constructor(transport: ConnectionTransport) {
|
||||
constructor(transport: ConnectionTransport, attachToDefaultContext: boolean) {
|
||||
super();
|
||||
this._connection = new WKConnection(transport, this._onDisconnect.bind(this));
|
||||
this._attachToDefaultContext = attachToDefaultContext;
|
||||
this._browserSession = this._connection.browserSession;
|
||||
|
||||
this._defaultContext = new WKBrowserContext(this, undefined, validateBrowserContextOptions({}));
|
||||
@ -108,27 +110,17 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||
// lifecycle events.
|
||||
context = this._contexts.get(pageProxyInfo.browserContextId);
|
||||
}
|
||||
if (!context && !this._attachToDefaultContext)
|
||||
return;
|
||||
if (!context)
|
||||
context = this._defaultContext;
|
||||
const pageProxySession = new WKSession(this._connection, pageProxyId, `The page has been closed.`, (message: any) => {
|
||||
this._connection.rawSend({ ...message, pageProxyId });
|
||||
});
|
||||
const pageProxy = new WKPageProxy(pageProxySession, context, () => {
|
||||
if (!pageProxyInfo.openerId)
|
||||
return null;
|
||||
const opener = this._pageProxies.get(pageProxyInfo.openerId);
|
||||
if (!opener)
|
||||
return null;
|
||||
return opener;
|
||||
});
|
||||
const opener = pageProxyInfo.openerId ? this._pageProxies.get(pageProxyInfo.openerId) : undefined;
|
||||
const pageProxy = new WKPageProxy(pageProxySession, context, opener || null);
|
||||
this._pageProxies.set(pageProxyId, pageProxy);
|
||||
|
||||
if (pageProxyInfo.openerId) {
|
||||
const opener = this._pageProxies.get(pageProxyInfo.openerId);
|
||||
if (opener)
|
||||
opener.onPopupCreated(pageProxy);
|
||||
}
|
||||
|
||||
if (this._firstPageProxyCallback) {
|
||||
this._firstPageProxyCallback();
|
||||
this._firstPageProxyCallback = undefined;
|
||||
@ -137,19 +129,25 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
|
||||
|
||||
_onPageProxyDestroyed(event: Protocol.Browser.pageProxyDestroyedPayload) {
|
||||
const pageProxyId = event.pageProxyId;
|
||||
const pageProxy = this._pageProxies.get(pageProxyId)!;
|
||||
const pageProxy = this._pageProxies.get(pageProxyId);
|
||||
if (!pageProxy)
|
||||
return;
|
||||
pageProxy.didClose();
|
||||
pageProxy.dispose();
|
||||
this._pageProxies.delete(pageProxyId);
|
||||
}
|
||||
|
||||
_onPageProxyMessageReceived(event: PageProxyMessageReceivedPayload) {
|
||||
const pageProxy = this._pageProxies.get(event.pageProxyId)!;
|
||||
const pageProxy = this._pageProxies.get(event.pageProxyId);
|
||||
if (!pageProxy)
|
||||
return;
|
||||
pageProxy.dispatchMessageToSession(event.message);
|
||||
}
|
||||
|
||||
_onProvisionalLoadFailed(event: Protocol.Browser.provisionalLoadFailedPayload) {
|
||||
const pageProxy = this._pageProxies.get(event.pageProxyId)!;
|
||||
const pageProxy = this._pageProxies.get(event.pageProxyId);
|
||||
if (!pageProxy)
|
||||
return;
|
||||
pageProxy.handleProvisionalLoadFailed(event);
|
||||
}
|
||||
|
||||
@ -218,14 +216,16 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
|
||||
|
||||
async pages(): Promise<Page[]> {
|
||||
const pageProxies = Array.from(this._browser._pageProxies.values()).filter(proxy => proxy._browserContext === this);
|
||||
return await Promise.all(pageProxies.map(proxy => proxy.page()));
|
||||
const pages = await Promise.all(pageProxies.map(proxy => proxy.page()));
|
||||
return pages.filter(page => !!page) 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)!;
|
||||
return await pageProxy.page();
|
||||
const page = await pageProxy.page();
|
||||
return page!;
|
||||
}
|
||||
|
||||
async cookies(...urls: string[]): Promise<network.NetworkCookie[]> {
|
||||
|
@ -34,6 +34,7 @@ import * as accessibility from '../accessibility';
|
||||
import * as platform from '../platform';
|
||||
import { getAccessibilityTree } from './wkAccessibility';
|
||||
import { WKProvisionalPage } from './wkProvisionalPage';
|
||||
import { WKPageProxy } from './wkPageProxy';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
||||
@ -45,7 +46,7 @@ export class WKPage implements PageDelegate {
|
||||
private _provisionalPage: WKProvisionalPage | null = null;
|
||||
readonly _page: Page;
|
||||
private readonly _pageProxySession: WKSession;
|
||||
private readonly _openerResolver: () => Promise<Page | null>;
|
||||
private readonly _opener: WKPageProxy | null;
|
||||
private readonly _requestIdToRequest = new Map<string, WKInterceptableRequest>();
|
||||
private readonly _workers: WKWorkers;
|
||||
private readonly _contextIdToContext: Map<number, dom.FrameExecutionContext>;
|
||||
@ -53,9 +54,9 @@ export class WKPage implements PageDelegate {
|
||||
private _sessionListeners: RegisteredListener[] = [];
|
||||
private readonly _bootstrapScripts: string[] = [];
|
||||
|
||||
constructor(browserContext: BrowserContext, pageProxySession: WKSession, openerResolver: () => Promise<Page | null>) {
|
||||
constructor(browserContext: BrowserContext, pageProxySession: WKSession, opener: WKPageProxy | null) {
|
||||
this._pageProxySession = pageProxySession;
|
||||
this._openerResolver = openerResolver;
|
||||
this._opener = opener;
|
||||
this.rawKeyboard = new RawKeyboardImpl(pageProxySession);
|
||||
this.rawMouse = new RawMouseImpl(pageProxySession);
|
||||
this._contextIdToContext = new Map();
|
||||
@ -436,8 +437,9 @@ export class WKPage implements PageDelegate {
|
||||
await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed.
|
||||
}
|
||||
|
||||
async opener() {
|
||||
return await this._openerResolver();
|
||||
async opener(): Promise<Page | null> {
|
||||
const openerPage = this._opener ? await this._opener.page() : null;
|
||||
return openerPage && !openerPage.isClosed() ? openerPage : null;
|
||||
}
|
||||
|
||||
async reload(): Promise<void> {
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { Page } from '../page';
|
||||
import { Protocol } from './protocol';
|
||||
@ -28,30 +27,34 @@ const isPovisionalSymbol = Symbol('isPovisional');
|
||||
export class WKPageProxy {
|
||||
private readonly _pageProxySession: WKSession;
|
||||
readonly _browserContext: BrowserContext;
|
||||
private readonly _openerResolver: () => WKPageProxy | null;
|
||||
private _pagePromise: Promise<Page> | null = null;
|
||||
private _wkPage: WKPage | null = null;
|
||||
private readonly _firstTargetPromise: Promise<void>;
|
||||
private _firstTargetCallback?: () => void;
|
||||
private _pagePausedOnStart: boolean = false;
|
||||
private readonly _opener: WKPageProxy | null;
|
||||
private readonly _pagePromise: Promise<Page | null>;
|
||||
private _pagePromiseFulfill: (page: Page | null) => void = () => {};
|
||||
private _pagePromiseReject: (error: Error) => void = () => {};
|
||||
private readonly _wkPage: WKPage;
|
||||
private _initialized = false;
|
||||
private readonly _sessions = new Map<string, WKSession>();
|
||||
private readonly _eventListeners: RegisteredListener[];
|
||||
|
||||
constructor(pageProxySession: WKSession, browserContext: BrowserContext, openerResolver: () => (WKPageProxy | null)) {
|
||||
constructor(pageProxySession: WKSession, browserContext: BrowserContext, opener: WKPageProxy | null) {
|
||||
this._pageProxySession = pageProxySession;
|
||||
this._browserContext = browserContext;
|
||||
this._openerResolver = openerResolver;
|
||||
this._firstTargetPromise = new Promise(r => this._firstTargetCallback = r);
|
||||
this._opener = opener;
|
||||
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, r) => {
|
||||
this._pagePromiseFulfill = f;
|
||||
this._pagePromiseReject = r;
|
||||
});
|
||||
this._wkPage = new WKPage(this._browserContext, this._pageProxySession, this._opener);
|
||||
}
|
||||
|
||||
didClose() {
|
||||
if (this._wkPage)
|
||||
if (this._initialized)
|
||||
this._wkPage.didClose(false);
|
||||
}
|
||||
|
||||
@ -61,7 +64,6 @@ export class WKPageProxy {
|
||||
for (const session of this._sessions.values())
|
||||
session.dispose();
|
||||
this._sessions.clear();
|
||||
if (this._wkPage)
|
||||
this._wkPage.dispose();
|
||||
}
|
||||
|
||||
@ -78,7 +80,7 @@ export class WKPageProxy {
|
||||
}
|
||||
|
||||
handleProvisionalLoadFailed(event: Protocol.Browser.provisionalLoadFailedPayload) {
|
||||
if (!this._wkPage)
|
||||
if (!this._initialized)
|
||||
return;
|
||||
if (!this._isProvisionalCrossProcessLoadInProgress())
|
||||
return;
|
||||
@ -88,46 +90,15 @@ export class WKPageProxy {
|
||||
this._wkPage._page._frameManager.provisionalLoadFailed(this._wkPage._page.mainFrame(), event.loaderId, errorText);
|
||||
}
|
||||
|
||||
async page(): Promise<Page> {
|
||||
if (!this._pagePromise)
|
||||
this._pagePromise = this._initializeWKPage();
|
||||
async page(): Promise<Page | null> {
|
||||
return this._pagePromise;
|
||||
}
|
||||
|
||||
existingPage(): Page | undefined {
|
||||
return this._wkPage ? this._wkPage._page : undefined;
|
||||
return this._initialized ? this._wkPage._page : undefined;
|
||||
}
|
||||
|
||||
onPopupCreated(popupPageProxy: WKPageProxy) {
|
||||
if (this._wkPage)
|
||||
popupPageProxy.page().then(page => this._wkPage!._page.emit(Events.Page.Popup, page));
|
||||
}
|
||||
|
||||
private async _initializeWKPage(): Promise<Page> {
|
||||
await this._firstTargetPromise;
|
||||
let session: WKSession | undefined;
|
||||
for (const anySession of this._sessions.values()) {
|
||||
if (!(anySession as any)[isPovisionalSymbol]) {
|
||||
session = anySession;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(session, 'One non-provisional target session must exist');
|
||||
this._wkPage = new WKPage(this._browserContext, this._pageProxySession, async () => {
|
||||
const pageProxy = this._openerResolver();
|
||||
if (!pageProxy)
|
||||
return null;
|
||||
return await pageProxy.page();
|
||||
});
|
||||
await this._wkPage.initialize(session);
|
||||
if (this._pagePausedOnStart) {
|
||||
this._resumeTarget(session.sessionId);
|
||||
this._pagePausedOnStart = false;
|
||||
}
|
||||
return this._wkPage._page;
|
||||
}
|
||||
|
||||
private _onTargetCreated(event: Protocol.Target.targetCreatedPayload) {
|
||||
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', {
|
||||
@ -138,26 +109,38 @@ export class WKPageProxy {
|
||||
});
|
||||
assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
|
||||
this._sessions.set(targetInfo.targetId, session);
|
||||
if (this._firstTargetCallback) {
|
||||
this._firstTargetCallback();
|
||||
this._firstTargetCallback = undefined;
|
||||
|
||||
if (!this._initialized) {
|
||||
assert(!targetInfo.isProvisional);
|
||||
this._initialized = true;
|
||||
let page: Page | null = null;
|
||||
let error: Error | undefined;
|
||||
try {
|
||||
await this._wkPage.initialize(session);
|
||||
page = this._wkPage._page;
|
||||
} catch (e) {
|
||||
if (!this._pageProxySession.isDisposed())
|
||||
error = e;
|
||||
}
|
||||
if (targetInfo.isProvisional) {
|
||||
if (error)
|
||||
this._pagePromiseReject(error);
|
||||
else
|
||||
this._pagePromiseFulfill(page);
|
||||
if (targetInfo.isPaused)
|
||||
this._resumeTarget(targetInfo.targetId);
|
||||
if (page && this._opener) {
|
||||
this._opener.page().then(openerPage => {
|
||||
if (!openerPage || page!.isClosed())
|
||||
return;
|
||||
openerPage.emit(Events.Page.Popup, page);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
assert(targetInfo.isProvisional);
|
||||
(session as any)[isPovisionalSymbol] = true;
|
||||
if (this._wkPage) {
|
||||
const provisionalPageInitialized = this._wkPage.initializeProvisionalPage(session);
|
||||
if (targetInfo.isPaused)
|
||||
provisionalPageInitialized.then(() => this._resumeTarget(targetInfo.targetId));
|
||||
} else if (targetInfo.isPaused) {
|
||||
this._resumeTarget(targetInfo.targetId);
|
||||
}
|
||||
} else if (this._pagePromise) {
|
||||
assert(!this._pagePausedOnStart);
|
||||
// This is the first time page target is created, will resume
|
||||
// after finishing intialization.
|
||||
this._pagePausedOnStart = !!targetInfo.isPaused;
|
||||
} else if (targetInfo.isPaused) {
|
||||
this._resumeTarget(targetInfo.targetId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +154,6 @@ export class WKPageProxy {
|
||||
assert(session, 'Unknown target destroyed: ' + targetId);
|
||||
session.dispose();
|
||||
this._sessions.delete(targetId);
|
||||
if (this._wkPage)
|
||||
this._wkPage.onSessionDestroyed(session, crashed);
|
||||
}
|
||||
|
||||
@ -191,7 +173,6 @@ export class WKPageProxy {
|
||||
// TODO: make some calls like screenshot catch swapped out error and retry.
|
||||
oldSession.errorText = 'Target was swapped out.';
|
||||
(newSession as any)[isPovisionalSymbol] = undefined;
|
||||
if (this._wkPage)
|
||||
this._wkPage.onProvisionalLoadCommitted(newSession);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user