Files
rrweb/src/utils.ts
Yanzhen Yu e4cb91e1e6 fix block strategy
If an element was blocked, its child nodes should also be blocked.
The interactions and mutations on the element and its child nodes
also need to be blocked.
2026-04-01 12:00:00 +08:00

129 lines
3.2 KiB
TypeScript

import {
Mirror,
throttleOptions,
listenerHandler,
hookResetter,
} from './types';
import { INode } from 'rrweb-snapshot';
export function on(
type: string,
fn: EventListenerOrEventListenerObject,
target: Document | Window = document,
): listenerHandler {
const options = { capture: true, passive: true };
target.addEventListener(type, fn, options);
return () => target.removeEventListener(type, fn, options);
}
export const mirror: Mirror = {
map: {},
getId(n) {
// if n is not a serialized INode, use -1 as its id.
if (!n.__sn) {
return -1;
}
return n.__sn.id;
},
getNode(id) {
return mirror.map[id] || null;
},
// TODO: use a weakmap to get rid of manually memory management
removeNodeFromMap(n) {
const id = n.__sn && n.__sn.id;
delete mirror.map[id];
if (n.childNodes) {
n.childNodes.forEach(child =>
mirror.removeNodeFromMap((child as Node) as INode),
);
}
},
has(id) {
return mirror.map.hasOwnProperty(id);
},
};
// copy from underscore and modified
export function throttle<T>(
func: (arg: T) => void,
wait: number,
options: throttleOptions = {},
) {
let timeout: number | null = null;
let previous = 0;
// tslint:disable-next-line: only-arrow-functions
return function() {
let now = Date.now();
if (!previous && options.leading === false) {
previous = now;
}
let remaining = wait - (now - previous);
let context = this;
let args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
window.clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout && options.trailing !== false) {
timeout = window.setTimeout(() => {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
func.apply(context, args);
}, remaining);
}
};
}
export function hookSetter<T>(
target: T,
key: string | number | symbol,
d: PropertyDescriptor,
): hookResetter {
const original = Object.getOwnPropertyDescriptor(target, key);
Object.defineProperty(target, key, {
set(value) {
// put hooked setter into event loop to avoid of set latency
setTimeout(() => {
d.set!.call(this, value);
}, 0);
if (original && original.set) {
original.set.call(this, value);
}
},
});
return () => hookSetter(target, key, original || {});
}
export function getWindowHeight(): number {
return (
window.innerHeight ||
(document.documentElement && document.documentElement.clientHeight) ||
(document.body && document.body.clientHeight)
);
}
export function getWindowWidth(): number {
return (
window.innerWidth ||
(document.documentElement && document.documentElement.clientWidth) ||
(document.body && document.body.clientWidth)
);
}
const BLOCK_CLASS = 'rr-block';
export function isBlocked(node: Node | null): boolean {
if (!node) {
return false;
}
if (node.nodeType === node.ELEMENT_NODE) {
return (
(node as HTMLElement).classList.contains(BLOCK_CLASS) ||
isBlocked(node.parentNode)
);
}
return isBlocked(node.parentNode);
}