diff --git a/data/frame.js b/data/frame.js index e3dea49..1fa222a 100644 --- a/data/frame.js +++ b/data/frame.js @@ -8,7 +8,8 @@ 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 {intercept, setExportFunction} = require("../lib/intercept.js"); + setExportFunction(Cu.exportFunction); const {ask} = require("../lib/askForPermission.js"); // Variable to "unload" the script diff --git a/lib/callingStack.js b/lib/callingStack.js index a3d7831..ef1c1b5 100644 --- a/lib/callingStack.js +++ b/lib/callingStack.js @@ -59,7 +59,6 @@ function parseErrorStack(errorStack){ "use strict"; 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 // Eliminate squashed stack. stack may contain 2+ stacks, but why... diff --git a/lib/intercept.js b/lib/intercept.js index 61ec293..609f626 100644 --- a/lib/intercept.js +++ b/lib/intercept.js @@ -10,6 +10,7 @@ setRandomSupply(randomSupplies.nonPersistent); var apiNames = Object.keys(changedFunctions); var undef; + var exportFunction; function setRandomSupplyByType(type){ switch (type){ @@ -38,28 +39,27 @@ } 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}); + var funcStatus = changedFunction.getStatus(this, status); + + if (funcStatus.active){ + if (funcStatus.mode === "ask"){ + funcStatus.mode = ask({window: window, type: changedFunction.type, canvas: this, errorStack: error.stack}); } - switch (status.mode){ + switch (funcStatus.mode){ case "allow": return original; case "fake": setRandomSupplyByType(prefs("rng")); - if (changedFunction.fake){ - notify({url: window.location.href, errorStack: error.stack}, window); - return changedFunction.fake; - } - else { - if (changedFunction.fakeGenerator) { - return changedFunction.fakeGenerator(prefs, function(){ - notify({url: window.location.href, errorStack: error.stack}, window); - }); - } - else { + var fake = changedFunction.fakeGenerator(prefs, function(messageId){ + notify({url: window.location.href, errorStack: error.stack, messageId}); + }); + switch (fake){ + case true: + return original; + case false: return undef; - } + default: + return exportFunction(fake, window.wrappedJSObject); } //case "block": default: @@ -74,4 +74,7 @@ ); }); }; + exports.setExportFunction = function(eFunc){ + exportFunction = eFunc; + }; }()); \ No newline at end of file diff --git a/lib/modifiedAPI.js b/lib/modifiedAPI.js index 3b1c1b9..874c8ea 100644 --- a/lib/modifiedAPI.js +++ b/lib/modifiedAPI.js @@ -7,80 +7,165 @@ var randomSupply = null; - function getFakeCanvas(window, original){ - var context = window.HTMLCanvasElement.prototype.getContext.call(original, "2d"); - var imageData, data, source; - if (context){ - imageData = window.CanvasRenderingContext2D.prototype.getImageData.call(context, 0, 0, original.width, original.height); - source = imageData.data; + function getContext(window, canvas){ + return window.HTMLCanvasElement.prototype.getContext.call(canvas, "2d") || + window.HTMLCanvasElement.prototype.getContext.call(canvas, "webgl") || + window.HTMLCanvasElement.prototype.getContext.call(canvas, "experimental-webgl") || + window.HTMLCanvasElement.prototype.getContext.call(canvas, "webgl2") || + window.HTMLCanvasElement.prototype.getContext.call(canvas, "experimental-webgl2"); + } + function getImageData(window, context){ + if (context instanceof window.CanvasRenderingContext2D){ + return window.CanvasRenderingContext2D.prototype.getImageData.call(context, 0, 0, context.canvas.width, context.canvas.height); } else { - context = - window.HTMLCanvasElement.prototype.getContext.call(original, "webgl") || - window.HTMLCanvasElement.prototype.getContext.call(original, "experimental-webgl") || - window.HTMLCanvasElement.prototype.getContext.call(original, "webgl2") || - window.HTMLCanvasElement.prototype.getContext.call(original, "experimental-webgl2"); - imageData = new window.wrappedJSObject.ImageData(original.width, original.height); - source = new window.wrappedJSObject.Uint8Array(imageData.data.length); + var imageData = new window.wrappedJSObject.ImageData(context.canvas.width, context.canvas.height); window.WebGLRenderingContext.prototype.readPixels.call( context, - 0, 0, original.width, original.height, + 0, 0, context.canvas.width, context.canvas.height, context.RGBA, context.UNSIGNED_BYTE, - source + imageData.data ); + return imageData; } - data = imageData.data; + } + + function getFakeCanvas(window, original){ + var context = getContext(window, original); + var imageData = getImageData(window, context); + var data = imageData.data; var l = data.length; var rng = randomSupply.getRng(l, window); for (var i = 0; i < l; i += 1){ - data[i] = rng(source[i], i); + data[i] = rng(data[i], i); } var canvas = original.cloneNode(true); context = window.HTMLCanvasElement.prototype.getContext.call(canvas, "2d"); context.putImageData(imageData, 0, 0); return canvas; } + function randomMixImageData(window, imageData1, imageData2){ + var data1 = imageData1.data; + var data2 = imageData2.data; + var l = data1.length; + if (l === data2.length){ + var rng = randomSupply.getRng(l, window); + + for (var i = 0; i < l; i += 1){ + if (data1[i] > data2[i]){ + data2[i] = data1[i] - rng(data1[i] - data2[i], i); + } + else if (data1[i] < data2[i]){ + data2[i] = data1[i] + rng(data2[i] - data1[i], i); + } + } + } + return imageData2; + } + function getWindow(canvas){ return canvas.ownerDocument.defaultView; } + function hasType(status, type){ + return status.type.indexOf(type) !== -1; + } exports.setRandomSupply = function(supply){ randomSupply = supply; }; + var canvasContextType = new WeakMap(); // changed functions and their fakes exports.changedFunctions = { getContext: { - type: "context", - object: "HTMLCanvasElement" + getStatus: function(obj, status){ + return { + mode: hasType(status, "block")? "block": "fake", + type: status.type, + active: true + }; + }, + object: "HTMLCanvasElement", + fakeGenerator: function(prefs, notify){ + return function(context, contextAttributes){ + var window = getWindow(this); + canvasContextType.set(this, context); + return window.HTMLCanvasElement.prototype.getContext.apply(this, arguments); + }; + } }, toDataURL: { - type: "readout", + getStatus: function(obj, status){ + if (hasType(status, "input")){ + var contextType = canvasContextType.get(obj); + status.active = contextType && contextType !== "2d"; + } + else { + status.active = hasType(status, "readout"); + } + return status; + }, object: "HTMLCanvasElement", - fake: function toDataURL(){ - var window = getWindow(this); - return window.HTMLCanvasElement.prototype.toDataURL.apply(getFakeCanvas(window, this), arguments); + fakeGenerator: function(prefs, notify){ + return function toDataURL(){ + notify("fakedReadout"); + var window = getWindow(this); + return window.HTMLCanvasElement.prototype.toDataURL.apply(getFakeCanvas(window, this), arguments); + }; } }, toBlob: { - type: "readout", + getStatus: function(obj, status){ + if (hasType(status, "input")){ + var contextType = canvasContextType.get(obj); + status.active = contextType && contextType !== "2d"; + } + else { + status.active = hasType(status, "readout"); + } + return status; + }, object: "HTMLCanvasElement", - fake: function toBlob(callback){ - var window = getWindow(this); - return window.HTMLCanvasElement.prototype.toBlob.apply(getFakeCanvas(window, this), arguments); + fakeGenerator: function(prefs, notify){ + return function toBlob(callback){ + notify("fakedReadout"); + var window = getWindow(this); + return window.HTMLCanvasElement.prototype.toBlob.apply(getFakeCanvas(window, this), arguments); + }; }, exportOptions: {allowCallbacks: true} }, mozGetAsFile: { - type: "readout", + getStatus: function(obj, status){ + if (hasType(status, "input")){ + var contextType = canvasContextType.get(obj); + status.active = contextType && contextType !== "2d"; + } + else { + status.active = hasType(status, "readout"); + } + return status; + }, object: "HTMLCanvasElement", - mozGetAsFile: function mozGetAsFile(callback){ - var window = getWindow(this); - return window.HTMLCanvasElement.prototype.mozGetAsFile.apply(getFakeCanvas(window, this), arguments); + fakeGenerator: function(prefs, notify){ + return function mozGetAsFile(callback){ + notify("fakedReadout"); + var window = getWindow(this); + return window.HTMLCanvasElement.prototype.mozGetAsFile.apply(getFakeCanvas(window, this), arguments); + }; } }, getImageData: { - type: "readout", + getStatus: function(obj, status){ + if (hasType(status, "input")){ + var contextType = canvasContextType.get(obj); + status.active = contextType && contextType !== "2d"; + } + else { + status.active = hasType(status, "readout"); + } + return status; + }, object: "CanvasRenderingContext2D", fakeGenerator: function(prefs, notify){ var maxSize = prefs("maxFakeSize") || Number.POSITIVE_INFINITY; @@ -91,7 +176,7 @@ context = this; } else { - notify(); + notify("fakedReadout"); context = window.HTMLCanvasElement.prototype.getContext.call( getFakeCanvas(window, this.canvas), "2d" @@ -107,14 +192,55 @@ }; } }, + fillText: { + getStatus: function(obj, status){ + status.active = hasType(status, "input"); + return status; + }, + object: "CanvasRenderingContext2D", + fakeGenerator: function(prefs, notify){ + return function fillText(str, x, y){ + notify("fakedInput"); + var window = getWindow(this.canvas); + var oldImageData = getImageData(window, this); + var ret = window.CanvasRenderingContext2D.prototype.fillText.apply(this, arguments); + var newImageData = getImageData(window, this); + this.putImageData(randomMixImageData(window, oldImageData, newImageData), 0, 0); + return ret; + }; + } + }, + strokeText: { + getStatus: function(obj, status){ + status.active = hasType(status, "input"); + return status; + }, + object: "CanvasRenderingContext2D", + fakeGenerator: function(prefs, notify){ + return function strokeText(str, x, y){ + notify("fakedInput"); + var window = getWindow(this.canvas); + var oldImageData = getImageData(window, this); + var ret = window.CanvasRenderingContext2D.prototype.strokeText.apply(this, arguments); + var newImageData = getImageData(window, this); + this.putImageData(randomMixImageData(window, oldImageData, newImageData), 0, 0); + return ret; + }; + } + }, readPixels: { - type: "readout", + getStatus: function(obj, status){ + status.active = hasType(status, "readout") || hasType(status, "input"); + return status; + }, object: "WebGLRenderingContext", - fake: function readPixels(x, y, width, height, format, type, pixels){ - var window = getWindow(this.canvas); - var context = window.HTMLCanvasElement.prototype.getContext.call(getFakeCanvas(window, this.canvas), "webGL"); - return window.WebGLRenderingContext.prototype.readPixels.apply(context, arguments); - + fakeGenerator: function(prefs, notify){ + return function readPixels(x, y, width, height, format, type, pixels){ + notify("fakedReadout"); + var window = getWindow(this.canvas); + var context = window.HTMLCanvasElement.prototype.getContext.call(getFakeCanvas(window, this.canvas), "webGL"); + return window.WebGLRenderingContext.prototype.readPixels.apply(context, arguments); + }; } } }; diff --git a/lib/notifications.js b/lib/notifications.js index 2c16161..98d9dcb 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -8,7 +8,7 @@ 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}){ +exports.notify = function({url, errorStack, messageId}, {lists, notificationPref, _, browser, window}){ "use strict"; var callingStackMsg = parseErrorStack(errorStack); @@ -16,7 +16,7 @@ exports.notify = function({url, errorStack}, {lists, notificationPref, _, browse if (notificationPref.doShow() && !lists.get("ignore").match(contentURL)){ url = contentURL.href; var domain = contentURL.hostname; - var message = _("fakedReadout").replace(/\{url\}/g, domain || url); + var message = _(messageId).replace(/\{url\}/g, domain || url); var tab, tabBrowser; if (browser){