fix(networkidle): ignore favicons and keep track of requests (#368)

This counters Firefox not cancelling existing requests on navigation.
This commit is contained in:
Dmitry Gozman 2020-01-03 12:59:27 -08:00 committed by Pavel Feldman
parent f1d6fe6bd8
commit 32edca7395
2 changed files with 40 additions and 29 deletions

View File

@ -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<ContextType, ContextData>();
private _childFrames = new Set<Frame>();
_name: string;
_inflightRequests = 0;
_inflightRequests = new Set<network.Request>();
readonly _networkIdleTimers = new Map<LifecycleEvent, NodeJS.Timer>();
constructor(page: Page, id: string, parentFrame: Frame | null) {
@ -437,8 +448,10 @@ export class Frame {
async setContent(html: string, options?: NavigateOptions): Promise<void> {
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();

View File

@ -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(`<script>fetch('foo.js');</script>`);
@ -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(`<script>fetch('foo.js');</script>`);