blob: 73b9560aec0ee61ae202e5af703f3488d1545b6f [file] [log] [blame]
import {
NodeWatcherHandler,
NodeMutation,
NodeMutationType,
} from './NodeWatcherHandler';
class NodeWatcher {
private handlers: Map<string, NodeWatcherHandler> = new Map();
private mutationObserver: MutationObserver;
constructor() {
this.mutationObserver = new MutationObserver(
this.mutationCallback.bind(this),
);
this.start();
}
start(): void {
this.mutationObserver.observe(document.body, {
childList: true,
subtree: true,
});
}
pause(): void {
this.mutationObserver.disconnect();
}
setHandler(key: string, handler: NodeWatcherHandler): void {
this.handlers.set(key, handler);
this.performInitialDiscovery(handler);
}
removeHandler(key: string): boolean {
return this.handlers.delete(key);
}
private mutationCallback(mutationRecords: MutationRecord[]): void {
for (const mutationRecord of mutationRecords) {
if (mutationRecord.type !== 'childList') continue;
this.handleAddedNodes(mutationRecord);
this.handleRemovedNodes(mutationRecord);
}
}
private handleAddedNodes(mutationRecord: MutationRecord): void {
for (const node of mutationRecord.addedNodes) {
this.handleMutatedNode({
node,
mutationRecord,
type: NodeMutationType.NewNode,
});
}
}
private handleRemovedNodes(mutationRecord: MutationRecord): void {
for (const node of mutationRecord.removedNodes) {
this.handleMutatedNode({
node,
mutationRecord,
type: NodeMutationType.RemovedNode,
});
}
}
private performInitialDiscovery(handler: NodeWatcherHandler): void {
if (handler.initialDiscoverySelector === undefined) return;
const candidateNodes = document.querySelectorAll(
handler.initialDiscoverySelector,
);
for (const candidateNode of candidateNodes) {
this.handleMutatedNodeWithHandler(
{
node: candidateNode,
type: NodeMutationType.InitialDiscovery,
mutationRecord: null,
},
handler,
);
}
}
private handleMutatedNode(nodeMutation: NodeMutation): void {
for (const [, handler] of this.handlers) {
this.handleMutatedNodeWithHandler(nodeMutation, handler);
}
}
private handleMutatedNodeWithHandler(
nodeMutation: NodeMutation,
handler: NodeWatcherHandler,
): void {
if (handler.nodeFilter(nodeMutation)) {
handler.onMutatedNode(nodeMutation);
}
}
}
export default class NodeWatcherSingleton {
private static instance: NodeWatcher;
/**
* @see {@link NodeWatcherSingleton.getInstance}
*/
private constructor() {}
public static getInstance(): NodeWatcher {
if (!NodeWatcherSingleton.instance) {
NodeWatcherSingleton.instance = new NodeWatcher();
}
return NodeWatcherSingleton.instance;
}
}