diff --git a/.changeset/nervous-actors-jam.md b/.changeset/nervous-actors-jam.md new file mode 100644 index 0000000000..641eff873d --- /dev/null +++ b/.changeset/nervous-actors-jam.md @@ -0,0 +1,5 @@ +--- +"rrweb": patch +--- + +fix: rrweb recorder may throw error when stopping recording after an iframe becomes cross-origin diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 1308c378a6..dbef08198f 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -617,7 +617,25 @@ function record( ); } 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 + src attribute is changed to a cross-origin URL, + * 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(); diff --git a/packages/rrweb/test/record.test.ts b/packages/rrweb/test/record.test.ts index cfba2b46a2..5ff1035975 100644 --- a/packages/rrweb/test/record.test.ts +++ b/packages/rrweb/test/record.test.ts @@ -990,6 +990,21 @@ 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'); + document.body.appendChild(iframe); + await new Promise((resolve) => setTimeout(resolve, 1000)); + iframe.src = 'https://www.example.com'; // Change the same origin iframe to a cross origin iframe after it's recorded + await new Promise((resolve) => setTimeout(resolve, 1000)); + stopRecord?.(); + }); + }); }); describe('record iframes', function (this: ISuite) {