diff options
Diffstat (limited to 'crocoite/data/click.js')
| -rw-r--r-- | crocoite/data/click.js | 107 | 
1 files changed, 107 insertions, 0 deletions
| diff --git a/crocoite/data/click.js b/crocoite/data/click.js new file mode 100644 index 0000000..ae189da --- /dev/null +++ b/crocoite/data/click.js @@ -0,0 +1,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; +}()) | 
