fix: rrweb recorder may throw error when stopping recording after an iframe becomes cross-origin (#1695)

* fix: rrweb recorder may throw error when stopping recording after an iframe becomes cross-origin

* add change set

* add failure message check

* Update packages/rrweb/src/record/index.ts

Co-authored-by: Eoghan Murray <eoghan@getthere.ie>

* remove settimeout

---------

Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
This commit is contained in:
Yun Feng
2025-08-05 00:45:57 -07:00
committed by GitHub
parent 4db9782d12
commit fc390a954c
3 changed files with 45 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"rrweb": patch
---
fix: rrweb recorder may throw error when stopping recording after an iframe becomes cross-origin

View File

@@ -617,7 +617,25 @@ function record<T = eventWithTime>(
);
}
return () => {
handlers.forEach((h) => h());
handlers.forEach((handler) => {
try {
handler();
} catch (error) {
const msg = String(error).toLowerCase();
/**
* https://github.com/rrweb-io/rrweb/pull/1695
* This error can occur in a known scenario:
* If an iframe is initially same-origin and observed, but later its
location is changed in an opaque way to a cross-origin URL (perhaps within the iframe via its `document.location` or a redirect)
* attempting to execute the handler in the stop record function will
throw a "cannot access cross-origin frame" error.
* This error is expected and can be safely ignored.
*/
if (!msg.includes('cross-origin')) {
console.warn(error);
}
}
});
processedNodeManager.destroy();
recording = false;
unregisterErrorHandler();

View File

@@ -990,6 +990,27 @@ describe('record', function (this: ISuite) {
await assertSnapshot(ctx.events);
});
it('does not throw error when stopping recording after iframe becomes cross-origin', async () => {
await ctx.page.evaluate(async () => {
const { record } = (window as unknown as IWindow).rrweb;
const stopRecord = record({
emit: (window as unknown as IWindow).emit,
});
const iframe = document.createElement('iframe');
(window as any).stopRecord = stopRecord;
(window as any).iframe = iframe;
document.body.appendChild(iframe);
});
await waitForRAF(ctx.page);
await ctx.page.evaluate(async () => {
(window as any).iframe.src = 'https://www.example.com'; // Change the same origin iframe to a cross origin iframe after it's recorded
});
await waitForRAF(ctx.page);
await ctx.page.evaluate(() => {
(window as any).stopRecord?.();
});
});
});
describe('record iframes', function (this: ISuite) {