diff --git a/src/frames.ts b/src/frames.ts index b84727ffa9..1d0f476c37 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -117,6 +117,7 @@ export class FrameManager { frame._name = name; frame._lastDocumentId = documentId; this.frameLifecycleEvent(frameId, 'clear'); + this.clearInflightRequests(frame); if (!initial) { for (const watcher of this._lifecycleWatchers) watcher._onCommittedNewDocumentNavigation(frame); @@ -162,12 +163,6 @@ export class FrameManager { return; if (event === 'clear') { frame._firedLifecycleEvents.clear(); - this._stopNetworkIdleTimer(frame, 'networkidle0'); - if (frame._inflightRequests === 0) - this._startNetworkIdleTimer(frame, 'networkidle0'); - this._stopNetworkIdleTimer(frame, 'networkidle2'); - if (frame._inflightRequests <= 2) - this._startNetworkIdleTimer(frame, 'networkidle2'); } else { frame._firedLifecycleEvents.add(event); for (const watcher of this._lifecycleWatchers) @@ -179,9 +174,19 @@ export class FrameManager { this._page.emit(Events.Page.DOMContentLoaded); } + clearInflightRequests(frame: Frame) { + // Keep the current navigation request if any. + frame._inflightRequests = new Set(Array.from(frame._inflightRequests).filter(request => request._documentId === frame._lastDocumentId)); + this._stopNetworkIdleTimer(frame, 'networkidle0'); + if (frame._inflightRequests.size === 0) + this._startNetworkIdleTimer(frame, 'networkidle0'); + this._stopNetworkIdleTimer(frame, 'networkidle2'); + if (frame._inflightRequests.size <= 2) + this._startNetworkIdleTimer(frame, 'networkidle2'); + } + requestStarted(request: network.Request) { - if (request.frame()) - this._incrementRequestCount(request.frame()); + this._inflightRequestStarted(request); if (request._documentId && request.frame() && !request.redirectChain().length) { for (const watcher of this._lifecycleWatchers) watcher._onNavigationRequest(request.frame(), request); @@ -194,14 +199,12 @@ export class FrameManager { } requestFinished(request: network.Request) { - if (request.frame()) - this._decrementRequestCount(request.frame()); + this._inflightRequestFinished(request); this._page.emit(Events.Page.RequestFinished, request); } requestFailed(request: network.Request, canceled: boolean) { - if (request.frame()) - this._decrementRequestCount(request.frame()); + this._inflightRequestFinished(request); if (request._documentId && request.frame()) { const isCurrentDocument = request.frame()._lastDocumentId === request._documentId; if (!isCurrentDocument) { @@ -225,19 +228,27 @@ export class FrameManager { this._page.emit(Events.Page.FrameDetached, frame); } - private _decrementRequestCount(frame: Frame) { - frame._inflightRequests--; - if (frame._inflightRequests === 0) + private _inflightRequestFinished(request: network.Request) { + if (!request.frame() || request.url().endsWith('favicon.ico')) + return; + const frame = request.frame(); + if (!frame._inflightRequests.has(request)) + return; + frame._inflightRequests.delete(request); + if (frame._inflightRequests.size === 0) this._startNetworkIdleTimer(frame, 'networkidle0'); - if (frame._inflightRequests === 2) + if (frame._inflightRequests.size === 2) this._startNetworkIdleTimer(frame, 'networkidle2'); } - private _incrementRequestCount(frame: Frame) { - frame._inflightRequests++; - if (frame._inflightRequests === 1) + private _inflightRequestStarted(request: network.Request) { + if (!request.frame() || request.url().endsWith('favicon.ico')) + return; + const frame = request.frame(); + frame._inflightRequests.add(request); + if (frame._inflightRequests.size === 1) this._stopNetworkIdleTimer(frame, 'networkidle0'); - if (frame._inflightRequests === 3) + if (frame._inflightRequests.size === 3) this._stopNetworkIdleTimer(frame, 'networkidle2'); } @@ -267,7 +278,7 @@ export class Frame { private _contextData = new Map(); private _childFrames = new Set(); _name: string; - _inflightRequests = 0; + _inflightRequests = new Set(); readonly _networkIdleTimers = new Map(); constructor(page: Page, id: string, parentFrame: Frame | null) { @@ -437,8 +448,10 @@ export class Frame { async setContent(html: string, options?: NavigateOptions): Promise { const context = await this._utilityContext(); - if (this._page._delegate.needsLifecycleResetOnSetContent()) + if (this._page._delegate.needsLifecycleResetOnSetContent()) { this._page._frameManager.frameLifecycleEvent(this._id, 'clear'); + this._page._frameManager.clearInflightRequests(this); + } await context.evaluate(html => { window.stop(); document.open(); diff --git a/test/navigation.spec.js b/test/navigation.spec.js index eab97cb736..36f6b668c2 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -426,7 +426,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME } const response = await actionPromise; - expect(performance.now() - lastResponseFinished).not.toBeLessThan(499); + expect(performance.now() - lastResponseFinished).not.toBeLessThan(450); if (!isSetContent) expect(response.ok()).toBe(true); @@ -490,12 +490,8 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME }, true); }); it.skip(FFOX)('should wait for networkidle0 in setContent with request from previous navigation', async({page, server}) => { - // TODO: there are two issues here which combined fail the test in firefox: - // - calling window.stop() does not cancel all outstanding requests in firefox; - // - we do not reset inflight request counter on lifecycle clear, so we wait for - // the first request indefinitely. - // Note that we cannot just reset inflight request counter, because the current navigation - // request is already inflight at that moment. + // TODO: in Firefox window.stop() does not cancel outstanding requests, and we also lack 'init' lifecycle, + // therefore we don't clear inglight requests at the right time. await page.goto(server.EMPTY_PAGE); server.setRoute('/foo.js', () => {}); await page.setContent(``); @@ -504,6 +500,8 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME }, true); }); it.skip(FFOX)('should wait for networkidle2 in setContent with request from previous navigation', async({page, server}) => { + // TODO: in Firefox window.stop() does not cancel outstanding requests, and we also lack 'init' lifecycle, + // therefore we don't clear inglight requests at the right time. await page.goto(server.EMPTY_PAGE); server.setRoute('/foo.js', () => {}); await page.setContent(``);