From 1733be23f2378e03fa0bf05baf7075d7ec535a00 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Mon, 7 Aug 2017 21:03:34 +0200 Subject: [PATCH] Added setting "ignoreFrequentColors" --- _locales/de/messages.json | 9 +++++ _locales/en/messages.json | 9 +++++ lib/colorStatistics.js | 80 ++++++++++++++++++++++++++++++++++++++ lib/defaultSettings.js | 1 + lib/modifiedAPI.js | 26 +++++++++---- manifest.json | 1 + options/buildPrefInputs.js | 9 +++++ releaseNotes.txt | 3 ++ 8 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 lib/colorStatistics.js diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 86d7204..d44dfc1 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -184,6 +184,15 @@ "description": "" }, + "ignoreFrequentColors_title": { + "message": "Ignoriere die häufigsten Farben", + "description": "" + }, + "ignoreFrequentColors_description": { + "message": "Anzahl der Farben, die pro Canvas nicht vorgetäuscht werden sollen. Dies ist ein Parameter, der die Detektion des Addons erschweren soll.\nACHTUNG: Dies kann die Geschwindigkeit des Addons beeinträchtigen, da für jedes Bild die Farbstatistik berechnet werden muss. Außerdem kann es die Sicherheit des Addons verringern, wenn der Wert zu hoch gestellt wird. Deswegen wird stark empfohlen, diesen Wert nicht über 3 zu setzen.", + "description": "" + }, + "disableNotifications": { "message": "Benachrichtigungen deaktivieren", "description": "" diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 040160f..117b9b0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -183,6 +183,15 @@ "message": "Clear", "description": "" }, + + "ignoreFrequentColors_title": { + "message": "Ignore the most frequent colors", + "description": "" + }, + "ignoreFrequentColors_description": { + "message": "Number of colors that should not be faked per canvas. This is a parameter to prevent detection.\nCAUTION: This can reduce the performance of the addon because the color statistic has to be calculated for every image. Additional this can lower the safety of the addon, therefore it is highly recommended not to set this value above 3.", + "description": "" + }, "disableNotifications": { "message": "disable notifications", diff --git a/lib/colorStatistics.js b/lib/colorStatistics.js new file mode 100644 index 0000000..513ccc0 --- /dev/null +++ b/lib/colorStatistics.js @@ -0,0 +1,80 @@ +/* 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"; + + + var scope; + if ((typeof exports) !== "undefined"){ + scope = exports; + } + else { + window.scope.colorStatistics = {}; + scope = window.scope.colorStatistics; + } + + class Statistic{ + constructor(){ + this.colors = Object.create(null); + this.numberOfColors = 0; + + this.minBoundary = {count: Number.NEGATIVE_INFINITY}; + this.maxBoundary = {count: Number.POSITIVE_INFINITY, previousColor: this.minBoundary}; + this.minBoundary.nextColor = this.maxBoundary; + } + addColor(r, g, b, a){ + var index = String.fromCharCode(r, g, b, a); + var color = this.colors[index]; + if (!color){ + color = {index, color: [r, g, b, a], count: 0, previousColor: this.minBoundary, nextColor: this.minBoundary.nextColor}; + this.numberOfColors += 1; + this.minBoundary.nextColor = color; + color.nextColor.previousColor = color; + this.colors[index] = color; + } + color.count += 1; + if (color.count > color.nextColor.count){ + // swap colors to remain in right order + // a -> b -> c -> d becomes a -> c -> b -> d + var a = color.previousColor; + var b = color; + var c = color.nextColor; + var d = color.nextColor.nextColor; + + a.nextColor = c; + c.previousColor = a; + + c.nextColor = b; + b.previousColor = c; + + b.nextColor = d; + d.previousColor = b; + } + } + getMaxColors(n){ + var n = Math.min(n, this.numberOfColors); + var colors = Object.create(null); + var current = this.maxBoundary; + for (;n && current;n -= 1){ + current = current.previousColor; + colors[current.index] = current; + } + return colors; + } + } + + scope.compute = function computeColorStatistics(rawData){ + var statistic = new Statistic(); + for (var i = 0, l = rawData.length; i < l; i += 4){ + statistic.addColor( + rawData[i + 0], + rawData[i + 1], + rawData[i + 2], + rawData[i + 3] + ); + } + return statistic; + } +}()); \ No newline at end of file diff --git a/lib/defaultSettings.js b/lib/defaultSettings.js index 2950937..4bc64c5 100644 --- a/lib/defaultSettings.js +++ b/lib/defaultSettings.js @@ -8,6 +8,7 @@ var settings = { minFakeSize: 1, maxFakeSize: 0, rng: "constant", + ignoreFrequentColors: 0, persistentRndStorage: "", storePersistentRnd: false, askOnlyOnce: true, diff --git a/lib/modifiedAPI.js b/lib/modifiedAPI.js index 494e3e1..0be8e0f 100644 --- a/lib/modifiedAPI.js +++ b/lib/modifiedAPI.js @@ -14,6 +14,8 @@ scope = window.scope.modifiedAPI; } + const colorStatistics = require("./colorStatistics"); + // let Cu = require("chrome").Cu; var randomSupply = null; @@ -52,7 +54,7 @@ }; } - function getFakeCanvas(window, original){ + function getFakeCanvas(window, original, prefs){ try { // original may not be a canvas -> we must not leak an error var context = getContext(window, original); @@ -61,6 +63,11 @@ var l = desc.length; var ignoredColors = {}; + if (prefs("ignoreFrequentColors")){ + var statistic = colorStatistics.compute(source); + ignoredColors = statistic.getMaxColors(prefs("ignoreFrequentColors")); + } + var rng = randomSupply.getPixelRng(l, window, ignoredColors); for (var i = 0; i < l; i += 4){ @@ -175,7 +182,7 @@ return function toDataURL(){ if (canvasSizeShouldBeFaked(this, prefs)){ notify.call(this, "fakedReadout"); - return original.apply(getFakeCanvas(window, this), window.Array.from(arguments)); + return original.apply(getFakeCanvas(window, this, prefs), window.Array.from(arguments)); } else { return original.apply(this, window.Array.from(arguments)); @@ -201,7 +208,7 @@ return function toBlob(callback){ if (canvasSizeShouldBeFaked(this, prefs)){ notify.call(this, "fakedReadout"); - return original.apply(getFakeCanvas(window, this), window.Array.from(arguments)); + return original.apply(getFakeCanvas(window, this, prefs), window.Array.from(arguments)); } else { return original.apply(this, window.Array.from(arguments)); @@ -228,7 +235,7 @@ return function mozGetAsFile(callback){ if (canvasSizeShouldBeFaked(this, prefs)){ notify.call(this, "fakedReadout"); - return original.apply(getFakeCanvas(window, this), window.Array.from(arguments)); + return original.apply(getFakeCanvas(window, this, prefs), window.Array.from(arguments)); } else { return original.apply(this, window.Array.from(arguments)); @@ -257,11 +264,11 @@ var fakeCanvas; var context = this; if (this && this.canvas) { - fakeCanvas = getFakeCanvas(window, this.canvas); + fakeCanvas = getFakeCanvas(window, this.canvas, prefs); } if (fakeCanvas && fakeCanvas !== this.canvas){ context = window.HTMLCanvasElement.prototype.getContext.call( - getFakeCanvas(window, this.canvas), + fakeCanvas, "2d" ); } @@ -349,7 +356,12 @@ var xPixels = pixels; var ret = original.apply(this, window.Array.from(arguments)); var l = xPixels.length; - var rng = randomSupply.getPixelRng(l, window, {}); + var ignoredColors = {}; + if (prefs("ignoreFrequentColors")){ + var statistic = colorStatistics.compute(pixels); + ignoredColors = statistic.getMaxColors(prefs("ignoreFrequentColors")); + } + var rng = randomSupply.getPixelRng(l, window, ignoredColors); for (var i = 0; i < l; i += 4){ var [r, g, b, a] = rng( diff --git a/manifest.json b/manifest.json index 4ea4e2c..8ed3eef 100644 --- a/manifest.json +++ b/manifest.json @@ -25,6 +25,7 @@ "lib/logging.js", + "lib/colorStatistics.js", "lib/modifiedAPI.js", "lib/randomSupplies.js", "lib/intercept.js", diff --git a/options/buildPrefInputs.js b/options/buildPrefInputs.js index 58d989a..9883436 100644 --- a/options/buildPrefInputs.js +++ b/options/buildPrefInputs.js @@ -130,6 +130,15 @@ document.body.appendChild(table); "rng": ["persistent"] } }, + { + "name": "ignoreFrequentColors", + "title": "Ignore most frequent colors", + "type": "integer", + "value": 0, + "displayDependencies": { + "blockMode": ["fakeReadout"] + } + }, { "name": "askOnlyOnce", "title": "Ask only once", diff --git a/releaseNotes.txt b/releaseNotes.txt index a78bad1..1f7a0cf 100644 --- a/releaseNotes.txt +++ b/releaseNotes.txt @@ -6,6 +6,7 @@ Version 0.4.0: - switched to webExtension - notifications are now done via page action - minimal and maximal fake size are now respected in all fakeable functions + - fake readout now fakes one pixel at once and no longer one single channel new features: - information of all fake events in one tab are visible @@ -13,6 +14,8 @@ Version 0.4.0: - new preferences: * minimal fake size * setting to enable the inspection of the content of the faked canvas + * new random number generator "constant" + * setting to not fake the most frequent colors in a canvas fixes: - ask mode did not work for input types