Skip mask check on leaf elements (#1512)

* Minor fixup for #1349; the 'we can avoid the check on leaf elements' optimisation wasn't being applied as `n.childNodes` was always truthy even when there were no childNodes.

Changing it to `n.childNodes.length` directly there (see #1402) actually caused a bug as during a mutation, we serialize the text node directly, and need to jump to the parentElement to do the check.
This is why I've reimplemented this optimisation inside `needMaskingText` where we are already had an `isElement` test

Thanks to @Paulhejia (https://github.com/Paulhejia/rrweb/) for spotting that `Boolean(n.childNodes)` is aways true.
This commit is contained in:
Eoghan Murray
2026-04-01 12:00:00 +08:00
committed by GitHub
parent e904dab56a
commit bbf7efd61c
2 changed files with 23 additions and 11 deletions

View File

@@ -0,0 +1,6 @@
---
"rrweb-snapshot": patch
"rrweb": patch
---
optimisation: skip mask check on leaf elements

View File

@@ -326,12 +326,21 @@ export function needMaskingText(
maskTextSelector: string | null,
checkAncestors: boolean,
): boolean {
let el: Element;
if (isElement(node)) {
el = node;
if (!el.childNodes.length) {
// optimisation: we can avoid any of the below checks on leaf elements
// as masking is applied to child text nodes only
return false;
}
} else if (node.parentElement === null) {
// should warn? maybe a text node isn't attached to a parent node yet?
return false;
} else {
el = node.parentElement;
}
try {
const el: HTMLElement | null =
node.nodeType === node.ELEMENT_NODE
? (node as HTMLElement)
: node.parentElement;
if (el === null) return false;
if (typeof maskTextClass === 'string') {
if (checkAncestors) {
if (el.closest(`.${maskTextClass}`)) return true;
@@ -440,7 +449,7 @@ function serializeNode(
mirror: Mirror;
blockClass: string | RegExp;
blockSelector: string | null;
needsMask: boolean | undefined;
needsMask: boolean;
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
maskTextFn: MaskTextFn | undefined;
@@ -544,7 +553,7 @@ function serializeTextNode(
n: Text,
options: {
doc: Document;
needsMask: boolean | undefined;
needsMask: boolean;
maskTextFn: MaskTextFn | undefined;
rootId: number | undefined;
},
@@ -1006,10 +1015,7 @@ export function serializeNodeWithId(
let { needsMask } = options;
let { preserveWhiteSpace = true } = options;
if (
!needsMask &&
n.childNodes // we can avoid the check on leaf elements, as masking is applied to child text nodes only
) {
if (!needsMask) {
// perf: if needsMask = true, children won't also need to check
const checkAncestors = needsMask === undefined; // if false, we've already checked ancestors
needsMask = needMaskingText(