Skip to content

Commit 4fdd378

Browse files
committed
fix(client): resource timeout + blank new tab
1 parent 2f74a1e commit 4fdd378

File tree

11 files changed

+79
-63
lines changed

11 files changed

+79
-63
lines changed

client/connections/ConnectionToCore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ export default abstract class ConnectionToCore extends TypedEventEmitter<{
294294

295295
protected cancelPendingRequests(): void {
296296
const host = String(this.resolvedHost);
297-
this.coreSessions.close(new DisconnectedFromCoreError(host));
298-
this.commandQueue.clearPending(new DisconnectedFromCoreError(host));
297+
this.coreSessions.stop(new DisconnectedFromCoreError(host));
298+
this.commandQueue.stop(new DisconnectedFromCoreError(host));
299299
for (const entry of this.pendingRequestsById.values()) {
300300
const id = entry.id;
301301
if (this.connectRequestId === id || this.disconnectRequestId === id) {

client/lib/Agent.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import CoreSession from './CoreSession';
3737
import IAgentConfigureOptions from '../interfaces/IAgentConfigureOptions';
3838
import ConnectionFactory from '../connections/ConnectionFactory';
3939
import ConnectionToCore from '../connections/ConnectionToCore';
40+
import DisconnectedFromCoreError from '../connections/DisconnectedFromCoreError';
4041

4142
export const DefaultOptions = {
4243
defaultBlockedResourceTypes: [BlockedResourceType.None],
@@ -148,12 +149,17 @@ export default class Agent extends AwaitedEventTarget<{ close: void }> {
148149

149150
// METHODS
150151

151-
public close(): Promise<void> {
152+
public async close(): Promise<void> {
152153
const { isClosing, connection } = getState(this);
153154
if (isClosing) return;
154155
setState(this, { isClosing: true });
155156

156-
return connection.close();
157+
try {
158+
return await connection.close();
159+
} catch (error) {
160+
if (error instanceof DisconnectedFromCoreError) return;
161+
throw error;
162+
}
157163
}
158164

159165
public async closeTab(tab: Tab): Promise<void> {

client/lib/CoreCommandQueue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default class CoreCommandQueue {
3333
});
3434
}
3535

36-
public clearPending(cancelError: CanceledPromiseError): void {
36+
public stop(cancelError: CanceledPromiseError): void {
3737
this.internalQueue.stop(cancelError);
3838
}
3939

client/lib/CoreSessions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default class CoreSessions {
2828
return this.sessionsById.get(sessionId);
2929
}
3030

31-
public close(closeError: Error): boolean {
31+
public stop(closeError: Error): boolean {
3232
const hasSessions = this.sessionsById.size > 0;
3333
this.queue.stop(closeError);
3434
this.sessionsById.clear();

client/lib/Resource.ts

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -84,25 +84,44 @@ export default class Resource {
8484
const timer = new Timer(options?.timeoutMs ?? 30e3);
8585

8686
const resourceFilter = { url: filter.url, type: filter.type };
87-
const resourceOptions = {
88-
...(options ?? {}),
87+
const resourceOptions: IWaitForResourceOptions = {
88+
sinceCommandId: options?.sinceCommandId,
8989
timeoutMs: 2e3,
9090
throwIfTimeout: false,
91-
} as IWaitForResourceOptions;
91+
};
9292

9393
let isComplete = false;
9494
const done = (): boolean => (isComplete = true);
9595

9696
do {
97-
let foundResources: IResourceMeta[] = [];
98-
9997
try {
10098
const waitForResourcePromise = coreTab.waitForResource(resourceFilter, resourceOptions);
101-
foundResources = await timer.waitForPromise(
99+
const foundResources = await timer.waitForPromise(
102100
waitForResourcePromise,
103101
'Timeout waiting for Resource(s)',
104102
);
105103
resourceOptions.sinceCommandId = coreTab.commandQueue.lastCommandId;
104+
105+
for (const resourceMeta of foundResources) {
106+
if (idsSeen.has(resourceMeta.id)) continue;
107+
idsSeen.add(resourceMeta.id);
108+
109+
const resource = createResource(resourceMeta, Promise.resolve(coreTab));
110+
111+
let shouldInclude = true;
112+
113+
if (filter.filterFn) {
114+
// resources can trigger commandQueue functions, so time them out
115+
shouldInclude = await timer.waitForPromise(
116+
Promise.resolve(filter.filterFn(resource, done)),
117+
'Timeout waiting for waitResource.filterFn',
118+
);
119+
}
120+
121+
if (shouldInclude) resources.push(resource);
122+
123+
if (isComplete) break;
124+
}
106125
} catch (err) {
107126
if (err instanceof TimeoutError) {
108127
if (options?.throwIfTimeout === false) {
@@ -112,22 +131,6 @@ export default class Resource {
112131
throw err;
113132
}
114133

115-
for (const resourceMeta of foundResources) {
116-
if (idsSeen.has(resourceMeta.id)) continue;
117-
idsSeen.add(resourceMeta.id);
118-
119-
const resource = createResource(resourceMeta, Promise.resolve(coreTab));
120-
121-
if (filter.filterFn) {
122-
const shouldIncludeResource = await filter.filterFn(resource, done);
123-
if (shouldIncludeResource) resources.push(resource);
124-
} else {
125-
resources.push(resource);
126-
}
127-
128-
if (isComplete) break;
129-
}
130-
131134
// if no filter callback provided, break after 1 found
132135
if (!filter.filterFn && resources.length) {
133136
done();

commons/Timer.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,12 @@ export default class Timer {
5050
return time[0] * 1000 + time[1] / 1000000;
5151
}
5252

53-
public waitForPromise<Z>(promise: Promise<Z>, message: string): Promise<Z> {
53+
public async waitForPromise<Z>(promise: Promise<Z>, message: string): Promise<Z> {
5454
this.timeoutMessage = message;
55-
return Promise.race([
56-
promise,
57-
this.expirePromise.then(() => {
58-
throw new TimeoutError(this.timeoutMessage);
59-
}),
60-
]) as Promise<Z>;
55+
const timeout = new TimeoutError(this.timeoutMessage);
56+
const result = await Promise.race([promise, this.expirePromise.then(() => timeout)]);
57+
if (result instanceof TimeoutError) throw timeout;
58+
return result;
6159
}
6260

6361
public waitForTimeout(): Promise<void> {

core/lib/Session.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export default class Session extends TypedEventEmitter<{
6969
}
7070

7171
private _isClosing = false;
72-
private pendingNavigationMitmResponses: IRequestSessionResponseEvent[] = [];
7372

7473
constructor(readonly options: ICreateTabOptions) {
7574
super();
@@ -210,8 +209,6 @@ export default class Session extends TypedEventEmitter<{
210209
sessionId: this.id,
211210
});
212211

213-
this.pendingNavigationMitmResponses.forEach(x => this.onMitmResponse(x));
214-
215212
await this.mitmRequestSession.close();
216213
await Promise.all(Object.values(this.tabs).map(x => x.close()));
217214
try {
@@ -279,12 +276,10 @@ export default class Session extends TypedEventEmitter<{
279276
const tabId = this.mitmRequestSession.browserRequestMatcher.requestIdToTabId.get(
280277
event.browserRequestId,
281278
);
282-
let tab = this.tabs.find(x => x.id === tabId);
283-
if (!tab && event.browserRequestId === 'fallback-navigation') {
284-
tab = this.tabs.find(x => x.url === event.request.url || x.url === event.redirectedToUrl);
285-
if (!tab) {
286-
return this.pendingNavigationMitmResponses.push(event);
287-
}
279+
const tab = this.tabs.find(x => x.id === tabId);
280+
if (!tab && !tabId) {
281+
this.logger.warn(`Mitm Response received without matching tab`, { event });
282+
return;
288283
}
289284

290285
const resource = this.sessionState.captureResource(tab?.id ?? tabId, event, true);
@@ -320,21 +315,7 @@ export default class Session extends TypedEventEmitter<{
320315
this.sessionState.captureTab(tab.id, page.id, page.devtoolsSessionId, parentTab.id);
321316
this.registerTab(tab, page);
322317
await tab.isReady;
323-
await page.mainFrame.waitForLoader();
324-
const startUrl = page.mainFrame.url;
325318
parentTab.emit('child-tab-created', tab);
326-
// make sure we match browser requests that weren't associated with a tab to the new tab
327-
if (this.pendingNavigationMitmResponses.length) {
328-
const replayPending = [...this.pendingNavigationMitmResponses];
329-
this.pendingNavigationMitmResponses.length = 0;
330-
while (replayPending.length) {
331-
const next = replayPending.pop();
332-
if (next.redirectedToUrl === startUrl || next.request.url === startUrl) {
333-
const resource = this.sessionState.captureResource(tab.id, next, true);
334-
tab.emit('resource', resource);
335-
}
336-
}
337-
}
338319
return tab;
339320
}
340321

core/lib/Tab.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ b) Use the UserProfile feature to set cookies for 1 or more domains before they'
428428
// last command is the one running right now
429429
const startCommandId = options?.sinceCommandId ?? this.lastCommandId - 1;
430430
let newTab: Tab;
431+
const startTime = new Date();
431432
if (startCommandId >= 0) {
432433
for (const tab of this.session.tabs) {
433434
if (tab.parentTabId === this.id && tab.createdAtCommandId >= startCommandId) {
@@ -437,6 +438,15 @@ b) Use the UserProfile feature to set cookies for 1 or more domains before they'
437438
}
438439
}
439440
if (!newTab) newTab = await this.waitOn('child-tab-created', undefined, options?.timeoutMs);
441+
442+
// wait for a real url to be requested
443+
if (newTab.url === 'about:blank' || !newTab.url) {
444+
let timeoutMs = options?.timeoutMs ?? 10e3;
445+
const millis = new Date().getTime() - startTime.getTime();
446+
timeoutMs -= millis;
447+
await newTab.navigations.waitOn('navigation-requested', null, timeoutMs).catch(() => null);
448+
}
449+
440450
await newTab.navigationsObserver.waitForNavigationResourceId();
441451
return newTab;
442452
}

core/models/DevtoolsMessagesTable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export default class DevtoolsMessagesTable extends SqliteTable<IDevtoolsMessageR
8080

8181
// clean out post data (we have these in resources table)
8282
if ((key === 'headers' || key === 'postData') && params.request) {
83-
return 'OMITTED';
83+
return 'SA_REMOVED_FOR_DB';
8484
}
8585
return value;
8686
}

full-client/test/resources.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,17 @@ describe('basic resource tests', () => {
6363
}
6464
await agent.close();
6565
});
66+
67+
it('cancels a pending resource on agent close', async () => {
68+
const exampleUrl = `${koaServer.baseUrl}/test`;
69+
const agent = await handler.createAgent();
70+
71+
await agent.goto(exampleUrl);
72+
73+
const waitForResource = agent.waitForResource({ type: 'Fetch' });
74+
// eslint-disable-next-line jest/valid-expect
75+
const waitError = expect(waitForResource).rejects.toThrowError('disconnected');
76+
await agent.close();
77+
await waitError;
78+
});
6679
});

0 commit comments

Comments
 (0)