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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
/* 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(){
const selectorFlag = Object.freeze ({
none: 0,
multi: 1, /* click item multiple times */
});
const defaultClickThrottle = 50; /* in ms */
const discoverInterval = 1000; /* 1 second */
const sites = Object.freeze ([
{
hostname: /^www\.facebook\.com$/i,
selector: [
/* show more comments */
{s: 'a.UFIPagerLink[role=button]', flags: selectorFlag.none},
/* show nested comments*/
{s: 'a.UFICommentLink[role=button]', flags: selectorFlag.none},
],
}, {
hostname: /^twitter\.com$/i,
selector: [
/* expand threads */
{s: 'a.ThreadedConversation-moreRepliesLink', flags: selectorFlag.none},
/* show hidden profiles */
{s: 'button.ProfileWarningTimeline-button', flags: selectorFlag.none},
/* show hidden/sensitive media */
{s: 'button.Tombstone-action.js-display-this-media', flags: selectorFlag.none},
],
}, {
hostname: /^disqus\.com$/i,
selector: [
/* load more comments */
{s: 'a.load-more__button', flags: selectorFlag.multi},
],
}, {
hostname: /^(www|np)\.reddit\.com$/i,
selector: [
/* show more comments, reddit’s javascript ignores events if too
* frequent */
{s: 'span.morecomments a', flags: selectorFlag.none, throttle: 500},
],
}
]);
/* pick selectors matching current location */
let hostname = document.location.hostname;
let selector = [];
for (let s of sites) {
if (s.hostname.test (hostname)) {
selector = selector.concat (s.selector);
}
}
function makeClickEvent () {
return new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
}
/* throttle clicking */
let queue = [];
let clickTimeout = null;
function click () {
if (queue.length > 0) {
const item = queue.shift ();
const o = item.o;
const selector = item.selector;
o.dispatchEvent (makeClickEvent ());
if (queue.length > 0) {
const nextTimeout = 'throttle' in selector ?
selector.throttle : defaultClickThrottle;
clickTimeout = window.setTimeout (click, nextTimeout);
} else {
clickTimeout = null;
}
}
}
/* 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);
}
/* some sites don’t remove/replace the element immediately, so keep track of
* which ones we already clicked */
let have = new Set ();
function discover () {
for (let s of selector) {
let obj = document.querySelectorAll (s.s);
for (let o of obj) {
if (!have.has (o) && isClickable (o)) {
queue.push ({o: o, selector: s});
if (!(s.flags & selectorFlag.multi)) {
have.add (o);
}
}
}
}
if (queue.length > 0 && clickTimeout === null) {
/* start clicking immediately */
clickTimeout = window.setTimeout (click, 0);
}
return true;
}
/* XXX: can we use a mutation observer instead? */
window.setInterval (discover, discoverInterval);
}());
|