summaryrefslogtreecommitdiff
path: root/crocoite/data/click.js
blob: f6f285247097dc92f5c2eece604500dc78a798a0 (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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*	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},
			],
	}, {
		hostname: /^www\.instagram\.com$/i,
		selector: [
			/* posts may have multiple images that load dynamically, click the arrow */
			{s: 'a[role=button].coreSpriteRightChevron', flags: selectorFlag.multi, throttle: 500},
			/* load more comments */
			{s: 'article div ul li a[role=button]', flags: selectorFlag.multi},
			],
	}, {
		hostname: /^www\.youtube\.com$/i,
		selector: [
			/* expand comment thread */
			{s: 'ytd-comment-thread-renderer div.more-button', flags: selectorFlag.none},
			],
	}
	]);

/* 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);
}());