Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8069055
Remove INode (`node.__sn`) and use Mirror as source of truth (#868)
Juice10 Apr 6, 2022
627d54f
Fix mutation edge case when blocked class gets unblocked (#867)
rahulrelicx Apr 15, 2022
1c851c9
Record canvas snapshots N times per second (#859)
Juice10 Apr 18, 2022
94e0007
Test: Stylesheet append text node (#886)
Juice10 May 5, 2022
b49f505
Fix for issue #890 (#891)
dkozlovskyi May 9, 2022
c3de806
Perf: Apply the latest text mutation only (#885)
Juice10 May 9, 2022
0e91e84
* rrdom: add a diff function for properties
Juice10 May 12, 2022
4711692
Introduce benchmark tests and improve snapshot attributes traversing …
Yuyz0112 May 14, 2022
dc64d49
Chore: Add issue/pr template and general housekeeping tools and docs …
Juice10 May 22, 2022
1d7fcbd
Fix #904 (#906)
Juice10 May 31, 2022
11cd5ca
inline stylesheets when loaded
Juice10 May 31, 2022
3514b4f
set empty link elements to loaded by default
Juice10 Jun 1, 2022
1dcd7ea
Clean up stylesheet manager
Juice10 Jun 1, 2022
a345326
Remove attribute mutation code
Juice10 Jun 2, 2022
e7e78fb
Bump minimist from 1.2.5 to 1.2.6 (#902)
dependabot[bot] Jun 5, 2022
e0215fe
Speed up snapshotting of many new dom nodes (#903)
Juice10 Jun 6, 2022
1e784f6
highlight fixes after merge
Vadman97 Jun 2, 2022
2637be9
cleanup script
Vadman97 Jun 9, 2022
2cc2b5f
bump versions
Vadman97 Jun 9, 2022
3df6929
update version dependencies
Vadman97 Jun 10, 2022
789073d
fix rebuild not restoring inlined images
Vadman97 Jun 10, 2022
5d0842f
inline stylesheets when loaded
Juice10 May 31, 2022
8d38f64
fixup! inline stylesheets when loaded
Vadman97 Jun 10, 2022
9c64602
avoid crash on rrdom diff issues
Vadman97 Jun 13, 2022
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
Next Next commit
Remove INode (node.__sn) and use Mirror as source of truth (#868)
* Move ids to weakmap

* Fix typo

* Move from INode to storing serialized data in mirror

* Update packages/rrweb-snapshot/src/rebuild.ts

Co-authored-by: Yun Feng <[email protected]>

* Remove unnessisary `as Node` typecastings

Fixes: rrweb-io/rrweb#868 (comment)

* Remove unnessisary `as unknown as ...`

* Remove unnessisary `as unknown as ...`

* Reset mirror when recording starts

Solves: rrweb-io/rrweb#868 (comment)

* API has changed for snapshot, change test to reflect that

* Allow for es5 compatibility

* Remove unnessisary as unknown as ... and change test to reflect the API change

* Refactor mirror to remove `nodeIdMap`

Fixes: rrweb-io/rrweb#868 (comment)

Co-authored-by: Yun Feng <[email protected]>
  • Loading branch information
2 people authored and Vadman97 committed Jun 2, 2022
commit 8069055ea73c01b19d8174c318fc24f4af8de7d3
2 changes: 1 addition & 1 deletion packages/rrweb-snapshot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ There are several things will be done during rebuild:

#### buildNodeWithSN

`buildNodeWithSN` will build DOM from serialized node and store serialized information in `__sn` property.
`buildNodeWithSN` will build DOM from serialized node and store serialized information in the `mirror.getMeta(node)`.
2 changes: 1 addition & 1 deletion packages/rrweb-snapshot/src/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ function addParent(obj: Stylesheet, parent?: Stylesheet) {
addParent(v, childParent);
});
} else if (value && typeof value === 'object') {
addParent((value as unknown) as Stylesheet, childParent);
addParent(value as Stylesheet, childParent);
}
}

Expand Down
69 changes: 38 additions & 31 deletions packages/rrweb-snapshot/src/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import {
NodeType,
tagMap,
elementNode,
idNodeMap,
INode,
BuildCache,
} from './types';
import { isElement } from './utils';
import { isElement, Mirror } from './utils';

const tagMap: tagMap = {
script: 'noscript',
Expand Down Expand Up @@ -262,7 +260,10 @@ function buildNode(
n.attributes.rr_dataURL
) {
// backup original img srcset
node.setAttribute('rrweb-original-srcset', n.attributes.srcset as string);
node.setAttribute(
'rrweb-original-srcset',
n.attributes.srcset as string,
);
} else {
node.setAttribute(name, value);
}
Expand Down Expand Up @@ -354,16 +355,16 @@ export function buildNodeWithSN(
n: serializedNodeWithId,
options: {
doc: Document;
map: idNodeMap;
mirror: Mirror;
skipChild?: boolean;
hackCss: boolean;
afterAppend?: (n: INode) => unknown;
afterAppend?: (n: Node) => unknown;
cache: BuildCache;
},
): INode | null {
): Node | null {
const {
doc,
map,
mirror,
skipChild = false,
hackCss = true,
afterAppend,
Expand All @@ -375,7 +376,7 @@ export function buildNodeWithSN(
}
if (n.rootId) {
console.assert(
((map[n.rootId] as unknown) as Document) === doc,
(mirror.getNode(n.rootId) as Document) === doc,
'Target document should has the same root id.',
);
}
Expand Down Expand Up @@ -409,8 +410,7 @@ export function buildNodeWithSN(
node = doc;
}

(node as INode).__sn = n;
map[n.id] = node as INode;
mirror.add(node, n);

if (
(n.type === NodeType.Document || n.type === NodeType.Element) &&
Expand All @@ -419,7 +419,7 @@ export function buildNodeWithSN(
for (const childN of n.childNodes) {
const childNode = buildNodeWithSN(childN, {
doc,
map,
mirror,
skipChild: false,
hackCss,
afterAppend,
Expand All @@ -441,27 +441,27 @@ export function buildNodeWithSN(
}
}

return node as INode;
return node;
}

function visit(idNodeMap: idNodeMap, onVisit: (node: INode) => void) {
function walk(node: INode) {
function visit(mirror: Mirror, onVisit: (node: Node) => void) {
function walk(node: Node) {
onVisit(node);
}

for (const key in idNodeMap) {
if (idNodeMap[key]) {
walk(idNodeMap[key]);
for (const id of mirror.getIds()) {
if (mirror.has(id)) {
walk(mirror.getNode(id)!);
}
}
}

function handleScroll(node: INode) {
const n = node.__sn;
if (n.type !== NodeType.Element) {
function handleScroll(node: Node, mirror: Mirror) {
const n = mirror.getMeta(node);
if (n?.type !== NodeType.Element) {
return;
}
const el = (node as Node) as HTMLElement;
const el = node as HTMLElement;
for (const name in n.attributes) {
if (!(n.attributes.hasOwnProperty(name) && name.startsWith('rr_'))) {
continue;
Expand All @@ -480,29 +480,36 @@ function rebuild(
n: serializedNodeWithId,
options: {
doc: Document;
onVisit?: (node: INode) => unknown;
onVisit?: (node: Node) => unknown;
hackCss?: boolean;
afterAppend?: (n: INode) => unknown;
afterAppend?: (n: Node) => unknown;
cache: BuildCache;
mirror: Mirror;
},
): [Node | null, idNodeMap] {
const { doc, onVisit, hackCss = true, afterAppend, cache } = options;
const idNodeMap: idNodeMap = {};
): Node | null {
const {
doc,
onVisit,
hackCss = true,
afterAppend,
cache,
mirror = new Mirror(),
} = options;
const node = buildNodeWithSN(n, {
doc,
map: idNodeMap,
mirror,
skipChild: false,
hackCss,
afterAppend,
cache,
});
visit(idNodeMap, (visitedNode) => {
visit(mirror, (visitedNode) => {
if (onVisit) {
onVisit(visitedNode);
}
handleScroll(visitedNode);
handleScroll(visitedNode, mirror);
});
return [node, idNodeMap];
return node;
}

export default rebuild;
74 changes: 41 additions & 33 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import {
serializedNodeWithId,
NodeType,
attributes,
INode,
idNodeMap,
MaskInputOptions,
SlimDOMOptions,
DataURLOptions,
Expand All @@ -14,6 +12,7 @@ import {
ICanvas,
} from './types';
import {
Mirror,
is2DCanvasBlank,
isElement,
isShadowRoot,
Expand Down Expand Up @@ -377,6 +376,7 @@ function serializeNode(
n: Node,
options: {
doc: Document;
mirror: Mirror;
blockClass: string | RegExp;
blockSelector: string | null;
maskTextClass: string | RegExp;
Expand All @@ -396,6 +396,7 @@ function serializeNode(
): serializedNode | false {
const {
doc,
mirror,
blockClass,
blockSelector,
maskTextClass,
Expand All @@ -412,8 +413,8 @@ function serializeNode(
} = options;
// Only record root id when document object is not the base document
let rootId: number | undefined;
if (((doc as unknown) as INode).__sn) {
const docId = ((doc as unknown) as INode).__sn.id;
if (mirror.getMeta(doc)) {
const docId = mirror.getId(doc);
rootId = docId === 1 ? undefined : docId;
}
switch (n.nodeType) {
Expand Down Expand Up @@ -815,10 +816,10 @@ function slimDOMExcluded(
}

export function serializeNodeWithId(
n: Node | INode,
n: Node,
options: {
doc: Document;
map: idNodeMap;
mirror: Mirror;
blockClass: string | RegExp;
blockSelector: string | null;
maskTextClass: string | RegExp;
Expand All @@ -834,15 +835,18 @@ export function serializeNodeWithId(
inlineImages?: boolean;
recordCanvas?: boolean;
preserveWhiteSpace?: boolean;
onSerialize?: (n: INode) => unknown;
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
onSerialize?: (n: Node) => unknown;
onIframeLoad?: (
iframeNode: HTMLIFrameElement,
node: serializedNodeWithId,
) => unknown;
iframeLoadTimeout?: number;
enableStrictPrivacy: boolean;
},
): serializedNodeWithId | null {
const {
doc,
map,
mirror,
blockClass,
blockSelector,
maskTextClass,
Expand All @@ -865,6 +869,7 @@ export function serializeNodeWithId(
let { preserveWhiteSpace = true } = options;
const _serializedNode = serializeNode(n, {
doc,
mirror,
blockClass,
blockSelector,
maskTextClass,
Expand All @@ -885,10 +890,10 @@ export function serializeNodeWithId(
return null;
}

let id;
// Try to reuse the previous id
if ('__sn' in n) {
id = n.__sn.id;
let id: number | undefined;
if (mirror.hasNode(n)) {
// Reuse the previous id
id = mirror.getId(n);
} else if (
slimDOMExcluded(_serializedNode, slimDOMOptions) ||
(!preserveWhiteSpace &&
Expand All @@ -900,14 +905,16 @@ export function serializeNodeWithId(
} else {
id = genId();
}
const serializedNode = Object.assign(_serializedNode, { id });
(n as INode).__sn = serializedNode;
if (id === IGNORED_NODE) {
return null; // slimDOM
}
map[id] = n as INode;

const serializedNode = Object.assign(_serializedNode, { id });

mirror.add(n, serializedNode);

if (onSerialize) {
onSerialize(n as INode);
onSerialize(n);
}
let recordChild = !skipChild;
if (serializedNode.type === NodeType.Element) {
Expand All @@ -933,15 +940,15 @@ export function serializeNodeWithId(
) {
if (
slimDOMOptions.headWhitespace &&
_serializedNode.type === NodeType.Element &&
_serializedNode.tagName === 'head'
serializedNode.type === NodeType.Element &&
serializedNode.tagName === 'head'
// would impede performance: || getComputedStyle(n)['white-space'] === 'normal'
) {
preserveWhiteSpace = false;
}
const bypassOptions = {
doc,
map,
mirror,
blockClass,
blockSelector,
maskTextClass,
Expand Down Expand Up @@ -995,7 +1002,7 @@ export function serializeNodeWithId(
if (iframeDoc && onIframeLoad) {
const serializedIframeNode = serializeNodeWithId(iframeDoc, {
doc: iframeDoc,
map,
mirror,
blockClass,
blockSelector,
maskTextClass,
Expand All @@ -1018,7 +1025,7 @@ export function serializeNodeWithId(
});

if (serializedIframeNode) {
onIframeLoad(n as INode, serializedIframeNode);
onIframeLoad(n as HTMLIFrameElement, serializedIframeNode);
}
}
},
Expand All @@ -1032,6 +1039,7 @@ export function serializeNodeWithId(
function snapshot(
n: Document,
options?: {
mirror?: Mirror;
blockClass?: string | RegExp;
blockSelector?: string | null;
maskTextClass?: string | RegExp;
Expand All @@ -1045,14 +1053,18 @@ function snapshot(
inlineImages?: boolean;
recordCanvas?: boolean;
preserveWhiteSpace?: boolean;
onSerialize?: (n: INode) => unknown;
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
onSerialize?: (n: Node) => unknown;
onIframeLoad?: (
iframeNode: HTMLIFrameElement,
node: serializedNodeWithId,
) => unknown;
iframeLoadTimeout?: number;
keepIframeSrcFn?: KeepIframeSrcFn;
enableStrictPrivacy: boolean;
},
): [serializedNodeWithId | null, idNodeMap] {
): serializedNodeWithId | null {
const {
mirror = new Mirror(),
blockClass = 'highlight-block',
blockSelector = null,
maskTextClass = 'highlight-mask',
Expand All @@ -1072,7 +1084,6 @@ function snapshot(
keepIframeSrcFn = () => false,
enableStrictPrivacy = false,
} = options || {};
const idNodeMap: idNodeMap = {};
const maskInputOptions: MaskInputOptions =
maskAllInputs === true
? {
Expand Down Expand Up @@ -1116,10 +1127,9 @@ function snapshot(
: slimDOM === false
? {}
: slimDOM;
return [
serializeNodeWithId(n, {
doc: n,
map: idNodeMap,
return serializeNodeWithId(n, {
doc: n,
mirror,
blockClass,
blockSelector,
maskTextClass,
Expand All @@ -1139,9 +1149,7 @@ function snapshot(
iframeLoadTimeout,
keepIframeSrcFn,
enableStrictPrivacy,
}),
idNodeMap,
];
});
}

export function visitSnapshot(
Expand Down
Loading