Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Speed up snapshotting of many new dom nodes
By avoiding reflow we shave about 15-25% off our snapshotting time
  • Loading branch information
Juice10 committed May 25, 2022
commit ed86eca3e0fb7cab2275ea4c32b83555efa8ecf1
27 changes: 20 additions & 7 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ function serializeNode(
inlineImages: boolean;
recordCanvas: boolean;
keepIframeSrcFn: KeepIframeSrcFn;
newlyAddedElement?: boolean;
},
): serializedNode | false {
const {
Expand All @@ -406,6 +407,7 @@ function serializeNode(
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement = false,
} = options;
// Only record root id when document object is not the base document
let rootId: number | undefined;
Expand Down Expand Up @@ -462,7 +464,7 @@ function serializeNode(
});
let cssText: string | null = null;
if (stylesheet) {
cssText = getCssRulesString(stylesheet );
cssText = getCssRulesString(stylesheet);
}
if (cssText) {
delete attributes.rel;
Expand Down Expand Up @@ -595,12 +597,17 @@ function serializeNode(
: 'played';
attributes.rr_mediaCurrentTime = (n as HTMLMediaElement).currentTime;
}
// scroll
if ((n as HTMLElement).scrollLeft) {
attributes.rr_scrollLeft = (n as HTMLElement).scrollLeft;
}
if ((n as HTMLElement).scrollTop) {
attributes.rr_scrollTop = (n as HTMLElement).scrollTop;
// Scroll
if (!newlyAddedElement) {
// `scrollTop` and `scrollLeft` are expensive calls because they trigger reflow
// since `scrollTop` & `scrollLeft` are always 0 when an element is added
// to the DOM we can safely skip them for new elements
if ((n as HTMLElement).scrollLeft) {
attributes.rr_scrollLeft = (n as HTMLElement).scrollLeft;
}
if ((n as HTMLElement).scrollTop) {
attributes.rr_scrollTop = (n as HTMLElement).scrollTop;
}
}
// block element
if (needBlock) {
Expand All @@ -620,6 +627,7 @@ function serializeNode(
}
delete attributes.src; // prevent auto loading
}

return {
type: NodeType.Element,
tagName,
Expand Down Expand Up @@ -671,6 +679,7 @@ function serializeNode(
? maskTextFn(textContent)
: textContent.replace(/[\S]/g, '*');
}

return {
type: NodeType.Text,
textContent: textContent || '',
Expand Down Expand Up @@ -819,6 +828,7 @@ export function serializeNodeWithId(
node: serializedNodeWithId,
) => unknown;
iframeLoadTimeout?: number;
newlyAddedElement?: boolean;
},
): serializedNodeWithId | null {
const {
Expand All @@ -841,6 +851,7 @@ export function serializeNodeWithId(
onIframeLoad,
iframeLoadTimeout = 5000,
keepIframeSrcFn = () => false,
newlyAddedElement = false,
} = options;
let { preserveWhiteSpace = true } = options;
const _serializedNode = serializeNode(n, {
Expand All @@ -858,6 +869,7 @@ export function serializeNodeWithId(
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement,
});
if (!_serializedNode) {
// TODO: dev only
Expand Down Expand Up @@ -1109,6 +1121,7 @@ function snapshot(
onIframeLoad,
iframeLoadTimeout,
keepIframeSrcFn,
newlyAddedElement: false,
});
}

Expand Down
38 changes: 38 additions & 0 deletions packages/rrweb-snapshot/test/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,41 @@ describe('style elements', () => {
});
});
});

describe('scrollTop/scrollLeft', () => {
const serializeNode = (node: Node): serializedNodeWithId | null => {
return serializeNodeWithId(node, {
doc: document,
mirror: new Mirror(),
blockClass: 'blockblock',
blockSelector: null,
maskTextClass: 'maskmask',
maskTextSelector: null,
skipChild: false,
inlineStylesheet: true,
maskTextFn: undefined,
maskInputFn: undefined,
slimDOMOptions: {},
newlyAddedElement: false,
});
};

const render = (html: string): HTMLDivElement => {
document.write(html);
return document.querySelector('div')!;
};

it('should serialize scroll positions', () => {
const el = render(`<div stylel='overflow: auto; width: 1px; height: 1px;'>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this a typo? stylel

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>`);
el.scrollTop = 10;
el.scrollLeft = 20;
expect(serializeNode(el)).toMatchObject({
attributes: {
rr_scrollTop: 10,
rr_scrollLeft: 20,
},
});
});
});
1 change: 1 addition & 0 deletions packages/rrweb-snapshot/typings/snapshot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export declare function serializeNodeWithId(n: Node, options: {
onSerialize?: (n: Node) => unknown;
onIframeLoad?: (iframeNode: HTMLIFrameElement, node: serializedNodeWithId) => unknown;
iframeLoadTimeout?: number;
newlyAddedElement?: boolean;
}): serializedNodeWithId | null;
declare function snapshot(n: Document, options?: {
mirror?: Mirror;
Expand Down
13 changes: 13 additions & 0 deletions packages/rrweb/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,19 @@ if (process.env.BROWSER_ONLY) {

configs = [];

// browser record + replay, unminified (for profiling and performance testing)
configs.push({
input: './src/index.ts',
plugins: getPlugins(),
output: [
{
name: 'rrweb',
format: 'iife',
file: pkg.unpkg,
},
],
});

for (const c of browserOnlyBaseConfigs) {
configs.push({
input: c.input,
Expand Down
3 changes: 2 additions & 1 deletion packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ export default class MutationBuffer {
this.iframeManager.attachIframe(iframe, childSn, this.mirror);
this.shadowDomManager.observeAttachShadow(iframe);
},
newlyAddedElement: true,
});
if (sn) {
adds.push({
Expand Down Expand Up @@ -597,7 +598,7 @@ export default class MutationBuffer {
// if this node is blocked `serializeNode` will turn it into a placeholder element
// but we have to remove it's children otherwise they will be added as placeholders too
if (!isBlocked(n, this.blockClass))
(n ).childNodes.forEach((childN) => this.genAdds(childN));
n.childNodes.forEach((childN) => this.genAdds(childN));
};
}

Expand Down
120 changes: 120 additions & 0 deletions packages/rrweb/test/__snapshots__/record.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,126 @@ exports[`record is safe to checkout during async callbacks 1`] = `
]"
`;

exports[`record should record scroll position 1`] = `
"[
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [],
\\"id\\": 4
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 6
},
{
\\"type\\": 2,
\\"tagName\\": \\"input\\",
\\"attributes\\": {
\\"type\\": \\"text\\",
\\"size\\": \\"40\\"
},
\\"childNodes\\": [],
\\"id\\": 7
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\\\n \\\\n \\",
\\"id\\": 8
}
],
\\"id\\": 5
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 0,
\\"texts\\": [],
\\"attributes\\": [],
\\"removes\\": [],
\\"adds\\": [
{
\\"parentId\\": 5,
\\"nextId\\": null,
\\"node\\": {
\\"type\\": 2,
\\"tagName\\": \\"p\\",
\\"attributes\\": {
\\"style\\": \\"overflow: auto; height: Npx; width: Npx;\\"
},
\\"childNodes\\": [],
\\"id\\": 9
}
},
{
\\"parentId\\": 9,
\\"nextId\\": null,
\\"node\\": {
\\"type\\": 3,
\\"textContent\\": \\"testtesttesttesttesttesttesttesttesttest\\",
\\"id\\": 10
}
}
]
}
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 3,
\\"id\\": 9,
\\"x\\": 10,
\\"y\\": 10
}
}
]"
`;

exports[`record without CSSGroupingRule support captures nested stylesheet rules 1`] = `
"[
{
Expand Down
Loading