From 951277e92223c29d567b9807792eaae01c0ea88d Mon Sep 17 00:00:00 2001 From: kkapsner Date: Sat, 13 Feb 2016 12:28:36 +0100 Subject: [PATCH] e10s ready! Fixes #60 and #42 --- data/frame.js | 75 +++++++++ lib/askForPermission.js | 17 +- lib/{sharedFunctions.js => callingStack.js} | 87 ++--------- lib/check.js | 95 ++++++++++++ lib/intercept.js | 51 ++++++ lib/lists.js | 2 +- lib/main.js | 163 +++++++++++--------- lib/notifications.js | 47 +++--- package.json | 7 +- test/iFrame_test.html | 45 ++++++ 10 files changed, 411 insertions(+), 178 deletions(-) create mode 100644 data/frame.js rename lib/{sharedFunctions.js => callingStack.js} (58%) create mode 100644 lib/check.js create mode 100644 lib/intercept.js create mode 100644 test/iFrame_test.html diff --git a/data/frame.js b/data/frame.js new file mode 100644 index 0000000..2b6fb40 --- /dev/null +++ b/data/frame.js @@ -0,0 +1,75 @@ +/* jslint moz: true */ +/* 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/. */ +(function(){ + "use strict"; + + const {utils: Cu} = Components; + const COMMONJS_URI = "resource://gre/modules/commonjs"; + const {require} = Cu.import(COMMONJS_URI + "/toolkit/require.js", {}); + const {intercept} = require("../lib/intercept.js"); + const {ask} = require("../lib/askForPermission.js"); + + // Variable to "unload" the script + var enabled = true; + + function check(message){ + if (enabled){ + var status = sendSyncMessage( + "canvasBlocker-check", + message + ); + return status[0]; + } + else { + return {type: [], mode: "allow"}; + } + } + function askWrapper(data){ + return ask(data, { + _: function(token){ + return sendSyncMessage( + "canvasBlocker-translate", + token + )[0]; + }, + prefs: function(name){ + return sendSyncMessage( + "canvasBlocker-pref-get", + name + )[0]; + } + }); + } + function notify(data){ + sendAsyncMessage("canvasBlocker-notify", data); + } + + function interceptWrapper(ev){ + if (enabled){ + // window is only equal to content for the top window. For susequent + // calls (e.g. iframe windows) the new generated window has to be + // used. + + var window = ev.target.defaultView; + intercept( + {subject: window}, + {check, ask: askWrapper, notify} + ); + } + } + + addEventListener("DOMWindowCreated", interceptWrapper); + var context = this; + addEventListener("unload", function(ev){ + if (ev.target === context){ + removeEventListener("DOMWindowCreated", interceptWrapper); + } + }); + addMessageListener("canvasBlocker-unload", function unload(){ + enabled = false; + removeEventListener("DOMWindowCreated", interceptWrapper); + removeMessageListener("canvasBlocker-unload", unload); + }); +}()); \ No newline at end of file diff --git a/lib/askForPermission.js b/lib/askForPermission.js index 248cbed..9dd3fa8 100644 --- a/lib/askForPermission.js +++ b/lib/askForPermission.js @@ -4,10 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ (function(){ "use strict"; - - const _ = require("sdk/l10n").get; - const preferences = require("sdk/simple-prefs"); - const prefs = preferences.prefs; + const {parseErrorStack} = require("./callingStack"); // Check canvas appearance function canvasAppearance(window, context){ @@ -59,7 +56,7 @@ } var modes = new WeakMap(); - function getAskMode(window, type){ + function getAskMode(window, type, _){ var mode = modes.get(window); if (mode){ return mode[type]; @@ -94,12 +91,12 @@ } } - exports.ask = function(window, type, canvas, callingStackMsg){ + exports.ask = function({window, type, canvas, errorStack}, {_, prefs}){ var answer; - var askMode = getAskMode(window, type); + var askMode = getAskMode(window, type, _); var askStatus = askMode.askStatus; var appearance = canvasAppearance(window, canvas); - if (prefs.askOnlyOnce && askStatus.alreadyAsked[appearance.askCategory]){ + if (prefs("askOnlyOnce") && askStatus.alreadyAsked[appearance.askCategory]){ // already asked appearance.reset(); return askStatus.answer[appearance.askCategory]; @@ -107,8 +104,8 @@ else { // asking var msg = _(askMode.askText[appearance.text]); - if (prefs.showCallingFile){ - msg += callingStackMsg; + if (prefs("showCallingFile")){ + msg += parseErrorStack(errorStack).toString(_); } answer = window.confirm(msg)? "allow": "block"; askStatus.alreadyAsked[appearance.text] = true; diff --git a/lib/sharedFunctions.js b/lib/callingStack.js similarity index 58% rename from lib/sharedFunctions.js rename to lib/callingStack.js index bf2a27d..6cb9ce4 100644 --- a/lib/sharedFunctions.js +++ b/lib/callingStack.js @@ -4,16 +4,18 @@ * 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/. */ -const lists = require("./lists"); const preferences = require("sdk/simple-prefs"); const prefs = preferences.prefs; // Translation var translate = require("sdk/l10n").get; -var _ = function(name, replace){ +var _ = function(name, replace, translateAPI){ "use strict"; + if (!translateAPI){ + translateAPI = translate; + } - var str = translate(name) || name; + var str = translateAPI(name) || name; if (replace){ // replace generic content in the transation by given parameter Object.keys(replace).forEach(function(name){ @@ -23,69 +25,6 @@ var _ = function(name, replace){ return str; }; -function check(stack, url, blockMode){ - if (prefs.enableStackList && checkStack(stack)){ - return "allow"; - } - else { - return checkURL(url, blockMode); - } -} - -function checkURL(url, blockMode){ - "use strict"; - - switch (url.protocol){ - case "about:": - if (url.href === "about:blank"){ - break; - } - case "chrome:": - return "allow"; - } - - var mode = "block"; - switch (blockMode){ - case "blockEverything": - mode = "block"; - break; - case "block": - case "blockContext": - case "blockReadout": - case "ask": - case "askContext": - case "askReadout": - case "fake": - case "fakeContext": - case "fakeReadout": - case "allow": - case "allowContext": - case "allowReadout": - if (url && lists.get("white").match(url)){ - mode = "allow"; - } - else if (url && lists.get("black").match(url)){ - mode = "block"; - } - else { - mode = blockMode; - } - break; - case "allowEverything": - mode = "allow"; - break; - default: - console.log("Unknown blocking mode (" + blockMode + "). Default to block everything."); - } - return mode; -} - -function checkStack(stack){ - "use strict"; - - return lists.get("stack").match(stack); -} - // Stack parsing function parseStackEntry(entry){ "use strict"; @@ -116,10 +55,10 @@ function stackRuleMatch(stackEntry, stackRule){ } // parse calling stack -function errorToCallingStack(error){ +function parseErrorStack(errorStack){ "use strict"; - var callers = error.stack.trim().split("\n"); + var callers = errorStack.trim().split("\n"); //console.log(callers); var findme = callers.shift(); // Remove us from the stack findme = findme.replace(/(:[0-9]+){1,2}$/, ""); // rm line & column @@ -131,16 +70,16 @@ function errorToCallingStack(error){ return !inDoubleStack; }).map(parseStackEntry); return { - toString: function(){ + toString: function(translateAPI){ var msg = ""; - msg += "\n\n" + _("sourceOutput") + ": "; + msg += "\n\n" + _("sourceOutput", undefined, translateAPI) + ": "; if (prefs.showCompleteCallingStack){ msg += callers.reduce(function(stack, c){ - return stack + "\n\t" + _("stackEntryOutput", c); + return stack + "\n\t" + _("stackEntryOutput", c, translateAPI); }, ""); } else{ - msg += _("stackEntryOutput", callers[0]); + msg += _("stackEntryOutput", callers[0], translateAPI); } return msg; @@ -162,6 +101,4 @@ function errorToCallingStack(error){ }; } -exports.check = check; -exports.parseStackEntry = parseStackEntry; -exports.errorToCallingStack = errorToCallingStack; \ No newline at end of file +exports.parseErrorStack = parseErrorStack; \ No newline at end of file diff --git a/lib/check.js b/lib/check.js new file mode 100644 index 0000000..06181e9 --- /dev/null +++ b/lib/check.js @@ -0,0 +1,95 @@ +/* global console,exports */ +/* jslint moz: true */ +/* 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/. */ +(function(){ + "use strict"; + + const lists = require("./lists"); + const preferences = require("sdk/simple-prefs"); + const prefs = preferences.prefs; + const {parseErrorStack} = require("./callingStack"); + const {URL} = require("sdk/url"); + + exports.check = function check({url, errorStack}){ + var callingStack = parseErrorStack(errorStack); + var match = checkBoth(callingStack, url, prefs.blockMode).match(/^(block|allow|fake|ask)(|Readout|Everything|Context)$/); + if (match){ + return { + type: (match[2] === "Everything" || match[2] === "")? + ["context", "readout"]: + [match[2].toLowerCase()], + mode: match[1] + }; + + } + else { + return { + type: ["context", "readout"], + mode: "block" + }; + } + + }; + + function checkBoth(stack, url, blockMode){ + if (prefs.enableStackList && checkStack(stack)){ + return "allow"; + } + else { + return checkURL(url, blockMode); + } + } + + function checkURL(url, blockMode){ + url = new URL(url); + switch (url.protocol){ + case "about:": + if (url.href === "about:blank"){ + break; + } + case "chrome:": + return "allow"; + } + + var mode = "block"; + switch (blockMode){ + case "blockEverything": + mode = "block"; + break; + case "block": + case "blockContext": + case "blockReadout": + case "ask": + case "askContext": + case "askReadout": + case "fake": + case "fakeContext": + case "fakeReadout": + case "allow": + case "allowContext": + case "allowReadout": + if (url && lists.get("white").match(url)){ + mode = "allow"; + } + else if (url && lists.get("black").match(url)){ + mode = "block"; + } + else { + mode = blockMode; + } + break; + case "allowEverything": + mode = "allow"; + break; + default: + console.log("Unknown blocking mode (" + blockMode + "). Default to block everything."); + } + return mode; + } + + function checkStack(stack){ + return lists.get("stack").match(stack); + } +}()); \ No newline at end of file diff --git a/lib/intercept.js b/lib/intercept.js new file mode 100644 index 0000000..1d1621d --- /dev/null +++ b/lib/intercept.js @@ -0,0 +1,51 @@ +/* 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/. */ +(function(){ + "use strict"; + + const {changedFunctions} = require("./modifiedAPI"); + var apiNames = Object.keys(changedFunctions); + var undef; + + exports.intercept = function intercept({subject: window}, {check, ask, notify}){ + apiNames.forEach(function(name){ + var changedFunction = changedFunctions[name]; + var original = window.wrappedJSObject[changedFunction.object].prototype[name]; + + Object.defineProperty( + window.wrappedJSObject[changedFunction.object].prototype, + name, + { + enumerable: true, + configureable: false, + get: function(){ + if (!window.location.href){ + return undef; + } + var error = new Error(); + var status = check({url: window.location.href, errorStack: error.stack}); + if (status.type.indexOf(changedFunction.type) !== -1){ + if (status.mode === "ask"){ + status.mode = ask({window: window, type: changedFunction.type, canvas: this, errorStack: error.stack}); + } + switch (status.mode){ + case "allow": + return original; + case "fake": + notify({url: window.location.href, errorStack: error.stack}, window); + return changedFunction.fake || undef; + //case "block": + default: + return undef; + } + } + else { + return original; + } + } + } + ); + }); + } +}()); \ No newline at end of file diff --git a/lib/lists.js b/lib/lists.js index 49592f0..b8167b6 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -31,7 +31,7 @@ function getDomainRegExpList(domainList){ return { match: function(url){ if (domain){ - return url.hostname.match(regExp); + return (url.hostname || "").match(regExp); } else { return url.href.match(regExp); diff --git a/lib/main.js b/lib/main.js index 35426b2..255b765 100644 --- a/lib/main.js +++ b/lib/main.js @@ -5,82 +5,107 @@ (function(){ "use strict"; require("./stylePreferencePane"); - const {changedFunctions} = require("./modifiedAPI"); + + + const {when: unload} = require("sdk/system/unload"); + const {check} = require("./check.js"); const {notify} = require("./notifications"); - const {ask} = require("./askForPermission"); + + const _ = require("sdk/l10n").get; const lists = require("./lists"); - - const sharedFunctions = require("./sharedFunctions"); - - const observers = require("sdk/system/events"); - const { when: unload } = require("sdk/system/unload"); - const preferences = require("sdk/simple-prefs"); + const prefService = require("sdk/preferences/service"); const prefs = preferences.prefs; - function check(callingStack, url){ - var match = sharedFunctions.check(callingStack, url, prefs.blockMode).match(/^(block|allow|fake|ask)(|Readout|Everything|Context)$/); - if (match){ - return { - type: (match[2] === "Everything" || match[2] === "")? - ["context", "readout"]: - [match[2].toLowerCase()], - mode: match[1] - }; - + const notificationPref = { + doShow: function(){ + return prefs.showNotifications; + }, + setShow: function(value){ + prefs.showNotifications = value; + prefService.set("extensions.CanvasBlocker@kkapsner.de.showNotifications", prefs.showNotifications); } - else { - return { - type: ["context", "readout"], - mode: "block" - }; - } - + }; + + // const observers = require("sdk/system/events"); + // const {intercept} = require("./intercept"); + // const {errorToCallingStack} = require("./callingStack.js"); + // const {ask} = require("./askForPermission"); + // function interceptWrapper(ev){ + // intercept(ev, { + // check, + // ask: function(data){ + // return ask( + // data, + // { + // _, + // prefs: function(name){ + // return prefs[ev.data]; + // } + // } + // ); + // }, + // notify: function(data, window){ + // notify( + // data, + // { + // lists, _, notificationPref, window + // } + // ); + // } + // }); + // } + // observers.on("content-document-global-created", interceptWrapper); + // unload(function(){ + // observers.off("content-document-global-created", interceptWrapper); + // }); + + const {Cc, Ci} = require("chrome"); + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); + var frameURL = require("sdk/self").data.url("frame.js?" + Math.random()); + globalMM.loadFrameScript(frameURL, true); + + var listeners = []; + function addMessageListener(name, func){ + listeners.push({name, func}); + globalMM.addMessageListener(name, func); } - - var apiNames = Object.keys(changedFunctions); - var undef; - - function intercept({subject: window}){ - apiNames.forEach(function(name){ - var changedFunction = changedFunctions[name]; - var original = window.wrappedJSObject[changedFunction.object].prototype[name]; - - Object.defineProperty( - window.wrappedJSObject[changedFunction.object].prototype, - name, - { - enumerable: true, - configureable: false, - get: function(){ - var callingStack = sharedFunctions.errorToCallingStack(new Error()); - var status = check(callingStack, window.location); - if (status.type.indexOf(changedFunction.type) !== -1){ - if (status.mode === "ask"){ - status.mode = ask(window, changedFunction.type, this, callingStack); - } - switch (status.mode){ - case "allow": - return original; - case "fake": - notify(window, callingStack); - return changedFunction.fake || undef; - //case "block": - default: - return undef; - } - } - else { - return original; - } - } - } - ); - }); - } - - observers.on("content-document-global-created", intercept); unload(function(){ - observers.off("content-document-global-created", intercept); + globalMM.removeDelayedFrameScript(frameURL); + globalMM.broadcastAsyncMessage("canvasBlocker-unload"); + listeners.forEach(function(listener){ + globalMM.removeMessageListener(listener.name, listener.func); + }); }); + + // messages from the frame.js + addMessageListener("canvasBlocker-check", function(ev){ + var status = check(ev.data); + return status; + }); + + addMessageListener("canvasBlocker-notify", function(ev){ + var browser = ev.target; + notify(ev.data, {lists, _, notificationPref, browser}); + }); + + addMessageListener("canvasBlocker-pref-get", function(ev){ + return prefs[ev.data]; + }); + addMessageListener("canvasBlocker-pref-set", function(ev){ + prefs[ev.data.name] = ev.data.value; + prefService.set("extensions.CanvasBlocker@kkapsner.de." + ev.data.name, ev.data.value); + }); + + addMessageListener("canvasBlocker-list-match", function(ev){ + return lists.get(ev.data.list).match(ev.data.url); + }); + addMessageListener("canvasBlocker-list-appendTo", function(ev){ + return lists.appendTo(ev.data.list, ev.data.entry); + }); + + addMessageListener("canvasBlocker-translate", function(ev){ + return _(ev.data); + }); + }()); \ No newline at end of file diff --git a/lib/notifications.js b/lib/notifications.js index 6c1e297..c2386e0 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -2,26 +2,32 @@ * 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/. */ -var _ = require("sdk/l10n").get; -var preferences = require("sdk/simple-prefs"); -var prefService = require("sdk/preferences/service"); -var prefs = preferences.prefs; -var tabUtils = require("sdk/tabs/utils"); -var lists = require("./lists"); -var URL = require("sdk/url").URL; -exports.notify = function(window, callingStackMsg){ +var URL = require("sdk/url").URL; +const {parseErrorStack} = require("./callingStack"); + +var tabUtils = require("sdk/tabs/utils"); +exports.notify = function({url, errorStack}, {lists, notificationPref, _, browser, window}){ "use strict"; + var callingStackMsg = parseErrorStack(errorStack); - var contentURL = new URL(window.location); - if (prefs.showNotifications && !lists.get("ignore").match(contentURL)){ + var contentURL = new URL(url); + if (notificationPref.doShow() && !lists.get("ignore").match(contentURL)){ var url = contentURL.href; var domain = contentURL.hostname; - var message = _("fakedReadout").replace(/\{url\}/g, domain); + var message = _("fakedReadout").replace(/\{url\}/g, domain || url); - var tab = tabUtils.getTabForContentWindow(window); - var tabBrowser = tabUtils.getTabBrowserForTab(tab); - var browser = tabUtils.getBrowserForTab(tab); + var tab, tabBrowser; + if (browser){ + window = tabUtils.getOwnerWindow(browser); + tab = tabUtils.getTabForBrowser(browser); + tabBrowser = tabUtils.getTabBrowser(window); + } + else if (window){ + tab = tabUtils.getTabForContentWindow(window); + tabBrowser = tabUtils.getTabBrowserForTab(tab); + browser = tabUtils.getBrowserForTab(tab); + } var notifyBox = tabBrowser.getNotificationBox(browser); var notification = notifyBox.getNotificationWithValue("fake-readout"); @@ -37,7 +43,7 @@ exports.notify = function(window, callingStackMsg){ label: _("displayFullURL"), accessKey: "", callback: function(){ - browser.contentWindow.alert(notification.url); + window.alert(notification.url); // only way to prevent closing... see https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/appendNotification#Notification_box_events throw new Error("Do not close notification."); } @@ -46,7 +52,7 @@ exports.notify = function(window, callingStackMsg){ label: _("displayCallingStack"), accessKey: "", callback: function(){ - browser.contentWindow.alert(notification.callingStackMsg); + window.alert(notification.callingStackMsg); // only way to prevent closing... see https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/appendNotification#Notification_box_events throw new Error("Do not close notification."); } @@ -55,7 +61,7 @@ exports.notify = function(window, callingStackMsg){ label: _("ignorelistDomain"), accessKey: "", callback: function(){ - var domain = browser.contentWindow.prompt( + var domain = window.prompt( _("inputIgnoreDomain"), notification.domain ); @@ -68,7 +74,7 @@ exports.notify = function(window, callingStackMsg){ label: _("whitelistURL"), accessKey: "", callback: function(){ - var url = browser.contentWindow.prompt( + var url = window.prompt( _("inputWhitelistDomain"), "^" + notification.url.replace(/([\\\+\*\?\[\^\]\$\(\)\{\}\=\!\|\.])/g, "\\$1") + "$" ); @@ -81,7 +87,7 @@ exports.notify = function(window, callingStackMsg){ label: _("whitelistDomain"), accessKey: "", callback: function(){ - var domain = browser.contentWindow.prompt( + var domain = window.prompt( _("inputWhitelistURL"), notification.domain ); @@ -95,8 +101,7 @@ exports.notify = function(window, callingStackMsg){ label: _("disableNotifications"), accessKey: "", callback: function(){ - prefs.showNotifications = false; - prefService.set("extensions.CanvasBlocker@kkapsner.de.showNotifications", prefs.showNotifications); + notificationPref.setShow(false); } } ]; diff --git a/package.json b/package.json index 4e6eddb..9616df2 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,9 @@ "main": "lib/main.js", "author": "Korbinian Kapsner", "license": "MPL 2.0", - "version": "0.2.3-Development", - "permissions": {"private-browsing": true} + "version": "0.3.0-Development", + "permissions": { + "private-browsing": true, + "multiprocess": true + } } diff --git a/test/iFrame_test.html b/test/iFrame_test.html new file mode 100644 index 0000000..341c0b6 --- /dev/null +++ b/test/iFrame_test.html @@ -0,0 +1,45 @@ + + + + +iFrame Test. Thanks to DocumentRoot. + + + + \ No newline at end of file