blob: ae189dab401de1a18c39e7d68adb0047e393c38e (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
/* Generic clicking
*
* We can’t just click every clickable object, since there may be side-effects
* like navigating to a different location. Thus whitelist known elements.
*/
(function() {
/* Element is visible if itself and all of its parents are
*/
function isVisible (o) {
if (o === null || !(o instanceof Element)) {
return true;
}
let style = window.getComputedStyle (o);
if ('parentNode' in o) {
return style.display !== 'none' && isVisible (o.parentNode);
} else {
return style.display !== 'none';
}
}
/* Elements are considered clickable if they are a) visible and b) not
* disabled
*/
function isClickable (o) {
return !o.hasAttribute ('disabled') && isVisible (o);
}
const defaultClickThrottle = 50; /* in ms */
const discoverInterval = 1000; /* 1 second */
class Click {
constructor(options) {
/* pick selectors matching current location */
let hostname = document.location.hostname;
this.selector = [];
for (let s of options['sites']) {
let r = new RegExp (s.match, 'i');
if (r.test (hostname)) {
this.selector = this.selector.concat (s.selector);
}
}
/* throttle clicking */
this.queue = [];
this.clickTimeout = null;
/* some sites don’t remove/replace the element immediately, so keep track of
* which ones we already clicked */
this.have = new Set ();
/* XXX: can we use a mutation observer instead? */
this.interval = window.setInterval (this.discover.bind (this), discoverInterval);
}
makeClickEvent () {
return new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
}
click () {
if (this.queue.length > 0) {
const item = this.queue.shift ();
const o = item.o;
const selector = item.selector;
o.dispatchEvent (this.makeClickEvent ());
if (this.queue.length > 0) {
const nextTimeout = 'throttle' in selector ?
selector.throttle : defaultClickThrottle;
this.clickTimeout = window.setTimeout (this.click.bind (this), nextTimeout);
} else {
this.clickTimeout = null;
}
}
}
discover () {
for (let s of this.selector) {
let obj = document.querySelectorAll (s.selector);
for (let o of obj) {
if (!this.have.has (o) && isClickable (o)) {
this.queue.push ({o: o, selector: s});
if (!s.multi) {
this.have.add (o);
}
}
}
}
if (this.queue.length > 0 && this.clickTimeout === null) {
/* start clicking immediately */
this.clickTimeout = window.setTimeout (this.click.bind (this), 0);
}
return true;
}
stop () {
window.clearInterval (this.interval);
window.clearTimeout (this.clickTimeout);
}
}
return Click;
}())
|