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.
129 lines
3.2 KiB
TypeScript
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);
|
|
}
|