From 4d93f45de5eb91ea8bee961dee828e4ec01faa69 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Mon, 9 Sep 2013 16:29:25 -0600 Subject: [PATCH 01/24] Add a[rel] and a[class] methods of finding the anchor --- content-script.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/content-script.js b/content-script.js index 0e36fbd..a32871d 100644 --- a/content-script.js +++ b/content-script.js @@ -244,6 +244,24 @@ Repaginator.prototype = { } } + // Fourth: See if there is rel=next or rel=prev + let rel = (el.getAttribute("rel") || "").trim(); + if (rel && (rel.contains("next") || rel.contains("prev"))) { + this.query += "//a[@rel='" + escapeXStr(rel) + "']"; + // no point in checking for numbers + this.attemptToIncrement = false; + log(LOG_DEBUG, "using a[@rel]"); + return; + } + + // Fifth: See if there is a class we may use + if (el.className) { + this.query += "//a[@class='" + escapeXStr(el.className) + "']"; + this.numberToken = /(\[@class='.*?)(\d+)(.*?'\])/; + log(LOG_DEBUG, "using a[@class]"); + return; + } + throw new Error("No anchor expression found!"); }).call(this); }).call(this); From d024a6606f3587d6f0408c04d30292623fbdd0fa Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Thu, 19 Mar 2015 23:19:48 -0600 Subject: [PATCH 02/24] Homestuck hack --- content-script.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/content-script.js b/content-script.js index a32871d..39afb08 100644 --- a/content-script.js +++ b/content-script.js @@ -158,6 +158,13 @@ Repaginator.prototype = { this.query = ""; (function buildQuery() { + // Homestuck hack + if(el.href.contains("mspaintadventures.com")) { + this.query = "//center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 6]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/font[position() = 1]/a[position() = 1]"; + this.numberToken = /(\[@href='.*)(\d+)(.*?'\])/; + return; + } + // See if the anchor has an ID // Note: cannot use the id() xpath function here, as there might // be duplicate ids From a0442d3aee626959bbfaa70328ef33a36d1488ce Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Fri, 1 May 2015 20:47:50 -0600 Subject: [PATCH 03/24] Use descendant instead of child, for cases like a > div > img --- content-script.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content-script.js b/content-script.js index 39afb08..167648f 100644 --- a/content-script.js +++ b/content-script.js @@ -225,8 +225,8 @@ Repaginator.prototype = { return; } - // Second: see if it has a child with a @src we may use - let srcEl = getFirstSnapshot(el.ownerDocument, el, "child::*[@src]"); + // Second: see if it has a descendant with a @src we may use + let srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@src]"); if (srcEl) { let src = srcEl.getAttribute("src") || ""; if (src.trim()) { @@ -238,8 +238,8 @@ Repaginator.prototype = { } } - // Third: See if there is a child with a @value we may use - srcEl = getFirstSnapshot(el.ownerDocument, el, "child::*[@value]"); + // Third: See if there is a descendant with a @value we may use + srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@value]"); if (srcEl) { let val = srcEl.getAttribute("value") || ""; if (val.trim()) { From 4f8e2e2174573d7fc046d804994b447f9400b6de Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Fri, 4 Sep 2015 15:54:34 -0600 Subject: [PATCH 04/24] Remove some same-origin checks, so that sites that auto-redirect from x.com to www.x.com will work. --- content-script.js | 3 --- main.js | 20 +------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/content-script.js b/content-script.js index 167648f..69b272f 100644 --- a/content-script.js +++ b/content-script.js @@ -70,9 +70,6 @@ const checkSameOrigin = (node, tryLoadUri) => { const createFrame = (window, src, allowScripts, loadFun) => { log(LOG_INFO, "creating frame for " + src); - if (!checkSameOrigin(window.document, src)) { - throw new Error("same origin mismatch; frame creation denied"); - } let frame = window.document.createElement("iframe"); frame.setAttribute("sandbox", "allow-scripts"); frame.style.display = "none"; diff --git a/main.js b/main.js index 4ee8a04..967e5df 100644 --- a/main.js +++ b/main.js @@ -34,23 +34,6 @@ lazy(this, "_", function() { }; }); -function checkSameOrigin(principal, tryLoadUri) { - try { - if (!(tryLoadUri instanceof Ci.nsIURI)) { - tryLoadUri = Services.io.newURI(tryLoadUri, null, null); - } - if (tryLoadUri.schemeIs("data")) { - return true; - } - principal.checkMayLoad(tryLoadUri, false, true); - return true; - } - catch (ex) { - log(LOG_DEBUG, "denied load of " + (tryLoadUri.spec || tryLoadUri), ex); - return false; - } -} - function main(window, document) { const $ = id => document.getElementById(id); const $$$ = q => document.querySelectorAll(q); @@ -209,8 +192,7 @@ function main(window, document) { log(LOG_DEBUG, `context menu showing for ${gContextMenu.frameOuterWindowID}!`); try { if (gContextMenu.onLink && /^https?$/.test(gContextMenu.linkURI.scheme)) { - setMenuHidden(!checkSameOrigin(gContextMenu.principal, - gContextMenu.linkURL)); + setMenuHidden(false); if (!RUNNING.has(gContextMenu.frameOuterWindowID)) { menuCurrent.stopMenu.hidden = true; } From 2405c51ba6364a0d309dc657772f590ad8a5365d Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Wed, 4 Nov 2015 22:21:56 -0700 Subject: [PATCH 05/24] Bump version --- install.rdf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rdf b/install.rdf index 770e4af..da9926d 100644 --- a/install.rdf +++ b/install.rdf @@ -7,7 +7,7 @@ {6072cb90-a0bd-11da-a746-0800200c9a66} Re-Pagination Load all consecutive pages in a single tab at once. - 2016.08.03 + 2016.08.08 true true From 7783addc37178355177dd7c3c12c7a950110b238 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Fri, 6 May 2016 22:09:41 -0600 Subject: [PATCH 06/24] One 'class' suffices; stopping only at ID's breaks wordpress sites in archive.org --- content-script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/content-script.js b/content-script.js index 69b272f..0471665 100644 --- a/content-script.js +++ b/content-script.js @@ -204,6 +204,7 @@ Repaginator.prototype = { log(LOG_DEBUG, "got class: " + pn.className); pieces.unshift("//" + pn.localName + "[@class='" + escapeXStr(pn.className) + "']"); + break; } } this.query = pieces.join(""); From 620d28c6435c4002507edae4aac293e3e3196a65 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Sat, 17 Sep 2016 00:16:35 -0600 Subject: [PATCH 07/24] Update for FF 48 --- content-script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content-script.js b/content-script.js index 0471665..35511c9 100644 --- a/content-script.js +++ b/content-script.js @@ -156,7 +156,7 @@ Repaginator.prototype = { (function buildQuery() { // Homestuck hack - if(el.href.contains("mspaintadventures.com")) { + if(el.href.includes("mspaintadventures.com")) { this.query = "//center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 6]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/font[position() = 1]/a[position() = 1]"; this.numberToken = /(\[@href='.*)(\d+)(.*?'\])/; return; @@ -251,7 +251,7 @@ Repaginator.prototype = { // Fourth: See if there is rel=next or rel=prev let rel = (el.getAttribute("rel") || "").trim(); - if (rel && (rel.contains("next") || rel.contains("prev"))) { + if (rel && (rel.includes("next") || rel.includes("prev"))) { this.query += "//a[@rel='" + escapeXStr(rel) + "']"; // no point in checking for numbers this.attemptToIncrement = false; From ee314474fb0151132855c0f6257504534b6b7037 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Tue, 25 Apr 2017 19:55:17 -0600 Subject: [PATCH 08/24] Rel is more useful --- content-script.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/content-script.js b/content-script.js index 35511c9..daa72fc 100644 --- a/content-script.js +++ b/content-script.js @@ -215,7 +215,17 @@ Repaginator.prototype = { (function findAnchor() { let text = el.textContent; - // First: try the node text + // See if there is rel=next or rel=prev + let rel = (el.getAttribute("rel") || "").trim(); + if (rel && (rel.includes("next") || rel.includes("prev"))) { + this.query += "//a[@rel='" + escapeXStr(rel) + "']"; + // no point in checking for numbers + this.attemptToIncrement = false; + log(LOG_DEBUG, "using a[@rel]"); + return; + } + + // Try the node text if (text.trim()) { this.query += "//a[.='" + escapeXStr(text) + "']"; this.numberToken = /(a\[.='.*?)(\d+)(.*?\])/; @@ -223,7 +233,7 @@ Repaginator.prototype = { return; } - // Second: see if it has a descendant with a @src we may use + // See if it has a descendant with a @src we may use let srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@src]"); if (srcEl) { let src = srcEl.getAttribute("src") || ""; @@ -236,7 +246,7 @@ Repaginator.prototype = { } } - // Third: See if there is a descendant with a @value we may use + // See if there is a descendant with a @value we may use srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@value]"); if (srcEl) { let val = srcEl.getAttribute("value") || ""; @@ -249,17 +259,7 @@ Repaginator.prototype = { } } - // Fourth: See if there is rel=next or rel=prev - let rel = (el.getAttribute("rel") || "").trim(); - if (rel && (rel.includes("next") || rel.includes("prev"))) { - this.query += "//a[@rel='" + escapeXStr(rel) + "']"; - // no point in checking for numbers - this.attemptToIncrement = false; - log(LOG_DEBUG, "using a[@rel]"); - return; - } - - // Fifth: See if there is a class we may use + // See if there is a class we may use if (el.className) { this.query += "//a[@class='" + escapeXStr(el.className) + "']"; this.numberToken = /(\[@class='.*?)(\d+)(.*?'\])/; From 94bcd2cb100040ca63f597e145b6b9a5ea19d1f9 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Sat, 3 Jun 2017 21:42:06 -0600 Subject: [PATCH 09/24] Noscript elements have content, don't remove them --- content-script.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/content-script.js b/content-script.js index daa72fc..a2e5f7b 100644 --- a/content-script.js +++ b/content-script.js @@ -428,9 +428,6 @@ Repaginator.prototype = { // Note: This is NOT a security mechanism, but a performance thing. Array.forEach(doc.querySelectorAll("script"), s => s.parentNode.removeChild(s)); - Array.forEach(doc.querySelectorAll("noscript"), - s => s.parentNode.removeChild(s)); - yield true; // Do the dirty deed From 1b247a80295446db5925e4701cf2b076590bf8a9 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Sat, 3 Jun 2017 21:47:44 -0600 Subject: [PATCH 10/24] Remove same-origin check that used deprecated (now removed) API --- content-script.js | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/content-script.js b/content-script.js index a2e5f7b..64ff327 100644 --- a/content-script.js +++ b/content-script.js @@ -40,34 +40,6 @@ const _ = function() { const getFirstSnapshot = (doc, node, query) => doc.evaluate(query, node, null, 7, null).snapshotItem(0); -const checkSameOrigin = (node, tryLoadUri) => { - try { - if (!(tryLoadUri instanceof Ci.nsIURI)) { - tryLoadUri = Services.io.newURI(tryLoadUri, null, null); - } - if (tryLoadUri.schemeIs("data")) { - return true; - } - let pr = node.nodePrincipal; - pr = Cc["@mozilla.org/scriptsecuritymanager;1"]. - getService(Ci.nsIScriptSecurityManager). - getAppCodebasePrincipal(pr.URI, - pr.appId, - pr.isInBrowserElement); - if (pr.checkMayLoad.length == 3) { - pr.checkMayLoad(tryLoadUri, false, false); - } - else { - pr.checkMayLoad(tryLoadUri, false); - } - return true; - } - catch (ex) { - log(LOG_DEBUG, "denied load of " + (tryLoadUri.spec || tryLoadUri), ex); - return false; - } -}; - const createFrame = (window, src, allowScripts, loadFun) => { log(LOG_INFO, "creating frame for " + src); let frame = window.document.createElement("iframe"); @@ -416,9 +388,6 @@ Repaginator.prototype = { } var doc = element.contentDocument; - if (!checkSameOrigin(ownerDoc, doc.defaultView.location)) { - throw new Error("not in the same origin anymore"); - } this.pageCount++; // Remove scripts from frame From 2a7cd8eab479996ad3226b429d8bdcf37c3880aa Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Fri, 15 Sep 2017 10:17:29 -0600 Subject: [PATCH 11/24] Turn into a WebExtension --- _locales/de/messages.json | 85 +++ _locales/en/messages.json | 83 +++ bootstrap.js | 24 - chrome.manifest | 4 - clicked_element.js | 6 + content-script.js | 934 ++++++++++++--------------- defaults/preferences/prefs.js | 6 - install.rdf | 40 -- loader.jsm | 242 ------- locale/de/options.dtd | 10 - locale/de/repagination.dtd | 9 - locale/de/repagination.properties | 11 - locale/en-US/options.dtd | 10 - locale/en-US/repagination.dtd | 9 - locale/en-US/repagination.properties | 11 - main.js | 399 +++++------- manifest.json | 44 ++ options.html | 70 ++ options.js | 50 ++ options.xul | 21 - repagination.xul | 75 --- sdk/cothreads.js | 244 ------- sdk/logging.js | 146 ----- sdk/observers.js | 59 -- sdk/preferences.js | 177 ----- sdk/request.js | 119 ---- sdk/strings.js | 41 -- sdk/timers.js | 90 --- sdk/windows.js | 222 ------- 29 files changed, 921 insertions(+), 2320 deletions(-) create mode 100644 _locales/de/messages.json create mode 100644 _locales/en/messages.json delete mode 100644 bootstrap.js delete mode 100644 chrome.manifest create mode 100644 clicked_element.js delete mode 100644 defaults/preferences/prefs.js delete mode 100644 install.rdf delete mode 100644 loader.jsm delete mode 100644 locale/de/options.dtd delete mode 100644 locale/de/repagination.dtd delete mode 100644 locale/de/repagination.properties delete mode 100644 locale/en-US/options.dtd delete mode 100644 locale/en-US/repagination.dtd delete mode 100644 locale/en-US/repagination.properties create mode 100644 manifest.json create mode 100644 options.html create mode 100644 options.js delete mode 100644 options.xul delete mode 100644 repagination.xul delete mode 100644 sdk/cothreads.js delete mode 100644 sdk/logging.js delete mode 100644 sdk/observers.js delete mode 100644 sdk/preferences.js delete mode 100644 sdk/request.js delete mode 100644 sdk/strings.js delete mode 100644 sdk/timers.js delete mode 100644 sdk/windows.js diff --git a/_locales/de/messages.json b/_locales/de/messages.json new file mode 100644 index 0000000..c6c761c --- /dev/null +++ b/_locales/de/messages.json @@ -0,0 +1,85 @@ +{ + "displaySubmenu.label": { + "message": "Untermenü anzeigen" + }, + "displaySubmenu.desc": { + "message": "Wenn diese Option aktiviert ist (Standard), dann wird Re-Pagination als Untermenü angezeigt anstatt die verschiedenen Aktionen direkt im Kontextmenü anzuzeigen." + }, + "showslideshow.label": { + "message": "'Slide-Show' Menüeintrag anzeigen" + }, + "showalldomain.label": { + "message": "'Alle Tabs der aktuellen Domain' Menüeintrag anzeigen" + }, + "loglevel.label": { + "message": "Protokoll Stufe" + }, + "loglevel.desc": { + "message": "Die Nachrichten werden in der Fehlerkonsole protokolliert. Man sollte diese Einstellung auf 'Keine Protokollierung' belassen, wenn nicht anders angewiesen." + }, + "loglevel.none.label": { + "message": "Keine Protokollierung" + }, + "loglevel.error.label": { + "message": "Fehler protokollieren" + }, + "loglevel.info.label": { + "message": "Fehler und Infos protokollieren" + }, + "loglevel.debug.label": { + "message": "Alles protokollieren!" + }, + "menu.label": { + "message": "Re-Pagination" + }, + "repagination_nolimit": { + "message": "Alle laden" + }, + "repagination_nolimit_domain": { + "message": "'Alle Tabs der aktuellen Domain'" + }, + "repagination_limit": { + "message": "Begrenzt" + }, + "repagination_slide": { + "message": "Slide-Show" + }, + "repagination_slide_0": { + "message": "Sofort" + }, + "repagination_slide_1": { + "message": "1 Sekunde" + }, + "repagination_slide_60": { + "message": "1 Minute" + }, + "repagination_stop": { + "message": "Stoppen!" + }, + "repagination_limit_x": { + "message": "%S Seiten" + }, + "repagination_slide_120": { + "message": "2 Minuten" + }, + "repagination_slide_x": { + "message": "%S Sekunden" + }, + + "repagination_limited": { + "message": "(%S of %S) Re-Pagination arbeitet..." + }, + "repagination_unlimited": { + "message": "(%S) Re-Pagination arbeitet..." + }, + "repagination_running": { + "message": "Re-Pagination arbeitet..." + }, + + "extensionName": { + "message": "Re-Pagination" + }, + "extensionDescription": { + "message": "Alle fortlaufenden Seiten in einem einzigem Tab auf einmal laden" + } +} \ No newline at end of file diff --git a/_locales/en/messages.json b/_locales/en/messages.json new file mode 100644 index 0000000..9c9d59c --- /dev/null +++ b/_locales/en/messages.json @@ -0,0 +1,83 @@ +{ + "displaySubmenu.label": { + "message": "Display submenu" + }, + "displaySubmenu.desc": { + "message": "When this option is enabled (default), then Re-Pagination will be displayed as a submenu instead of displaying the various actions directly within the context menu." + }, + "showslideshow.label": { + "message": "Show 'Slideshow' menu item" + }, + "showalldomain.label": { + "message": "Show 'All tabs for current domain' menu item" + }, + "loglevel.label": { + "message": "Log level" + }, + "loglevel.desc": { + "message": "The messages will be logged to the Error Console. You should leave this at 'No Logging' unless instructed otherwise" + }, + "loglevel.none.label": { + "message": "No Logging" + }, + "loglevel.error.label": { + "message": "Log Errors" + }, + "loglevel.info.label": { + "message": "Log Errors and Infos" + }, + "loglevel.debug.label": { + "message": "Log Everything!" + }, + "menu.label": { + "message": "Re-Pagination" + }, + "repagination_nolimit": { + "message": "Load all" + }, + "repagination_nolimit_domain": { + "message": "All tabs for current domain" + }, + "repagination_limit": { + "message": "Limited" + }, + "repagination_limit_x": { + "message": "$1 pages" + }, + "repagination_slide": { + "message": "Slideshow" + }, + "repagination_slide_0": { + "message": "Immediately" + }, + "repagination_slide_1": { + "message": "1 second" + }, + "repagination_slide_x": { + "message": "$1 seconds" + }, + "repagination_slide_60": { + "message": "1 minute" + }, + "repagination_slide_120": { + "message": "2 minutes" + }, + "repagination_stop": { + "message": "Stop!" + }, + "repagination_limited": { + "message": "($1 of $2) Re-Pagination is running..." + }, + "repagination_unlimited": { + "message": "($1) Re-Pagination is running..." + }, + "repagination_running": { + "message": "Re-Pagination is running..." + }, + "extensionName": { + "message": "Re-Pagination" + }, + "extensionDescription": { + "message": "Load all consecutive pages in a single tab at once." + } +} \ No newline at end of file diff --git a/bootstrap.js b/bootstrap.js deleted file mode 100644 index eeda145..0000000 --- a/bootstrap.js +++ /dev/null @@ -1,24 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -const global = this; - -function install() {} -function uninstall() {} -function startup(data) { - Components.utils.import("chrome://repagination/content/loader.jsm"); - _setupLoader(data, function real_startup() { - require("main"); - }); -} -function shutdown(reason) { - if (reason === APP_SHUTDOWN) { - // No need to cleanup; stuff will vanish anyway - return; - } - unload("shutdown"); -} - -/* vim: set et ts=2 sw=2 : */ diff --git a/chrome.manifest b/chrome.manifest deleted file mode 100644 index f574b5b..0000000 --- a/chrome.manifest +++ /dev/null @@ -1,4 +0,0 @@ -content repagination ./ - -locale repagination en-US locale/en-US/ -locale repagination de locale/de/ diff --git a/clicked_element.js b/clicked_element.js new file mode 100644 index 0000000..1ed6cf6 --- /dev/null +++ b/clicked_element.js @@ -0,0 +1,6 @@ +// https://bugzilla.mozilla.org/show_bug.cgi?id=1325814 +var clickedEl = null; + +document.addEventListener("contextmenu", function(event) { + clickedEl = event.target; +}, true); diff --git a/content-script.js b/content-script.js index 64ff327..2f65a1a 100644 --- a/content-script.js +++ b/content-script.js @@ -2,595 +2,473 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +if(!("port" in this)) { + const _ = function () { + let args = Array.map(arguments, e => e.toString()); + return browser.i18n.getMessage(args[0], args.slice(1)); + } -/* global - addMessageListener, - content, - removeMessageListener, - sendAsyncMessage, - sendSyncMessage -*/ - -(function() { - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -let {Services, Promise} = Cu.import("resource://gre/modules/Services.jsm", {}); -let {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); - -const [LOG_DEBUG, LOG_INFO, LOG_ERROR] = [0, 1, 2]; -const log = function(level, message, exception) { - sendAsyncMessage("repagination:log", { - level: level, - message: message, - exception: exception && { - message: exception.message, - fileName: exception.fileName, - lineNumber: exception.lineNumber, - stack: exception.stack - } - }); -}; - -const _ = function() { - return sendSyncMessage("repagination:_", { - arguments: Array.map(arguments, e => e.toString()) - })[0]; -}; - -const getFirstSnapshot = (doc, node, query) => - doc.evaluate(query, node, null, 7, null).snapshotItem(0); - -const createFrame = (window, src, allowScripts, loadFun) => { - log(LOG_INFO, "creating frame for " + src); - let frame = window.document.createElement("iframe"); - frame.setAttribute("sandbox", "allow-scripts"); - frame.style.display = "none"; - window.document.body.appendChild(frame); - let docShell = frame.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell); - docShell.allowImages = false; - docShell.allowPlugins = false; - docShell.allowJavascript = allowScripts; - - frame.addEventListener("load", function loadHandler() { - frame.removeEventListener("load", loadHandler, false); - log(LOG_INFO, "frame loaded, going to process"); - try { - loadFun(frame); - } - catch (ex) { - log(LOG_ERROR, "failed to invoke callback on frame", ex); - } - }, false); - frame.src = src; - - let errorCount = 0; - let errorHandler = function() { - log(LOG_INFO, "frame err'ed out"); - if (++errorCount > 5) { - frame.removeEventListener("error", errorHandler, false); - frame.removeEventListener("abort", errorHandler, false); - log(LOG_ERROR, "frame err'ed out, giving up"); - try { - loadFun(frame); - } - catch (ex) { - log(LOG_ERROR, "failed to invoke callback on frame", ex); - } - return; - } - if (frame.history) { - frame.history.reload(); - } - else { - frame.src = src; - } - }; - frame.addEventListener("error", errorHandler, false); - frame.addEventListener("abort", errorHandler, false); - - return frame; -}; - -const Repaginator = function Repaginator(focusElement, count, allowScripts, yielding) { - this.pageLimit = count || 0; - this.allowScripts = allowScripts; - this.yielding = yielding; - this.init(focusElement); -}; -Repaginator.prototype = { - slideshow: false, - pageLimit: 0, - seconds: 0, - pageCount: 0, - attemptToIncrement: true, - - init: function R_init(focusElement) { - // find anchor - (function findInitialAnchor() { - for (let parent = focusElement; parent; parent = parent.parentNode) { - if (parent.localName == "a") { - focusElement = parent; - return; + const getFirstSnapshot = (doc, node, query) => + doc.evaluate(query, node, null, 7, null).snapshotItem(0); + + const createFrame = (srcurl, allowScripts, loadFun) => { + let errorCount = 0; + console.info("creating frame for " + srcurl); + let myframe = document.createElement("iframe"); + myframe.setAttribute("sandbox", "allow-same-origin"); + myframe.style.display = "none"; + document.body.appendChild(myframe); + + function sendRequest() { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.status == 200) { + console.info("XHR loaded"); + myframe.addEventListener("load", function loadHandler() { + myframe.removeEventListener("load", loadHandler, false); + console.info("myframe loaded, going to process"); + try { + loadFun(myframe); + } + catch (ex) { + console.error("failed to invoke callback on myframe", ex); + } + }, false); + myframe.srcdoc = xhr.responseText; + } else if (++errorCount <= 5) { + console.info("XHR err'ed out, retrying"); + sendRequest(); + } else { + myframe.removeEventListener("error", errorHandler, false); + myframe.removeEventListener("abort", errorHandler, false); + console.error("XHR err'ed out, giving up"); + try { + loadFun(myframe); + } catch (ex) { + console.error("failed to invoke callback on myframe", ex); + } } } - throw new Error("No focus element"); - })(); - - this._window = focusElement.ownerDocument.defaultView; - }, - buildQuery: function R_buildQuery(el) { - function escapeXStr(str) { - return str.replace(/'/g, "\\'"); + xhr.open('GET', srcurl); + xhr.send(); } - - this.query = ""; - - (function buildQuery() { - // Homestuck hack - if(el.href.includes("mspaintadventures.com")) { - this.query = "//center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 6]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/font[position() = 1]/a[position() = 1]"; - this.numberToken = /(\[@href='.*)(\d+)(.*?'\])/; + + let errorHandler = function() { + if (++errorCount > 5) { + myframe.removeEventListener("error", errorHandler, false); + myframe.removeEventListener("abort", errorHandler, false); + console.error("myframe load err'ed out, giving up"); + try { + loadFun(myframe); + } catch (ex) { + console.error("failed to invoke callback on myframe", ex); + } return; + } else { + console.info("myframe load err'ed out, retrying"); + sendRequest(); } + }; + myframe.addEventListener("error", errorHandler, false); + myframe.addEventListener("abort", errorHandler, false); - // See if the anchor has an ID - // Note: cannot use the id() xpath function here, as there might - // be duplicate ids - if (el.id) { - this.query = "//a[@id='" + escapeXStr(el.id) + "']"; - this.numberToken = /(\[@id='.*?)(\d+)(.*?'\])/; - return; - } + sendRequest(); + return myframe; + }; - // See if the document has a link rel="..." pointing to the same place - let linkEl = getFirstSnapshot(el.ownerDocument, el.ownerDocument, - "//head//link[@href='" + - escapeXStr(el.href) + "']"); - if (linkEl) { - let rel = linkEl.getAttribute("rel") || ""; - if (rel.trim()) { - this.query = "/html/head//link[@rel='" + escapeXStr(rel) + "']"; - // no point in checking for numbers - this.attemptToIncrement = false; - log(LOG_DEBUG, "using link[@rel]"); - return; + const Repaginator = function Repaginator(focusElement, count, allowScripts, yielding) { + this.pageLimit = count || 0; + this.allowScripts = allowScripts; + this.yielding = yielding; + this.init(focusElement); + }; + Repaginator.prototype = { + slideshow: false, + pageLimit: 0, + seconds: 0, + pageCount: 0, + attemptToIncrement: true, + + init: function R_init(focusElement) { + // find anchor + (function findInitialAnchor() { + for (let parent = focusElement; parent; parent = parent.parentNode) { + if (parent.localName == "a") { + focusElement = parent; + return; + } } + throw new Error("No focus element"); + })(); + + this._window = focusElement.ownerDocument.defaultView; + }, + buildQuery: function R_buildQuery(el) { + function escapeXStr(str) { + return str.replace(/'/g, "\\'"); } - // Find an id in the ancestor chain, or alternatively a class - // that we may operate on - (function findPathPrefix() { - let pieces = []; - for (let pn = el.parentNode; pn; pn = pn.parentNode) { - if (pn.localName == "body") { - break; - } - if (pn.id) { - log(LOG_DEBUG, "got id: " + pn.id); - pieces.unshift("//" + pn.localName + "[@id='" + - escapeXStr(pn.id) + "']"); - break; // one id is enough - } - if (pn.className) { - log(LOG_DEBUG, "got class: " + pn.className); - pieces.unshift("//" + pn.localName + "[@class='" + - escapeXStr(pn.className) + "']"); - break; - } - } - this.query = pieces.join(""); - log(LOG_DEBUG, "findPathPrefix result: " + this.query); - }).call(this); + this.query = ""; - // find the anchor - (function findAnchor() { - let text = el.textContent; - - // See if there is rel=next or rel=prev - let rel = (el.getAttribute("rel") || "").trim(); - if (rel && (rel.includes("next") || rel.includes("prev"))) { - this.query += "//a[@rel='" + escapeXStr(rel) + "']"; - // no point in checking for numbers - this.attemptToIncrement = false; - log(LOG_DEBUG, "using a[@rel]"); + (function buildQuery() { + // Homestuck hack + if(el.href.includes("mspaintadventures.com")) { + this.query = "//center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 6]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/font[position() = 1]/a[position() = 1]"; + this.numberToken = /(\[@href='.*)(\d+)(.*?'\])/; return; } - // Try the node text - if (text.trim()) { - this.query += "//a[.='" + escapeXStr(text) + "']"; - this.numberToken = /(a\[.='.*?)(\d+)(.*?\])/; - log(LOG_DEBUG, "using text"); + // See if the anchor has an ID + // Note: cannot use the id() xpath function here, as there might + // be duplicate ids + if (el.id) { + this.query = "//a[@id='" + escapeXStr(el.id) + "']"; + this.numberToken = /(\[@id='.*?)(\d+)(.*?'\])/; return; } - // See if it has a descendant with a @src we may use - let srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@src]"); - if (srcEl) { - let src = srcEl.getAttribute("src") || ""; - if (src.trim()) { - this.query += "//" + srcEl.localName + "[@src='" + escapeXStr(src) + - "']/ancestor::a"; - this.numberToken = /(\[@src='.*?)(\d+)(.*?'\])/; - log(LOG_DEBUG, "using @src"); + // See if the document has a link rel="..." pointing to the same place + let linkEl = getFirstSnapshot(el.ownerDocument, el.ownerDocument, + "//head//link[@href='" + + escapeXStr(el.href) + "']"); + if (linkEl) { + let rel = linkEl.getAttribute("rel") || ""; + if (rel.trim()) { + this.query = "/html/head//link[@rel='" + escapeXStr(rel) + "']"; + // no point in checking for numbers + this.attemptToIncrement = false; + console.log("using link[@rel]"); return; } } - // See if there is a descendant with a @value we may use - srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@value]"); - if (srcEl) { - let val = srcEl.getAttribute("value") || ""; - if (val.trim()) { - this.query += "//" + srcEl.localName + "[@value='" + - escapeXStr(val) + "']/ancestor::a"; - this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; - log(LOG_DEBUG, "using @value"); + // Find an id in the ancestor chain, or alternatively a class + // that we may operate on + (function findPathPrefix() { + let pieces = []; + for (let pn = el.parentNode; pn; pn = pn.parentNode) { + if (pn.localName == "body") { + break; + } + if (pn.id) { + console.log("got id: " + pn.id); + pieces.unshift("//" + pn.localName + "[@id='" + + escapeXStr(pn.id) + "']"); + break; // one id is enough + } + if (pn.className) { + console.log("got class: " + pn.className); + pieces.unshift("//" + pn.localName + "[@class='" + + escapeXStr(pn.className) + "']"); + break; + } + } + this.query = pieces.join(""); + console.log("findPathPrefix result: " + this.query); + }).call(this); + + // find the anchor + (function findAnchor() { + let text = el.textContent; + + // See if there is rel=next or rel=prev + let rel = (el.getAttribute("rel") || "").trim(); + if (rel && (rel.includes("next") || rel.includes("prev"))) { + this.query += "//a[@rel='" + escapeXStr(rel) + "']"; + // no point in checking for numbers + this.attemptToIncrement = false; + console.log("using a[@rel]"); return; } - } - // See if there is a class we may use - if (el.className) { - this.query += "//a[@class='" + escapeXStr(el.className) + "']"; - this.numberToken = /(\[@class='.*?)(\d+)(.*?'\])/; - log(LOG_DEBUG, "using a[@class]"); - return; - } + // Try the node text + if (text.trim()) { + this.query += "//a[.='" + escapeXStr(text) + "']"; + this.numberToken = /(a\[.='.*?)(\d+)(.*?\])/; + console.log("using text"); + return; + } - throw new Error("No anchor expression found!"); - }).call(this); - }).call(this); + // See if it has a descendant with a @src we may use + let srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@src]"); + if (srcEl) { + let src = srcEl.getAttribute("src") || ""; + if (src.trim()) { + this.query += "//" + srcEl.localName + "[@src='" + escapeXStr(src) + + "']/ancestor::a"; + this.numberToken = /(\[@src='.*?)(\d+)(.*?'\])/; + console.log("using @src"); + return; + } + } + // See if there is a descendant with a @value we may use + srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@value]"); + if (srcEl) { + let val = srcEl.getAttribute("value") || ""; + if (val.trim()) { + this.query += "//" + srcEl.localName + "[@value='" + + escapeXStr(val) + "']/ancestor::a"; + this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; + console.log("using @value"); + return; + } + } - // We're after the last result - this.query = "(" + this.query + ")[last()]"; - log(LOG_INFO, "query: " + this.query); - }, - setTitle: function R_setTitle() { - let wnd = this._window; - if (!wnd) { - return; - } - if (!this._title) { - this._title = wnd.document.title; - } - if (this.pageLimit) { - wnd.document.title = - _("repagination_limited", this.pageCount, this.pageLimit); - } - else if (this.pageCount > 0) { - wnd.document.title = _("repagination_unlimited", this.pageCount); - } - else { - wnd.document.title = _("repagination_running"); - } - }, - restoreTitle: function R_restoreTitle() { - let wnd = this._window; - if (this._title && wnd) { - wnd.document.title = this._title; - delete this._title; - } - }, - unregister: function R_unregister() { - sendAsyncMessage("repagination:unregister", {id: this.frameId}); - if (this._window) { - this._window.document.body.removeAttribute("repagination"); - this._window.removeEventListener("beforeunload", this.unload, true); - } - }, - repaginate: function R_repaginate() { - this.setTitle(); - let wnd = this._window; - if (!wnd) { - log(LOG_INFO, "window is gone!"); - return; - } - this.frameId = wnd.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils). - outerWindowID; - sendAsyncMessage("repagination:register", {id: this.frameId}); - this.unload = () => { - log(LOG_DEBUG, "unload"); - this.unregister(); - }; - wnd.addEventListener("beforeunload", this.unload, true); + // See if there is a class we may use + if (el.className) { + this.query += "//a[@class='" + escapeXStr(el.className) + "']"; + this.numberToken = /(\[@class='.*?)(\d+)(.*?'\])/; + console.log("using a[@class]"); + return; + } - try { - let node = wnd.document.evaluate( - this.query, wnd.document, null, 9, null).singleNodeValue; - if (!node) { - throw new Error("no node"); + // See if there is a descendant with an id we may use + srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@id]"); + if (srcEl) { + let val = srcEl.getAttribute("id") || ""; + if (val.trim()) { + this.query += "//" + srcEl.localName + "[@id='" + + escapeXStr(val) + "']/ancestor::a"; + this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; + console.log("using descendant class"); + return; + } + } + + throw new Error("No anchor expression found!"); + }).call(this); + }).call(this); + + + // We're after the last result + this.query = "(" + this.query + ")[last()]"; + console.info("query: " + this.query); + }, + setTitle: function R_setTitle() { + if (this.pageLimit) { + document.title = + _("repagination_limited", this.pageCount, this.pageLimit); } - wnd.document.body.setAttribute("repagination", "true"); - createFrame(wnd, node.href, this.allowScripts, frame => { - this.loadNext(frame, 0); - }); - } - catch (ex) { - this.unregister(); - this.restoreTitle(); - log(LOG_ERROR, "repaginate failed", ex); - } - }, - incrementQuery: function R_incrementQuery() { - return this.query.replace(this.numberToken, function(g, pre, num, post) { - return pre + (parseInt(num, 10) + 1) + post; - }); - }, - loadNext: function R_loadNext(element, delay) { - if (!this.yielding) { - if (delay > 0) { - log(LOG_DEBUG, "delaying (sto) " + delay); - content.setTimeout(() => this.loadNext(element, 0), delay); - return; + else if (this.pageCount > 0) { + document.title = _("repagination_unlimited", this.pageCount); } - - try { - for (let f in this._loadNext_gen.bind(this, element)()) { - if (!f) { - break; - } - } + else { + document.title = _("repagination_running"); } - catch (ex) { - log(LOG_ERROR, "failed to process loadNext (non-yielding)", ex); + }, + restoreTitle: function R_restoreTitle() { + if("_title" in this) { + document.title = this._title; + delete this._title; } - return; - } - - Task.spawn(function*() { + }, + unregister: function R_unregister() { + document.body.removeAttribute("repagination"); + }, + repaginate: function R_repaginate() { + this._title = document.title; + this.setTitle(); try { - if (delay > 0) { - yield new Promise(r => { - log(LOG_DEBUG, "delaying " + delay); - content.setTimeout(() => { - log(LOG_DEBUG, "invoke " + delay); - r(); - }, delay); - }); - } - let gen = this._loadNext_gen(element); - let deadline = +(new Date()) + 60; - for (let r in gen) { - if (!r) { - break; - } - if (deadline < +(new Date())) { - yield new Promise(r => { - content.setTimeout(() => r(), 0); - }); - deadline = +(new Date()) + 60; - } + let node = document.evaluate( + this.query, document, null, 9, null).singleNodeValue; + if (!node) { + throw new Error("no node"); } + document.body.setAttribute("repagination", "true"); + createFrame(node.href, this.allowScripts, frame => { + this.loadNext(node.href, frame, 0); + }); } catch (ex) { - log(LOG_ERROR, "failed to iterate loadNext", ex); - } - }.bind(this)); - }, - _loadNext_gen: function R__loadNext_gen(element) { - try { - let ownerDoc = element.ownerDocument; - if (!ownerDoc || !this._window) { - yield true; this.unregister(); this.restoreTitle(); - log(LOG_INFO, "gone, giving up!"); + console.error("repaginate failed", ex); + } + }, + incrementQuery: function R_incrementQuery() { + return this.query.replace(this.numberToken, function(g, pre, num, post) { + return pre + (parseInt(num, 10) + 1) + post; + }); + }, + loadNext: function R_loadNext(src, element, delay) { + if (delay > 0) { + console.log("delaying (sto) " + delay); + content.setTimeout(() => this.loadNext(src, element, 0), delay); return; } try { - if (!ownerDoc.body.hasAttribute("repagination")) { - throw new Error("not running"); - } + this._loadNext_gen.bind(this, src, element)(); + } catch (ex) { + console.error("failed to process loadNext (non-yielding)", ex); + } + return; + }, + _loadNext_gen: function R__loadNext_gen(src, element) { + try { + let ownerDoc = document; - var doc = element.contentDocument; - this.pageCount++; - - // Remove scripts from frame - // The scripts should already be present in the parent (first page) - // Duplicate scripts would cause more havoc (performance-wise) than - // behaviour failures due to missing scripts - // Note: This is NOT a security mechanism, but a performance thing. - Array.forEach(doc.querySelectorAll("script"), - s => s.parentNode.removeChild(s)); - yield true; - - // Do the dirty deed - // Note: same-origin checked; see above - if (this.slideshow) { - log(LOG_INFO, "replacing content (slideshow)"); - ownerDoc.body.innerHTML = doc.body.innerHTML; - ownerDoc.body.setAttribute("repagination", "true"); - } - else { - log(LOG_INFO, "inserting content (normal)"); - // Remove non-same-origin iframes, such as ad iframes - // Otherwise we might create a shitload of (nearly) identical frames - // which might even kill the browser - if (!this.pageLimit || this.pageLimit > 10) { - log(LOG_INFO, "removing non-same-origin iframes to avoid dupes"); - let host = ownerDoc.defaultView.location.hostName; - Array.forEach(doc.querySelectorAll("iframe"), function(f) { - if (f.contentWindow.location.hostname != host) { - f.parentNode.removeChild(f); - } - }); - yield true; - } - for (let c = doc.body.firstChild; c; c = c.nextSibling) { - ownerDoc.body.appendChild(ownerDoc.importNode(c, true)); - yield true; + try { + if (!document.body.hasAttribute("repagination")) { + throw new Error("not running"); } - } - - // Synthesize load events to trigger other add-ons and - // page scripts. - if (ownerDoc.defaultView) { - log(LOG_DEBUG, "about to fire load events"); - - let levt = ownerDoc.createEvent("Events"); - levt.initEvent("DOMContentLoaded", true, true); - ownerDoc.defaultView.dispatchEvent(levt); - log(LOG_DEBUG, "fired DOMContentLoaded"); - - levt = ownerDoc.createEvent("Events"); - levt.initEvent("load", true, true); - ownerDoc.defaultView.dispatchEvent(levt); - log(LOG_DEBUG, "fired load"); - } - yield true; - - if (this.pageLimit && this.pageCount >= this.pageLimit) { - throw new Error("done"); - } - let savedQuery; - if (this.attemptToIncrement) { - log(LOG_DEBUG, "attempting to increment query"); - let nq = this.incrementQuery(); - if (nq == this.query) { - log(LOG_DEBUG, "query did not increment"); - this.attemptToIncrement = false; + var doc = element.contentDocument; + this.pageCount++; + + // Remove scripts from frame + // The scripts should already be present in the parent (first page) + // Duplicate scripts would cause more havoc (performance-wise) than + // behaviour failures due to missing scripts + // Note: This is NOT a security mechanism, but a performance thing. + Array.forEach(doc.querySelectorAll("script"), + s => s.parentNode.removeChild(s)); + //yield true; + + // Do the dirty deed + // Note: same-origin checked; see above + if (this.slideshow) { + console.info("replacing content (slideshow)"); + ownerDoc.body.innerHTML = doc.body.innerHTML; + ownerDoc.body.setAttribute("repagination", "true"); } else { - log(LOG_DEBUG, "query did increment"); - savedQuery = this.query; - this.query = nq; + console.info("inserting content (normal)"); + // Remove non-same-origin iframes, such as ad iframes + // Otherwise we might create a shitload of (nearly) identical frames + // which might even kill the browser + if (!this.pageLimit || this.pageLimit > 10) { + console.info("removing non-same-origin iframes to avoid dupes"); + let host = ownerDoc.defaultView.location.hostName; + Array.forEach(doc.querySelectorAll("iframe"), function(f) { + if (f.contentWindow.location.hostname != host) { + f.parentNode.removeChild(f); + } + }); + //yield true; + } + for (let c = doc.body.firstChild; c; c = c.nextSibling) { + ownerDoc.body.appendChild(ownerDoc.importNode(c, true)); + //yield true; + } } - } - let node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; - let loc = (doc.location || {}).href || null; - if (this.attemptToIncrement && (!node || node.href == loc)) { - log(LOG_DEBUG, "no result after incrementing; restoring"); - log(LOG_DEBUG, "inc:" + this.query + " orig:" + savedQuery); - this.query = savedQuery; - node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; - this.attemptToIncrement = false; - } - if (!node) { - throw new Error("no next node found for query: " + this.query); - } - if (loc && loc == node.href) { - throw new Error("location did not change for query" + this.query); - } - this.setTitle(); - log(LOG_INFO, "next please"); - createFrame(ownerDoc.defaultView, node.href, this.allowScripts, - frame => { - if (!this._window || this._window.closed) { - log(LOG_DEBUG, "self is gone by now"); - this.unregister(); - this.restoreTitle(); - return; + if (this.pageLimit && this.pageCount >= this.pageLimit) { + throw new Error("done"); } - if (this.slideshow && this.seconds) { - log(LOG_INFO, "slideshow; delay: " + this.seconds * 1000); - this.loadNext(frame, this.seconds * 1000); + + let savedQuery; + if (this.attemptToIncrement) { + console.log("attempting to increment query"); + let nq = this.incrementQuery(); + if (nq == this.query) { + console.log("query did not increment"); + this.attemptToIncrement = false; + } + else { + console.log("query did increment"); + savedQuery = this.query; + this.query = nq; + } } - else { - log(LOG_INFO, "regular; no-delay"); - this.loadNext(frame, 0); + let node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; + let loc = src || null; + if (this.attemptToIncrement && (!node || node.href == loc)) { + console.log("no result after incrementing; restoring"); + console.log("inc:" + this.query + " orig:" + savedQuery); + this.query = savedQuery; + node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; + this.attemptToIncrement = false; } - }); - } - catch (ex) { - log(LOG_INFO, "loadNext complete", ex); - this.unregister(); - this.restoreTitle(); + if (!node) { + throw new Error("no next node found for query: " + this.query); + } + let nexturl = node.href.toString(); + if (loc && loc == nexturl) { + throw new Error("location did not change for query" + this.query); + } + + this.setTitle(); + console.info("next please: " + nexturl); + createFrame(nexturl, this.allowScripts, frame => { + if (!this._window || this._window.closed) { + console.log("self is gone by now"); + this.unregister(); + this.restoreTitle(); + return; + } + if (this.slideshow && this.seconds) { + console.info("slideshow; delay: " + this.seconds * 1000); + this.loadNext(nexturl, frame, this.seconds * 1000); + } + else { + console.info("regular; no-delay"); + this.loadNext(nexturl, frame, 0); + } + }); + } + catch (ex) { + console.log(ex); + console.info("loadNext complete"); + this.unregister(); + this.restoreTitle(); + } } - } - finally { - element.parentElement.removeChild(element); - } - } -}; -Object.seal(Repaginator.prototype); - -const Slideshow = function Slideshow(focusElement, seconds, allowScripts, yielding) { - this.seconds = seconds || 0; - this.slideshow = true; - this.allowScripts = allowScripts; - this.yielding = yielding; - this.init(focusElement); - this.buildQuery(focusElement); -}; -Slideshow.prototype = Repaginator.prototype; - -const fe = () => { - let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); - let focusedWindow = {}; - let elt = fm.getFocusedElementForWindow(content, true, focusedWindow); - return elt; -}; - -const repaginate = m => { - let {num, slideshow, allowScripts, yielding} = m.data; - try { - let Ctor = slideshow ? Slideshow : Repaginator; - let rep; - if ("query" in m.data) { - let el = getFirstSnapshot(content.document, content.document, - m.data.query); - if (!el) { - log(LOG_DEBUG, "did not find explicit query element"); - return; + finally { + element.parentElement.removeChild(element); } - rep = new Ctor(el, num, allowScripts, yielding); - rep.query = m.data.query; } - else { - let el = fe(); - rep = new Ctor(fe(), num, allowScripts, yielding); + }; + Object.seal(Repaginator.prototype); + + const Slideshow = function Slideshow(focusElement, seconds, allowScripts, yielding) { + this.seconds = seconds || 0; + this.slideshow = true; + this.allowScripts = allowScripts; + this.yielding = yielding; + this.init(focusElement); + this.buildQuery(focusElement); + }; + Slideshow.prototype = Repaginator.prototype; + + const repaginate = (num, slideshow, allowScripts, yielding) => { + try { + let Ctor = slideshow ? Slideshow : Repaginator; + let rep; + // c.f. clicked_element.js + let el = clickedEl; + rep = new Ctor(clickedEl, num, allowScripts, yielding); rep.buildQuery(el); + rep.repaginate(); } - rep.repaginate(); - } - catch (ex) { - log(LOG_ERROR, "Failed to run repaginate", ex); - } -}; - -const query = () => { - let el = fe(); - let rv = new Repaginator(el); - rv.buildQuery(el); - sendAsyncMessage("repagination:query", rv.query); -}; - -const stop = () => { - try { - if (!content) { - return; - } - let body = content.document.getElementsByTagName("body")[0]; - if (body) { - body.removeAttribute("repagination"); + catch (ex) { + console.error("Failed to run repaginate", ex); } - } - catch (ex) { - log(LOG_ERROR, "failed to stop repagination", ex); - } -}; - -const shutdown = () => { - removeMessageListener("repagination:normal", repaginate); - removeMessageListener("repagination:query", query); - removeMessageListener("repagination:stop", stop); - removeMessageListener("repagination:shutdown", shutdown); -}; + }; -addMessageListener("repagination:normal", repaginate); -addMessageListener("repagination:query", query); -addMessageListener("repagination:stop", stop); -addMessageListener("repagination:shutdown", shutdown); + const stop = () => { + try { + let body = document.body; + if (body) { + body.removeAttribute("repagination"); + } + } + catch (ex) { + console.error("failed to stop repagination", ex); + } + }; -log(LOG_DEBUG, "Framescript loaded!"); -})(); // "module" + console.log("Framescript loaded!"); + this.port = browser.runtime.connect(); + this.port.onMessage.addListener(msg => { + switch (msg.msg) { + case "normal": repaginate(msg.num, msg.slideshow, msg.allowScripts, msg.yielding); break; + case "stop" : stop(); break; + } + }); +} + /* vim: set et ts=2 sw=2 : */ diff --git a/defaults/preferences/prefs.js b/defaults/preferences/prefs.js deleted file mode 100644 index 68093cc..0000000 --- a/defaults/preferences/prefs.js +++ /dev/null @@ -1,6 +0,0 @@ -pref("extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.submenu", true); -pref("extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.loglevel", 0x7fffffff); -pref("extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.showalldomain", false); -pref("extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.showslideshow", true); -pref("extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.yielding", false); -pref("extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.allowscripts", true); diff --git a/install.rdf b/install.rdf deleted file mode 100644 index da9926d..0000000 --- a/install.rdf +++ /dev/null @@ -1,40 +0,0 @@ - - - - - {6072cb90-a0bd-11da-a746-0800200c9a66} - Re-Pagination - Load all consecutive pages in a single tab at once. - 2016.08.08 - - true - true - 2 - - chrome://repagination/content/options.xul - 2 - - Nils Maier - Nils Maier - Lev, Omar - - - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 40.0 - 50.* - - - - - - {92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a} - 2.35 - 2.47.* - - - - diff --git a/loader.jsm b/loader.jsm deleted file mode 100644 index 6a7da1a..0000000 --- a/loader.jsm +++ /dev/null @@ -1,242 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -var EXPORTED_SYMBOLS = ["BASE_PATH", "require", "unload", "_setupLoader"]; - -var { - classes: Cc, - interfaces: Ci, - utils: Cu, - results: Cr, - Constructor: ctor, - manager: Cm -} = Components; - -var weak = Cu.getWeakReference.bind(Cu); -var reportError = Cu.reportError.bind(Cu); - -Cm.QueryInterface(Ci.nsIComponentRegistrar); - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -var lazy = XPCOMUtils.defineLazyGetter; - -// hide our internals -// Since require() uses .scriptloader, the loaded require scopes will have -// access to the named stuff within this module scope, but we actually want -// them to have access to certain stuff. -(function setup_scope(exports) { - Services = exports.Services = Object.create(Services); - let dlsg = XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils, Services); - dlsg("catman", "@mozilla.org/categorymanager;1", "nsICategoryManager"); - dlsg("clipbrd", "@mozilla.org/widget/clipboard;1", "nsIClipboard"); - dlsg("drags", "@mozilla.org/widget/dragservice;1", "nsIDragService"); - dlsg("eps", "@mozilla.org/uriloader/external-protocol-service;1", - "nsIExternalProtocolService"); - dlsg("fixups", "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"); - dlsg("memrm", "@mozilla.org/memory-reporter-manager;1", - "nsIMemoryReporterManager"); - dlsg("mime", "@mozilla.org/uriloader/external-helper-app-service;1", - "nsIMIMEService"); - dlsg("mimeheader", "@mozilla.org/network/mime-hdrparam;1", - "nsIMIMEHeaderParam"); - dlsg("sysprincipal", "@mozilla.org/systemprincipal;1", "nsIPrincipal"); - dlsg("uuid", "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"); - - const Instances = exports.Instances = { - get: function I_get(symbol, contract, iface, initializer) { - if (!(symbol in this)) { - this.register(symbol, contract, iface, initializer); - } - return this[symbol]; - }, - register: function I_register(symbol, contract, iface, initializer) { - if (symbol in this) { - let msg = "Symbol " + symbol + " already in Instances"; - log(LOG_ERROR, msg); - throw new Error(msg); - } - if (initializer) { - lazy(this, symbol, () => ctor(contract, iface, initializer)); - lazy(this, symbol + "_p", () => ctor(contract, iface)); - } - else { - lazy(this, symbol, () => ctor(contract, iface)); - lazy(this, symbol.toLowerCase(), () => new (ctor(contract, iface))()); - } - } - }; - - const {SELF_PATH, BASE_PATH} = (function() { - let rv; - try { throw new Error("narf"); } - catch (ex) { - rv = { - SELF_PATH: ex.fileName, - BASE_PATH: /^(.+\/).*?$/.exec(ex.fileName)[1] - }; - } - return rv; - })(); - exports.BASE_PATH = BASE_PATH; - - // logging stubs - var log = function() {}; // stub - var LOG_DEBUG = 0, LOG_INFO = 0, LOG_ERROR = 0; - - var _unloaders = []; - let _runUnloader = function _runUnloader(fn) { - try { - fn(); - } - catch (ex) { - log(LOG_ERROR, "unloader failed", ex); - } - }; - exports.unload = function unload(fn) { - if (fn == "shutdown") { - log(LOG_INFO, "shutdown"); - for (let i = _unloaders.length; ~(--i);) { - _runUnloader(_unloaders[i]); - } - _unloaders.splice(0); - return; - } - - // add an unloader - if (typeof(fn) != "function") { - throw new Error("unloader is not a function"); - } - _unloaders.push(fn); - return function() { - _runUnloader(fn); - _unloaders = _unloaders.filter(c => c != fn); - }; - }; - - const _registry = new Map(); - exports.require = function require(mod) { - mod = BASE_PATH + mod + ".js"; - - // already loaded? - let scope = _registry.get(mod); - if (scope) { - return scope.exports; - } - - // try to load the mod - log(LOG_DEBUG, "going to load: " + mod); - scope = Object.create(exports); - scope.exports = Object.create(null); - try { - scope = Cu.Sandbox(Services.sysprincipal, { - sandboxName: mod, - sandboxPrototype: scope, - wantXRays: false - }); - Services.scriptloader.loadSubScript(mod, scope, "utf-8"); - } - catch (ex) { - log(LOG_ERROR, "failed to load " + mod, ex); - throw ex; - } - - _registry.set(mod, scope); - log(LOG_DEBUG, "loaded module: " + mod); - - return scope.exports; - }; - - exports.lazyRequire = function lazyRequire(mod) { - function lazyBind(props, prop) { - log(LOG_DEBUG, "lazily binding " + props + " for module " + mod); - let m = require(mod); - for (let [,p] in new Iterator(props)) { - delete this[p]; - this[p] = m[p]; - } - return this[prop]; - } - - // Already loaded? - let scope = _registry.get(mod); - if (scope) { - return scope.exports; - } - - let props = Array.slice(arguments, 1); - let rv = {}; - let binder = lazyBind.bind(rv, props); - for (let [,p] in new Iterator(props)) { - let _p = p; - lazy(rv, _p, () => binder(_p)); - } - return rv; - }; - - unload(function() { - for (let [mod, scope] of _registry) { - _registry.delete(mod); - Cu.nukeSandbox(scope); - } - if (_registry.clear) { - _registry.clear(); - } - // unload ourselves - Cu.unload(SELF_PATH); - }); - - exports._setupLoader = function _setupLoader(data, callback) { - delete exports._setupLoader; - - let _am = {}; - Cu.import("resource://gre/modules/AddonManager.jsm", _am); - _am.AddonManager.getAddonByID(data.id, function loader_startup(addon) { - exports.ADDON = addon; - unload(() => delete exports.ADDON); - - let logging; - try { - logging = require("sdk/logging"); - for (let [k,v] in new Iterator(logging)) { - exports[k] = v; - } - - let prefs = require("sdk/preferences"); - exports.prefs = prefs.prefs; - exports.globalPrefs = prefs.globalPrefs; - - try { - prefs.prefs.observe("loglevel", - (p, v) => logging.setLogLevel(v), - logging.LOG_NONE); - } - catch (ex) { - logging.log(logging.LOG_ERROR, "failed to set log level", ex); - } - } - catch (ex) { - // probably do not have a working log() yet - reportError(ex); - return; - } - - try { - if (callback) { - logging.log(logging.LOG_DEBUG, "loader: running callback"); - callback(); - } - } - catch (ex) { - logging.log(logging.LOG_ERROR, "callback failed!", ex); - } - logging.log(logging.LOG_DEBUG, "loader: done"); - }); - }; - -})(this); - -/* vim: set et ts=2 sw=2 : */ diff --git a/locale/de/options.dtd b/locale/de/options.dtd deleted file mode 100644 index 932c656..0000000 --- a/locale/de/options.dtd +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/locale/de/repagination.dtd b/locale/de/repagination.dtd deleted file mode 100644 index e6c5a21..0000000 --- a/locale/de/repagination.dtd +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/locale/de/repagination.properties b/locale/de/repagination.properties deleted file mode 100644 index cd3e05c..0000000 --- a/locale/de/repagination.properties +++ /dev/null @@ -1,11 +0,0 @@ -pages.label=%S Seiten -minutes.label=%S Minuten -seconds.label=%S Sekunden - -repagination_limited=(%S of %S) Re-Pagination arbeitet... -repagination_unlimited=(%S) Re-Pagination arbeitet... -repagination_running=Re-Pagination arbeitet... - -extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.name=Re-Pagination -extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.description=Alle fortlaufenden Seiten in einem einzigem Tab auf einmal laden -extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.translator.1=Nils Maier diff --git a/locale/en-US/options.dtd b/locale/en-US/options.dtd deleted file mode 100644 index 6100f15..0000000 --- a/locale/en-US/options.dtd +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/locale/en-US/repagination.dtd b/locale/en-US/repagination.dtd deleted file mode 100644 index 61a26ec..0000000 --- a/locale/en-US/repagination.dtd +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/locale/en-US/repagination.properties b/locale/en-US/repagination.properties deleted file mode 100644 index cdb1b4a..0000000 --- a/locale/en-US/repagination.properties +++ /dev/null @@ -1,11 +0,0 @@ -pages.label=%S pages -minutes.label=%S minutes -seconds.label=%S seconds - -repagination_limited=(%S of %S) Re-Pagination is running... -repagination_unlimited=(%S) Re-Pagination is running... -repagination_running=Re-Pagination is running... - -extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.name=Re-Pagination -extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.description=Load all consecutive pages in a single tab at once. -extensions.{6072cb90-a0bd-11da-a746-0800200c9a66}.translator.1=Nils Maier diff --git a/main.js b/main.js index 967e5df..a9f3dce 100644 --- a/main.js +++ b/main.js @@ -2,254 +2,209 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +console.info("main called!"); -const {registerOverlay, unloadWindow} = require("sdk/windows"); +var i = 0; -const RUNNING = new Set(); -const globalMM = Cc["@mozilla.org/globalmessagemanager;1"]. - getService(Ci.nsIMessageListenerManager); +function onError(error) { + console.error(error); +} -function registerRunning(m) { - RUNNING.add(m.data.id); - log(LOG_DEBUG, `added ${m.data.id} to running`); +const MENU = { + NOLIMIT: 'REPAGINATION_NOLIMIT', + LIMIT: 'REPAGINATION_LIMIT', + SLIDE: 'REPAGINATION_SLIDE', + STOP: 'REPAGINATION_STOP' } -function unregisterRunning(m) { - RUNNING.delete(m.data.id); - log(LOG_DEBUG, `removed ${m.data.id} from running`); +function onCreated(n) { + if (browser.runtime.lastError) { + console.error("Error creating menu item: %o", browser.runtime.lastError); + } else { + console.log(`Menu Item ${i++} created successfully`); + } } -globalMM.addMessageListener("repagination:register", registerRunning); -globalMM.addMessageListener("repagination:unregister", unregisterRunning); -unload(() => { - globalMM.removeMessageListener("repagination:register", registerRunning); - globalMM.removeMessageListener("repagination:unregister", unregisterRunning); -}); - -/* globals _ */ -lazy(this, "_", function() { - let bundle = require("sdk/strings"). - getBundle("chrome://repagination/locale/repagination.properties"); - return function() { - return bundle.getString.apply(bundle, arguments); - }; -}); - -function main(window, document) { - const $ = id => document.getElementById(id); - const $$$ = q => document.querySelectorAll(q); - - function repaginate(num, slideshow) { - log(LOG_INFO, "repaginate: " + num + "/" + slideshow); - try { - let mm = window.gBrowser.selectedBrowser.messageManager; - mm.sendAsyncMessage("repagination:normal", { - num: num, - slideshow: slideshow, - allowScripts: prefs.get("allowscripts", true), - yielding: prefs.yielding - }); - } - catch (ex) { - log(LOG_ERROR, "failed to run repaginate", ex); + +function createMenu(prefs) { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1325758 + // insertbefore="context-sep-open" + + i = 0; + + browser.contextMenus.create({ + id: MENU.NOLIMIT, + title: browser.i18n.getMessage("repagination_nolimit"), + contexts: ["all"] + }, onCreated); + + browser.contextMenus.create({ + id: MENU.LIMIT, + title: browser.i18n.getMessage("repagination_limit"), + contexts: ["all"] + }, onCreated); + + const limits = [2,5,10,15,20,25,30,40,50,100]; + + for(let i in limits) { + browser.contextMenus.create({ + id: MENU.LIMIT + "_" + limits[i], + parentId: MENU.LIMIT, + title: browser.i18n.getMessage("repagination_limit_x",limits[i]), + contexts: ["all"] + }, onCreated); + + } + + if(prefs.slideshows) { + browser.contextMenus.create({ + id: MENU.SLIDE, + title: browser.i18n.getMessage("repagination_slide"), + contexts: ["all"] + }, onCreated); + + const slides = [0,1,2,4,5,10,15,30,60,120]; + + for(let i in slides) { + browser.contextMenus.create({ + id: MENU.SLIDE + "_" + i, + parentId: MENU.SLIDE, + title: [0,1,60,120].indexOf(i) != -1 ? browser.i18n.getMessage("repagination_slide_" + i) : browser.i18n.getMessage("repagination_slide_x",i), + contexts: ["all"] + }, onCreated); } } - function repaginate_domain() { - log(LOG_INFO, "repaginate_domain"); - try { - let mm = window.gBrowser.selectedBrowser.messageManager; - let queried = m => { - mm.removeMessageListener("repagination:query", queried); - log(LOG_DEBUG, "recv query " + m.data); - if (!m.data) { - return; - } - const host = window.gBrowser.selectedBrowser.currentURI.host; - for (let i = 0; i < window.gBrowser.browsers.length; ++i) { - let browser = window.gBrowser.getBrowserAtIndex(i); - if (!browser) { - continue; - } - if (browser.currentURI.host != host) { - continue; - } - browser.messageManager.sendAsyncMessage("repagination:normal", { - num: 0, - slideshow: false, - allowScripts: prefs.get("allowscripts", true), - yielding: prefs.yielding, - query: m.data - }); - } - }; - mm.addMessageListener("repagination:query", queried); - mm.sendAsyncMessage("repagination:query"); - } - catch (ex) { - log(LOG_ERROR, "failed to run repaginate_domain", ex); + browser.contextMenus.create({ + id: MENU.STOP, + title: browser.i18n.getMessage("repagination_stop"), + contexts: ["all"] + }, onCreated); + + /* https://bugzilla.mozilla.org/show_bug.cgi?id=1215376 + gContextMenu.onLink && /^https?$/.test(gContextMenu.linkURI.scheme)) { + setMenuHidden(false); + if (!RUNNING.has(gContextMenu.frameOuterWindowID)) { + menuCurrent.stopMenu.hidden = true; } + */ +} + +var defaultSettings = { + loglevel: "none", + slideshows: false, + yielding: false, + allowScripts: false, + exists: true // special pref to restore defaults +}; + +function prefReset(newSettings, areaName) { + console.log("prefs changed") + if (areaName == "local" && ("slideshows" in newSettings)) { + browser.contextMenus.removeAll(); + console.log("recreating menu") + browser.storage.local.get().then(initSettings, onError); } +} - function stop() { - log(LOG_INFO, "stop"); - let mm = window.gBrowser.selectedBrowser.messageManager; - mm.sendAsyncMessage("repagination:stop"); +function initSettings(prefs) { + if (!("exists" in prefs) || !prefs.exists) { + browser.storage.onChanged.removeListener(prefReset); + browser.storage.local.set(defaultSettings); + browser.storage.onChanged.addListener(prefReset); + prefs = defaultSettings; } + + createMenu(prefs); +} - function onAll() { repaginate(); } - function onAllDomain() { repaginate_domain(); } - function onStop() { stop(); } - function onLimitCommand(evt) { - let t = evt.target; - if (t.localName != "menuitem") { - return; - } - repaginate(parseInt(t.getAttribute("value"), 10)); +function myinit(prefs) { + if (!("exists" in prefs) || !prefs.exists) { + browser.storage.onChanged.removeListener(prefReset); + browser.storage.local.set(defaultSettings); + browser.storage.onChanged.addListener(prefReset); + prefs = defaultSettings; } - function onSlideCommand(evt) { - let t = evt.target; - if (t.localName != "menuitem") { - return; + + createMenu(prefs); + + const PORTS = {}; + + function repaginate(tab, num, slideshow) { + console.info("repaginate: " + num + "/" + slideshow); + try { + PORTS[tab].postMessage({ + msg: "normal", + num: num, + slideshow: slideshow, + allowScripts: prefs.allowScripts, + yielding: prefs.yielding + }); + } catch (ex) { + console.log(ex); + console.error("failed to run repaginate"); } - repaginate(parseInt(t.getAttribute("value"), 10), true); } - log(LOG_INFO, "main called!"); - - let frameToLog = m => log(m.data.level, m.data.message, m.data.exception); - let frameL10N = m => { - return _.apply(null, m.data.arguments); - }; - window.messageManager.addMessageListener("repagination:log", frameToLog); - window.messageManager.addMessageListener("repagination:_", frameL10N); - let fs = "chrome://repagination/content/content-script.js?" + (+new Date()); - window.messageManager.loadFrameScript(fs, true); - unloadWindow(window, () => { - window.messageManager.broadcastAsyncMessage("repagination:shutdown"); - window.messageManager.removeMessageListener("repagination:log", frameToLog); - window.messageManager.removeMessageListener("repagination:_", frameL10N); - window.messageManager.removeDelayedFrameScript(fs); - }); - - // finish the localization - let nodes = $$$(":-moz-any(#repagination_limit, " + - "#repagination_menu_limit) menuitem"); - for (let n of nodes) { - n.setAttribute("label", _("pages.label", n.getAttribute("value"))); + function stop(tab) { + console.info("stop"); + PORTS[tab].postMessage({ + msg: "stop" + }); } - nodes = $$$(":-moz-any(#repagination_slide, #repagination_menu_slide) " + - "menuitem:not([label])"); - for (let n of nodes) { - let s = parseInt(n.getAttribute("value"), 10); - if (s < 60) { - n.setAttribute("label", _("seconds.label", s)); - } - else { - n.setAttribute("label", _("minutes.label", parseInt(s / 60, 10))); + + function process(info, tab) { + var str = info.menuItemId; + switch (str) { + case MENU.NOLIMIT: repaginate(tab); break; + case MENU.STOP: stop(tab); break; } - } - let contextMenu = $("contentAreaContextMenu"); - - let menuCascaded = { - menu: $("repagination_menu"), - allMenu: $("repagination_menu_nolimit"), - allDomainMenu: $("repagination_menu_nolimit_domain"), - stopMenu: $("repagination_menu_stop"), - limitMenu: $("repagination_menu_limit"), - slideMenu: $("repagination_menu_slide") - }; - let menuPlain = { - menu: {}, - allMenu: $("repagination_nolimit"), - allDomainMenu: $("repagination_nolimit_domain"), - stopMenu: $("repagination_stop"), - limitMenu: $("repagination_limit"), - slideMenu: $("repagination_slide") - }; - let menuCurrent; - - prefs.observe("submenu", function(pref, value) { - menuCurrent = value ? menuCascaded : menuPlain; - let menuDisabled = value ? menuPlain : menuCascaded; - for (let [,mi] in new Iterator(menuDisabled)) { - mi.hidden = true; + + if(str.startsWith(MENU.LIMIT)) { + // https://stackoverflow.com/questions/5555518/split-variable-from-last-slash-jquery + var last = str.substring(str.lastIndexOf("_") + 1, str.length); + repaginate(tab, parseInt(last, 10), false); } - }, true); - - let onContextMenu = function onContextMenu() { - function setMenuHidden(hidden) { - log(LOG_DEBUG, "set menu hidden = " + hidden); - for (let [,mi] in new Iterator(menuCurrent)) { - mi.hidden = hidden; - } - menuCurrent.slideMenu.hidden = - menuCurrent.slideMenu.hidden || !prefs.showslideshow; - menuCurrent.allDomainMenu.hidden = - menuCurrent.allDomainMenu.hidden || !prefs.showalldomain; + if(str.startsWith(MENU.SLIDE)) { + var last = str.substring(str.lastIndexOf("_") + 1, str.length); + repaginate(tab, parseInt(last, 10), true); } + } - let {gContextMenu} = window; - log(LOG_DEBUG, `context menu showing for ${gContextMenu.frameOuterWindowID}!`); - try { - if (gContextMenu.onLink && /^https?$/.test(gContextMenu.linkURI.scheme)) { - setMenuHidden(false); - if (!RUNNING.has(gContextMenu.frameOuterWindowID)) { - menuCurrent.stopMenu.hidden = true; - } - return; - } + // We lazily inject the main content script in a vague hope for efficiency + // We use ports for messaging but have to store the messages until the port is opened. + const PENDING = {}; + + browser.contextMenus.onClicked.addListener((info, tab) => { + console.log(info, tab); + if(tab.id in PORTS) { + process(info, tab.id); + } else { + console.log("injecting " + tab.id); + browser.tabs.executeScript(tab.id, { file: "content-script.js" } ); + PENDING[tab.id] = info; } - catch (ex) { - log(LOG_ERROR, "failed to setup menu (onLink)", ex); - } - try { - setMenuHidden(true); - if (RUNNING.has(gContextMenu.frameOuterWindowID)) { - menuCurrent.menu.hidden = menuCurrent.stopMenu.hidden = false; - } - } - catch (ex) { - log(LOG_ERROR, "failed to setup menu (plain)", ex); + }); + + + browser.runtime.onConnect.addListener(function(port) { + let tabid = port.sender.tab.id; + PORTS[tabid] = port; + port.onDisconnect.addListener((p) => { + delete PORTS[tabid]; + }); + + if (port.sender.tab.id in PENDING) { + var info = PENDING[port.sender.tab.id]; + delete PENDING[port.sender.tab.id]; + process(info, port.sender.tab.id); } - }; - - contextMenu.addEventListener("popupshowing", onContextMenu, true); - menuCascaded.allMenu.addEventListener("command", onAll, true); - menuPlain.allMenu.addEventListener("command", onAll, true); - menuCascaded.allDomainMenu.addEventListener("command", onAllDomain, true); - menuPlain.allDomainMenu.addEventListener("command", onAllDomain, true); - menuCascaded.stopMenu.addEventListener("command", onStop, true); - menuPlain.stopMenu.addEventListener("command", onStop, true); - menuCascaded.limitMenu.addEventListener("command", onLimitCommand, true); - menuPlain.limitMenu.addEventListener("command", onLimitCommand, true); - menuCascaded.slideMenu.addEventListener("command", onSlideCommand, true); - menuPlain.slideMenu.addEventListener("command", onSlideCommand, true); - unloadWindow(window, function() { - contextMenu.removeEventListener("popuphowing", onContextMenu, true); - menuCascaded.allMenu.removeEventListener("command", onAll, true); - menuPlain.allMenu.removeEventListener("command", onAll, true); - menuCascaded.allDomainMenu.removeEventListener("command", onAllDomain, - true); - menuPlain.allDomainMenu.removeEventListener("command", onAllDomain, true); - menuCascaded.stopMenu.removeEventListener("command", onStop, true); - menuPlain.stopMenu.removeEventListener("command", onStop, true); - menuCascaded.limitMenu.removeEventListener("command", onLimitCommand, true); - menuPlain.limitMenu.removeEventListener("command", onLimitCommand, true); - menuCascaded.slideMenu.removeEventListener("command", onSlideCommand, true); - menuPlain.slideMenu.removeEventListener("command", onSlideCommand, true); - contextMenu = menuPlain = menuCascaded = null; }); - log(LOG_INFO, "all good!"); } -registerOverlay( - "repagination.xul", - "chrome://browser/content/browser.xul", - main -); -registerOverlay( - "repagination.xul", - "chrome://navigator/content/navigator.xul", - main -); + +browser.storage.local.get().then(myinit, onError); + +browser.storage.onChanged.addListener(prefReset); + +console.info("all good!"); /* vim: set et ts=2 sw=2 : */ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..220b459 --- /dev/null +++ b/manifest.json @@ -0,0 +1,44 @@ +{ + "manifest_version": 2, + "name": "__MSG_extensionName__", + "version": "2017.09.14", + "description": "__MSG_extensionDescription__", + "homepage_url": "https://github.com/Mathnerd314/repagination/", + + "default_locale": "en", + "options_ui": { + "page": "options.html", + "browser_style": true + }, + "icons": { "32": "icon32.png", + "48": "icon.png", + "64": "icon64.png"}, + + "applications": { + "gecko": { + "id": "{6072cb90-a0bd-11da-a746-0800200c9a66}", + "strict_min_version": "48.0", + "strict_max_version": "*" + } + }, + + "content_scripts": [ + { + "matches": [""], + "js": ["clicked_element.js"] + } + ], + + "background": { + "scripts": ["main.js"] + }, + + "permissions": [ + "tabs", + "activeTab", + "storage", + "webNavigation", + "contextMenus", + "" + ] +} diff --git a/options.html b/options.html new file mode 100644 index 0000000..9a6fbc9 --- /dev/null +++ b/options.html @@ -0,0 +1,70 @@ + + + + + + + + +
+ + + + + + + + +
+ +

Credits

    diff --git a/options.js b/options.js index 043451d..3af0b55 100644 --- a/options.js +++ b/options.js @@ -1,19 +1,11 @@ /* see also main.js */ -var nullSettings = { exists: false }; /* Update the options UI with the settings values retrieved from storage, or the default settings if the stored settings are empty. */ function updateUI(restoredSettings) { - document.querySelector("#loglevel").value = restoredSettings.loglevel; - - const checkboxes = document.querySelectorAll(".data-types [type=checkbox]"); - for (let item of checkboxes) { - item.checked = !!restoredSettings[item.getAttribute("data-type")]; - } - - settings = restoredSettings; + document.getElementById("slideshows").checked = restoredSettings.slideshows || false; } function onError(e) { @@ -22,29 +14,6 @@ function onError(e) { browser.storage.local.get().then(updateUI, onError); -/* Save and restore */ -function storeSettings() { - settings.loglevel = document.querySelector("#loglevel").value; - const checkboxes = document.querySelectorAll(".data-types [type=checkbox]"); - for (let item of checkboxes) { - settings[item.getAttribute("data-type")] = item.checked; - } - browser.storage.local.set(settings); -} - -function restoreSettings() { - function logStorageChange(changes, area) { - browser.storage.onChanged.removeListener(logStorageChange); - browser.storage.local.get().then(updateUI, onError); - } - browser.storage.onChanged.addListener(logStorageChange); - browser.storage.local.set(nullSettings); -} - - -const saveButton = document.querySelector("#save-button"); -saveButton.addEventListener("click", storeSettings); - -const restoreButton = document.querySelector("#restore-button"); -restoreButton.addEventListener("click", restoreSettings); - +document.getElementById("slideshows").onchange = function setSlideshows() { + browser.storage.local.set({ exists: true, slideshows: document.getElementById("slideshows").checked}); +}; From ef9813e519ddbff822c94a286afcb24320e37750 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Thu, 30 Nov 2017 13:42:47 -0700 Subject: [PATCH 17/24] Update Readme.md --- Readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 7b853a8..d94b2a2 100644 --- a/Readme.md +++ b/Readme.md @@ -1,12 +1,12 @@ Re-Pagination === -This add-on is mostly a rewrite of the old re-pagination/antipagination extension for Firefox. -Lev assigned ownership over to this project, so that this incarnation is now the official one. +This add-on is an update of the old re-pagination/antipagination extension for Firefox 57+. +Nils Maier has not assigned ownership over to this project, so this is still a fork. Features === -* Fully restartless -* Should prevent some |jank|ing compared to the original extension +* WebExtension compatible with Firefox Quantum +* Compatible with more sites * All, limited and slideshow From 0850b3e2841ec98e52b8771f934956f2801ed1cb Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Thu, 22 Feb 2018 18:02:25 -0700 Subject: [PATCH 18/24] Update menu to use only 1 level and hide/show Stop properly Fix bug when doing repaginate -> navigate -> repaginate Reduce manifest permissions Fix slideshow times --- content-script.js | 755 +++++++++++++++++++++++----------------------- main.js | 80 ++--- manifest.json | 4 +- 3 files changed, 423 insertions(+), 416 deletions(-) diff --git a/content-script.js b/content-script.js index 1f5787d..ca96c5c 100644 --- a/content-script.js +++ b/content-script.js @@ -2,429 +2,436 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -if(!("port" in this)) { - var focusElement = null; - const _ = function () { - let args = Array.map(arguments, e => e.toString()); - return browser.i18n.getMessage(args[0], args.slice(1)); - } - const equalLinks = (left, right) => - left.pathname === right.pathname && - left.search === right.search && - left.host === right.host && - left.protocol === right.protocol; - - const getFirstSnapshot = (doc, node, query) => - doc.evaluate(query, node, null, 7, null).snapshotItem(0); - - const createFrame = (srcurl, allowScripts, loadFun) => { - let errorCount = 0; - function sendRequest() { - var xhr = new XMLHttpRequest(); - xhr.onload = function() { - if (xhr.status == 200) { - console.info("XHR loaded, going to process"); - try { - loadFun(xhr.responseXML); - } - catch (ex) { - console.error("failed to invoke callback on XHR", ex); - } - } else if (++errorCount <= 5) { - console.info("XHR err'ed out, retrying"); - setTimeout(function(){ sendRequest(); }, 500 * (2**errorCount)); - } else { - console.error("XHR err'ed out, giving up"); +{ +console.log("Framescript loaded!"); +let port = browser.runtime.connect(); + +var focusElement = null; + +let _ = function () { + let args = Array.map(arguments, e => e.toString()); + return browser.i18n.getMessage(args[0], args.slice(1)); +} + +let equalLinks = (left, right) => + left.pathname === right.pathname && + left.search === right.search && + left.host === right.host && + left.protocol === right.protocol; + +let getFirstSnapshot = (doc, node, query) => + doc.evaluate(query, node, null, 7, null).snapshotItem(0); + +let createFrame = (srcurl, allowScripts, loadFun) => { + let errorCount = 0; + function sendRequest() { + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.status == 200) { + console.info("XHR loaded, going to process"); + try { + loadFun(xhr.responseXML); + } + catch (ex) { + console.error("failed to invoke callback on XHR", ex); } + } else if (++errorCount <= 5) { + console.info("XHR err'ed out, retrying"); + setTimeout(function(){ sendRequest(); }, 500 * (2**errorCount)); + } else { + console.error("XHR err'ed out, giving up"); } - xhr.open('GET', srcurl); - xhr.responseType = "document"; - xhr.send(); } - sendRequest(); - }; - - const Repaginator = function Repaginator(count, allowScripts, yielding) { - this.pageLimit = count || 0; - this.allowScripts = allowScripts; - this.yielding = yielding; - this.init(); - }; - Repaginator.prototype = { - slideshow: false, - pageLimit: 0, - seconds: 0, - pageCount: 0, - attemptToIncrement: true, - - init: function R_init() { - // find anchor - (function findInitialAnchor() { - for (let parent = focusElement; parent; parent = parent.parentNode) { - if (parent.localName == "a") { - focusElement = parent; - return; - } + xhr.open('GET', srcurl); + xhr.responseType = "document"; + xhr.send(); + } + sendRequest(); +}; + +let Repaginator = function Repaginator(count, allowScripts, yielding) { + this.pageLimit = count || 0; + this.allowScripts = allowScripts; + this.yielding = yielding; + this.init(); +}; +Repaginator.prototype = { + slideshow: false, + pageLimit: 0, + seconds: 0, + pageCount: 0, + attemptToIncrement: true, + + init: function R_init() { + // find anchor + (function findInitialAnchor() { + for (let parent = focusElement; parent; parent = parent.parentNode) { + if (parent.localName == "a") { + focusElement = parent; + return; } - throw new Error("No focus element"); - })(); - }, - buildQuery: function R_buildQuery(el) { - function escapeXStr(mystr) { - return mystr.replace(/'/g, "\\'"); + } + throw new Error("No focus element"); + })(); + }, + buildQuery: function R_buildQuery(el) { + function escapeXStr(mystr) { + return mystr.replace(/'/g, "\\'"); + } + + this.query = ""; + + (function buildQuery() { + // Homestuck hack + if(el.href && el.href.includes("mspaintadventures.com")) { + this.query = "//center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 6]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/font[position() = 1]/a[position() = 1]"; + this.numberToken = /(\[@href='.*)(\d+)(.*?'\])/; + return; } - this.query = ""; + // See if the anchor has an ID + // Note: cannot use the id() xpath function here, as there might + // be duplicate ids + if (el.id) { + this.query = "//a[@id='" + escapeXStr(el.id) + "']"; + this.numberToken = /(\[@id='.*?)(\d+)(.*?'\])/; + return; + } - (function buildQuery() { - // Homestuck hack - if(el.href && el.href.includes("mspaintadventures.com")) { - this.query = "//center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 2]/td[position() = 1]/center[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 6]/td[position() = 1]/table[position() = 1]/tbody[position() = 1]/tr[position() = 1]/td[position() = 1]/font[position() = 1]/a[position() = 1]"; - this.numberToken = /(\[@href='.*)(\d+)(.*?'\])/; + // See if the document has a link rel="..." pointing to the same place + let linkEl = getFirstSnapshot(el.ownerDocument, el.ownerDocument, + "//head//link[@href='" + + escapeXStr(el.href) + "']"); + if (linkEl) { + let rel = linkEl.getAttribute("rel") || ""; + if (rel.trim()) { + this.query = "/html/head//link[@rel='" + escapeXStr(rel) + "']"; + // no point in checking for numbers + this.attemptToIncrement = false; + console.log("using link[@rel]"); return; } + } - // See if the anchor has an ID - // Note: cannot use the id() xpath function here, as there might - // be duplicate ids - if (el.id) { - this.query = "//a[@id='" + escapeXStr(el.id) + "']"; - this.numberToken = /(\[@id='.*?)(\d+)(.*?'\])/; + // Find an id in the ancestor chain, or alternatively a class + // that we may operate on + (function findPathPrefix() { + let pieces = []; + for (let pn = el.parentNode; pn; pn = pn.parentNode) { + if (pn.localName == "body") { + break; + } + if (pn.id) { + console.log("got id: " + pn.id); + pieces.unshift("//" + pn.localName + "[@id='" + + escapeXStr(pn.id) + "']"); + break; // one id is enough + } + if (pn.className) { + console.log("got class: " + pn.className); + pieces.unshift("//" + pn.localName + "[@class='" + + escapeXStr(pn.className) + "']"); + break; + } + } + this.query = pieces.join(""); + console.log("findPathPrefix result: " + this.query); + }).call(this); + + // find the anchor + (function findAnchor() { + let text = el.textContent; + + // See if there is rel=next or rel=prev + let rel = (el.getAttribute("rel") || "").trim(); + if (rel && (rel.includes("next") || rel.includes("prev"))) { + this.query += "//a[@rel='" + escapeXStr(rel) + "']"; + // no point in checking for numbers + this.attemptToIncrement = false; + console.log("using a[@rel]"); return; } - // See if the document has a link rel="..." pointing to the same place - let linkEl = getFirstSnapshot(el.ownerDocument, el.ownerDocument, - "//head//link[@href='" + - escapeXStr(el.href) + "']"); - if (linkEl) { - let rel = linkEl.getAttribute("rel") || ""; - if (rel.trim()) { - this.query = "/html/head//link[@rel='" + escapeXStr(rel) + "']"; - // no point in checking for numbers - this.attemptToIncrement = false; - console.log("using link[@rel]"); - return; - } + // Try the node text + if (text.trim()) { + this.query += "//a[.='" + escapeXStr(text) + "']"; + this.numberToken = /(a\[.='.*?)(\d+)(.*?\])/; + console.log("using text"); + return; } - // Find an id in the ancestor chain, or alternatively a class - // that we may operate on - (function findPathPrefix() { - let pieces = []; - for (let pn = el.parentNode; pn; pn = pn.parentNode) { - if (pn.localName == "body") { - break; - } - if (pn.id) { - console.log("got id: " + pn.id); - pieces.unshift("//" + pn.localName + "[@id='" + - escapeXStr(pn.id) + "']"); - break; // one id is enough - } - if (pn.className) { - console.log("got class: " + pn.className); - pieces.unshift("//" + pn.localName + "[@class='" + - escapeXStr(pn.className) + "']"); - break; - } - } - this.query = pieces.join(""); - console.log("findPathPrefix result: " + this.query); - }).call(this); - - // find the anchor - (function findAnchor() { - let text = el.textContent; - - // See if there is rel=next or rel=prev - let rel = (el.getAttribute("rel") || "").trim(); - if (rel && (rel.includes("next") || rel.includes("prev"))) { - this.query += "//a[@rel='" + escapeXStr(rel) + "']"; - // no point in checking for numbers - this.attemptToIncrement = false; - console.log("using a[@rel]"); + // See if it has a descendant with a @src we may use + let srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@src]"); + if (srcEl) { + let src = srcEl.getAttribute("src") || ""; + if (src.trim()) { + this.query += "//" + srcEl.localName + "[@src='" + escapeXStr(src) + + "']/ancestor::a"; + this.numberToken = /(\[@src='.*?)(\d+)(.*?'\])/; + console.log("using @src"); return; } + } - // Try the node text - if (text.trim()) { - this.query += "//a[.='" + escapeXStr(text) + "']"; - this.numberToken = /(a\[.='.*?)(\d+)(.*?\])/; - console.log("using text"); + // See if there is a descendant with a @value we may use + srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@value]"); + if (srcEl) { + let val = srcEl.getAttribute("value") || ""; + if (val.trim()) { + this.query += "//" + srcEl.localName + "[@value='" + + escapeXStr(val) + "']/ancestor::a"; + this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; + console.log("using @value"); return; } + } - // See if it has a descendant with a @src we may use - let srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@src]"); - if (srcEl) { - let src = srcEl.getAttribute("src") || ""; - if (src.trim()) { - this.query += "//" + srcEl.localName + "[@src='" + escapeXStr(src) + - "']/ancestor::a"; - this.numberToken = /(\[@src='.*?)(\d+)(.*?'\])/; - console.log("using @src"); - return; - } - } - - // See if there is a descendant with a @value we may use - srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@value]"); - if (srcEl) { - let val = srcEl.getAttribute("value") || ""; - if (val.trim()) { - this.query += "//" + srcEl.localName + "[@value='" + - escapeXStr(val) + "']/ancestor::a"; - this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; - console.log("using @value"); - return; - } - } + // See if there is a class we may use + if (el.className) { + this.query += "//a[@class='" + escapeXStr(el.className) + "']"; + this.numberToken = /(\[@class='.*?)(\d+)(.*?'\])/; + console.log("using a[@class]"); + return; + } - // See if there is a class we may use - if (el.className) { - this.query += "//a[@class='" + escapeXStr(el.className) + "']"; - this.numberToken = /(\[@class='.*?)(\d+)(.*?'\])/; - console.log("using a[@class]"); + // See if there is a descendant with an id we may use + srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@id]"); + if (srcEl) { + let val = srcEl.getAttribute("id") || ""; + if (val.trim()) { + this.query += "//" + srcEl.localName + "[@id='" + + escapeXStr(val) + "']/ancestor::a"; + this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; + console.log("using descendant class"); return; } + } - // See if there is a descendant with an id we may use - srcEl = getFirstSnapshot(el.ownerDocument, el, "descendant::*[@id]"); - if (srcEl) { - let val = srcEl.getAttribute("id") || ""; - if (val.trim()) { - this.query += "//" + srcEl.localName + "[@id='" + - escapeXStr(val) + "']/ancestor::a"; - this.numberToken = /(\[@value='.*?)(\d+)(.*?'\])/; - console.log("using descendant class"); - return; - } - } - - throw new Error("No anchor expression found!"); - }).call(this); + throw new Error("No anchor expression found!"); }).call(this); + }).call(this); - // We're after the last result - this.query = "(" + this.query + ")[last()]"; - console.info("query: " + this.query); - }, - setTitle: function R_setTitle() { - if (this.pageLimit) { - document.title = - _("repagination_limited", this.pageCount, this.pageLimit); - } - else if (this.pageCount > 0) { - document.title = _("repagination_unlimited", this.pageCount); - } - else { - document.title = _("repagination_running"); - } - }, - restoreTitle: function R_restoreTitle() { - if("_title" in this) { - document.title = this._title; - delete this._title; - } - }, - unregister: function R_unregister() { - document.body.removeAttribute("repagination"); - }, - repaginate: function R_repaginate() { - this._title = document.title; - this.setTitle(); - try { - let node = document.evaluate( - this.query, document, null, 9, null).singleNodeValue; - if (!node) { - throw new Error("no node"); - } - document.body.setAttribute("repagination", "true"); - createFrame(node.href, this.allowScripts, frame => { - this.loadNext(node.href, frame, 0); - }); - } - catch (ex) { - this.unregister(); - this.restoreTitle(); - console.error("repaginate failed", ex); + // We're after the last result + this.query = "(" + this.query + ")[last()]"; + console.info("query: " + this.query); + }, + setTitle: function R_setTitle() { + if (this.pageLimit) { + document.title = + _("repagination_limited", this.pageCount, this.pageLimit); + } + else if (this.pageCount > 0) { + document.title = _("repagination_unlimited", this.pageCount); + } + else { + document.title = _("repagination_running"); + } + }, + restoreTitle: function R_restoreTitle() { + if("_title" in this) { + document.title = this._title; + delete this._title; + } + }, + unregister: function R_unregister() { + port.postMessage({msg: "unregister"}); + document.body.removeAttribute("repagination"); + }, + repaginate: function R_repaginate() { + this._title = document.title; + this.setTitle(); + try { + let node = document.evaluate( + this.query, document, null, 9, null).singleNodeValue; + if (!node) { + throw new Error("no node"); } - }, - incrementQuery: function R_incrementQuery() { - return this.query.replace(this.numberToken, function(g, pre, num, post) { - return pre + (parseInt(num, 10) + 1) + post; + document.body.setAttribute("repagination", "true"); + createFrame(node.href, this.allowScripts, frame => { + this.loadNext(node.href, frame, 0); }); - }, - loadNext: function R_loadNext(src, element, delay) { - if (delay > 0) { - console.log("delaying (sto) " + delay); - setTimeout(() => this.loadNext(src, element, 0), delay); - return; - } - - try { - this._loadNext_gen.bind(this, src, element)(); - } catch (ex) { - console.error("failed to process loadNext (non-yielding)", ex); - } + } + catch (ex) { + this.unregister(); + this.restoreTitle(); + console.error("repaginate failed", ex); + } + }, + incrementQuery: function R_incrementQuery() { + return this.query.replace(this.numberToken, function(g, pre, num, post) { + return pre + (parseInt(num, 10) + 1) + post; + }); + }, + loadNext: function R_loadNext(src, element, delay) { + if (delay > 0) { + console.log("delaying (sto) " + delay); + setTimeout(() => this.loadNext(src, element, 0), delay); return; - }, - _loadNext_gen: function R__loadNext_gen(src, element) { - let ownerDoc = document; + } - try { - if (!ownerDoc.body.hasAttribute("repagination")) { - throw new Error("not running"); - } + try { + this._loadNext_gen.bind(this, src, element)(); + } catch (ex) { + console.error("failed to process loadNext (non-yielding)", ex); + } + return; + }, + _loadNext_gen: function R__loadNext_gen(src, element) { + let ownerDoc = document; - var doc = element; - this.pageCount++; - - // Remove scripts from frame - // The scripts should already be present in the parent (first page) - // Duplicate scripts would cause more havoc (performance-wise) than - // behaviour failures due to missing scripts - // Note: This is NOT a security mechanism, but a performance thing. - Array.forEach(doc.querySelectorAll("script"), - s => s.parentNode.removeChild(s)); - - // Do the dirty deed - if (this.slideshow) { - console.info("replacing content (slideshow)"); - // this should be safe since innerHTML returns serialized HTML - ownerDoc.body.innerHTML = doc.body.innerHTML; - ownerDoc.body.setAttribute("repagination", "true"); + try { + if (!ownerDoc.body.hasAttribute("repagination")) { + throw new Error("not running"); + } + + var doc = element; + this.pageCount++; + + // Remove scripts from frame + // The scripts should already be present in the parent (first page) + // Duplicate scripts would cause more havoc (performance-wise) than + // behaviour failures due to missing scripts + // Note: This is NOT a security mechanism, but a performance thing. + Array.forEach(doc.querySelectorAll("script"), + s => s.parentNode.removeChild(s)); + + // Do the dirty deed + if (this.slideshow) { + console.info("replacing content (slideshow)"); + // this should be safe since innerHTML returns serialized HTML + ownerDoc.body.innerHTML = doc.body.innerHTML; + ownerDoc.body.setAttribute("repagination", "true"); + } + else { + console.info("inserting content (normal)"); + // Remove non-same-origin iframes, such as ad iframes + // Otherwise we might create a shitload of (nearly) identical frames + // which might even kill the browser + if (!this.pageLimit || this.pageLimit > 10) { + console.info("removing non-same-origin iframes to avoid dupes"); + let host = ownerDoc.defaultView.location.hostName; + Array.forEach(doc.querySelectorAll("iframe"), function(f) { + var url = new URL(f.src, ownerDoc.defaultView.location.href); + if (url.hostname != host) { + f.parentNode.removeChild(f); + } + }); } - else { - console.info("inserting content (normal)"); - // Remove non-same-origin iframes, such as ad iframes - // Otherwise we might create a shitload of (nearly) identical frames - // which might even kill the browser - if (!this.pageLimit || this.pageLimit > 10) { - console.info("removing non-same-origin iframes to avoid dupes"); - let host = ownerDoc.defaultView.location.hostName; - Array.forEach(doc.querySelectorAll("iframe"), function(f) { - var url = new URL(f.src, ownerDoc.defaultView.location.href); - if (url.hostname != host) { - f.parentNode.removeChild(f); - } - }); - } - for (let c = doc.body.firstChild; c; c = c.nextSibling) { - ownerDoc.body.appendChild(ownerDoc.importNode(c, true)); - } + for (let c = doc.body.firstChild; c; c = c.nextSibling) { + ownerDoc.body.appendChild(ownerDoc.importNode(c, true)); } + } - if (this.pageLimit && this.pageCount >= this.pageLimit) { - throw new Error("done"); - } + if (this.pageLimit && this.pageCount >= this.pageLimit) { + throw new Error("done"); + } - let savedQuery; - if (this.attemptToIncrement) { - console.log("attempting to increment query"); - let nq = this.incrementQuery(); - if (nq == this.query) { - console.log("query did not increment"); - this.attemptToIncrement = false; - } - else { - console.log("query did increment"); - savedQuery = this.query; - this.query = nq; - } - } - let node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; - let loc = src || null; - if (this.attemptToIncrement && (!node || node.href == loc)) { - console.log("no result after incrementing; restoring"); - console.log("inc:" + this.query + " orig:" + savedQuery); - this.query = savedQuery; - node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; + let savedQuery; + if (this.attemptToIncrement) { + console.log("attempting to increment query"); + let nq = this.incrementQuery(); + if (nq == this.query) { + console.log("query did not increment"); this.attemptToIncrement = false; } - if (!node) { - throw new Error("no next node found for query: " + this.query); - } - let nexturl = node.href.toString(); - if (loc && loc == nexturl) { - throw new Error("location did not change for query" + this.query); - } - if (equalLinks(node,window.location)) { - throw new Error("loop back to first item"); + else { + console.log("query did increment"); + savedQuery = this.query; + this.query = nq; } - - this.setTitle(); - console.info("next please: " + nexturl); - createFrame(nexturl, this.allowScripts, frame => { - if (this.slideshow && this.seconds) { - console.info("slideshow; delay: " + this.seconds * 1000); - this.loadNext(nexturl, frame, this.seconds * 1000); - } - else { - console.info("regular; no-delay"); - this.loadNext(nexturl, frame, 0); - } - }); } - catch (ex) { - console.log(ex); - console.info("loadNext complete"); - this.unregister(); - this.restoreTitle(); + let node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; + let loc = src || null; + if (this.attemptToIncrement && (!node || node.href == loc)) { + console.log("no result after incrementing; restoring"); + console.log("inc:" + this.query + " orig:" + savedQuery); + this.query = savedQuery; + node = doc.evaluate(this.query, doc, null, 9, null).singleNodeValue; + this.attemptToIncrement = false; } - } - }; - Object.seal(Repaginator.prototype); - - const Slideshow = function Slideshow(seconds, allowScripts, yielding) { - this.seconds = seconds || 0; - this.slideshow = true; - this.allowScripts = allowScripts; - this.yielding = yielding; - this.init(); - }; - Slideshow.prototype = Repaginator.prototype; - - const repaginate = (num, slideshow, allowScripts, yielding) => { - try { - let Ctor = slideshow ? Slideshow : Repaginator; - let rep; - // c.f. clicked_element.js - focusElement = clickedEl; - rep = new Ctor(num, allowScripts, yielding); - rep.buildQuery(focusElement); - rep.repaginate(); + if (!node) { + throw new Error("no next node found for query: " + this.query); + } + let nexturl = node.href.toString(); + if (loc && loc == nexturl) { + throw new Error("location did not change for query" + this.query); + } + if (equalLinks(node,window.location)) { + throw new Error("loop back to first item"); + } + + this.setTitle(); + console.info("next please: " + nexturl); + createFrame(nexturl, this.allowScripts, frame => { + if (this.slideshow && this.seconds) { + console.info("slideshow; delay: " + this.seconds * 1000); + this.loadNext(nexturl, frame, this.seconds * 1000); + } + else { + console.info("regular; no-delay"); + this.loadNext(nexturl, frame, 0); + } + }); } catch (ex) { - console.error("Failed to run repaginate", ex); + console.log(ex); + console.info("loadNext complete"); + this.unregister(); + this.restoreTitle(); } - }; + } +}; +Object.seal(Repaginator.prototype); + +let Slideshow = function Slideshow(seconds, allowScripts, yielding) { + this.seconds = seconds || 0; + this.slideshow = true; + this.allowScripts = allowScripts; + this.yielding = yielding; + this.init(); +}; +Slideshow.prototype = Repaginator.prototype; + +let repaginate = (num, slideshow, allowScripts, yielding) => { + try { + let Ctor = slideshow ? Slideshow : Repaginator; + let rep; + // c.f. clicked_element.js + focusElement = clickedEl; + rep = new Ctor(num, allowScripts, yielding); + rep.buildQuery(focusElement); + rep.repaginate(); + } + catch (ex) { + console.error("Failed to run repaginate", ex); + } +}; - const stop = () => { - try { - let body = document.body; - if (body) { - body.removeAttribute("repagination"); - } +let stop = () => { + try { + let body = document.body; + if (body) { + body.removeAttribute("repagination"); } - catch (ex) { - console.error("failed to stop repagination", ex); - } - }; + } + catch (ex) { + console.error("failed to stop repagination", ex); + } +}; +port.onMessage.addListener(msg => { + switch (msg.msg) { + case "normal": repaginate(msg.num, msg.slideshow, msg.allowScripts, msg.yielding); break; + case "stop" : stop(); break; + } +}); - console.log("Framescript loaded!"); - this.port = browser.runtime.connect(); - this.port.onMessage.addListener(msg => { - switch (msg.msg) { - case "normal": repaginate(msg.num, msg.slideshow, msg.allowScripts, msg.yielding); break; - case "stop" : stop(); break; - } - }); +// https://bugzilla.mozilla.org/show_bug.cgi?id=1370368 +window.addEventListener('pagehide', function(event) { + port.disconnect(); +}); -} - +} /* vim: set et ts=2 sw=2 : */ diff --git a/main.js b/main.js index 251c245..ce40542 100644 --- a/main.js +++ b/main.js @@ -2,9 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -console.info("main called!"); -var i = 0; +const PORTS = {}; +const PENDING = {}; +const RUNNING = new Set(); function onError(error) { console.error(error); @@ -20,8 +21,6 @@ const MENU = { function onCreated(n) { if (browser.runtime.lastError) { console.error("Error creating menu item: %o", browser.runtime.lastError); - } else { - console.log(`Menu Item ${i++} created successfully`); } } @@ -29,28 +28,20 @@ function createMenu(prefs) { // https://bugzilla.mozilla.org/show_bug.cgi?id=1325758 // insertbefore="context-sep-open" - i = 0; - browser.contextMenus.create({ id: MENU.NOLIMIT, title: browser.i18n.getMessage("repagination_nolimit"), - contexts: ["all"] - }, onCreated); - - browser.contextMenus.create({ - id: MENU.LIMIT, - title: browser.i18n.getMessage("repagination_limit"), - contexts: ["all"] + contexts: ["link"] }, onCreated); - const limits = [2,5,10,15,20,25,30,40,50,100]; + let limits = [5,10,25,50,100]; - for(let i in limits) { + for(let j in limits) { + let i = limits[j]; browser.contextMenus.create({ - id: MENU.LIMIT + "_" + limits[i], - parentId: MENU.LIMIT, - title: browser.i18n.getMessage("repagination_limit_x",limits[i]), - contexts: ["all"] + id: MENU.LIMIT + "_" + i, + title: browser.i18n.getMessage("repagination_limit_x",i), + contexts: ["link"] }, onCreated); } @@ -59,34 +50,35 @@ function createMenu(prefs) { browser.contextMenus.create({ id: MENU.SLIDE, title: browser.i18n.getMessage("repagination_slide"), - contexts: ["all"] + contexts: ["link"] }, onCreated); - const slides = [0,1,2,4,5,10,15,30,60,120]; + let slides = [0,1,2,4,5,10,15,30,60,120]; - for(let i in slides) { + for(let j in slides) { + let i = slides[j]; browser.contextMenus.create({ id: MENU.SLIDE + "_" + i, parentId: MENU.SLIDE, - title: [0,1,60,120].indexOf(i) != -1 ? browser.i18n.getMessage("repagination_slide_" + i) : browser.i18n.getMessage("repagination_slide_x",i), - contexts: ["all"] + title: ([0,1,60,120].indexOf(i) != -1) ? browser.i18n.getMessage("repagination_slide_" + i) : browser.i18n.getMessage("repagination_slide_x",i), + contexts: ["link"] }, onCreated); } } - browser.contextMenus.create({ - id: MENU.STOP, - title: browser.i18n.getMessage("repagination_stop"), - contexts: ["all"] - }, onCreated); + updStop(); +} - /* https://bugzilla.mozilla.org/show_bug.cgi?id=1215376 - gContextMenu.onLink && /^https?$/.test(gContextMenu.linkURI.scheme)) { - setMenuHidden(false); - if (!RUNNING.has(gContextMenu.frameOuterWindowID)) { - menuCurrent.stopMenu.hidden = true; - } - */ +function updStop() { + if(RUNNING.size > 0) { + browser.contextMenus.create({ + id: MENU.STOP, + title: browser.i18n.getMessage("repagination_stop"), + contexts: ["all"] + }, onCreated); + } else { + browser.contextMenus.remove(MENU.STOP); + } } var defaultSettings = { @@ -117,8 +109,6 @@ function initSettings(prefs) { function myinit(prefs) { initSettings(prefs); - const PORTS = {}; - function repaginate(tab, num, slideshow) { console.info("repaginate: " + num + "/" + slideshow); try { @@ -129,6 +119,8 @@ function myinit(prefs) { allowScripts: prefs.allowScripts, yielding: prefs.yielding }); + RUNNING.add(tab); + updStop(); } catch (ex) { console.log(ex); console.error("failed to run repaginate"); @@ -162,7 +154,6 @@ function myinit(prefs) { // We lazily inject the main content script in a vague hope for efficiency // We use ports for messaging but have to store the messages until the port is opened. - const PENDING = {}; browser.contextMenus.onClicked.addListener((info, tab) => { console.log(info, tab); @@ -181,6 +172,17 @@ function myinit(prefs) { PORTS[tabid] = port; port.onDisconnect.addListener((p) => { delete PORTS[tabid]; + delete PENDING[tabid]; + RUNNING.delete(tabid); + updStop(); + }); + port.onMessage.addListener(msg => { + switch (msg.msg) { + case "unregister": + RUNNING.delete(tabid); + updStop(); + break; + } }); if (port.sender.tab.id in PENDING) { diff --git a/manifest.json b/manifest.json index 9fbdb95..f740d47 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "__MSG_extensionName__", - "version": "2017.11.29.1", + "version": "2018.02.22", "description": "__MSG_extensionDescription__", "homepage_url": "https://github.com/Mathnerd314/repagination/", @@ -33,10 +33,8 @@ }, "permissions": [ - "tabs", "activeTab", "storage", - "webNavigation", "contextMenus", "" ] From 1ba653899ff850c47f13ecdde61e4e31b6634c5a Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Thu, 22 Feb 2018 20:17:25 -0700 Subject: [PATCH 19/24] Fix validation errors --- _locales/de/messages.json | 22 +++++++++++----------- _locales/en/messages.json | 22 +++++++++++----------- manifest.json | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index c6c761c..9ac40b8 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -1,35 +1,35 @@ { - "displaySubmenu.label": { + "displaySubmenu_label": { "message": "Untermenü anzeigen" }, - "displaySubmenu.desc": { + "displaySubmenu_desc": { "message": "Wenn diese Option aktiviert ist (Standard), dann wird Re-Pagination als Untermenü angezeigt anstatt die verschiedenen Aktionen direkt im Kontextmenü anzuzeigen." }, - "showslideshow.label": { + "showslideshow_label": { "message": "'Slide-Show' Menüeintrag anzeigen" }, - "showalldomain.label": { + "showalldomain_label": { "message": "'Alle Tabs der aktuellen Domain' Menüeintrag anzeigen" }, - "loglevel.label": { + "loglevel_label": { "message": "Protokoll Stufe" }, - "loglevel.desc": { + "loglevel_desc": { "message": "Die Nachrichten werden in der Fehlerkonsole protokolliert. Man sollte diese Einstellung auf 'Keine Protokollierung' belassen, wenn nicht anders angewiesen." }, - "loglevel.none.label": { + "loglevel_none_label": { "message": "Keine Protokollierung" }, - "loglevel.error.label": { + "loglevel_error_label": { "message": "Fehler protokollieren" }, - "loglevel.info.label": { + "loglevel_info_label": { "message": "Fehler und Infos protokollieren" }, - "loglevel.debug.label": { + "loglevel_debug_label": { "message": "Alles protokollieren!" }, - "menu.label": { + "menu_label": { "message": "Re-Pagination" }, "repagination_nolimit": { diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9c9d59c..5d54487 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1,35 +1,35 @@ { - "displaySubmenu.label": { + "displaySubmenu_label": { "message": "Display submenu" }, - "displaySubmenu.desc": { + "displaySubmenu_desc": { "message": "When this option is enabled (default), then Re-Pagination will be displayed as a submenu instead of displaying the various actions directly within the context menu." }, - "showslideshow.label": { + "showslideshow_label": { "message": "Show 'Slideshow' menu item" }, - "showalldomain.label": { + "showalldomain_label": { "message": "Show 'All tabs for current domain' menu item" }, - "loglevel.label": { + "loglevel_label": { "message": "Log level" }, - "loglevel.desc": { + "loglevel_desc": { "message": "The messages will be logged to the Error Console. You should leave this at 'No Logging' unless instructed otherwise" }, - "loglevel.none.label": { + "loglevel_none_label": { "message": "No Logging" }, - "loglevel.error.label": { + "loglevel_error_label": { "message": "Log Errors" }, - "loglevel.info.label": { + "loglevel_info_label": { "message": "Log Errors and Infos" }, - "loglevel.debug.label": { + "loglevel_debug_label": { "message": "Log Everything!" }, - "menu.label": { + "menu_label": { "message": "Re-Pagination" }, "repagination_nolimit": { diff --git a/manifest.json b/manifest.json index f740d47..c431659 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "__MSG_extensionName__", - "version": "2018.02.22", + "version": "2018.2.22", "description": "__MSG_extensionDescription__", "homepage_url": "https://github.com/Mathnerd314/repagination/", From da4edd295e9f1ced117bd405f8a4089e4bef5862 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Sat, 12 May 2018 15:25:38 -0600 Subject: [PATCH 20/24] Fix Google repagination --- content-script.js | 3 +++ manifest.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/content-script.js b/content-script.js index ca96c5c..7a3592c 100644 --- a/content-script.js +++ b/content-script.js @@ -297,6 +297,9 @@ Repaginator.prototype = { Array.forEach(doc.querySelectorAll("script"), s => s.parentNode.removeChild(s)); + Array.forEach(doc.querySelectorAll("style"), + s => s.parentNode.removeChild(s)); + // Do the dirty deed if (this.slideshow) { console.info("replacing content (slideshow)"); diff --git a/manifest.json b/manifest.json index c431659..7c3ad40 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "__MSG_extensionName__", - "version": "2018.2.22", + "version": "2018.5.12", "description": "__MSG_extensionDescription__", "homepage_url": "https://github.com/Mathnerd314/repagination/", From 0334bffb44d5daa6324118b546b6f0112a9a7224 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Thu, 17 Oct 2019 15:31:47 -0600 Subject: [PATCH 21/24] Update for FF71 and other maintenance --- build.py | 6 ++-- clicked_element.js | 6 ---- content-script.js | 24 ++++++------- main.js | 84 ++++++++++++++++++++++------------------------ manifest.json | 13 ++----- 5 files changed, 57 insertions(+), 76 deletions(-) delete mode 100644 clicked_element.js diff --git a/build.py b/build.py index 02bc342..2411991 100644 --- a/build.py +++ b/build.py @@ -50,9 +50,9 @@ def __exit__(self, type, value, traceback): self.close() -if os.path.exists(destination): - print >>sys.stderr, destination, "is in the way" - sys.exit(1) +# if os.path.exists(destination): + # print >>sys.stderr, destination, "is in the way" + # sys.exit(1) with Minor(), ZipOutFile(destination) as zp: for f in sorted(get_files(resources), key=str.lower): diff --git a/clicked_element.js b/clicked_element.js deleted file mode 100644 index 1ed6cf6..0000000 --- a/clicked_element.js +++ /dev/null @@ -1,6 +0,0 @@ -// https://bugzilla.mozilla.org/show_bug.cgi?id=1325814 -var clickedEl = null; - -document.addEventListener("contextmenu", function(event) { - clickedEl = event.target; -}, true); diff --git a/content-script.js b/content-script.js index 7a3592c..37077ac 100644 --- a/content-script.js +++ b/content-script.js @@ -10,7 +10,7 @@ let port = browser.runtime.connect(); var focusElement = null; let _ = function () { - let args = Array.map(arguments, e => e.toString()); + let args = Array.from(arguments).map(e => e.toString()); return browser.i18n.getMessage(args[0], args.slice(1)); } @@ -23,7 +23,7 @@ let equalLinks = (left, right) => let getFirstSnapshot = (doc, node, query) => doc.evaluate(query, node, null, 7, null).snapshotItem(0); -let createFrame = (srcurl, allowScripts, loadFun) => { +let createPageRequest = (srcurl, allowScripts, loadFun) => { let errorCount = 0; function sendRequest() { var xhr = new XMLHttpRequest(); @@ -249,7 +249,7 @@ Repaginator.prototype = { throw new Error("no node"); } document.body.setAttribute("repagination", "true"); - createFrame(node.href, this.allowScripts, frame => { + createPageRequest(node.href, this.allowScripts, frame => { this.loadNext(node.href, frame, 0); }); } @@ -294,11 +294,8 @@ Repaginator.prototype = { // Duplicate scripts would cause more havoc (performance-wise) than // behaviour failures due to missing scripts // Note: This is NOT a security mechanism, but a performance thing. - Array.forEach(doc.querySelectorAll("script"), - s => s.parentNode.removeChild(s)); - - Array.forEach(doc.querySelectorAll("style"), - s => s.parentNode.removeChild(s)); + doc.querySelectorAll("script").forEach(s => s.parentNode.removeChild(s)); + doc.querySelectorAll("style").forEach(s => s.parentNode.removeChild(s)); // Do the dirty deed if (this.slideshow) { @@ -315,7 +312,7 @@ Repaginator.prototype = { if (!this.pageLimit || this.pageLimit > 10) { console.info("removing non-same-origin iframes to avoid dupes"); let host = ownerDoc.defaultView.location.hostName; - Array.forEach(doc.querySelectorAll("iframe"), function(f) { + doc.querySelectorAll("iframe").forEach(function(f) { var url = new URL(f.src, ownerDoc.defaultView.location.href); if (url.hostname != host) { f.parentNode.removeChild(f); @@ -367,7 +364,7 @@ Repaginator.prototype = { this.setTitle(); console.info("next please: " + nexturl); - createFrame(nexturl, this.allowScripts, frame => { + createPageRequest(nexturl, this.allowScripts, frame => { if (this.slideshow && this.seconds) { console.info("slideshow; delay: " + this.seconds * 1000); this.loadNext(nexturl, frame, this.seconds * 1000); @@ -397,12 +394,11 @@ let Slideshow = function Slideshow(seconds, allowScripts, yielding) { }; Slideshow.prototype = Repaginator.prototype; -let repaginate = (num, slideshow, allowScripts, yielding) => { +let repaginate = (target, num, slideshow, allowScripts, yielding) => { try { let Ctor = slideshow ? Slideshow : Repaginator; let rep; - // c.f. clicked_element.js - focusElement = clickedEl; + focusElement = browser.menus.getTargetElement(target); rep = new Ctor(num, allowScripts, yielding); rep.buildQuery(focusElement); rep.repaginate(); @@ -426,7 +422,7 @@ let stop = () => { port.onMessage.addListener(msg => { switch (msg.msg) { - case "normal": repaginate(msg.num, msg.slideshow, msg.allowScripts, msg.yielding); break; + case "normal": repaginate(msg.target, msg.num, msg.slideshow, msg.allowScripts, msg.yielding); break; case "stop" : stop(); break; } }); diff --git a/main.js b/main.js index ce40542..5eb9003 100644 --- a/main.js +++ b/main.js @@ -25,43 +25,42 @@ function onCreated(n) { } function createMenu(prefs) { - // https://bugzilla.mozilla.org/show_bug.cgi?id=1325758 - // insertbefore="context-sep-open" - - browser.contextMenus.create({ + let c = ["link"]; // ContextTypes for most menu items + + browser.menus.create({ id: MENU.NOLIMIT, title: browser.i18n.getMessage("repagination_nolimit"), - contexts: ["link"] + contexts: c }, onCreated); let limits = [5,10,25,50,100]; for(let j in limits) { let i = limits[j]; - browser.contextMenus.create({ + browser.menus.create({ id: MENU.LIMIT + "_" + i, title: browser.i18n.getMessage("repagination_limit_x",i), - contexts: ["link"] + contexts: c }, onCreated); } - + if(prefs.slideshows) { - browser.contextMenus.create({ + browser.menus.create({ id: MENU.SLIDE, title: browser.i18n.getMessage("repagination_slide"), - contexts: ["link"] + contexts: c }, onCreated); let slides = [0,1,2,4,5,10,15,30,60,120]; for(let j in slides) { let i = slides[j]; - browser.contextMenus.create({ + browser.menus.create({ id: MENU.SLIDE + "_" + i, parentId: MENU.SLIDE, title: ([0,1,60,120].indexOf(i) != -1) ? browser.i18n.getMessage("repagination_slide_" + i) : browser.i18n.getMessage("repagination_slide_x",i), - contexts: ["link"] + contexts: c }, onCreated); } } @@ -71,13 +70,13 @@ function createMenu(prefs) { function updStop() { if(RUNNING.size > 0) { - browser.contextMenus.create({ + browser.menus.create({ id: MENU.STOP, title: browser.i18n.getMessage("repagination_stop"), contexts: ["all"] }, onCreated); } else { - browser.contextMenus.remove(MENU.STOP); + browser.menus.remove(MENU.STOP); } } @@ -89,7 +88,7 @@ var defaultSettings = { function prefReset(newSettings, areaName) { console.log("prefs changed") if (areaName == "local" && ("exists" in newSettings)) { - browser.contextMenus.removeAll(); + browser.menus.removeAll(); console.log("recreating menu") browser.storage.local.get().then(initSettings, onError); } @@ -102,18 +101,42 @@ function initSettings(prefs) { browser.storage.onChanged.addListener(prefReset); prefs = defaultSettings; } - + createMenu(prefs); } function myinit(prefs) { initSettings(prefs); - function repaginate(tab, num, slideshow) { + function process(info, tab) { + let str = info.menuItemId; + + if(str == MENU.STOP) { + console.info("stop"); + PORTS[tab].postMessage({ + msg: "stop" + }); + return; + } + + let num, slideshow; + if(str == MENU.NOLIMIT) { + num = 0; + slideshow = false; + } else if(str.startsWith(MENU.LIMIT)) { + // https://stackoverflow.com/questions/5555518/split-variable-from-last-slash-jquery + num = parseInt(str.substring(str.lastIndexOf("_") + 1, str.length), 10); + slideshow = false; + } else if(str.startsWith(MENU.SLIDE)) { + num = parseInt(str.substring(str.lastIndexOf("_") + 1, str.length), 10); + slideshow = true; + } + console.info("repaginate: " + num + "/" + slideshow); try { PORTS[tab].postMessage({ msg: "normal", + target: info.targetElementId, num: num, slideshow: slideshow, allowScripts: prefs.allowScripts, @@ -127,35 +150,10 @@ function myinit(prefs) { } } - function stop(tab) { - console.info("stop"); - PORTS[tab].postMessage({ - msg: "stop" - }); - } - - function process(info, tab) { - var str = info.menuItemId; - switch (str) { - case MENU.NOLIMIT: repaginate(tab); break; - case MENU.STOP: stop(tab); break; - } - - if(str.startsWith(MENU.LIMIT)) { - // https://stackoverflow.com/questions/5555518/split-variable-from-last-slash-jquery - var last = str.substring(str.lastIndexOf("_") + 1, str.length); - repaginate(tab, parseInt(last, 10), false); - } - if(str.startsWith(MENU.SLIDE)) { - var last = str.substring(str.lastIndexOf("_") + 1, str.length); - repaginate(tab, parseInt(last, 10), true); - } - } - // We lazily inject the main content script in a vague hope for efficiency // We use ports for messaging but have to store the messages until the port is opened. - browser.contextMenus.onClicked.addListener((info, tab) => { + browser.menus.onClicked.addListener((info, tab) => { console.log(info, tab); if(tab.id in PORTS) { process(info, tab.id); diff --git a/manifest.json b/manifest.json index 7c3ad40..8b7d66b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "__MSG_extensionName__", - "version": "2018.5.12", + "version": "2019.10.19", "description": "__MSG_extensionDescription__", "homepage_url": "https://github.com/Mathnerd314/repagination/", @@ -17,17 +17,10 @@ "applications": { "gecko": { "id": "repagination-fork-mathnerd314@github.com", - "strict_min_version": "48.0" + "strict_min_version": "63.0" } }, - "content_scripts": [ - { - "matches": [""], - "js": ["clicked_element.js"] - } - ], - "background": { "scripts": ["main.js"] }, @@ -35,7 +28,7 @@ "permissions": [ "activeTab", "storage", - "contextMenus", + "menus", "" ] } From 18796db824525c1098d59d0f985b63abe420e508 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Wed, 27 May 2020 08:45:31 -0600 Subject: [PATCH 22/24] Fix German translation --- _locales/de/messages.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 9ac40b8..8477d08 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -57,20 +57,20 @@ "message": "Stoppen!" }, "repagination_limit_x": { - "message": "%S Seiten" + "message": "$1 Seiten" }, "repagination_slide_120": { "message": "2 Minuten" }, "repagination_slide_x": { - "message": "%S Sekunden" + "message": "$1 Sekunden" }, "repagination_limited": { - "message": "(%S of %S) Re-Pagination arbeitet..." + "message": "($1 of $2) Re-Pagination arbeitet..." }, "repagination_unlimited": { - "message": "(%S) Re-Pagination arbeitet..." + "message": "($1) Re-Pagination arbeitet..." }, "repagination_running": { "message": "Re-Pagination arbeitet..." From b0870f81b6f8c66e7e47b67da496b25f94b79957 Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Wed, 27 May 2020 08:49:30 -0600 Subject: [PATCH 23/24] Bump version --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 8b7d66b..640435a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "__MSG_extensionName__", - "version": "2019.10.19", + "version": "2020.5.27", "description": "__MSG_extensionDescription__", "homepage_url": "https://github.com/Mathnerd314/repagination/", From b0214aa1e6b5d4b7df1b546a6b82db91a848ae8b Mon Sep 17 00:00:00 2001 From: Mathnerd314 Date: Mon, 12 Apr 2021 09:38:35 -0600 Subject: [PATCH 24/24] Port back changes from Chrome --- content-script.js | 34 +++++++++++++++++----------------- main.js | 17 ++++++++++++----- options.html | 2 +- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/content-script.js b/content-script.js index 37077ac..b263df9 100644 --- a/content-script.js +++ b/content-script.js @@ -229,16 +229,15 @@ Repaginator.prototype = { document.title = _("repagination_running"); } }, - restoreTitle: function R_restoreTitle() { + finished: function R_finished() { + port.postMessage({msg: "finished"}); + stop(); + // restore title if("_title" in this) { document.title = this._title; delete this._title; } }, - unregister: function R_unregister() { - port.postMessage({msg: "unregister"}); - document.body.removeAttribute("repagination"); - }, repaginate: function R_repaginate() { this._title = document.title; this.setTitle(); @@ -252,10 +251,8 @@ Repaginator.prototype = { createPageRequest(node.href, this.allowScripts, frame => { this.loadNext(node.href, frame, 0); }); - } - catch (ex) { - this.unregister(); - this.restoreTitle(); + } catch (ex) { + this.finished(); console.error("repaginate failed", ex); } }, @@ -275,6 +272,7 @@ Repaginator.prototype = { this._loadNext_gen.bind(this, src, element)(); } catch (ex) { console.error("failed to process loadNext (non-yielding)", ex); + this.finished(); } return; }, @@ -378,8 +376,7 @@ Repaginator.prototype = { catch (ex) { console.log(ex); console.info("loadNext complete"); - this.unregister(); - this.restoreTitle(); + this.finished(); } } }; @@ -402,8 +399,7 @@ let repaginate = (target, num, slideshow, allowScripts, yielding) => { rep = new Ctor(num, allowScripts, yielding); rep.buildQuery(focusElement); rep.repaginate(); - } - catch (ex) { + } catch (ex) { console.error("Failed to run repaginate", ex); } }; @@ -414,8 +410,7 @@ let stop = () => { if (body) { body.removeAttribute("repagination"); } - } - catch (ex) { + } catch (ex) { console.error("failed to stop repagination", ex); } }; @@ -429,8 +424,13 @@ port.onMessage.addListener(msg => { // https://bugzilla.mozilla.org/show_bug.cgi?id=1370368 window.addEventListener('pagehide', function(event) { - port.disconnect(); + stop(); + try { + port.disconnect(); + } catch (ex) { + console.log(ex) + } }); -} +} /* vim: set et ts=2 sw=2 : */ diff --git a/main.js b/main.js index 5eb9003..6767e66 100644 --- a/main.js +++ b/main.js @@ -39,7 +39,7 @@ function createMenu(prefs) { let i = limits[j]; browser.menus.create({ id: MENU.LIMIT + "_" + i, - title: browser.i18n.getMessage("repagination_limit_x",i), + title: browser.i18n.getMessage("repagination_limit_x",[i]), contexts: c }, onCreated); @@ -59,7 +59,7 @@ function createMenu(prefs) { browser.menus.create({ id: MENU.SLIDE + "_" + i, parentId: MENU.SLIDE, - title: ([0,1,60,120].indexOf(i) != -1) ? browser.i18n.getMessage("repagination_slide_" + i) : browser.i18n.getMessage("repagination_slide_x",i), + title: ([0,1,60,120].indexOf(i) != -1) ? browser.i18n.getMessage("repagination_slide_" + i) : browser.i18n.getMessage("repagination_slide_x",[i]), contexts: c }, onCreated); } @@ -68,15 +68,19 @@ function createMenu(prefs) { updStop(); } +var added = false; + function updStop() { - if(RUNNING.size > 0) { + if(RUNNING.size > 0 && !added) { browser.menus.create({ id: MENU.STOP, title: browser.i18n.getMessage("repagination_stop"), contexts: ["all"] }, onCreated); - } else { + added = true; + } else if(added) { browser.menus.remove(MENU.STOP); + added = false; } } @@ -147,6 +151,9 @@ function myinit(prefs) { } catch (ex) { console.log(ex); console.error("failed to run repaginate"); + delete PORTS[tabid]; + delete PENDING[tabid]; + RUNNING.delete(tabid); } } @@ -176,7 +183,7 @@ function myinit(prefs) { }); port.onMessage.addListener(msg => { switch (msg.msg) { - case "unregister": + case "finished": RUNNING.delete(tabid); updStop(); break; diff --git a/options.html b/options.html index 90b66ae..10acd23 100644 --- a/options.html +++ b/options.html @@ -41,7 +41,7 @@
    - +

    Credits