summaryrefslogtreecommitdiff
path: root/crocoite/data/click.js
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;
}())