Skip to content

Commit db3439d

Browse files
authored
chore: introduce DocumentInfo (#2765)
It encapsulates documentId and request.
1 parent 15ddb5d commit db3439d

File tree

6 files changed

+70
-74
lines changed

6 files changed

+70
-74
lines changed

src/chromium/crNetworkManager.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,6 @@ export class CRNetworkManager {
220220
}
221221
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
222222
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
223-
if (isNavigationRequest)
224-
this._page._frameManager.frameUpdatedDocumentIdForNavigation(requestWillBeSentEvent.frameId!, documentId!);
225223
const request = new InterceptableRequest({
226224
client: this._client,
227225
frame,

src/chromium/crPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ class FrameSession {
498498

499499
_onFrameRequestedNavigation(payload: Protocol.Page.frameRequestedNavigationPayload) {
500500
if (payload.disposition === 'currentTab')
501-
this._page._frameManager.frameRequestedNavigation(payload.frameId, '');
501+
this._page._frameManager.frameRequestedNavigation(payload.frameId);
502502
}
503503

504504
_onFrameNavigatedWithinDocument(frameId: string, url: string) {

src/firefox/ffPage.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,7 @@ export class FFPage implements PageDelegate {
140140
}
141141

142142
_onNavigationAborted(params: Protocol.Page.navigationAbortedPayload) {
143-
const frame = this._page._frameManager.frame(params.frameId)!;
144-
for (const task of frame._frameTasks)
145-
task.onNewDocument(params.navigationId, new Error(params.errorText));
143+
this._page._frameManager.frameAbortedNavigation(params.frameId, params.errorText, params.navigationId);
146144
}
147145

148146
_onNavigationCommitted(params: Protocol.Page.navigationCommittedPayload) {

src/frames.ts

Lines changed: 58 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ type ContextData = {
3636
rerunnableTasks: Set<RerunnableTask>;
3737
};
3838

39+
type DocumentInfo = {
40+
// Unfortunately, we don't have documentId when we find out about
41+
// a pending navigation from things like frameScheduledNavigaiton.
42+
documentId: string | undefined,
43+
request: network.Request | undefined,
44+
};
45+
3946
export type GotoResult = {
4047
newDocumentId?: string,
4148
};
@@ -125,32 +132,34 @@ export class FrameManager {
125132
barrier.release();
126133
}
127134

128-
frameRequestedNavigation(frameId: string, documentId: string) {
135+
frameRequestedNavigation(frameId: string, documentId?: string) {
129136
const frame = this._frames.get(frameId);
130137
if (!frame)
131138
return;
132139
for (const barrier of this._signalBarriers)
133140
barrier.addFrameNavigation(frame);
134-
frame._pendingDocumentId = documentId;
135-
}
136-
137-
frameUpdatedDocumentIdForNavigation(frameId: string, documentId: string) {
138-
const frame = this._frames.get(frameId);
139-
if (!frame)
141+
if (frame._pendingDocument && frame._pendingDocument.documentId === documentId) {
142+
// Do not override request with undefined.
140143
return;
141-
frame._pendingDocumentId = documentId;
144+
}
145+
frame._pendingDocument = { documentId, request: undefined };
142146
}
143147

144148
frameCommittedNewDocumentNavigation(frameId: string, url: string, name: string, documentId: string, initial: boolean) {
145149
const frame = this._frames.get(frameId)!;
146150
this.removeChildFramesRecursively(frame);
147151
frame._url = url;
148152
frame._name = name;
149-
debugAssert(!frame._pendingDocumentId || frame._pendingDocumentId === documentId);
150-
frame._lastDocumentId = documentId;
151-
frame._pendingDocumentId = '';
153+
if (frame._pendingDocument && frame._pendingDocument.documentId === undefined)
154+
frame._pendingDocument.documentId = documentId;
155+
debugAssert(!frame._pendingDocument || frame._pendingDocument.documentId === documentId);
156+
if (frame._pendingDocument && frame._pendingDocument.documentId === documentId)
157+
frame._currentDocument = frame._pendingDocument;
158+
else
159+
frame._currentDocument = { documentId, request: undefined };
160+
frame._pendingDocument = undefined;
152161
for (const task of frame._frameTasks)
153-
task.onNewDocument(documentId);
162+
task.onNewDocument(frame._currentDocument);
154163
this.clearFrameLifecycle(frame);
155164
if (!initial)
156165
this._page.emit(Events.Page.FrameNavigated, frame);
@@ -166,6 +175,18 @@ export class FrameManager {
166175
this._page.emit(Events.Page.FrameNavigated, frame);
167176
}
168177

178+
frameAbortedNavigation(frameId: string, errorText: string, documentId?: string) {
179+
const frame = this._frames.get(frameId);
180+
if (!frame || !frame._pendingDocument)
181+
return;
182+
if (documentId !== undefined && frame._pendingDocument.documentId !== documentId)
183+
return;
184+
const pending = frame._pendingDocument;
185+
frame._pendingDocument = undefined;
186+
for (const task of frame._frameTasks)
187+
task.onNewDocument(pending, new Error(errorText));
188+
}
189+
169190
frameDetached(frameId: string) {
170191
const frame = this._frames.get(frameId);
171192
if (frame)
@@ -194,16 +215,17 @@ export class FrameManager {
194215
clearFrameLifecycle(frame: Frame) {
195216
frame._firedLifecycleEvents.clear();
196217
// Keep the current navigation request if any.
197-
frame._inflightRequests = new Set(Array.from(frame._inflightRequests).filter(request => request._documentId === frame._lastDocumentId));
218+
frame._inflightRequests = new Set(Array.from(frame._inflightRequests).filter(request => request === frame._currentDocument.request));
198219
frame._stopNetworkIdleTimer();
199220
if (frame._inflightRequests.size === 0)
200221
frame._startNetworkIdleTimer();
201222
}
202223

203224
requestStarted(request: network.Request) {
225+
const frame = request.frame();
204226
this._inflightRequestStarted(request);
205-
for (const task of request.frame()._frameTasks)
206-
task.onRequest(request);
227+
if (request._documentId)
228+
frame._pendingDocument = { documentId: request._documentId, request };
207229
if (request._isFavicon) {
208230
const route = request._route();
209231
if (route)
@@ -225,27 +247,18 @@ export class FrameManager {
225247
}
226248

227249
requestFailed(request: network.Request, canceled: boolean) {
250+
const frame = request.frame();
228251
this._inflightRequestFinished(request);
229-
if (request._documentId) {
230-
const isPendingDocument = request.frame()._pendingDocumentId === request._documentId;
231-
if (isPendingDocument) {
232-
request.frame()._pendingDocumentId = '';
233-
let errorText = request.failure()!.errorText;
234-
if (canceled)
235-
errorText += '; maybe frame was detached?';
236-
for (const task of request.frame()._frameTasks)
237-
task.onNewDocument(request._documentId, new Error(errorText));
238-
}
252+
if (frame._pendingDocument && frame._pendingDocument.request === request) {
253+
let errorText = request.failure()!.errorText;
254+
if (canceled)
255+
errorText += '; maybe frame was detached?';
256+
this.frameAbortedNavigation(frame._id, errorText, frame._pendingDocument.documentId);
239257
}
240258
if (!request._isFavicon)
241259
this._page.emit(Events.Page.RequestFailed, request);
242260
}
243261

244-
provisionalLoadFailed(frame: Frame, documentId: string, error: string) {
245-
for (const task of frame._frameTasks)
246-
task.onNewDocument(documentId, new Error(error));
247-
}
248-
249262
private _notifyLifecycle(frame: Frame, lifecycleEvent: types.LifecycleEvent) {
250263
for (let parent: Frame | null = frame; parent; parent = parent.parentFrame()) {
251264
for (const frameTask of parent._frameTasks)
@@ -301,8 +314,8 @@ export class FrameManager {
301314
export class Frame {
302315
_id: string;
303316
readonly _firedLifecycleEvents: Set<types.LifecycleEvent>;
304-
_lastDocumentId = '';
305-
_pendingDocumentId = '';
317+
_currentDocument: DocumentInfo;
318+
_pendingDocument?: DocumentInfo;
306319
_frameTasks = new Set<FrameTask>();
307320
readonly _page: Page;
308321
private _parentFrame: Frame | null;
@@ -322,6 +335,7 @@ export class Frame {
322335
this._firedLifecycleEvents = new Set();
323336
this._page = page;
324337
this._parentFrame = parentFrame;
338+
this._currentDocument = { documentId: undefined, request: undefined };
325339

326340
this._detachedPromise = new Promise<void>(x => this._detachedCallback = x);
327341

@@ -358,14 +372,14 @@ export class Frame {
358372
sameDocumentPromise.catch(e => {});
359373
throw e;
360374
});
375+
let request: network.Request | undefined;
361376
if (navigateResult.newDocumentId) {
362377
// Do not leave sameDocumentPromise unhandled.
363378
sameDocumentPromise.catch(e => {});
364-
await frameTask.waitForSpecificDocument(navigateResult.newDocumentId);
379+
request = await frameTask.waitForSpecificDocument(navigateResult.newDocumentId);
365380
} else {
366381
await sameDocumentPromise;
367382
}
368-
const request = (navigateResult && navigateResult.newDocumentId) ? frameTask.request(navigateResult.newDocumentId) : null;
369383
await frameTask.waitForLifecycle(options.waitUntil === undefined ? 'load' : options.waitUntil);
370384
frameTask.done();
371385
return request ? request._finalRequest().response() : null;
@@ -377,12 +391,11 @@ export class Frame {
377391
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
378392
progress.logger.info(`waiting for navigation${toUrl} until "${options.waitUntil || 'load'}"`);
379393
const frameTask = new FrameTask(this, progress);
380-
let documentId: string | undefined;
394+
let request: network.Request | undefined;
381395
await Promise.race([
382-
frameTask.waitForNewDocument(options.url).then(id => documentId = id),
396+
frameTask.waitForNewDocument(options.url).then(r => request = r),
383397
frameTask.waitForSameDocumentNavigation(options.url),
384398
]);
385-
const request = documentId ? frameTask.request(documentId) : null;
386399
await frameTask.waitForLifecycle(options.waitUntil === undefined ? 'load' : options.waitUntil);
387400
frameTask.done();
388401
return request ? request._finalRequest().response() : null;
@@ -1026,11 +1039,10 @@ class SignalBarrier {
10261039

10271040
class FrameTask {
10281041
private readonly _frame: Frame;
1029-
private readonly _requestMap = new Map<string, network.Request>();
10301042
private readonly _progress: Progress | null = null;
10311043
private _onSameDocument?: { url?: types.URLMatch, resolve: () => void };
1032-
private _onSpecificDocument?: { expectedDocumentId: string, resolve: () => void, reject: (error: Error) => void };
1033-
private _onNewDocument?: { url?: types.URLMatch, resolve: (documentId: string) => void, reject: (error: Error) => void };
1044+
private _onSpecificDocument?: { expectedDocumentId: string, resolve: (request: network.Request | undefined) => void, reject: (error: Error) => void };
1045+
private _onNewDocument?: { url?: types.URLMatch, resolve: (request: network.Request | undefined) => void, reject: (error: Error) => void };
10341046
private _onLifecycle?: { waitUntil: types.LifecycleEvent, resolve: () => void };
10351047

10361048
constructor(frame: Frame, progress: Progress | null) {
@@ -1041,32 +1053,22 @@ class FrameTask {
10411053
progress.cleanupWhenAborted(() => this.done());
10421054
}
10431055

1044-
onRequest(request: network.Request) {
1045-
if (!request._documentId || request.redirectedFrom())
1046-
return;
1047-
this._requestMap.set(request._documentId, request);
1048-
}
1049-
1050-
request(documentId: string): network.Request | undefined {
1051-
return this._requestMap.get(documentId);
1052-
}
1053-
10541056
onSameDocument() {
10551057
if (this._progress)
10561058
this._progress.logger.info(`navigated to "${this._frame._url}"`);
10571059
if (this._onSameDocument && helper.urlMatches(this._frame.url(), this._onSameDocument.url))
10581060
this._onSameDocument.resolve();
10591061
}
10601062

1061-
onNewDocument(documentId: string, error?: Error) {
1063+
onNewDocument(documentInfo: DocumentInfo, error?: Error) {
10621064
if (this._progress && !error)
10631065
this._progress.logger.info(`navigated to "${this._frame._url}"`);
10641066
if (this._onSpecificDocument) {
1065-
if (documentId === this._onSpecificDocument.expectedDocumentId) {
1067+
if (documentInfo.documentId === this._onSpecificDocument.expectedDocumentId) {
10661068
if (error)
10671069
this._onSpecificDocument.reject(error);
10681070
else
1069-
this._onSpecificDocument.resolve();
1071+
this._onSpecificDocument.resolve(documentInfo.request);
10701072
} else if (!error) {
10711073
this._onSpecificDocument.reject(new Error('Navigation interrupted by another one'));
10721074
}
@@ -1075,7 +1077,7 @@ class FrameTask {
10751077
if (error)
10761078
this._onNewDocument.reject(error);
10771079
else if (helper.urlMatches(this._frame.url(), this._onNewDocument.url))
1078-
this._onNewDocument.resolve(documentId);
1080+
this._onNewDocument.resolve(documentInfo.request);
10791081
}
10801082
}
10811083

@@ -1093,14 +1095,14 @@ class FrameTask {
10931095
});
10941096
}
10951097

1096-
waitForSpecificDocument(expectedDocumentId: string): Promise<void> {
1098+
waitForSpecificDocument(expectedDocumentId: string): Promise<network.Request | undefined> {
10971099
return new Promise((resolve, reject) => {
10981100
assert(!this._onSpecificDocument);
10991101
this._onSpecificDocument = { expectedDocumentId, resolve, reject };
11001102
});
11011103
}
11021104

1103-
waitForNewDocument(url?: types.URLMatch): Promise<string> {
1105+
waitForNewDocument(url?: types.URLMatch): Promise<network.Request | undefined> {
11041106
return new Promise((resolve, reject) => {
11051107
assert(!this._onNewDocument);
11061108
this._onNewDocument = { url, resolve, reject };

src/webkit/wkBrowser.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ export class WKBrowser extends BrowserBase {
8989
const page = this._wkPages.get(payload.pageProxyId);
9090
if (!page)
9191
return;
92-
const frameManager = page._page._frameManager;
93-
const frame = frameManager.frame(payload.frameId);
94-
if (frame) {
95-
// In some cases, e.g. blob url download, we receive only frameScheduledNavigation
96-
// but no signals that the navigation was canceled and replaced by download. Fix it
97-
// here by simulating cancelled provisional load which matches downloads from network.
98-
frameManager.provisionalLoadFailed(frame, '', 'Download is starting');
99-
}
92+
// In some cases, e.g. blob url download, we receive only frameScheduledNavigation
93+
// but no signals that the navigation was canceled and replaced by download. Fix it
94+
// here by simulating cancelled provisional load which matches downloads from network.
95+
//
96+
// TODO: this is racy, because download might be unrelated any navigation, and we will
97+
// abort navgitation that is still running. We should be able to fix this by
98+
// instrumenting policy decision start/proceed/cancel.
99+
page._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
100100
let originPage = page._initializedPage;
101101
// If it's a new window download, report it on the opener page.
102102
if (!originPage) {

src/webkit/wkPage.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export class WKPage implements PageDelegate {
248248
let errorText = event.error;
249249
if (errorText.includes('cancelled'))
250250
errorText += '; maybe frame was detached?';
251-
this._page._frameManager.provisionalLoadFailed(this._page.mainFrame(), event.loaderId, errorText);
251+
this._page._frameManager.frameAbortedNavigation(this._page.mainFrame()._id, errorText, event.loaderId);
252252
}
253253

254254
handleWindowOpen(event: Protocol.Playwright.windowOpenPayload) {
@@ -366,7 +366,7 @@ export class WKPage implements PageDelegate {
366366
}
367367

368368
private _onFrameScheduledNavigation(frameId: string) {
369-
this._page._frameManager.frameRequestedNavigation(frameId, '');
369+
this._page._frameManager.frameRequestedNavigation(frameId);
370370
}
371371

372372
private _onFrameStoppedLoading(frameId: string) {
@@ -835,8 +835,6 @@ export class WKPage implements PageDelegate {
835835
// TODO(einbinder) this will fail if we are an XHR document request
836836
const isNavigationRequest = event.type === 'Document';
837837
const documentId = isNavigationRequest ? event.loaderId : undefined;
838-
if (isNavigationRequest)
839-
this._page._frameManager.frameUpdatedDocumentIdForNavigation(event.frameId, documentId!);
840838
// We do not support intercepting redirects.
841839
const allowInterception = this._page._needsRequestInterception() && !redirectedFrom;
842840
const request = new WKInterceptableRequest(session, allowInterception, frame, event, redirectedFrom, documentId);

0 commit comments

Comments
 (0)