blob: 0d46e4771a9e4121a9445410f2d412eb6843a641 [file] [log] [blame]
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +02001import {
2 NodeWatcherHandler,
3 NodeMutation,
4 NodeMutationType,
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +02005} from '../../../presentation/nodeWatcher/NodeWatcherHandler';
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +02006
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +02007export class NodeWatcherAdapter {
8 private started = false;
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +02009 private handlers: Map<string, NodeWatcherHandler> = new Map();
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +020010 private mutationObserver: MutationObserver | undefined;
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +020011
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +020012 start(): void {
13 if (this.started) return;
14
15 this.started = true;
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +020016 this.mutationObserver = new MutationObserver(
17 this.mutationCallback.bind(this),
18 );
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +020019 this.mutationObserver.observe(document.body, {
20 childList: true,
21 subtree: true,
22 });
23 }
24
25 pause(): void {
26 this.mutationObserver.disconnect();
27 }
28
29 setHandler(key: string, handler: NodeWatcherHandler): void {
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +020030 this.start();
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +020031 this.handlers.set(key, handler);
32 this.performInitialDiscovery(handler);
33 }
34
35 removeHandler(key: string): boolean {
36 return this.handlers.delete(key);
37 }
38
39 private mutationCallback(mutationRecords: MutationRecord[]): void {
40 for (const mutationRecord of mutationRecords) {
41 if (mutationRecord.type !== 'childList') continue;
42 this.handleAddedNodes(mutationRecord);
43 this.handleRemovedNodes(mutationRecord);
44 }
45 }
46
47 private handleAddedNodes(mutationRecord: MutationRecord): void {
48 for (const node of mutationRecord.addedNodes) {
49 this.handleMutatedNode({
50 node,
51 mutationRecord,
52 type: NodeMutationType.NewNode,
53 });
54 }
55 }
56
57 private handleRemovedNodes(mutationRecord: MutationRecord): void {
58 for (const node of mutationRecord.removedNodes) {
59 this.handleMutatedNode({
60 node,
61 mutationRecord,
62 type: NodeMutationType.RemovedNode,
63 });
64 }
65 }
66
67 private performInitialDiscovery(handler: NodeWatcherHandler): void {
68 if (handler.initialDiscoverySelector === undefined) return;
69 const candidateNodes = document.querySelectorAll(
70 handler.initialDiscoverySelector,
71 );
72 for (const candidateNode of candidateNodes) {
73 this.handleMutatedNodeWithHandler(
74 {
75 node: candidateNode,
76 type: NodeMutationType.InitialDiscovery,
77 mutationRecord: null,
78 },
79 handler,
80 );
81 }
82 }
83
84 private handleMutatedNode(nodeMutation: NodeMutation): void {
85 for (const [, handler] of this.handlers) {
86 this.handleMutatedNodeWithHandler(nodeMutation, handler);
87 }
88 }
89
90 private handleMutatedNodeWithHandler(
91 nodeMutation: NodeMutation,
92 handler: NodeWatcherHandler,
93 ): void {
94 if (handler.nodeFilter(nodeMutation)) {
95 handler.onMutatedNode(nodeMutation);
96 }
97 }
98}
99
100export default class NodeWatcherSingleton {
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +0200101 private static instance: NodeWatcherAdapter;
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +0200102
103 /**
104 * @see {@link NodeWatcherSingleton.getInstance}
105 */
106 private constructor() {}
107
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +0200108 public static getInstance(): NodeWatcherAdapter {
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +0200109 if (!NodeWatcherSingleton.instance) {
Adrià Vilanova Martínez43c75202024-10-19 15:55:15 +0200110 NodeWatcherSingleton.instance = new NodeWatcherAdapter();
Adrià Vilanova Martínez18d03c42024-04-21 16:43:01 +0200111 }
112 return NodeWatcherSingleton.instance;
113 }
114}