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) { builtinSetTimeout(callback: Function, timeout: number) {
if (this.window.__pwClock?.builtin) if (this.window.__pwClock?.builtin)
return this.window.__pwClock.builtin.setTimeout(callback, timeout); 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) { builtinRequestAnimationFrame(callback: FrameRequestCallback) {
if (this.window.__pwClock?.builtin) if (this.window.__pwClock?.builtin)
return this.window.__pwClock.builtin.requestAnimationFrame(callback); return this.window.__pwClock.builtin.requestAnimationFrame(callback);
return requestAnimationFrame(callback); return this.window.requestAnimationFrame(callback);
} }
eval(expression: string): any { eval(expression: string): any {
@ -1558,6 +1564,7 @@ declare global {
__pwClock?: { __pwClock?: {
builtin: { builtin: {
setTimeout: Window['setTimeout'], setTimeout: Window['setTimeout'],
clearTimeout: Window['clearTimeout'],
requestAnimationFrame: Window['requestAnimationFrame'], requestAnimationFrame: Window['requestAnimationFrame'],
} }
} }

View File

@ -1092,7 +1092,7 @@ export class Recorder {
recreationInterval = this.injectedScript.builtinSetTimeout(recreate, 500); recreationInterval = this.injectedScript.builtinSetTimeout(recreate, 500);
}; };
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.highlight.appendChild(createSvgElement(this.document, clipPaths));
this.overlay?.install(); this.overlay?.install();

View File

@ -269,6 +269,10 @@ function createRecorders(recorders: { recorder: Recorder, frameSelector: string
const recorder = new Recorder(injectedScript); const recorder = new Recorder(injectedScript);
win._injectedScript = injectedScript; win._injectedScript = injectedScript;
win._recorder = { recorder, frameSelector: parentFrameSelector }; 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); 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'); 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 }) => { test('should serve css without content-type', async ({ page, runAndTrace, server }) => {
server.setRoute('/one-style.css', (req, res) => { server.setRoute('/one-style.css', (req, res) => {
res.writeHead(200); res.writeHead(200);