Handle negative ids in rrdom correctly + extra tests (#927)
* inline stylesheets when loaded * set empty link elements to loaded by default * Clean up stylesheet manager * Remove attribute mutation code * Update packages/rrweb/test/record.test.ts * Update packages/rrweb/test/record.test.ts * Update packages/rrweb/test/record.test.ts * Update packages/rrweb/scripts/repl.js * Update packages/rrweb/test/record.test.ts * Update packages/rrweb/src/record/index.ts * Add todo * Move require out of time sensitive assert * Add waitForRAF, its more reliable than waitForTimeout * Remove flaky tests * Add recording stylesheets in iframes * Remove variability from flaky test * Make test more robust * Fix naming * Add test cases for inlineImages * Add test cases for inlineImages * Record iframe mutations cross page * Test: should record images inside iframe with blob url after iframe was reloaded * Handle negative ids in rrdom correctly When iframes get inserted they create untracked elements, both on the dom and rrdom side. Because they are untracked they generate negative numbers when fetching the id from mirror. This creates a problem when comparing and fetching ids across mirrors. This commit tries to get away from using negative ids as much as possible in rrdom's comparisons * Update packages/rrdom/src/diff.ts Co-authored-by: Yun Feng <yun.feng@anu.edu.au> * Start unserialized nodes at -2 This way we don't accidentally think of them as mirror misses * Set unserialized id starting number at -2 * Remove duplication Co-authored-by: Yun Feng <yun.feng@anu.edu.au>
This commit is contained in:
@@ -272,37 +272,57 @@ function diffChildren(
|
||||
let oldIdToIndex: Record<number, number> | undefined = undefined,
|
||||
indexInOld;
|
||||
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
|
||||
const oldStartId = replayer.mirror.getId(oldStartNode);
|
||||
const oldEndId = replayer.mirror.getId(oldEndNode);
|
||||
const newStartId = rrnodeMirror.getId(newStartNode);
|
||||
const newEndId = rrnodeMirror.getId(newEndNode);
|
||||
|
||||
// rrdom contains elements with negative ids, we don't want to accidentally match those to a mirror mismatch (-1) id.
|
||||
// Negative oldStartId happen when nodes are not in the mirror, but are in the DOM.
|
||||
// eg.iframes come with a document, html, head and body nodes.
|
||||
// thats why below we always check if an id is negative.
|
||||
|
||||
if (oldStartNode === undefined) {
|
||||
oldStartNode = oldChildren[++oldStartIndex];
|
||||
} else if (oldEndNode === undefined) {
|
||||
oldEndNode = oldChildren[--oldEndIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldStartNode) === rrnodeMirror.getId(newStartNode)
|
||||
oldStartId !== -1 &&
|
||||
// same first element?
|
||||
oldStartId === newStartId
|
||||
) {
|
||||
diff(oldStartNode, newStartNode, replayer, rrnodeMirror);
|
||||
oldStartNode = oldChildren[++oldStartIndex];
|
||||
newStartNode = newChildren[++newStartIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldEndNode) === rrnodeMirror.getId(newEndNode)
|
||||
oldEndId !== -1 &&
|
||||
// same last element?
|
||||
oldEndId === newEndId
|
||||
) {
|
||||
diff(oldEndNode, newEndNode, replayer, rrnodeMirror);
|
||||
oldEndNode = oldChildren[--oldEndIndex];
|
||||
newEndNode = newChildren[--newEndIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldStartNode) === rrnodeMirror.getId(newEndNode)
|
||||
oldStartId !== -1 &&
|
||||
// is the first old element the same as the last new element?
|
||||
oldStartId === newEndId
|
||||
) {
|
||||
parentNode.insertBefore(oldStartNode, oldEndNode.nextSibling);
|
||||
diff(oldStartNode, newEndNode, replayer, rrnodeMirror);
|
||||
oldStartNode = oldChildren[++oldStartIndex];
|
||||
newEndNode = newChildren[--newEndIndex];
|
||||
} else if (
|
||||
replayer.mirror.getId(oldEndNode) === rrnodeMirror.getId(newStartNode)
|
||||
oldEndId !== -1 &&
|
||||
// is the last old element the same as the first new element?
|
||||
oldEndId === newStartId
|
||||
) {
|
||||
parentNode.insertBefore(oldEndNode, oldStartNode);
|
||||
diff(oldEndNode, newStartNode, replayer, rrnodeMirror);
|
||||
oldEndNode = oldChildren[--oldEndIndex];
|
||||
newStartNode = newChildren[++newStartIndex];
|
||||
} else {
|
||||
// none of the elements matched
|
||||
|
||||
if (!oldIdToIndex) {
|
||||
oldIdToIndex = {};
|
||||
for (let i = oldStartIndex; i <= oldEndIndex; i++) {
|
||||
@@ -378,8 +398,11 @@ export function createOrGetNode(
|
||||
domMirror: NodeMirror,
|
||||
rrnodeMirror: Mirror,
|
||||
): Node {
|
||||
let node = domMirror.getNode(rrnodeMirror.getId(rrNode));
|
||||
const nodeId = rrnodeMirror.getId(rrNode);
|
||||
const sn = rrnodeMirror.getMeta(rrNode);
|
||||
let node: Node | null = null;
|
||||
// negative ids shouldn't be compared accross mirrors
|
||||
if (nodeId > -1) node = domMirror.getNode(nodeId);
|
||||
if (node !== null) return node;
|
||||
switch (rrNode.RRNodeType) {
|
||||
case RRNodeType.Document:
|
||||
|
||||
@@ -33,10 +33,11 @@ import {
|
||||
} from './document';
|
||||
|
||||
export class RRDocument extends BaseRRDocumentImpl(RRNode) {
|
||||
private UNSERIALIZED_STARTING_ID = -2;
|
||||
// In the rrweb replayer, there are some unserialized nodes like the element that stores the injected style rules.
|
||||
// These unserialized nodes may interfere the execution of the diff algorithm.
|
||||
// The id of serialized node is larger than 0. So this value less than 0 is used as id for these unserialized nodes.
|
||||
private _unserializedId = -1;
|
||||
private _unserializedId = this.UNSERIALIZED_STARTING_ID;
|
||||
|
||||
/**
|
||||
* Every time the id is used, it will minus 1 automatically to avoid collisions.
|
||||
@@ -135,7 +136,7 @@ export class RRDocument extends BaseRRDocumentImpl(RRNode) {
|
||||
|
||||
open() {
|
||||
super.open();
|
||||
this._unserializedId = -1;
|
||||
this._unserializedId = this.UNSERIALIZED_STARTING_ID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,118 +1,118 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RRDocument for browser environment create a RRDocument from a html document can build from a common html 1`] = `
|
||||
"-1 RRDocument
|
||||
-2 RRDocumentType
|
||||
-3 HTML lang=\\"en\\"
|
||||
-4 HEAD
|
||||
-5 RRText text=\\"\\\\n \\"
|
||||
-6 META charset=\\"UTF-8\\"
|
||||
-7 RRText text=\\"\\\\n \\"
|
||||
-8 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-9 RRText text=\\"\\\\n \\"
|
||||
-10 TITLE
|
||||
-11 RRText text=\\"Main\\"
|
||||
-12 RRText text=\\"\\\\n \\"
|
||||
-13 LINK rel=\\"stylesheet\\" href=\\"somelink\\"
|
||||
-14 RRText text=\\"\\\\n \\"
|
||||
-15 STYLE
|
||||
-16 RRText text=\\"\\\\n h1 {\\\\n color: 'black';\\\\n }\\\\n .blocks {\\\\n padding: 0;\\\\n }\\\\n .blocks1 {\\\\n margin: 0;\\\\n }\\\\n #block1 {\\\\n width: 100px;\\\\n height: 200px;\\\\n }\\\\n @import url('main.css');\\\\n \\"
|
||||
-17 RRText text=\\"\\\\n \\"
|
||||
-18 RRText text=\\"\\\\n \\"
|
||||
-19 BODY
|
||||
-20 RRText text=\\"\\\\n \\"
|
||||
-21 H1
|
||||
-22 RRText text=\\"This is a h1 heading\\"
|
||||
-23 RRText text=\\"\\\\n \\"
|
||||
-24 H1 style=\\"font-size: 16px\\"
|
||||
-25 RRText text=\\"This is a h1 heading with styles\\"
|
||||
-26 RRText text=\\"\\\\n \\"
|
||||
-27 DIV id=\\"block1\\" class=\\"blocks blocks1\\"
|
||||
-28 RRText text=\\"\\\\n \\"
|
||||
-29 DIV id=\\"block2\\" class=\\"blocks blocks1 :hover\\"
|
||||
-30 RRText text=\\"\\\\n Text 1\\\\n \\"
|
||||
-31 DIV id=\\"block3\\"
|
||||
-32 RRText text=\\"\\\\n \\"
|
||||
-33 P
|
||||
-34 RRText text=\\"This is a paragraph\\"
|
||||
-35 RRText text=\\"\\\\n \\"
|
||||
-36 BUTTON
|
||||
-37 RRText text=\\"button1\\"
|
||||
-38 RRText text=\\"\\\\n \\"
|
||||
-39 RRText text=\\"\\\\n Text 2\\\\n \\"
|
||||
-40 RRText text=\\"\\\\n \\"
|
||||
-41 IMG src=\\"somelink\\" alt=\\"This is an image\\"
|
||||
-42 RRText text=\\"\\\\n \\"
|
||||
-43 RRComment text=\\" This is a line of comment \\"
|
||||
-44 RRText text=\\"\\\\n \\"
|
||||
-45 FORM
|
||||
-46 RRText text=\\"\\\\n \\"
|
||||
-47 INPUT type=\\"text\\" id=\\"input1\\"
|
||||
-48 RRText text=\\"\\\\n \\"
|
||||
-49 RRText text=\\"\\\\n \\"
|
||||
-50 RRText text=\\"\\\\n \\\\n\\\\n\\"
|
||||
"-2 RRDocument
|
||||
-3 RRDocumentType
|
||||
-4 HTML lang=\\"en\\"
|
||||
-5 HEAD
|
||||
-6 RRText text=\\"\\\\n \\"
|
||||
-7 META charset=\\"UTF-8\\"
|
||||
-8 RRText text=\\"\\\\n \\"
|
||||
-9 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-10 RRText text=\\"\\\\n \\"
|
||||
-11 TITLE
|
||||
-12 RRText text=\\"Main\\"
|
||||
-13 RRText text=\\"\\\\n \\"
|
||||
-14 LINK rel=\\"stylesheet\\" href=\\"somelink\\"
|
||||
-15 RRText text=\\"\\\\n \\"
|
||||
-16 STYLE
|
||||
-17 RRText text=\\"\\\\n h1 {\\\\n color: 'black';\\\\n }\\\\n .blocks {\\\\n padding: 0;\\\\n }\\\\n .blocks1 {\\\\n margin: 0;\\\\n }\\\\n #block1 {\\\\n width: 100px;\\\\n height: 200px;\\\\n }\\\\n @import url('main.css');\\\\n \\"
|
||||
-18 RRText text=\\"\\\\n \\"
|
||||
-19 RRText text=\\"\\\\n \\"
|
||||
-20 BODY
|
||||
-21 RRText text=\\"\\\\n \\"
|
||||
-22 H1
|
||||
-23 RRText text=\\"This is a h1 heading\\"
|
||||
-24 RRText text=\\"\\\\n \\"
|
||||
-25 H1 style=\\"font-size: 16px\\"
|
||||
-26 RRText text=\\"This is a h1 heading with styles\\"
|
||||
-27 RRText text=\\"\\\\n \\"
|
||||
-28 DIV id=\\"block1\\" class=\\"blocks blocks1\\"
|
||||
-29 RRText text=\\"\\\\n \\"
|
||||
-30 DIV id=\\"block2\\" class=\\"blocks blocks1 :hover\\"
|
||||
-31 RRText text=\\"\\\\n Text 1\\\\n \\"
|
||||
-32 DIV id=\\"block3\\"
|
||||
-33 RRText text=\\"\\\\n \\"
|
||||
-34 P
|
||||
-35 RRText text=\\"This is a paragraph\\"
|
||||
-36 RRText text=\\"\\\\n \\"
|
||||
-37 BUTTON
|
||||
-38 RRText text=\\"button1\\"
|
||||
-39 RRText text=\\"\\\\n \\"
|
||||
-40 RRText text=\\"\\\\n Text 2\\\\n \\"
|
||||
-41 RRText text=\\"\\\\n \\"
|
||||
-42 IMG src=\\"somelink\\" alt=\\"This is an image\\"
|
||||
-43 RRText text=\\"\\\\n \\"
|
||||
-44 RRComment text=\\" This is a line of comment \\"
|
||||
-45 RRText text=\\"\\\\n \\"
|
||||
-46 FORM
|
||||
-47 RRText text=\\"\\\\n \\"
|
||||
-48 INPUT type=\\"text\\" id=\\"input1\\"
|
||||
-49 RRText text=\\"\\\\n \\"
|
||||
-50 RRText text=\\"\\\\n \\"
|
||||
-51 RRText text=\\"\\\\n \\\\n\\\\n\\"
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`RRDocument for browser environment create a RRDocument from a html document can build from a html containing nested shadow doms 1`] = `
|
||||
"-1 RRDocument
|
||||
-2 RRDocumentType
|
||||
-3 HTML lang=\\"en\\"
|
||||
-4 HEAD
|
||||
-5 RRText text=\\"\\\\n \\"
|
||||
-6 META charset=\\"UTF-8\\"
|
||||
-7 RRText text=\\"\\\\n \\"
|
||||
-8 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-9 RRText text=\\"\\\\n \\"
|
||||
-10 TITLE
|
||||
-11 RRText text=\\"shadow dom\\"
|
||||
-12 RRText text=\\"\\\\n \\"
|
||||
-13 RRText text=\\"\\\\n \\"
|
||||
-14 BODY
|
||||
-15 RRText text=\\"\\\\n \\"
|
||||
-16 DIV
|
||||
-17 SHADOWROOT
|
||||
-18 RRText text=\\"\\\\n \\"
|
||||
-19 SPAN
|
||||
-20 RRText text=\\" shadow dom one \\"
|
||||
-21 RRText text=\\"\\\\n \\"
|
||||
-22 DIV
|
||||
-23 SHADOWROOT
|
||||
-24 RRText text=\\"\\\\n \\"
|
||||
-25 SPAN
|
||||
-26 RRText text=\\" shadow dom two \\"
|
||||
-27 RRText text=\\"\\\\n \\"
|
||||
-28 RRText text=\\"\\\\n \\\\n \\"
|
||||
-29 RRText text=\\"\\\\n \\"
|
||||
-30 RRText text=\\"\\\\n \\\\n \\"
|
||||
-31 RRText text=\\"\\\\n \\\\n\\\\n\\"
|
||||
"-2 RRDocument
|
||||
-3 RRDocumentType
|
||||
-4 HTML lang=\\"en\\"
|
||||
-5 HEAD
|
||||
-6 RRText text=\\"\\\\n \\"
|
||||
-7 META charset=\\"UTF-8\\"
|
||||
-8 RRText text=\\"\\\\n \\"
|
||||
-9 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-10 RRText text=\\"\\\\n \\"
|
||||
-11 TITLE
|
||||
-12 RRText text=\\"shadow dom\\"
|
||||
-13 RRText text=\\"\\\\n \\"
|
||||
-14 RRText text=\\"\\\\n \\"
|
||||
-15 BODY
|
||||
-16 RRText text=\\"\\\\n \\"
|
||||
-17 DIV
|
||||
-18 SHADOWROOT
|
||||
-19 RRText text=\\"\\\\n \\"
|
||||
-20 SPAN
|
||||
-21 RRText text=\\" shadow dom one \\"
|
||||
-22 RRText text=\\"\\\\n \\"
|
||||
-23 DIV
|
||||
-24 SHADOWROOT
|
||||
-25 RRText text=\\"\\\\n \\"
|
||||
-26 SPAN
|
||||
-27 RRText text=\\" shadow dom two \\"
|
||||
-28 RRText text=\\"\\\\n \\"
|
||||
-29 RRText text=\\"\\\\n \\\\n \\"
|
||||
-30 RRText text=\\"\\\\n \\"
|
||||
-31 RRText text=\\"\\\\n \\\\n \\"
|
||||
-32 RRText text=\\"\\\\n \\\\n\\\\n\\"
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`RRDocument for browser environment create a RRDocument from a html document can build from a xml page 1`] = `
|
||||
"-1 RRDocument
|
||||
-2 XML
|
||||
-3 RRCDATASection data=\\"Some <CDATA> data & then some\\"
|
||||
"-2 RRDocument
|
||||
-3 XML
|
||||
-4 RRCDATASection data=\\"Some <CDATA> data & then some\\"
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`RRDocument for browser environment create a RRDocument from a html document can build from an iframe html 1`] = `
|
||||
"-1 RRDocument
|
||||
-2 RRDocumentType
|
||||
-3 HTML lang=\\"en\\"
|
||||
-4 HEAD
|
||||
-5 RRText text=\\"\\\\n \\"
|
||||
-6 META charset=\\"UTF-8\\"
|
||||
-7 RRText text=\\"\\\\n \\"
|
||||
-8 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-9 RRText text=\\"\\\\n \\"
|
||||
-10 TITLE
|
||||
-11 RRText text=\\"Iframe\\"
|
||||
-12 RRText text=\\"\\\\n \\"
|
||||
-13 RRText text=\\"\\\\n \\"
|
||||
-14 BODY
|
||||
-15 RRText text=\\"\\\\n \\"
|
||||
-16 IFRAME id=\\"iframe1\\" srcdoc=\\"
|
||||
"-2 RRDocument
|
||||
-3 RRDocumentType
|
||||
-4 HTML lang=\\"en\\"
|
||||
-5 HEAD
|
||||
-6 RRText text=\\"\\\\n \\"
|
||||
-7 META charset=\\"UTF-8\\"
|
||||
-8 RRText text=\\"\\\\n \\"
|
||||
-9 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-10 RRText text=\\"\\\\n \\"
|
||||
-11 TITLE
|
||||
-12 RRText text=\\"Iframe\\"
|
||||
-13 RRText text=\\"\\\\n \\"
|
||||
-14 RRText text=\\"\\\\n \\"
|
||||
-15 BODY
|
||||
-16 RRText text=\\"\\\\n \\"
|
||||
-17 IFRAME id=\\"iframe1\\" srcdoc=\\"
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8' />
|
||||
@@ -126,35 +126,35 @@ exports[`RRDocument for browser environment create a RRDocument from a html docu
|
||||
<iframe id='iframe3' srcdoc='<div>This is a block inside the iframe3.</div>'>
|
||||
</body>
|
||||
</html>\\"
|
||||
-17 RRDocument
|
||||
-18 HTML
|
||||
-19 HEAD
|
||||
-20 RRText text=\\"\\\\n \\"
|
||||
-21 META charset=\\"UTF-8\\"
|
||||
-22 RRText text=\\"\\\\n \\"
|
||||
-23 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-24 RRText text=\\"\\\\n \\"
|
||||
-25 RRText text=\\"\\\\n \\"
|
||||
-26 BODY
|
||||
-27 RRText text=\\"\\\\n \\"
|
||||
-28 DIV
|
||||
-29 RRText text=\\"This is a block inside the iframe1.\\"
|
||||
-30 RRText text=\\"\\\\n \\"
|
||||
-31 IFRAME id=\\"iframe3\\" srcdoc=\\"<div>This is a block inside the iframe3.</div>\\"
|
||||
-32 RRDocument
|
||||
-33 HTML
|
||||
-34 HEAD
|
||||
-35 BODY
|
||||
-36 DIV
|
||||
-37 RRText text=\\"This is a block inside the iframe3.\\"
|
||||
-38 RRText text=\\"\\\\n \\"
|
||||
-39 IFRAME id=\\"iframe2\\" srcdoc=\\"<div>This is a block inside the iframe2.</div>\\"
|
||||
-40 RRDocument
|
||||
-41 HTML
|
||||
-42 HEAD
|
||||
-43 BODY
|
||||
-44 DIV
|
||||
-45 RRText text=\\"This is a block inside the iframe2.\\"
|
||||
-46 RRText text=\\"\\\\n \\\\n\\\\n\\"
|
||||
-18 RRDocument
|
||||
-19 HTML
|
||||
-20 HEAD
|
||||
-21 RRText text=\\"\\\\n \\"
|
||||
-22 META charset=\\"UTF-8\\"
|
||||
-23 RRText text=\\"\\\\n \\"
|
||||
-24 META name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"
|
||||
-25 RRText text=\\"\\\\n \\"
|
||||
-26 RRText text=\\"\\\\n \\"
|
||||
-27 BODY
|
||||
-28 RRText text=\\"\\\\n \\"
|
||||
-29 DIV
|
||||
-30 RRText text=\\"This is a block inside the iframe1.\\"
|
||||
-31 RRText text=\\"\\\\n \\"
|
||||
-32 IFRAME id=\\"iframe3\\" srcdoc=\\"<div>This is a block inside the iframe3.</div>\\"
|
||||
-33 RRDocument
|
||||
-34 HTML
|
||||
-35 HEAD
|
||||
-36 BODY
|
||||
-37 DIV
|
||||
-38 RRText text=\\"This is a block inside the iframe3.\\"
|
||||
-39 RRText text=\\"\\\\n \\"
|
||||
-40 IFRAME id=\\"iframe2\\" srcdoc=\\"<div>This is a block inside the iframe2.</div>\\"
|
||||
-41 RRDocument
|
||||
-42 HTML
|
||||
-43 HEAD
|
||||
-44 BODY
|
||||
-45 DIV
|
||||
-46 RRText text=\\"This is a block inside the iframe2.\\"
|
||||
-47 RRText text=\\"\\\\n \\\\n\\\\n\\"
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -1074,6 +1074,112 @@ describe('diff algorithm for rrdom', () => {
|
||||
expect(element.nodeType).toBe(element.DOCUMENT_TYPE_NODE);
|
||||
expect(mirror.getId(element)).toEqual(1);
|
||||
});
|
||||
|
||||
it('should remove children from document before adding new nodes 2', () => {
|
||||
document.write('<html><iframe></iframe></html>');
|
||||
|
||||
const iframe = document.querySelector('iframe')!;
|
||||
// Remove everthing from the iframe but the root html element
|
||||
// `buildNodeWithSn` injects docType elements to trigger compatMode in iframes
|
||||
iframe.contentDocument!.write(
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "">',
|
||||
);
|
||||
|
||||
replayer.mirror.add(iframe.contentDocument!, {
|
||||
id: 1,
|
||||
type: 0,
|
||||
childNodes: [
|
||||
{
|
||||
id: 2,
|
||||
rootId: 1,
|
||||
type: 2,
|
||||
tagName: 'html',
|
||||
childNodes: [],
|
||||
attributes: {},
|
||||
},
|
||||
],
|
||||
} as serializedNodeWithId);
|
||||
replayer.mirror.add(iframe.contentDocument!.childNodes[0], {
|
||||
id: 2,
|
||||
rootId: 1,
|
||||
type: 2,
|
||||
tagName: 'html',
|
||||
childNodes: [],
|
||||
attributes: {},
|
||||
} as serializedNodeWithId);
|
||||
|
||||
const rrDocument = new RRDocument();
|
||||
rrDocument.mirror.add(rrDocument, getDefaultSN(rrDocument, 1));
|
||||
const docType = rrDocument.createDocumentType('html', '', '');
|
||||
rrDocument.mirror.add(docType, getDefaultSN(docType, 2));
|
||||
rrDocument.appendChild(docType);
|
||||
const htmlEl = rrDocument.createElement('html');
|
||||
rrDocument.mirror.add(htmlEl, getDefaultSN(htmlEl, 3));
|
||||
rrDocument.appendChild(htmlEl);
|
||||
const styleEl = rrDocument.createElement('style');
|
||||
rrDocument.mirror.add(styleEl, getDefaultSN(styleEl, 4));
|
||||
htmlEl.appendChild(styleEl);
|
||||
const headEl = rrDocument.createElement('head');
|
||||
rrDocument.mirror.add(headEl, getDefaultSN(headEl, 5));
|
||||
htmlEl.appendChild(headEl);
|
||||
const bodyEl = rrDocument.createElement('body');
|
||||
rrDocument.mirror.add(bodyEl, getDefaultSN(bodyEl, 6));
|
||||
htmlEl.appendChild(bodyEl);
|
||||
|
||||
diff(iframe.contentDocument!, rrDocument, replayer);
|
||||
expect(iframe.contentDocument!.childNodes.length).toBe(2);
|
||||
const element = iframe.contentDocument!.childNodes[0] as HTMLElement;
|
||||
expect(element.nodeType).toBe(element.DOCUMENT_TYPE_NODE);
|
||||
expect(mirror.getId(element)).toEqual(2);
|
||||
});
|
||||
|
||||
it('should remove children from document before adding new nodes 3', () => {
|
||||
document.write('<html><body><iframe></iframe></body></html>');
|
||||
|
||||
const iframeInDom = document.querySelector('iframe')!;
|
||||
|
||||
replayer.mirror.add(iframeInDom, {
|
||||
id: 3,
|
||||
type: 2,
|
||||
rootId: 1,
|
||||
tagName: 'iframe',
|
||||
childNodes: [],
|
||||
attributes: {},
|
||||
} as serializedNodeWithId);
|
||||
replayer.mirror.add(iframeInDom.contentDocument!, {
|
||||
id: 4,
|
||||
type: 0,
|
||||
childNodes: [],
|
||||
} as serializedNodeWithId);
|
||||
|
||||
const rrDocument = new RRDocument();
|
||||
|
||||
const rrIframeEl = rrDocument.createElement('iframe');
|
||||
rrDocument.mirror.add(rrIframeEl, getDefaultSN(rrIframeEl, 3));
|
||||
rrDocument.appendChild(rrIframeEl);
|
||||
rrDocument.mirror.add(
|
||||
rrIframeEl.contentDocument!,
|
||||
getDefaultSN(rrIframeEl.contentDocument!, 4),
|
||||
);
|
||||
|
||||
const rrDocType = rrDocument.createDocumentType('html', '', '');
|
||||
rrIframeEl.contentDocument.appendChild(rrDocType);
|
||||
const rrHtmlEl = rrDocument.createElement('html');
|
||||
rrDocument.mirror.add(rrHtmlEl, getDefaultSN(rrHtmlEl, 6));
|
||||
rrIframeEl.contentDocument.appendChild(rrHtmlEl);
|
||||
const rrHeadEl = rrDocument.createElement('head');
|
||||
rrDocument.mirror.add(rrHeadEl, getDefaultSN(rrHeadEl, 8));
|
||||
rrHtmlEl.appendChild(rrHeadEl);
|
||||
const bodyEl = rrDocument.createElement('body');
|
||||
rrDocument.mirror.add(bodyEl, getDefaultSN(bodyEl, 9));
|
||||
rrHtmlEl.appendChild(bodyEl);
|
||||
|
||||
diff(iframeInDom, rrIframeEl, replayer);
|
||||
expect(iframeInDom.contentDocument!.childNodes.length).toBe(2);
|
||||
const element = iframeInDom.contentDocument!.childNodes[0] as HTMLElement;
|
||||
expect(element.nodeType).toBe(element.DOCUMENT_TYPE_NODE);
|
||||
expect(mirror.getId(element)).toEqual(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create or get a Node', () => {
|
||||
|
||||
@@ -78,24 +78,24 @@ describe('RRDocument for browser environment', () => {
|
||||
const rrdom = new RRDocument();
|
||||
let rrNode = buildFromNode(document, rrdom, mirror)!;
|
||||
expect(mirror.getMeta(document)).toBeDefined();
|
||||
expect(mirror.getId(document)).toEqual(-1);
|
||||
expect(mirror.getId(document)).toEqual(-2);
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Document);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-1);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-2);
|
||||
expect(rrNode).toBe(rrdom);
|
||||
|
||||
// build from document type
|
||||
expect(mirror.getMeta(document.doctype!)).toBeNull();
|
||||
rrNode = buildFromNode(document.doctype!, rrdom, mirror)!;
|
||||
expect(mirror.getMeta(document.doctype!)).toBeDefined();
|
||||
expect(mirror.getId(document.doctype)).toEqual(-2);
|
||||
expect(mirror.getId(document.doctype)).toEqual(-3);
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(
|
||||
RRNodeType.DocumentType,
|
||||
);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-2);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-3);
|
||||
|
||||
// build from element
|
||||
expect(mirror.getMeta(document.documentElement)).toBeNull();
|
||||
@@ -105,33 +105,33 @@ describe('RRDocument for browser environment', () => {
|
||||
mirror,
|
||||
)!;
|
||||
expect(mirror.getMeta(document.documentElement)).toBeDefined();
|
||||
expect(mirror.getId(document.documentElement)).toEqual(-3);
|
||||
expect(mirror.getId(document.documentElement)).toEqual(-4);
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Element);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-3);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-4);
|
||||
|
||||
// build from text
|
||||
const text = document.createTextNode('text');
|
||||
expect(mirror.getMeta(text)).toBeNull();
|
||||
rrNode = buildFromNode(text, rrdom, mirror)!;
|
||||
expect(mirror.getMeta(text)).toBeDefined();
|
||||
expect(mirror.getId(text)).toEqual(-4);
|
||||
expect(mirror.getId(text)).toEqual(-5);
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Text);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-4);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-5);
|
||||
|
||||
// build from comment
|
||||
const comment = document.createComment('comment');
|
||||
expect(mirror.getMeta(comment)).toBeNull();
|
||||
rrNode = buildFromNode(comment, rrdom, mirror)!;
|
||||
expect(mirror.getMeta(comment)).toBeDefined();
|
||||
expect(mirror.getId(comment)).toEqual(-5);
|
||||
expect(mirror.getId(comment)).toEqual(-6);
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Comment);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-5);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-6);
|
||||
|
||||
// build from CDATASection
|
||||
const xmlDoc = new DOMParser().parseFromString(
|
||||
@@ -144,11 +144,11 @@ describe('RRDocument for browser environment', () => {
|
||||
expect(mirror.getMeta(cdataSection)).toBeNull();
|
||||
rrNode = buildFromNode(cdataSection, rrdom, mirror)!;
|
||||
expect(mirror.getMeta(cdataSection)).toBeDefined();
|
||||
expect(mirror.getId(cdataSection)).toEqual(-6);
|
||||
expect(mirror.getId(cdataSection)).toEqual(-7);
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.CDATA);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-6);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-7);
|
||||
expect(rrNode.textContent).toEqual(cdata);
|
||||
});
|
||||
|
||||
@@ -184,8 +184,8 @@ describe('RRDocument for browser environment', () => {
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getMeta(rrNode)!.type).toEqual(RRNodeType.Document);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-1);
|
||||
expect(mirror.getId(iframe.contentDocument)).toEqual(-1);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-2);
|
||||
expect(mirror.getId(iframe.contentDocument)).toEqual(-2);
|
||||
expect(rrNode).toBe(RRIFrame.contentDocument);
|
||||
});
|
||||
|
||||
@@ -203,8 +203,8 @@ describe('RRDocument for browser environment', () => {
|
||||
)!;
|
||||
expect(rrNode).not.toBeNull();
|
||||
expect(rrdom.mirror.getMeta(rrNode)).toBeDefined();
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-1);
|
||||
expect(mirror.getId(div.shadowRoot)).toEqual(-1);
|
||||
expect(rrdom.mirror.getId(rrNode)).toEqual(-2);
|
||||
expect(mirror.getId(div.shadowRoot)).toEqual(-2);
|
||||
expect(rrNode.RRNodeType).toEqual(RRNodeType.Element);
|
||||
expect((rrNode as RRElement).tagName).toEqual('SHADOWROOT');
|
||||
expect(rrNode).toBe(parentRRNode.shadowRoot);
|
||||
@@ -296,7 +296,7 @@ describe('RRDocument for browser environment', () => {
|
||||
describe('RRDocument build for virtual dom', () => {
|
||||
it('can access a unique, decremented unserializedId every time', () => {
|
||||
const node = new RRDocument();
|
||||
for (let i = 1; i <= 100; i++) expect(node.unserializedId).toBe(-i);
|
||||
for (let i = 2; i <= 100; i++) expect(node.unserializedId).toBe(-i);
|
||||
});
|
||||
|
||||
it('can create a new RRDocument', () => {
|
||||
@@ -357,12 +357,12 @@ describe('RRDocument for browser environment', () => {
|
||||
const documentType = dom.createDocumentType('html', '', '');
|
||||
dom.appendChild(documentType);
|
||||
expect(dom.childNodes[0]).toBe(documentType);
|
||||
expect(dom.unserializedId).toBe(-1);
|
||||
expect(dom.unserializedId).toBe(-2);
|
||||
expect(dom.unserializedId).toBe(-3);
|
||||
expect(dom.close());
|
||||
expect(dom.open());
|
||||
expect(dom.childNodes.length).toEqual(0);
|
||||
expect(dom.unserializedId).toBe(-1);
|
||||
expect(dom.unserializedId).toBe(-2);
|
||||
});
|
||||
|
||||
it('can execute a dummy getContext function in RRCanvasElement', () => {
|
||||
|
||||
Reference in New Issue
Block a user