pick #286, export slim DOM options

This commit is contained in:
Yanzhen Yu
2026-04-01 12:00:00 +08:00
parent e9a5aeed06
commit ec5b7a4635
5 changed files with 78 additions and 21 deletions

View File

@@ -1,4 +1,4 @@
import { snapshot, MaskInputOptions } from 'rrweb-snapshot'; import { snapshot, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
import { initObservers, mutationBuffer } from './observer'; import { initObservers, mutationBuffer } from './observer';
import { import {
mirror, mirror,
@@ -38,6 +38,7 @@ function record<T = eventWithTime>(
inlineStylesheet = true, inlineStylesheet = true,
maskAllInputs, maskAllInputs,
maskInputOptions: _maskInputOptions, maskInputOptions: _maskInputOptions,
slimDOMOptions: _slimDOMOptions,
maskInputFn, maskInputFn,
hooks, hooks,
packFn, packFn,
@@ -78,6 +79,26 @@ function record<T = eventWithTime>(
? _maskInputOptions ? _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(); polyfill();
let lastFullSnapshotEvent: eventWithTime; let lastFullSnapshotEvent: eventWithTime;
@@ -134,6 +155,7 @@ function record<T = eventWithTime>(
blockSelector, blockSelector,
inlineStylesheet, inlineStylesheet,
maskAllInputs: maskInputOptions, maskAllInputs: maskInputOptions,
slimDOM: slimDOMOptions,
recordCanvas, recordCanvas,
}); });
@@ -299,6 +321,7 @@ function record<T = eventWithTime>(
sampling, sampling,
recordCanvas, recordCanvas,
collectFonts, collectFonts,
slimDOMOptions,
}, },
hooks, hooks,
), ),

View File

@@ -3,6 +3,8 @@ import {
serializeNodeWithId, serializeNodeWithId,
transformAttribute, transformAttribute,
MaskInputOptions, MaskInputOptions,
SlimDOMOptions,
IGNORED_NODE,
} from 'rrweb-snapshot'; } from 'rrweb-snapshot';
import { import {
mutationRecord, mutationRecord,
@@ -13,7 +15,7 @@ import {
removedNodeMutation, removedNodeMutation,
addedNodeMutation, addedNodeMutation,
} from '../types'; } from '../types';
import { mirror, isBlocked, isAncestorRemoved } from '../utils'; import { mirror, isBlocked, isAncestorRemoved, isIgnored } from '../utils';
type DoubleLinkedListNode = { type DoubleLinkedListNode = {
previous: DoubleLinkedListNode | null; previous: DoubleLinkedListNode | null;
@@ -145,6 +147,7 @@ export default class MutationBuffer {
private inlineStylesheet: boolean; private inlineStylesheet: boolean;
private maskInputOptions: MaskInputOptions; private maskInputOptions: MaskInputOptions;
private recordCanvas: boolean; private recordCanvas: boolean;
private slimDOMOptions: SlimDOMOptions;
public init( public init(
cb: mutationCallBack, cb: mutationCallBack,
@@ -153,12 +156,14 @@ export default class MutationBuffer {
inlineStylesheet: boolean, inlineStylesheet: boolean,
maskInputOptions: MaskInputOptions, maskInputOptions: MaskInputOptions,
recordCanvas: boolean, recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
) { ) {
this.blockClass = blockClass; this.blockClass = blockClass;
this.blockSelector = blockSelector; this.blockSelector = blockSelector;
this.inlineStylesheet = inlineStylesheet; this.inlineStylesheet = inlineStylesheet;
this.maskInputOptions = maskInputOptions; this.maskInputOptions = maskInputOptions;
this.recordCanvas = recordCanvas; this.recordCanvas = recordCanvas;
this.slimDOMOptions = slimDOMOptions;
this.emissionCallback = cb; this.emissionCallback = cb;
} }
@@ -193,8 +198,12 @@ export default class MutationBuffer {
*/ */
const addList = new DoubleLinkedList(); const addList = new DoubleLinkedList();
const getNextId = (n: Node): number | null => { const getNextId = (n: Node): number | null => {
let nextId = let ns: Node | null = n;
n.nextSibling && mirror.getId((n.nextSibling as unknown) as INode); 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)) { if (nextId === -1 && isBlocked(n.nextSibling, this.blockClass)) {
nextId = null; nextId = null;
} }
@@ -209,21 +218,24 @@ export default class MutationBuffer {
if (parentId === -1 || nextId === -1) { if (parentId === -1 || nextId === -1) {
return addList.addNode(n); return addList.addNode(n);
} }
adds.push({ let sn = serializeNodeWithId(n, {
parentId, doc: document,
nextId, map: mirror.map,
node: serializeNodeWithId(n, { blockClass: this.blockClass,
doc: document, blockSelector: this.blockSelector,
map: mirror.map, skipChild: true,
blockClass: this.blockClass, inlineStylesheet: this.inlineStylesheet,
blockSelector: this.blockSelector, maskInputOptions: this.maskInputOptions,
skipChild: true, slimDOMOptions: this.slimDOMOptions,
inlineStylesheet: this.inlineStylesheet, recordCanvas: this.recordCanvas,
maskInputOptions: this.maskInputOptions,
slimDOMOptions: {},
recordCanvas: this.recordCanvas,
})!,
}); });
if (sn) {
adds.push({
parentId,
nextId,
node: sn,
});
}
}; };
while (this.mapRemoves.length) { while (this.mapRemoves.length) {
@@ -332,6 +344,9 @@ export default class MutationBuffer {
}; };
private processMutation = (m: mutationRecord) => { private processMutation = (m: mutationRecord) => {
if (isIgnored(m.target)) {
return;
}
switch (m.type) { switch (m.type) {
case 'characterData': { case 'characterData': {
const value = m.target.textContent; const value = m.target.textContent;
@@ -373,7 +388,8 @@ export default class MutationBuffer {
const parentId = mirror.getId(m.target as INode); const parentId = mirror.getId(m.target as INode);
if ( if (
isBlocked(n, this.blockClass) || isBlocked(n, this.blockClass) ||
isBlocked(m.target, this.blockClass) isBlocked(m.target, this.blockClass) ||
isIgnored(n)
) { ) {
return; return;
} }
@@ -421,6 +437,9 @@ export default class MutationBuffer {
return; return;
} }
if (isINode(n)) { if (isINode(n)) {
if (isIgnored(n)) {
return;
}
this.movedSet.add(n); this.movedSet.add(n);
let targetId: number | null = null; let targetId: number | null = null;
if (target && isINode(target)) { if (target && isINode(target)) {

View File

@@ -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 { FontFaceDescriptors, FontFaceSet } from 'css-font-loading-module';
import { import {
mirror, mirror,
@@ -48,6 +48,7 @@ function initMutationObserver(
inlineStylesheet: boolean, inlineStylesheet: boolean,
maskInputOptions: MaskInputOptions, maskInputOptions: MaskInputOptions,
recordCanvas: boolean, recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
): MutationObserver { ): MutationObserver {
// see mutation.ts for details // see mutation.ts for details
mutationBuffer.init( mutationBuffer.init(
@@ -57,6 +58,7 @@ function initMutationObserver(
inlineStylesheet, inlineStylesheet,
maskInputOptions, maskInputOptions,
recordCanvas, recordCanvas,
slimDOMOptions,
); );
const observer = new MutationObserver( const observer = new MutationObserver(
mutationBuffer.processMutations.bind(mutationBuffer), mutationBuffer.processMutations.bind(mutationBuffer),
@@ -584,6 +586,7 @@ export function initObservers(
o.inlineStylesheet, o.inlineStylesheet,
o.maskInputOptions, o.maskInputOptions,
o.recordCanvas, o.recordCanvas,
o.slimDOMOptions,
); );
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling); const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling);
const mouseInteractionHandler = initMouseInteractionObserver( const mouseInteractionHandler = initMouseInteractionObserver(

View File

@@ -3,6 +3,7 @@ import {
idNodeMap, idNodeMap,
INode, INode,
MaskInputOptions, MaskInputOptions,
SlimDOMOptions,
} from 'rrweb-snapshot'; } from 'rrweb-snapshot';
import { PackFn, UnpackFn } from './packer/base'; import { PackFn, UnpackFn } from './packer/base';
import { FontFaceDescriptors } from 'css-font-loading-module'; import { FontFaceDescriptors } from 'css-font-loading-module';
@@ -176,6 +177,7 @@ export type recordOptions<T> = {
maskAllInputs?: boolean; maskAllInputs?: boolean;
maskInputOptions?: MaskInputOptions; maskInputOptions?: MaskInputOptions;
maskInputFn?: MaskInputFn; maskInputFn?: MaskInputFn;
slimDOMOptions?: SlimDOMOptions;
inlineStylesheet?: boolean; inlineStylesheet?: boolean;
hooks?: hooksParam; hooks?: hooksParam;
packFn?: PackFn; packFn?: PackFn;
@@ -206,6 +208,7 @@ export type observerParam = {
sampling: SamplingStrategy; sampling: SamplingStrategy;
recordCanvas: boolean; recordCanvas: boolean;
collectFonts: boolean; collectFonts: boolean;
slimDOMOptions: SlimDOMOptions;
}; };
export type hooksParam = { export type hooksParam = {

View File

@@ -15,7 +15,7 @@ import {
scrollData, scrollData,
inputData, inputData,
} from './types'; } from './types';
import { INode } from 'rrweb-snapshot'; import { INode, IGNORED_NODE } from 'rrweb-snapshot';
export function on( export function on(
type: string, type: string,
@@ -197,6 +197,15 @@ export function isBlocked(node: Node | null, blockClass: blockClass): boolean {
return isBlocked(node.parentNode, blockClass); 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 { export function isAncestorRemoved(target: INode): boolean {
const id = mirror.getId(target); const id = mirror.getId(target);
if (!mirror.has(id)) { if (!mirror.has(id)) {