diff --git a/src/record/index.ts b/src/record/index.ts index ba0cf21e..642170a2 100644 --- a/src/record/index.ts +++ b/src/record/index.ts @@ -1,4 +1,4 @@ -import { snapshot, MaskInputOptions } from 'rrweb-snapshot'; +import { snapshot, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot'; import { initObservers, mutationBuffer } from './observer'; import { mirror, @@ -38,6 +38,7 @@ function record( inlineStylesheet = true, maskAllInputs, maskInputOptions: _maskInputOptions, + slimDOMOptions: _slimDOMOptions, maskInputFn, hooks, packFn, @@ -78,6 +79,26 @@ function record( ? _maskInputOptions : {}; + const slimDOMOptions: SlimDOMOptions = + _slimDOMOptions === true || _slimDOMOptions === 'all' + ? { + script: true, + comment: true, + headFavicon: true, + headWhitespace: true, + headMetaSocial: true, + headMetaRobots: true, + headMetaHttpEquiv: true, + headMetaVerification: true, + // the following are off for slimDOMOptions === true, + // as they destroy some (hidden) info: + headMetaAuthorship: _slimDOMOptions === 'all', + headMetaDescKeywords: _slimDOMOptions === 'all', + } + : _slimDOMOptions === false + ? {} + : _slimDOMOptions; + polyfill(); let lastFullSnapshotEvent: eventWithTime; @@ -134,6 +155,7 @@ function record( blockSelector, inlineStylesheet, maskAllInputs: maskInputOptions, + slimDOM: slimDOMOptions, recordCanvas, }); @@ -299,6 +321,7 @@ function record( sampling, recordCanvas, collectFonts, + slimDOMOptions, }, hooks, ), diff --git a/src/record/mutation.ts b/src/record/mutation.ts index 6c49fd4c..f0ef9cc2 100644 --- a/src/record/mutation.ts +++ b/src/record/mutation.ts @@ -3,6 +3,8 @@ import { serializeNodeWithId, transformAttribute, MaskInputOptions, + SlimDOMOptions, + IGNORED_NODE, } from 'rrweb-snapshot'; import { mutationRecord, @@ -13,7 +15,7 @@ import { removedNodeMutation, addedNodeMutation, } from '../types'; -import { mirror, isBlocked, isAncestorRemoved } from '../utils'; +import { mirror, isBlocked, isAncestorRemoved, isIgnored } from '../utils'; type DoubleLinkedListNode = { previous: DoubleLinkedListNode | null; @@ -145,6 +147,7 @@ export default class MutationBuffer { private inlineStylesheet: boolean; private maskInputOptions: MaskInputOptions; private recordCanvas: boolean; + private slimDOMOptions: SlimDOMOptions; public init( cb: mutationCallBack, @@ -153,12 +156,14 @@ export default class MutationBuffer { inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, recordCanvas: boolean, + slimDOMOptions: SlimDOMOptions, ) { this.blockClass = blockClass; this.blockSelector = blockSelector; this.inlineStylesheet = inlineStylesheet; this.maskInputOptions = maskInputOptions; this.recordCanvas = recordCanvas; + this.slimDOMOptions = slimDOMOptions; this.emissionCallback = cb; } @@ -193,8 +198,12 @@ export default class MutationBuffer { */ const addList = new DoubleLinkedList(); const getNextId = (n: Node): number | null => { - let nextId = - n.nextSibling && mirror.getId((n.nextSibling as unknown) as INode); + let ns: Node | null = n; + let nextId: number | null = IGNORED_NODE; // slimDOM: ignored + while (nextId === IGNORED_NODE) { + ns = ns && ns.nextSibling; + nextId = ns && mirror.getId((ns as unknown) as INode); + } if (nextId === -1 && isBlocked(n.nextSibling, this.blockClass)) { nextId = null; } @@ -209,21 +218,24 @@ export default class MutationBuffer { if (parentId === -1 || nextId === -1) { return addList.addNode(n); } - adds.push({ - parentId, - nextId, - node: serializeNodeWithId(n, { - doc: document, - map: mirror.map, - blockClass: this.blockClass, - blockSelector: this.blockSelector, - skipChild: true, - inlineStylesheet: this.inlineStylesheet, - maskInputOptions: this.maskInputOptions, - slimDOMOptions: {}, - recordCanvas: this.recordCanvas, - })!, + let sn = serializeNodeWithId(n, { + doc: document, + map: mirror.map, + blockClass: this.blockClass, + blockSelector: this.blockSelector, + skipChild: true, + inlineStylesheet: this.inlineStylesheet, + maskInputOptions: this.maskInputOptions, + slimDOMOptions: this.slimDOMOptions, + recordCanvas: this.recordCanvas, }); + if (sn) { + adds.push({ + parentId, + nextId, + node: sn, + }); + } }; while (this.mapRemoves.length) { @@ -332,6 +344,9 @@ export default class MutationBuffer { }; private processMutation = (m: mutationRecord) => { + if (isIgnored(m.target)) { + return; + } switch (m.type) { case 'characterData': { const value = m.target.textContent; @@ -373,7 +388,8 @@ export default class MutationBuffer { const parentId = mirror.getId(m.target as INode); if ( isBlocked(n, this.blockClass) || - isBlocked(m.target, this.blockClass) + isBlocked(m.target, this.blockClass) || + isIgnored(n) ) { return; } @@ -421,6 +437,9 @@ export default class MutationBuffer { return; } if (isINode(n)) { + if (isIgnored(n)) { + return; + } this.movedSet.add(n); let targetId: number | null = null; if (target && isINode(target)) { diff --git a/src/record/observer.ts b/src/record/observer.ts index 6073e2dd..96243616 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -1,4 +1,4 @@ -import { INode, MaskInputOptions } from 'rrweb-snapshot'; +import { INode, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot'; import { FontFaceDescriptors, FontFaceSet } from 'css-font-loading-module'; import { mirror, @@ -48,6 +48,7 @@ function initMutationObserver( inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, recordCanvas: boolean, + slimDOMOptions: SlimDOMOptions, ): MutationObserver { // see mutation.ts for details mutationBuffer.init( @@ -57,6 +58,7 @@ function initMutationObserver( inlineStylesheet, maskInputOptions, recordCanvas, + slimDOMOptions, ); const observer = new MutationObserver( mutationBuffer.processMutations.bind(mutationBuffer), @@ -584,6 +586,7 @@ export function initObservers( o.inlineStylesheet, o.maskInputOptions, o.recordCanvas, + o.slimDOMOptions, ); const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling); const mouseInteractionHandler = initMouseInteractionObserver( diff --git a/src/types.ts b/src/types.ts index cddb2f52..4f1b8a64 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import { idNodeMap, INode, MaskInputOptions, + SlimDOMOptions, } from 'rrweb-snapshot'; import { PackFn, UnpackFn } from './packer/base'; import { FontFaceDescriptors } from 'css-font-loading-module'; @@ -176,6 +177,7 @@ export type recordOptions = { maskAllInputs?: boolean; maskInputOptions?: MaskInputOptions; maskInputFn?: MaskInputFn; + slimDOMOptions?: SlimDOMOptions; inlineStylesheet?: boolean; hooks?: hooksParam; packFn?: PackFn; @@ -206,6 +208,7 @@ export type observerParam = { sampling: SamplingStrategy; recordCanvas: boolean; collectFonts: boolean; + slimDOMOptions: SlimDOMOptions; }; export type hooksParam = { diff --git a/src/utils.ts b/src/utils.ts index 15b8c70b..5b70dd9d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -15,7 +15,7 @@ import { scrollData, inputData, } from './types'; -import { INode } from 'rrweb-snapshot'; +import { INode, IGNORED_NODE } from 'rrweb-snapshot'; export function on( type: string, @@ -197,6 +197,15 @@ export function isBlocked(node: Node | null, blockClass: blockClass): boolean { return isBlocked(node.parentNode, blockClass); } +export function isIgnored(n: Node | INode): boolean { + if ('__sn' in n) { + return (n as INode).__sn.id === IGNORED_NODE; + } + // The main part of the slimDOM check happens in + // rrweb-snapshot::serializeNodeWithId + return false; +} + export function isAncestorRemoved(target: INode): boolean { const id = mirror.getId(target); if (!mirror.has(id)) {