1
0
mirror of https://github.com/kkapsner/CanvasBlocker synced 2024-12-22 12:50:36 +01:00

e10s ready!

Fixes #60 and #42
This commit is contained in:
kkapsner 2016-02-13 12:28:36 +01:00
parent 35c6a82480
commit 951277e922
10 changed files with 411 additions and 178 deletions

75
data/frame.js Normal file
View File

@ -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);
});
}());

View File

@ -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;

View File

@ -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;
exports.parseErrorStack = parseErrorStack;

95
lib/check.js Normal file
View File

@ -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);
}
}());

51
lib/intercept.js Normal file
View File

@ -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;
}
}
}
);
});
}
}());

View File

@ -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);

View File

@ -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);
});
}());

View File

@ -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);
}
}
];

View File

@ -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
}
}

45
test/iFrame_test.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<body>
iFrame Test. Thanks to DocumentRoot.
<img id="display" width="100%">
<iframe id="iframe" sandbox="allow-same-origin" style="display:none"></iframe>
<script>
(function(){
document.getElementById("display").src = after();
}());
function after(){
var fp_text = "BrowserLeaks,com <canvas> 10";
// create window canvas
var canvas = document.createElement('canvas');
canvas.setAttribute("width", 220);
canvas.setAttribute("height", 30);
// draw image in window canvas
var ctx = canvas.getContext('2d');
ctx.textBaseline = "top";
ctx.font = "14px 'Arial'";
ctx.textBaseline = "alphabetic";
ctx.fillStyle = "#f60";
ctx.fillRect(125, 1, 62, 20);
ctx.fillStyle = "#069";
ctx.fillText(fp_text, 2, 15);
ctx.fillStyle = "rgba(102, 204, 0, 07)";
ctx.fillText(fp_text, 4, 17);
// create iframe canvas and ctx
var iframe_canvas = document.getElementById("iframe").contentDocument.createElement('canvas');
iframe_canvas.setAttribute("width", 220);
iframe_canvas.setAttribute("height", 30);
var iframe_ctx = iframe_canvas.getContext('2d');
// copy image from window canvas to iframe ctx
iframe_ctx.drawImage(canvas, 0, 0);
return iframe_canvas.toDataURL();
}
</script>
</body></html>