fix(recorder): do not leak when instantiated in snapshots (#33240)

This commit is contained in:
Dmitry Gozman 2024-10-23 10:24:53 -07:00 committed by GitHub
parent f1f2a7b33a
commit 993a6b2a2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 41 additions and 3 deletions

View File

@ -143,13 +143,19 @@ export class InjectedScript {
builtinSetTimeout(callback: Function, timeout: number) {
if (this.window.__pwClock?.builtin)
return this.window.__pwClock.builtin.setTimeout(callback, timeout);
return setTimeout(callback, timeout);
return this.window.setTimeout(callback, timeout);
}
builtinClearTimeout(timeout: number | undefined) {
if (this.window.__pwClock?.builtin)
return this.window.__pwClock.builtin.clearTimeout(timeout);
return this.window.clearTimeout(timeout);
}
builtinRequestAnimationFrame(callback: FrameRequestCallback) {
if (this.window.__pwClock?.builtin)
return this.window.__pwClock.builtin.requestAnimationFrame(callback);
return requestAnimationFrame(callback);
return this.window.requestAnimationFrame(callback);
}
eval(expression: string): any {
@ -1558,6 +1564,7 @@ declare global {
__pwClock?: {
builtin: {
setTimeout: Window['setTimeout'],
clearTimeout: Window['clearTimeout'],
requestAnimationFrame: Window['requestAnimationFrame'],
}
}

View File

@ -1092,7 +1092,7 @@ export class Recorder {
recreationInterval = this.injectedScript.builtinSetTimeout(recreate, 500);
};
recreationInterval = this.injectedScript.builtinSetTimeout(recreate, 500);
this._listeners.push(() => clearInterval(recreationInterval));
this._listeners.push(() => this.injectedScript.builtinClearTimeout(recreationInterval));
this.highlight.appendChild(createSvgElement(this.document, clipPaths));
this.overlay?.install();

View File

@ -269,6 +269,10 @@ function createRecorders(recorders: { recorder: Recorder, frameSelector: string
const recorder = new Recorder(injectedScript);
win._injectedScript = injectedScript;
win._recorder = { recorder, frameSelector: parentFrameSelector };
if (isUnderTest) {
(window as any)._weakRecordersForTest = (window as any)._weakRecordersForTest || new Set();
(window as any)._weakRecordersForTest.add(new WeakRef(recorder));
}
}
recorders.push(win._recorder);

View File

@ -1410,6 +1410,33 @@ test('should show baseURL in metadata pane', {
await expect(traceViewer.metadataTab).toContainText('baseURL:https://example.com');
});
test('should not leak recorders', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33086' },
}, async ({ showTraceViewer }) => {
const traceViewer = await showTraceViewer([traceFile]);
const counts = async () => {
return await traceViewer.page.evaluate(() => {
const weakSet = (window as any)._weakRecordersForTest || new Set();
const weakList = [...weakSet];
const aliveList = weakList.filter(r => !!r.deref());
return { total: weakList.length, alive: aliveList.length };
});
};
await traceViewer.snapshotFrame('page.goto');
await traceViewer.snapshotFrame('page.evaluate');
await traceViewer.page.requestGC();
await expect.poll(() => counts()).toEqual({ total: 4, alive: 1 });
await traceViewer.snapshotFrame('page.setContent');
await traceViewer.snapshotFrame('page.goto');
await traceViewer.snapshotFrame('page.evaluate');
await traceViewer.snapshotFrame('page.setContent');
await traceViewer.page.requestGC();
await expect.poll(() => counts()).toEqual({ total: 8, alive: 1 });
});
test('should serve css without content-type', async ({ page, runAndTrace, server }) => {
server.setRoute('/one-style.css', (req, res) => {
res.writeHead(200);