Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/injected-scripts/MouseEvents.ts
1028 views
1
// eslint-disable-next-line max-classes-per-file
2
import IMouseUpResult from '@secret-agent/interfaces/IMouseUpResult';
3
4
// eslint-disable-next-line @typescript-eslint/no-unused-vars
5
class MouseEvents {
6
private static pendingMouseover?: EventResolvable<MouseEvent, boolean>;
7
private static pendingMouseup?: EventResolvable<MouseEvent, IMouseUpResult>;
8
9
public static listenFor(mouseEvent: 'mouseup' | 'mouseover', nodeId: number) {
10
this.clearEvent(mouseEvent);
11
12
const node = NodeTracker.getWatchedNodeWithId(nodeId);
13
if (!node) throw new Error('Node not found');
14
if (!node.isConnected) {
15
throw new Error(
16
`Target node for "${mouseEvent}" is not connected to the DOM, and won't receive mouse events.`,
17
);
18
}
19
20
if (mouseEvent === 'mouseover') {
21
this.pendingMouseover = new EventResolvable(nodeId, () => {
22
this.pendingMouseover?.resolve(true);
23
});
24
25
node.addEventListener('mouseover', this.pendingMouseover.onEventFn, {
26
once: true,
27
});
28
} else {
29
this.pendingMouseup = new EventResolvable(nodeId, event => {
30
const targetNodeId = event.target ? NodeTracker.watchNode(event.target as Node) : undefined;
31
const relatedTargetNodeId = event.relatedTarget
32
? NodeTracker.watchNode(event.relatedTarget as Node)
33
: undefined;
34
35
const result: IMouseUpResult = {
36
pageX: event.pageX - window.scrollX,
37
pageY: event.pageY - window.scrollY,
38
targetNodeId,
39
relatedTargetNodeId,
40
didClickLocation: node.contains(event.target as Node) || node === event.target,
41
};
42
43
if (!result.didClickLocation) {
44
// @ts-ignore
45
result.targetNodePreview = generateNodePreview(event.target);
46
// @ts-ignore
47
result.expectedNodePreview = generateNodePreview(node);
48
const expectedNode = new ObjectAtPath();
49
expectedNode.objectAtPath = node;
50
result.expectedNodeVisibility = expectedNode.getComputedVisibility();
51
}
52
53
this.pendingMouseup?.resolve(result);
54
});
55
56
window.addEventListener('mouseup', this.pendingMouseup.onEventFn, {
57
once: true,
58
});
59
}
60
}
61
62
public static didTrigger(mouseEvent: 'mouseup' | 'mouseover', nodeId: number) {
63
try {
64
const pendingEvent = mouseEvent === 'mouseup' ? this.pendingMouseup : this.pendingMouseover;
65
if (pendingEvent?.nodeId !== nodeId) {
66
throw new Error(`${mouseEvent.toUpperCase()} listener not found`);
67
}
68
69
return pendingEvent.trigger;
70
} finally {
71
this.clearEvent(mouseEvent);
72
}
73
}
74
75
private static clearEvent(mouseEvent: 'mouseup' | 'mouseover') {
76
if (mouseEvent === 'mouseover') this.clearPendingMouseover();
77
if (mouseEvent === 'mouseup') this.clearPendingMouseup();
78
}
79
80
private static clearPendingMouseover() {
81
if (this.pendingMouseover) {
82
const node = NodeTracker.getWatchedNodeWithId(this.pendingMouseover.nodeId);
83
node?.removeEventListener('mouseover', this.pendingMouseover.onEventFn);
84
this.pendingMouseover = null;
85
}
86
}
87
88
private static clearPendingMouseup() {
89
if (this.pendingMouseup) {
90
window.removeEventListener('mouseup', this.pendingMouseup.onEventFn);
91
this.pendingMouseup = null;
92
}
93
}
94
}
95
96
class EventResolvable<EventType, T> {
97
trigger: T;
98
99
constructor(readonly nodeId, readonly onEventFn: (ev: EventType) => void) {}
100
101
resolve(result: T) {
102
this.trigger = result;
103
}
104
}
105
106