chore: simplify page initialization logic across browser types (#34002)
This commit is contained in:
parent
1e4239f48d
commit
f713d3adaf
|
|
@ -22,7 +22,7 @@ import { Browser } from '../browser';
|
||||||
import { assertBrowserContextIsNotOwned, BrowserContext } from '../browserContext';
|
import { assertBrowserContextIsNotOwned, BrowserContext } from '../browserContext';
|
||||||
import type { SdkObject } from '../instrumentation';
|
import type { SdkObject } from '../instrumentation';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import type { InitScript, Page, PageDelegate } from '../page';
|
import type { InitScript, Page } from '../page';
|
||||||
import type { ConnectionTransport } from '../transport';
|
import type { ConnectionTransport } from '../transport';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
import type { BidiSession } from './bidiConnection';
|
import type { BidiSession } from './bidiConnection';
|
||||||
|
|
@ -99,8 +99,8 @@ export class BidiBrowser extends Browser {
|
||||||
browser._defaultContext = new BidiBrowserContext(browser, undefined, options.persistent);
|
browser._defaultContext = new BidiBrowserContext(browser, undefined, options.persistent);
|
||||||
await (browser._defaultContext as BidiBrowserContext)._initialize();
|
await (browser._defaultContext as BidiBrowserContext)._initialize();
|
||||||
// Create default page as we cannot get access to the existing one.
|
// Create default page as we cannot get access to the existing one.
|
||||||
const pageDelegate = await browser._defaultContext.newPageDelegate();
|
const page = await browser._defaultContext.doCreateNewPage();
|
||||||
await pageDelegate.pageOrError();
|
await page.waitForInitializedOrError();
|
||||||
}
|
}
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
@ -207,21 +207,17 @@ export class BidiBrowserContext extends BrowserContext {
|
||||||
return [...this._browser._bidiPages.values()].filter(bidiPage => bidiPage._browserContext === this);
|
return [...this._browser._bidiPages.values()].filter(bidiPage => bidiPage._browserContext === this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pages(): Page[] {
|
override possiblyUninitializedPages(): Page[] {
|
||||||
return this._bidiPages().map(bidiPage => bidiPage._initializedPage).filter(Boolean) as Page[];
|
return this._bidiPages().map(bidiPage => bidiPage._page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pagesOrErrors() {
|
override async doCreateNewPage(): Promise<Page> {
|
||||||
return this._bidiPages().map(bidiPage => bidiPage.pageOrError());
|
|
||||||
}
|
|
||||||
|
|
||||||
async newPageDelegate(): Promise<PageDelegate> {
|
|
||||||
assertBrowserContextIsNotOwned(this);
|
assertBrowserContextIsNotOwned(this);
|
||||||
const { context } = await this._browser._browserSession.send('browsingContext.create', {
|
const { context } = await this._browser._browserSession.send('browsingContext.create', {
|
||||||
type: bidi.BrowsingContext.CreateType.Window,
|
type: bidi.BrowsingContext.CreateType.Window,
|
||||||
userContext: this._browserContextId,
|
userContext: this._browserContextId,
|
||||||
});
|
});
|
||||||
return this._browser._bidiPages.get(context)!;
|
return this._browser._bidiPages.get(context)!._page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ export class BidiPage implements PageDelegate {
|
||||||
readonly rawKeyboard: RawKeyboardImpl;
|
readonly rawKeyboard: RawKeyboardImpl;
|
||||||
readonly rawTouchscreen: RawTouchscreenImpl;
|
readonly rawTouchscreen: RawTouchscreenImpl;
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
private readonly _pagePromise: Promise<Page | Error>;
|
|
||||||
readonly _session: BidiSession;
|
readonly _session: BidiSession;
|
||||||
readonly _opener: BidiPage | null;
|
readonly _opener: BidiPage | null;
|
||||||
private readonly _realmToContext: Map<string, dom.FrameExecutionContext>;
|
private readonly _realmToContext: Map<string, dom.FrameExecutionContext>;
|
||||||
|
|
@ -51,7 +50,6 @@ export class BidiPage implements PageDelegate {
|
||||||
readonly _browserContext: BidiBrowserContext;
|
readonly _browserContext: BidiBrowserContext;
|
||||||
readonly _networkManager: BidiNetworkManager;
|
readonly _networkManager: BidiNetworkManager;
|
||||||
private readonly _pdf: BidiPDF;
|
private readonly _pdf: BidiPDF;
|
||||||
_initializedPage: Page | null = null;
|
|
||||||
private _initScriptIds: string[] = [];
|
private _initScriptIds: string[] = [];
|
||||||
|
|
||||||
constructor(browserContext: BidiBrowserContext, bidiSession: BidiSession, opener: BidiPage | null) {
|
constructor(browserContext: BidiBrowserContext, bidiSession: BidiSession, opener: BidiPage | null) {
|
||||||
|
|
@ -81,16 +79,10 @@ export class BidiPage implements PageDelegate {
|
||||||
];
|
];
|
||||||
|
|
||||||
// Initialize main frame.
|
// Initialize main frame.
|
||||||
this._pagePromise = this._initialize().finally(async () => {
|
// TODO: Wait for first execution context to be created and maybe about:blank navigated.
|
||||||
await this._page.initOpener(this._opener);
|
this._initialize().then(
|
||||||
}).then(() => {
|
() => this._page.reportAsNew(this._opener?._page),
|
||||||
this._initializedPage = this._page;
|
error => this._page.reportAsNew(this._opener?._page, error));
|
||||||
this._page.reportAsNew();
|
|
||||||
return this._page;
|
|
||||||
}).catch(e => {
|
|
||||||
this._page.reportAsNew(e);
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _initialize() {
|
private async _initialize() {
|
||||||
|
|
@ -109,21 +101,12 @@ export class BidiPage implements PageDelegate {
|
||||||
return Promise.all(this._page.allInitScripts().map(initScript => this.addInitScript(initScript)));
|
return Promise.all(this._page.allInitScripts().map(initScript => this.addInitScript(initScript)));
|
||||||
}
|
}
|
||||||
|
|
||||||
potentiallyUninitializedPage(): Page {
|
|
||||||
return this._page;
|
|
||||||
}
|
|
||||||
|
|
||||||
didClose() {
|
didClose() {
|
||||||
this._session.dispose();
|
this._session.dispose();
|
||||||
eventsHelper.removeEventListeners(this._sessionListeners);
|
eventsHelper.removeEventListeners(this._sessionListeners);
|
||||||
this._page._didClose();
|
this._page._didClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async pageOrError(): Promise<Page | Error> {
|
|
||||||
// TODO: Wait for first execution context to be created and maybe about:blank navigated.
|
|
||||||
return this._pagePromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onFrameAttached(frameId: string, parentFrameId: string | null): frames.Frame {
|
private _onFrameAttached(frameId: string, parentFrameId: string | null): frames.Frame {
|
||||||
return this._page._frameManager.frameAttached(frameId, parentFrameId);
|
return this._page._frameManager.frameAttached(frameId, parentFrameId);
|
||||||
}
|
}
|
||||||
|
|
@ -372,7 +355,7 @@ export class BidiPage implements PageDelegate {
|
||||||
private async _onScriptMessage(event: bidi.Script.MessageParameters) {
|
private async _onScriptMessage(event: bidi.Script.MessageParameters) {
|
||||||
if (event.channel !== kPlaywrightBindingChannel)
|
if (event.channel !== kPlaywrightBindingChannel)
|
||||||
return;
|
return;
|
||||||
const pageOrError = await this.pageOrError();
|
const pageOrError = await this._page.waitForInitializedOrError();
|
||||||
if (pageOrError instanceof Error)
|
if (pageOrError instanceof Error)
|
||||||
return;
|
return;
|
||||||
const context = this._realmToContext.get(event.source.realm);
|
const context = this._realmToContext.get(event.source.realm);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import type * as frames from './frames';
|
||||||
import { helper } from './helper';
|
import { helper } from './helper';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
import { InitScript } from './page';
|
import { InitScript } from './page';
|
||||||
import type { PageDelegate } from './page';
|
|
||||||
import { Page, PageBinding } from './page';
|
import { Page, PageBinding } from './page';
|
||||||
import type { Progress, ProgressController } from './progress';
|
import type { Progress, ProgressController } from './progress';
|
||||||
import type { Selectors } from './selectors';
|
import type { Selectors } from './selectors';
|
||||||
|
|
@ -257,10 +256,13 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
this.emit(BrowserContext.Events.Close);
|
this.emit(BrowserContext.Events.Close);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pages(): Page[] {
|
||||||
|
return this.possiblyUninitializedPages().filter(page => page.initializedOrUndefined());
|
||||||
|
}
|
||||||
|
|
||||||
// BrowserContext methods.
|
// BrowserContext methods.
|
||||||
abstract pages(): Page[];
|
abstract possiblyUninitializedPages(): Page[];
|
||||||
abstract pagesOrErrors(): Promise<Page | Error>[];
|
abstract doCreateNewPage(): Promise<Page>;
|
||||||
abstract newPageDelegate(): Promise<PageDelegate>;
|
|
||||||
abstract addCookies(cookies: channels.SetNetworkCookie[]): Promise<void>;
|
abstract addCookies(cookies: channels.SetNetworkCookie[]): Promise<void>;
|
||||||
abstract setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
abstract setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
||||||
abstract setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void>;
|
abstract setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void>;
|
||||||
|
|
@ -359,38 +361,34 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadDefaultContextAsIs(progress: Progress): Promise<Page> {
|
async _loadDefaultContextAsIs(progress: Progress): Promise<Page | undefined> {
|
||||||
let pageOrError;
|
if (!this.possiblyUninitializedPages().length) {
|
||||||
if (!this.pagesOrErrors().length) {
|
|
||||||
const waitForEvent = helper.waitForEvent(progress, this, BrowserContext.Events.Page);
|
const waitForEvent = helper.waitForEvent(progress, this, BrowserContext.Events.Page);
|
||||||
progress.cleanupWhenAborted(() => waitForEvent.dispose);
|
progress.cleanupWhenAborted(() => waitForEvent.dispose);
|
||||||
// Race against BrowserContext.close
|
// Race against BrowserContext.close
|
||||||
pageOrError = await Promise.race([
|
await Promise.race([waitForEvent.promise, this._closePromise]);
|
||||||
waitForEvent.promise as Promise<Page>,
|
|
||||||
this._closePromise,
|
|
||||||
]);
|
|
||||||
// Consider Page initialization errors
|
|
||||||
if (pageOrError instanceof Page)
|
|
||||||
pageOrError = await pageOrError._delegate.pageOrError();
|
|
||||||
} else {
|
|
||||||
pageOrError = await this.pagesOrErrors()[0];
|
|
||||||
}
|
}
|
||||||
|
const page = this.possiblyUninitializedPages()[0];
|
||||||
|
if (!page)
|
||||||
|
return;
|
||||||
|
const pageOrError = await page.waitForInitializedOrError();
|
||||||
if (pageOrError instanceof Error)
|
if (pageOrError instanceof Error)
|
||||||
throw pageOrError;
|
throw pageOrError;
|
||||||
await pageOrError.mainFrame()._waitForLoadState(progress, 'load');
|
await page.mainFrame()._waitForLoadState(progress, 'load');
|
||||||
return pageOrError;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadDefaultContext(progress: Progress) {
|
async _loadDefaultContext(progress: Progress) {
|
||||||
const defaultPage = await this._loadDefaultContextAsIs(progress);
|
const defaultPage = await this._loadDefaultContextAsIs(progress);
|
||||||
|
if (!defaultPage)
|
||||||
|
return;
|
||||||
const browserName = this._browser.options.name;
|
const browserName = this._browser.options.name;
|
||||||
if ((this._options.isMobile && browserName === 'chromium') || (this._options.locale && browserName === 'webkit')) {
|
if ((this._options.isMobile && browserName === 'chromium') || (this._options.locale && browserName === 'webkit')) {
|
||||||
// Workaround for:
|
// Workaround for:
|
||||||
// - chromium fails to change isMobile for existing page;
|
// - chromium fails to change isMobile for existing page;
|
||||||
// - webkit fails to change locale for existing page.
|
// - webkit fails to change locale for existing page.
|
||||||
const oldPage = defaultPage;
|
|
||||||
await this.newPage(progress.metadata);
|
await this.newPage(progress.metadata);
|
||||||
await oldPage.close(progress.metadata);
|
await defaultPage.close(progress.metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,10 +486,10 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
async newPage(metadata: CallMetadata): Promise<Page> {
|
async newPage(metadata: CallMetadata): Promise<Page> {
|
||||||
const pageDelegate = await this.newPageDelegate();
|
const page = await this.doCreateNewPage();
|
||||||
if (metadata.isServerSide)
|
if (metadata.isServerSide)
|
||||||
pageDelegate.potentiallyUninitializedPage().markAsServerSideOnly();
|
page.markAsServerSideOnly();
|
||||||
const pageOrError = await pageDelegate.pageOrError();
|
const pageOrError = await page.waitForInitializedOrError();
|
||||||
if (pageOrError instanceof Page) {
|
if (pageOrError instanceof Page) {
|
||||||
if (pageOrError.isClosed())
|
if (pageOrError.isClosed())
|
||||||
throw new Error('Page has been closed.');
|
throw new Error('Page has been closed.');
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { Browser } from '../browser';
|
||||||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||||
import { assert, createGuid } from '../../utils';
|
import { assert, createGuid } from '../../utils';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import type { InitScript, PageDelegate, Worker } from '../page';
|
import type { InitScript, Worker } from '../page';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
import { Frame } from '../frames';
|
import { Frame } from '../frames';
|
||||||
import type { Dialog } from '../dialog';
|
import type { Dialog } from '../dialog';
|
||||||
|
|
@ -146,7 +146,7 @@ export class CRBrowser extends Browser {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _waitForAllPagesToBeInitialized() {
|
async _waitForAllPagesToBeInitialized() {
|
||||||
await Promise.all([...this._crPages.values()].map(page => page.pageOrError()));
|
await Promise.all([...this._crPages.values()].map(crPage => crPage._page.waitForInitializedOrError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }: Protocol.Target.attachedToTargetPayload) {
|
_onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }: Protocol.Target.attachedToTargetPayload) {
|
||||||
|
|
@ -259,10 +259,10 @@ export class CRBrowser extends Browser {
|
||||||
}
|
}
|
||||||
page.willBeginDownload();
|
page.willBeginDownload();
|
||||||
|
|
||||||
let originPage = page._initializedPage;
|
let originPage = page._page.initializedOrUndefined();
|
||||||
// If it's a new window download, report it on the opener page.
|
// If it's a new window download, report it on the opener page.
|
||||||
if (!originPage && page._opener)
|
if (!originPage && page._opener)
|
||||||
originPage = page._opener._initializedPage;
|
originPage = page._opener._page.initializedOrUndefined();
|
||||||
if (!originPage)
|
if (!originPage)
|
||||||
return;
|
return;
|
||||||
this._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
|
this._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
|
||||||
|
|
@ -364,15 +364,11 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
return [...this._browser._crPages.values()].filter(crPage => crPage._browserContext === this);
|
return [...this._browser._crPages.values()].filter(crPage => crPage._browserContext === this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pages(): Page[] {
|
override possiblyUninitializedPages(): Page[] {
|
||||||
return this._crPages().map(crPage => crPage._initializedPage).filter(Boolean) as Page[];
|
return this._crPages().map(crPage => crPage._page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pagesOrErrors() {
|
override async doCreateNewPage(): Promise<Page> {
|
||||||
return this._crPages().map(crPage => crPage.pageOrError());
|
|
||||||
}
|
|
||||||
|
|
||||||
async newPageDelegate(): Promise<PageDelegate> {
|
|
||||||
assertBrowserContextIsNotOwned(this);
|
assertBrowserContextIsNotOwned(this);
|
||||||
|
|
||||||
const oldKeys = this._browser.isClank() ? new Set(this._browser._crPages.keys()) : undefined;
|
const oldKeys = this._browser.isClank() ? new Set(this._browser._crPages.keys()) : undefined;
|
||||||
|
|
@ -395,7 +391,7 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
assert(newKeys.size === 1);
|
assert(newKeys.size === 1);
|
||||||
[targetId] = [...newKeys];
|
[targetId] = [...newKeys];
|
||||||
}
|
}
|
||||||
return this._browser._crPages.get(targetId)!;
|
return this._browser._crPages.get(targetId)!._page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
||||||
|
|
@ -548,7 +544,7 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
// When persistent context is closed, we do not necessary get Target.detachedFromTarget
|
// When persistent context is closed, we do not necessary get Target.detachedFromTarget
|
||||||
// for all the background pages.
|
// for all the background pages.
|
||||||
for (const [targetId, backgroundPage] of this._browser._backgroundPages.entries()) {
|
for (const [targetId, backgroundPage] of this._browser._backgroundPages.entries()) {
|
||||||
if (backgroundPage._browserContext === this && backgroundPage._initializedPage) {
|
if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined()) {
|
||||||
backgroundPage.didClose();
|
backgroundPage.didClose();
|
||||||
this._browser._backgroundPages.delete(targetId);
|
this._browser._backgroundPages.delete(targetId);
|
||||||
}
|
}
|
||||||
|
|
@ -573,8 +569,8 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
backgroundPages(): Page[] {
|
backgroundPages(): Page[] {
|
||||||
const result: Page[] = [];
|
const result: Page[] = [];
|
||||||
for (const backgroundPage of this._browser._backgroundPages.values()) {
|
for (const backgroundPage of this._browser._backgroundPages.values()) {
|
||||||
if (backgroundPage._browserContext === this && backgroundPage._initializedPage)
|
if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined())
|
||||||
result.push(backgroundPage._initializedPage);
|
result.push(backgroundPage._page);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,6 @@ export class CRPage implements PageDelegate {
|
||||||
private readonly _pdf: CRPDF;
|
private readonly _pdf: CRPDF;
|
||||||
private readonly _coverage: CRCoverage;
|
private readonly _coverage: CRCoverage;
|
||||||
readonly _browserContext: CRBrowserContext;
|
readonly _browserContext: CRBrowserContext;
|
||||||
private readonly _pagePromise: Promise<Page | Error>;
|
|
||||||
_initializedPage: Page | null = null;
|
|
||||||
private _isBackgroundPage: boolean;
|
private _isBackgroundPage: boolean;
|
||||||
|
|
||||||
// Holds window features for the next popup being opened via window.open,
|
// Holds window features for the next popup being opened via window.open,
|
||||||
|
|
@ -108,30 +106,11 @@ export class CRPage implements PageDelegate {
|
||||||
if (viewportSize)
|
if (viewportSize)
|
||||||
this._page._emulatedSize = { viewport: viewportSize, screen: viewportSize };
|
this._page._emulatedSize = { viewport: viewportSize, screen: viewportSize };
|
||||||
}
|
}
|
||||||
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
|
|
||||||
// so that anyone who awaits pageOrError got a ready and reported page.
|
|
||||||
this._pagePromise = this._mainFrameSession._initialize(bits.hasUIWindow).then(async r => {
|
|
||||||
await this._page.initOpener(this._opener);
|
|
||||||
return r;
|
|
||||||
}).catch(async e => {
|
|
||||||
await this._page.initOpener(this._opener);
|
|
||||||
throw e;
|
|
||||||
}).then(() => {
|
|
||||||
this._initializedPage = this._page;
|
|
||||||
this._reportAsNew();
|
|
||||||
return this._page;
|
|
||||||
}).catch(e => {
|
|
||||||
this._reportAsNew(e);
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
potentiallyUninitializedPage(): Page {
|
const createdEvent = this._isBackgroundPage ? CRBrowserContext.CREvents.BackgroundPage : BrowserContext.Events.Page;
|
||||||
return this._page;
|
this._mainFrameSession._initialize(bits.hasUIWindow).then(
|
||||||
}
|
() => this._page.reportAsNew(this._opener?._page, undefined, createdEvent),
|
||||||
|
error => this._page.reportAsNew(this._opener?._page, error, createdEvent));
|
||||||
private _reportAsNew(error?: Error) {
|
|
||||||
this._page.reportAsNew(error, this._isBackgroundPage ? CRBrowserContext.CREvents.BackgroundPage : BrowserContext.Events.Page);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _forAllFrameSessions(cb: (frame: FrameSession) => Promise<any>) {
|
private async _forAllFrameSessions(cb: (frame: FrameSession) => Promise<any>) {
|
||||||
|
|
@ -168,10 +147,6 @@ export class CRPage implements PageDelegate {
|
||||||
this._mainFrameSession._willBeginDownload();
|
this._mainFrameSession._willBeginDownload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async pageOrError(): Promise<Page | Error> {
|
|
||||||
return this._pagePromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
didClose() {
|
didClose() {
|
||||||
for (const session of this._sessions.values())
|
for (const session of this._sessions.values())
|
||||||
session.dispose();
|
session.dispose();
|
||||||
|
|
@ -492,7 +467,7 @@ class FrameSession {
|
||||||
// Note: it is important to start video recorder before sending Page.startScreencast,
|
// Note: it is important to start video recorder before sending Page.startScreencast,
|
||||||
// and it is equally important to send Page.startScreencast before sending Runtime.runIfWaitingForDebugger.
|
// and it is equally important to send Page.startScreencast before sending Runtime.runIfWaitingForDebugger.
|
||||||
await this._createVideoRecorder(screencastId, screencastOptions);
|
await this._createVideoRecorder(screencastId, screencastOptions);
|
||||||
this._crPage.pageOrError().then(p => {
|
this._crPage._page.waitForInitializedOrError().then(p => {
|
||||||
if (p instanceof Error)
|
if (p instanceof Error)
|
||||||
this._stopVideoRecording().catch(() => {});
|
this._stopVideoRecording().catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
@ -833,7 +808,7 @@ class FrameSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
|
async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) {
|
||||||
const pageOrError = await this._crPage.pageOrError();
|
const pageOrError = await this._crPage._page.waitForInitializedOrError();
|
||||||
if (!(pageOrError instanceof Error)) {
|
if (!(pageOrError instanceof Error)) {
|
||||||
const context = this._contextIdToContext.get(event.executionContextId);
|
const context = this._contextIdToContext.get(event.executionContextId);
|
||||||
if (context)
|
if (context)
|
||||||
|
|
@ -898,8 +873,7 @@ class FrameSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
_willBeginDownload() {
|
_willBeginDownload() {
|
||||||
const originPage = this._crPage._initializedPage;
|
if (!this._crPage._page.initializedOrUndefined()) {
|
||||||
if (!originPage) {
|
|
||||||
// Resume the page creation with an error. The page will automatically close right
|
// Resume the page creation with an error. The page will automatically close right
|
||||||
// after the download begins.
|
// after the download begins.
|
||||||
this._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
|
this._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
|
||||||
|
|
@ -939,7 +913,7 @@ class FrameSession {
|
||||||
});
|
});
|
||||||
// Wait for the first frame before reporting video to the client.
|
// Wait for the first frame before reporting video to the client.
|
||||||
gotFirstFrame.then(() => {
|
gotFirstFrame.then(() => {
|
||||||
this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage.pageOrError());
|
this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage._page.waitForInitializedOrError());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import type { BrowserOptions } from '../browser';
|
||||||
import { Browser } from '../browser';
|
import { Browser } from '../browser';
|
||||||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import type { InitScript, Page, PageDelegate } from '../page';
|
import type { InitScript, Page } from '../page';
|
||||||
import { PageBinding } from '../page';
|
import { PageBinding } from '../page';
|
||||||
import type { ConnectionTransport } from '../transport';
|
import type { ConnectionTransport } from '../transport';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
|
|
@ -136,14 +136,14 @@ export class FFBrowser extends Browser {
|
||||||
// Abort the navigation that turned into download.
|
// Abort the navigation that turned into download.
|
||||||
ffPage._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
|
ffPage._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
|
||||||
|
|
||||||
let originPage = ffPage._initializedPage;
|
let originPage = ffPage._page.initializedOrUndefined();
|
||||||
// If it's a new window download, report it on the opener page.
|
// If it's a new window download, report it on the opener page.
|
||||||
if (!originPage) {
|
if (!originPage) {
|
||||||
// Resume the page creation with an error. The page will automatically close right
|
// Resume the page creation with an error. The page will automatically close right
|
||||||
// after the download begins.
|
// after the download begins.
|
||||||
ffPage._markAsError(new Error('Starting new page download'));
|
ffPage._markAsError(new Error('Starting new page download'));
|
||||||
if (ffPage._opener)
|
if (ffPage._opener)
|
||||||
originPage = ffPage._opener._initializedPage;
|
originPage = ffPage._opener._page.initializedOrUndefined();
|
||||||
}
|
}
|
||||||
if (!originPage)
|
if (!originPage)
|
||||||
return;
|
return;
|
||||||
|
|
@ -267,15 +267,11 @@ export class FFBrowserContext extends BrowserContext {
|
||||||
return Array.from(this._browser._ffPages.values()).filter(ffPage => ffPage._browserContext === this);
|
return Array.from(this._browser._ffPages.values()).filter(ffPage => ffPage._browserContext === this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pages(): Page[] {
|
override possiblyUninitializedPages(): Page[] {
|
||||||
return this._ffPages().map(ffPage => ffPage._initializedPage).filter(pageOrNull => !!pageOrNull) as Page[];
|
return this._ffPages().map(ffPage => ffPage._page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pagesOrErrors() {
|
override async doCreateNewPage(): Promise<Page> {
|
||||||
return this._ffPages().map(ffPage => ffPage.pageOrError());
|
|
||||||
}
|
|
||||||
|
|
||||||
async newPageDelegate(): Promise<PageDelegate> {
|
|
||||||
assertBrowserContextIsNotOwned(this);
|
assertBrowserContextIsNotOwned(this);
|
||||||
const { targetId } = await this._browser.session.send('Browser.newPage', {
|
const { targetId } = await this._browser.session.send('Browser.newPage', {
|
||||||
browserContextId: this._browserContextId
|
browserContextId: this._browserContextId
|
||||||
|
|
@ -284,7 +280,7 @@ export class FFBrowserContext extends BrowserContext {
|
||||||
throw new Error(`Invalid timezone ID: ${this._options.timezoneId}`);
|
throw new Error(`Invalid timezone ID: ${this._options.timezoneId}`);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
return this._browser._ffPages.get(targetId)!;
|
return this._browser._ffPages.get(targetId)!._page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import type { Protocol } from './protocol';
|
||||||
import type { Progress } from '../progress';
|
import type { Progress } from '../progress';
|
||||||
import { splitErrorMessage } from '../../utils/stackTrace';
|
import { splitErrorMessage } from '../../utils/stackTrace';
|
||||||
import { debugLogger } from '../../utils/debugLogger';
|
import { debugLogger } from '../../utils/debugLogger';
|
||||||
import { ManualPromise } from '../../utils/manualPromise';
|
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { TargetClosedError } from '../errors';
|
import { TargetClosedError } from '../errors';
|
||||||
|
|
||||||
|
|
@ -49,9 +48,7 @@ export class FFPage implements PageDelegate {
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
readonly _networkManager: FFNetworkManager;
|
readonly _networkManager: FFNetworkManager;
|
||||||
readonly _browserContext: FFBrowserContext;
|
readonly _browserContext: FFBrowserContext;
|
||||||
private _pagePromise = new ManualPromise<Page | Error>();
|
private _reportedAsNew = false;
|
||||||
_initializedPage: Page | null = null;
|
|
||||||
private _initializationFailed = false;
|
|
||||||
readonly _opener: FFPage | null;
|
readonly _opener: FFPage | null;
|
||||||
private readonly _contextIdToContext: Map<string, dom.FrameExecutionContext>;
|
private readonly _contextIdToContext: Map<string, dom.FrameExecutionContext>;
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[];
|
||||||
|
|
@ -102,40 +99,23 @@ export class FFPage implements PageDelegate {
|
||||||
eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this)),
|
eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this)),
|
||||||
|
|
||||||
];
|
];
|
||||||
this._session.once('Page.ready', async () => {
|
this._session.once('Page.ready', () => {
|
||||||
await this._page.initOpener(this._opener);
|
if (this._reportedAsNew)
|
||||||
if (this._initializationFailed)
|
|
||||||
return;
|
return;
|
||||||
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
|
this._reportedAsNew = true;
|
||||||
// so that anyone who awaits pageOrError got a ready and reported page.
|
this._page.reportAsNew(this._opener?._page);
|
||||||
this._initializedPage = this._page;
|
|
||||||
this._page.reportAsNew();
|
|
||||||
this._pagePromise.resolve(this._page);
|
|
||||||
});
|
});
|
||||||
// Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
|
// Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
|
||||||
// Therefore, we can end up with an initialized page without utility world, although very unlikely.
|
// Therefore, we can end up with an initialized page without utility world, although very unlikely.
|
||||||
this.addInitScript(new InitScript('', true), UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
|
this.addInitScript(new InitScript('', true), UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
potentiallyUninitializedPage(): Page {
|
|
||||||
return this._page;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _markAsError(error: Error) {
|
async _markAsError(error: Error) {
|
||||||
// Same error may be report twice: channer disconnected and session.send fails.
|
// Same error may be reported twice: channel disconnected and session.send fails.
|
||||||
if (this._initializationFailed)
|
if (this._reportedAsNew)
|
||||||
return;
|
return;
|
||||||
this._initializationFailed = true;
|
this._reportedAsNew = true;
|
||||||
|
this._page.reportAsNew(this._opener?._page, error);
|
||||||
if (!this._initializedPage) {
|
|
||||||
await this._page.initOpener(this._opener);
|
|
||||||
this._page.reportAsNew(error);
|
|
||||||
this._pagePromise.resolve(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pageOrError(): Promise<Page | Error> {
|
|
||||||
return this._pagePromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWebSocketCreated(event: Protocol.Page.webSocketCreatedPayload) {
|
_onWebSocketCreated(event: Protocol.Page.webSocketCreatedPayload) {
|
||||||
|
|
@ -268,7 +248,7 @@ export class FFPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onBindingCalled(event: Protocol.Page.bindingCalledPayload) {
|
async _onBindingCalled(event: Protocol.Page.bindingCalledPayload) {
|
||||||
const pageOrError = await this.pageOrError();
|
const pageOrError = await this._page.waitForInitializedOrError();
|
||||||
if (!(pageOrError instanceof Error)) {
|
if (!(pageOrError instanceof Error)) {
|
||||||
const context = this._contextIdToContext.get(event.executionContextId);
|
const context = this._contextIdToContext.get(event.executionContextId);
|
||||||
if (context)
|
if (context)
|
||||||
|
|
@ -333,7 +313,7 @@ export class FFPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onVideoRecordingStarted(event: Protocol.Page.videoRecordingStartedPayload) {
|
_onVideoRecordingStarted(event: Protocol.Page.videoRecordingStartedPayload) {
|
||||||
this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this.pageOrError());
|
this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this._page.waitForInitializedOrError());
|
||||||
}
|
}
|
||||||
|
|
||||||
didClose() {
|
didClose() {
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,6 @@ export interface PageDelegate {
|
||||||
addInitScript(initScript: InitScript): Promise<void>;
|
addInitScript(initScript: InitScript): Promise<void>;
|
||||||
removeNonInternalInitScripts(): Promise<void>;
|
removeNonInternalInitScripts(): Promise<void>;
|
||||||
closePage(runBeforeUnload: boolean): Promise<void>;
|
closePage(runBeforeUnload: boolean): Promise<void>;
|
||||||
potentiallyUninitializedPage(): Page;
|
|
||||||
pageOrError(): Promise<Page | Error>;
|
|
||||||
|
|
||||||
navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;
|
navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;
|
||||||
|
|
||||||
|
|
@ -139,7 +137,8 @@ export class Page extends SdkObject {
|
||||||
|
|
||||||
private _closedState: 'open' | 'closing' | 'closed' = 'open';
|
private _closedState: 'open' | 'closing' | 'closed' = 'open';
|
||||||
private _closedPromise = new ManualPromise<void>();
|
private _closedPromise = new ManualPromise<void>();
|
||||||
private _initialized = false;
|
private _initialized: Page | Error | undefined;
|
||||||
|
private _initializedPromise = new ManualPromise<Page | Error>();
|
||||||
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
|
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
|
||||||
private _crashed = false;
|
private _crashed = false;
|
||||||
readonly openScope = new LongStandingScope();
|
readonly openScope = new LongStandingScope();
|
||||||
|
|
@ -193,15 +192,16 @@ export class Page extends SdkObject {
|
||||||
this.coverage = delegate.coverage ? delegate.coverage() : null;
|
this.coverage = delegate.coverage ? delegate.coverage() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initOpener(opener: PageDelegate | null) {
|
async reportAsNew(opener: Page | undefined, error: Error | undefined = undefined, contextEvent: string = BrowserContext.Events.Page) {
|
||||||
if (!opener)
|
if (opener) {
|
||||||
return;
|
const openerPageOrError = await opener.waitForInitializedOrError();
|
||||||
const openerPage = await opener.pageOrError();
|
if (openerPageOrError instanceof Page && !openerPageOrError.isClosed())
|
||||||
if (openerPage instanceof Page && !openerPage.isClosed())
|
this._opener = openerPageOrError;
|
||||||
this._opener = openerPage;
|
}
|
||||||
|
this._markInitialized(error, contextEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
reportAsNew(error: Error | undefined = undefined, contextEvent: string = BrowserContext.Events.Page) {
|
private _markInitialized(error: Error | undefined = undefined, contextEvent: string = BrowserContext.Events.Page) {
|
||||||
if (error) {
|
if (error) {
|
||||||
// Initialization error could have happened because of
|
// Initialization error could have happened because of
|
||||||
// context/browser closure. Just ignore the page.
|
// context/browser closure. Just ignore the page.
|
||||||
|
|
@ -209,7 +209,7 @@ export class Page extends SdkObject {
|
||||||
return;
|
return;
|
||||||
this._frameManager.createDummyMainFrameIfNeeded();
|
this._frameManager.createDummyMainFrameIfNeeded();
|
||||||
}
|
}
|
||||||
this._initialized = true;
|
this._initialized = error || this;
|
||||||
this.emitOnContext(contextEvent, this);
|
this.emitOnContext(contextEvent, this);
|
||||||
|
|
||||||
for (const { event, args } of this._eventsToEmitAfterInitialized)
|
for (const { event, args } of this._eventsToEmitAfterInitialized)
|
||||||
|
|
@ -223,12 +223,20 @@ export class Page extends SdkObject {
|
||||||
this.emit(Page.Events.Close);
|
this.emit(Page.Events.Close);
|
||||||
else
|
else
|
||||||
this.instrumentation.onPageOpen(this);
|
this.instrumentation.onPageOpen(this);
|
||||||
|
|
||||||
|
// Note: it is important to resolve _initializedPromise at the end,
|
||||||
|
// so that anyone who awaits waitForInitializedOrError got a ready and reported page.
|
||||||
|
this._initializedPromise.resolve(this._initialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializedOrUndefined() {
|
initializedOrUndefined(): Page | undefined {
|
||||||
return this._initialized ? this : undefined;
|
return this._initialized ? this : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
waitForInitializedOrError(): Promise<Page | Error> {
|
||||||
|
return this._initializedPromise;
|
||||||
|
}
|
||||||
|
|
||||||
emitOnContext(event: string | symbol, ...args: any[]) {
|
emitOnContext(event: string | symbol, ...args: any[]) {
|
||||||
if (this._isServerSideOnly)
|
if (this._isServerSideOnly)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { Browser } from '../browser';
|
||||||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||||
import { assert } from '../../utils';
|
import { assert } from '../../utils';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import type { InitScript, Page, PageDelegate } from '../page';
|
import type { InitScript, Page } from '../page';
|
||||||
import type { ConnectionTransport } from '../transport';
|
import type { ConnectionTransport } from '../transport';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
@ -121,14 +121,14 @@ export class WKBrowser extends Browser {
|
||||||
// abort navigation that is still running. We should be able to fix this by
|
// abort navigation that is still running. We should be able to fix this by
|
||||||
// instrumenting policy decision start/proceed/cancel.
|
// instrumenting policy decision start/proceed/cancel.
|
||||||
page._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
|
page._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
|
||||||
let originPage = page._initializedPage;
|
let originPage = page._page.initializedOrUndefined();
|
||||||
// If it's a new window download, report it on the opener page.
|
// If it's a new window download, report it on the opener page.
|
||||||
if (!originPage) {
|
if (!originPage) {
|
||||||
// Resume the page creation with an error. The page will automatically close right
|
// Resume the page creation with an error. The page will automatically close right
|
||||||
// after the download begins.
|
// after the download begins.
|
||||||
page._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
|
page._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
|
||||||
if (page._opener)
|
if (page._opener)
|
||||||
originPage = page._opener._initializedPage;
|
originPage = page._opener._page.initializedOrUndefined();
|
||||||
}
|
}
|
||||||
if (!originPage)
|
if (!originPage)
|
||||||
return;
|
return;
|
||||||
|
|
@ -239,18 +239,14 @@ export class WKBrowserContext extends BrowserContext {
|
||||||
return Array.from(this._browser._wkPages.values()).filter(wkPage => wkPage._browserContext === this);
|
return Array.from(this._browser._wkPages.values()).filter(wkPage => wkPage._browserContext === this);
|
||||||
}
|
}
|
||||||
|
|
||||||
pages(): Page[] {
|
override possiblyUninitializedPages(): Page[] {
|
||||||
return this._wkPages().map(wkPage => wkPage._initializedPage).filter(pageOrNull => !!pageOrNull) as Page[];
|
return this._wkPages().map(wkPage => wkPage._page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pagesOrErrors() {
|
override async doCreateNewPage(): Promise<Page> {
|
||||||
return this._wkPages().map(wkPage => wkPage.pageOrError());
|
|
||||||
}
|
|
||||||
|
|
||||||
async newPageDelegate(): Promise<PageDelegate> {
|
|
||||||
assertBrowserContextIsNotOwned(this);
|
assertBrowserContextIsNotOwned(this);
|
||||||
const { pageProxyId } = await this._browser._browserSession.send('Playwright.createPage', { browserContextId: this._browserContextId });
|
const { pageProxyId } = await this._browser._browserSession.send('Playwright.createPage', { browserContextId: this._browserContextId });
|
||||||
return this._browser._wkPages.get(pageProxyId)!;
|
return this._browser._wkPages.get(pageProxyId)!._page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest';
|
||||||
import { WKProvisionalPage } from './wkProvisionalPage';
|
import { WKProvisionalPage } from './wkProvisionalPage';
|
||||||
import { WKWorkers } from './wkWorkers';
|
import { WKWorkers } from './wkWorkers';
|
||||||
import { debugLogger } from '../../utils/debugLogger';
|
import { debugLogger } from '../../utils/debugLogger';
|
||||||
import { ManualPromise } from '../../utils/manualPromise';
|
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { TargetClosedError } from '../errors';
|
import { TargetClosedError } from '../errors';
|
||||||
|
|
||||||
|
|
@ -56,7 +55,6 @@ export class WKPage implements PageDelegate {
|
||||||
_session: WKSession;
|
_session: WKSession;
|
||||||
private _provisionalPage: WKProvisionalPage | null = null;
|
private _provisionalPage: WKProvisionalPage | null = null;
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
private readonly _pagePromise = new ManualPromise<Page | Error>();
|
|
||||||
private readonly _pageProxySession: WKSession;
|
private readonly _pageProxySession: WKSession;
|
||||||
readonly _opener: WKPage | null;
|
readonly _opener: WKPage | null;
|
||||||
private readonly _requestIdToRequest = new Map<string, WKInterceptableRequest>();
|
private readonly _requestIdToRequest = new Map<string, WKInterceptableRequest>();
|
||||||
|
|
@ -66,7 +64,6 @@ export class WKPage implements PageDelegate {
|
||||||
private _sessionListeners: RegisteredListener[] = [];
|
private _sessionListeners: RegisteredListener[] = [];
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[];
|
||||||
readonly _browserContext: WKBrowserContext;
|
readonly _browserContext: WKBrowserContext;
|
||||||
_initializedPage: Page | null = null;
|
|
||||||
private _firstNonInitialNavigationCommittedPromise: Promise<void>;
|
private _firstNonInitialNavigationCommittedPromise: Promise<void>;
|
||||||
private _firstNonInitialNavigationCommittedFulfill = () => {};
|
private _firstNonInitialNavigationCommittedFulfill = () => {};
|
||||||
_firstNonInitialNavigationCommittedReject = (e: Error) => {};
|
_firstNonInitialNavigationCommittedReject = (e: Error) => {};
|
||||||
|
|
@ -111,10 +108,6 @@ export class WKPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
potentiallyUninitializedPage(): Page {
|
|
||||||
return this._page;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _initializePageProxySession() {
|
private async _initializePageProxySession() {
|
||||||
if (this._page._browserContext.isSettingStorageState())
|
if (this._page._browserContext.isSettingStorageState())
|
||||||
return;
|
return;
|
||||||
|
|
@ -283,7 +276,7 @@ export class WKPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleProvisionalLoadFailed(event: Protocol.Playwright.provisionalLoadFailedPayload) {
|
handleProvisionalLoadFailed(event: Protocol.Playwright.provisionalLoadFailedPayload) {
|
||||||
if (!this._initializedPage) {
|
if (!this._page.initializedOrUndefined()) {
|
||||||
this._firstNonInitialNavigationCommittedReject(new Error('Initial load failed'));
|
this._firstNonInitialNavigationCommittedReject(new Error('Initial load failed'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -300,10 +293,6 @@ export class WKPage implements PageDelegate {
|
||||||
this._nextWindowOpenPopupFeatures = event.windowFeatures;
|
this._nextWindowOpenPopupFeatures = event.windowFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
async pageOrError(): Promise<Page | Error> {
|
|
||||||
return this._pagePromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _onTargetCreated(event: Protocol.Target.targetCreatedPayload) {
|
private async _onTargetCreated(event: Protocol.Target.targetCreatedPayload) {
|
||||||
const { targetInfo } = event;
|
const { targetInfo } = event;
|
||||||
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, (message: any) => {
|
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, (message: any) => {
|
||||||
|
|
@ -316,7 +305,7 @@ export class WKPage implements PageDelegate {
|
||||||
assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
|
assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
|
||||||
|
|
||||||
if (!targetInfo.isProvisional) {
|
if (!targetInfo.isProvisional) {
|
||||||
assert(!this._initializedPage);
|
assert(!this._page.initializedOrUndefined());
|
||||||
let pageOrError: Page | Error;
|
let pageOrError: Page | Error;
|
||||||
try {
|
try {
|
||||||
this._setSession(session);
|
this._setSession(session);
|
||||||
|
|
@ -343,12 +332,7 @@ export class WKPage implements PageDelegate {
|
||||||
// Avoid rejection on disconnect.
|
// Avoid rejection on disconnect.
|
||||||
this._firstNonInitialNavigationCommittedPromise.catch(() => {});
|
this._firstNonInitialNavigationCommittedPromise.catch(() => {});
|
||||||
}
|
}
|
||||||
await this._page.initOpener(this._opener);
|
this._page.reportAsNew(this._opener?._page, pageOrError instanceof Page ? undefined : pageOrError);
|
||||||
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
|
|
||||||
// so that anyone who awaits pageOrError got a ready and reported page.
|
|
||||||
this._initializedPage = pageOrError instanceof Page ? pageOrError : null;
|
|
||||||
this._page.reportAsNew(pageOrError instanceof Page ? undefined : pageOrError);
|
|
||||||
this._pagePromise.resolve(pageOrError);
|
|
||||||
} else {
|
} else {
|
||||||
assert(targetInfo.isProvisional);
|
assert(targetInfo.isProvisional);
|
||||||
assert(!this._provisionalPage);
|
assert(!this._provisionalPage);
|
||||||
|
|
@ -515,7 +499,7 @@ export class WKPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onBindingCalled(contextId: Protocol.Runtime.ExecutionContextId, argument: string) {
|
private async _onBindingCalled(contextId: Protocol.Runtime.ExecutionContextId, argument: string) {
|
||||||
const pageOrError = await this.pageOrError();
|
const pageOrError = await this._page.waitForInitializedOrError();
|
||||||
if (!(pageOrError instanceof Error)) {
|
if (!(pageOrError instanceof Error)) {
|
||||||
const context = this._contextIdToContext.get(contextId);
|
const context = this._contextIdToContext.get(contextId);
|
||||||
if (context)
|
if (context)
|
||||||
|
|
@ -821,7 +805,7 @@ export class WKPage implements PageDelegate {
|
||||||
toolbarHeight: this._toolbarHeight()
|
toolbarHeight: this._toolbarHeight()
|
||||||
});
|
});
|
||||||
this._recordingVideoFile = options.outputFile;
|
this._recordingVideoFile = options.outputFile;
|
||||||
this._browserContext._browser._videoStarted(this._browserContext, screencastId, options.outputFile, this.pageOrError());
|
this._browserContext._browser._videoStarted(this._browserContext, screencastId, options.outputFile, this._page.waitForInitializedOrError());
|
||||||
}
|
}
|
||||||
|
|
||||||
async _stopVideo(): Promise<void> {
|
async _stopVideo(): Promise<void> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue