summaryrefslogtreecommitdiff
path: root/crocoite
diff options
context:
space:
mode:
authorLars-Dominik Braun <lars@6xq.net>2018-03-05 19:06:55 +0100
committerLars-Dominik Braun <lars@6xq.net>2018-03-05 19:06:55 +0100
commit95ee40fa8cdca9cff0aba033a4352fc0621a9583 (patch)
tree0d554d825e9b70d5829e515fcca7c5c259aeadd6 /crocoite
parentd571bed665cee1af6b2212c04791a7a11f18cf7d (diff)
downloadcrocoite-95ee40fa8cdca9cff0aba033a4352fc0621a9583.tar.gz
crocoite-95ee40fa8cdca9cff0aba033a4352fc0621a9583.tar.bz2
crocoite-95ee40fa8cdca9cff0aba033a4352fc0621a9583.zip
Add generic click behavior script
Configureable. Clicks elements matching one (or more) CSS selectors once or multiple times. Currently supported: Facebook, Twitter, Disqus (embedded iframe)
Diffstat (limited to 'crocoite')
-rw-r--r--crocoite/behavior.py15
-rw-r--r--crocoite/data/click.js111
-rw-r--r--crocoite/data/per-site/twitter.js30
3 files changed, 119 insertions, 37 deletions
diff --git a/crocoite/behavior.py b/crocoite/behavior.py
index e4ec93b..26841aa 100644
--- a/crocoite/behavior.py
+++ b/crocoite/behavior.py
@@ -222,17 +222,18 @@ class Screenshot (Behavior):
'X-Chrome-Viewport': viewport})
writer.write_record (record)
-### Site-specific scripts ###
+class Click (JsOnload):
+ """ Generic link clicking """
+
+ name = 'click'
+ scriptPath = 'click.js'
-class Twitter (HostnameFilter, JsOnload):
- name = 'twitter'
- scriptPath = 'per-site/twitter.js'
- hostname = ['com', 'twitter']
+### Site-specific scripts ###
# available behavior scripts. Order matters, move those modifying the page
# towards the end of available
-generic = [Scroll, EmulateScreenMetrics]
-perSite = [Twitter]
+generic = [Scroll, EmulateScreenMetrics, Click]
+perSite = []
available = generic + perSite + [Screenshot, DomSnapshot]
availableNames = set (map (lambda x: x.name, available))
diff --git a/crocoite/data/click.js b/crocoite/data/click.js
new file mode 100644
index 0000000..7013487
--- /dev/null
+++ b/crocoite/data/click.js
@@ -0,0 +1,111 @@
+/* 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 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},
+ ],
+ }
+ ]);
+
+/* 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 = [];
+function click () {
+ let o = queue.shift ();
+ if (o !== undefined) {
+ o.dispatchEvent (makeClickEvent ());
+ }
+ return queue.length > 0;
+}
+
+/* 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);
+ if (!(s.flags & selectorFlag.multi)) {
+ have.add (o);
+ }
+ }
+ }
+ }
+ if (queue.length > 0) {
+ window.setInterval (click, 50);
+ }
+ return true;
+}
+
+/* XXX: can we use a mutation observer instead? */
+window.setInterval (discover, 1000);
+}());
diff --git a/crocoite/data/per-site/twitter.js b/crocoite/data/per-site/twitter.js
deleted file mode 100644
index 2773f64..0000000
--- a/crocoite/data/per-site/twitter.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/* Fixups for twitter:
- * - Some accounts are hidden behind a “suspicious activity” message, click
- * that.
- * - Click “more replies” buttons periodically (as they popup when scrolling)
- */
-(function(){
-function makeClickEvent () {
- return new MouseEvent('click', {
- view: window,
- bubbles: true,
- cancelable: true
- });
-}
-function expandThread () {
- let links = document.querySelectorAll('a.ThreadedConversation-moreRepliesLink');
- for (let i = 0; i < links.length; i++) {
- links[i].dispatchEvent (makeClickEvent ());
- }
- return true;
-}
-function showProfile () {
- var show = document.querySelector ("button.ProfileWarningTimeline-button");
- if (show) {
- show.dispatchEvent (makeClickEvent ());
- }
-}
-window.addEventListener("load", showProfile);
-/* XXX: can we use a mutation observer instead? */
-window.setInterval (expandThread, 1000);
-}());