Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dark-rooms-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rrweb-snapshot": patch
---

Omit the `srcdoc` attribute when rebuilding iframe elements, so the browser doesn't race its own async document load against rrweb's own reconstruction of the iframe's contents (which could desync the mirror and throw `insertBefore` errors)
13 changes: 13 additions & 0 deletions packages/rrweb-snapshot/src/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,19 @@ function buildNode(
'rrweb-original-srcset',
n.attributes.srcset as string,
);
} else if (tagName === 'iframe' && name === 'srcdoc') {
/**
* Setting `srcdoc` makes the browser asynchronously parse and load
* its own document into the iframe, racing against (and getting
* clobbered by or clobbering) the document we reconstruct for this
* iframe from its separately-recorded child nodes/mutations. That
* race can leave the mirror out of sync with the live DOM, causing
* later mutations to target nodes that no longer exist (e.g.
* insertBefore throwing "parameter 1 is not of type 'Node'").
* Omit it; our own reconstruction is the source of truth.
* @see https://github.com/rrweb-io/rrweb/issues/1736
*/
// ignore
} else {
node.setAttribute(name, value.toString());
}
Expand Down
32 changes: 32 additions & 0 deletions packages/rrweb-snapshot/test/rebuild.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,38 @@ describe('rebuild', function () {
});
});

describe('iframe srcdoc', function () {
it('omits the srcdoc attribute so the browser does not load its own document into the iframe', function () {
/**
* Setting `srcdoc` on a live iframe makes the browser asynchronously
* parse and load that markup into the iframe's contentDocument, racing
* against rrweb's own reconstruction of the iframe's document (built
* separately from recorded child nodes/mutations). That race can
* desync the mirror from the live DOM, later causing mutations to
* target nodes that no longer exist.
* @see https://github.com/rrweb-io/rrweb/issues/1736
*/
const node = buildNodeWithSN(
{
id: 1,
tagName: 'iframe',
type: NodeType.Element,
attributes: {
srcdoc: '<html><body>hi</body></html>',
},
childNodes: [],
},
{
doc: document,
mirror,
hackCss: false,
cache,
},
) as HTMLIFrameElement;
expect(node.hasAttribute('srcdoc')).toBe(false);
});
});

describe('rr_width/rr_height', function () {
it('rebuild blocked element with correct dimensions', function () {
const node = buildNodeWithSN(
Expand Down