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

Decoupled block mode from protected part

Fixes #287
This commit is contained in:
kkapsner 2018-10-23 08:26:23 +02:00
parent 5932ac2292
commit 8b9197a68a
17 changed files with 356 additions and 211 deletions

View File

@ -1,19 +1,32 @@
Dieses Add-on ermöglicht es Nutzern, Webseiten davon abzuhalten, sie über die Javascript canvas-API zu identifizieren. Nutzer können auswählen, ob die canvas-API komplett auf bestimmten oder allen Seiten blockiert wird (dies wird die Funktionalität einiger Seiten beeinträchtigen) oder nur die identifikationsfreundliche Auslese-API zu blockieren oder dort falsche Werte vorzutäuschen. Nähere Informationen zum canvas-fingerprinting können Sie auf http://www.browserleaks.com/canvas finden. Dieses Add-on ermöglicht es Nutzern, Webseiten davon abzuhalten, sie über Javascript APIs zu identifizieren. Nutzer können auswählen, ob die APIs komplett auf bestimmten oder allen Seiten blockiert werden (dies wird die Funktionalität einiger Seiten beeinträchtigen) oder bei den identifikationsfreundlichen Auslese-Funktionen falsche Werte vorzutäuschen.
Nähere Informationen zum Fingerprinting können Sie finden auf:
<ul>
<li>&lt;canvas&gt;: http://www.browserleaks.com/canvas</li>
<li>audio:
<ul>
<li>https://audiofingerprint.openwpm.com/ (sehr schlecht geschrieben = langsam)</li>
<li>https://webtransparency.cs.princeton.edu/webcensus/#audio-fp</li>
</ul>
</li>
<li>DOMRect:
<ul>
<li>http://jcarlosnorte.com/security/2016/03/06/advanced-tor-browser-fingerprinting.html</li>
<li>https://browserleaks.com/rects</li>
</ul>
</li>
</ul>
Die verschiedenen Blockiermodi sind: Die verschiedenen Blockiermodi sind:
<ul> <ul>
<li>Auslese-API blockieren: Alle Webseiten, die nicht auf der Whitelist oder Blacklist gelistet sind, können die canvas-API zur Darstellung verwenden, aber die Auslese-API darf nicht verwendet werden. Dies kann das korrekte Funktionieren von Webseiten wie z.B. Google Maps verhindern.</li> <li>Auslese-API vortäuschen: Standardeinstellung und mein Favorit! Alle Webseiten, die nicht auf der Whitelist oder Blacklist gelistet sind, können die beschützten APIs zur Darstellung verwenden. Werte, die über die APIs zurückgegeben werden, werde so verändert, dass kein konsistenter Fingerprinting erstellt werden kann.</li>
<li>Auslese-API vortäuschen: Standardeinstellung und mein Favorit! Alle Webseiten, die nicht auf der Whitelist oder Blacklist gelistet sind, können die canvas-API zur Darstellung verwenden, aber die Auslese-API gibt zufällige Werte zurück, sodass das Fingerprinting immer einen anderen Wert liefert.</li> <li>um Erlaubnis fragen: Wenn eine Seite weder auf der Whitelist noch auf der Blacklist gelistet ist, wird der Nutzer gefragt, ob die Webseite die beschützten APIs verwenden darf, wenn sie benutzt wird.</li>
<li>Bei Ausgabe vortäuschen: Bei der Darstellung von Text werden die beschriebenen Pixel leicht verändert. Dies erschwert die Detektion des Add-ons, aber ist weniger sicher. Bei WebGL-Canvas ist das Verhalten identisch zu "Auslese-API vortäuschen".</li> <li>alles blockieren: Ignoriert alle Listen und blockiert die beschützten APIs auf allen Webseiten.</li>
<li>bei Auslese-API um Erlaubnis fragen: Alle Webseiten, die nicht auf der Whitelist oder Blacklist gelistet sind, können die canvas-API zur Darstellung verwenden, aber der Nutzer wird jedes Mal um Erlaubnis gefragt, wenn die Webseite die Readout-API verwenden möchte.</li> <li>nur Einträge der Whitelist erlauben: Nur Seiten, die in der Whitelist gelistet sind, dürfen die beschützten APIs verwenden.</li>
<li>alles blockieren: Ignoriert alle Listen und blockiert die canvas-API auf allen Webseiten.</li> <li>nur Einträge der Blacklist blockieren: Blockiere die beschützten APIs nur auf den Seiten der Blacklist.</li>
<li>nur Einträge der Whitelist erlauben: Nur Seiten, die in der Whitelist gelistet sind, dürfen die canvas-API verwenden.</li> <li>alles erlauben: Ignoriere alle Listen und erlaube die beschützten APIs auf allen Webseiten.</li>
<li>um Erlaubnis fragen: Wenn eine Seite weder auf der Whitelist noch auf der Blacklist gelistet ist, wird der Nutzer gefragt, ob die Webseite die canvas-API verwenden darf, wenn sie benutzt wird.</li> </ul>
<li>nur Einträge der Blacklist blockieren: Blockiere die canvas-API nur auf den Seiten der Blacklist.</li>
<li>alles erlauben: Ignoriere alle Listen und erlaube die canvas-API auf allen Webseiten.</li>
</ul>
Geschützte "Fingerprinting"-APIs: Beschützte "Fingerprinting"-APIs:
<ul> <ul>
<li>canvas 2d</li> <li>canvas 2d</li>
<li>webGL</li> <li>webGL</li>

View File

@ -1,17 +1,31 @@
This add-on allows users to prevent websites from using the Javascript canvas API to fingerprint them. Users can choose to block the canvas API entirely on some or all websites (which may break some websites) or just block or fake its fingerprinting-friendly readout API. More information on canvas fingerprinting can be found at http://www.browserleaks.com/canvas. This add-on allows users to prevent websites from using the some Javascript APIs to fingerprint them. Users can choose to block the APIs entirely on some or all websites (which may break some websites) or fake its fingerprinting-friendly readout API.
More information on fingerprinting can be found at:
<ul>
<li> &lt;canvas&gt;: http://www.browserleaks.com/canvas</li>
<li> audio:
<ul>
<li>https://audiofingerprint.openwpm.com/ (very poorly written = slow)</li>
<li>https://webtransparency.cs.princeton.edu/webcensus/#audio-fp</li>
</ul>
</li>
<li> DOMRect:
<ul>
<li>http://jcarlosnorte.com/security/2016/03/06/advanced-tor-browser-fingerprinting.html</li>
<li>https://browserleaks.com/rects</li>
</ul>
</li>
</ul>
The different block modes are: The different block modes are:
<ul> <ul>
<li>block readout API: All websites not on the white list or black list can use the canvas API to display something on the page, but the readout API is not allowed to return values to the website. This may break websites like Google Maps.</li> <li> fake: Canvas Blocker's default setting, and my favorite! All websites not on the white list or black list can use the protected APIs. But values obtained by the APIs are altered so that a consistent fingerprinting is not possible</li>
<li>fake readout API: Canvas Blocker's default setting, and my favorite! All websites not on the white list or black list can use the canvas API to display something on the page, but the readout API is forced to return a new random value each time it is called.</li> <li> ask for permission: If a website is not listed on the white list or black list, the user will be asked if the website should be allowed to use the protected APIs each time they are called.</li>
<li>fake at input: on display of text the drawn pixels get modified slightly. This makes the detection of the add-on harder but is less secure. On WebGL-canvas the behaviour is identical to "fake readout API".</li> <li> block everything: Ignore all lists and block the protected APIs on all websites.</li>
<li>ask for readout API permission: All websites not on the white list or black list can use the canvas API to display something on the page, but the user will be asked if the website should be allowed to use the readout API each time it is called.</li> <li> allow only white list: Only websites in the white list are allowed to use the protected APIs.</li>
<li>block everything: Ignore all lists and block the canvas API on all websites.</li> <li> block only black list: Block the protected APIs only for websites on the black list.</li>
<li>allow only white list: Only websites in the white list are allowed to use the canvas API.</li> <li> allow everything: Ignore all lists and allow the protected APIs on all websites.</li>
<li>ask for permission: If a website is not listed on the white list or black list, the user will be asked if the website should be allowed to use the canvas API each time it is called.</li> </ul>
<li>block only black list: Block the canvas API only for websites on the black list.</li>
<li>allow everything: Ignore all lists and allow the canvas API on all websites.</li>
</ul>
Protected "fingerprinting" APIs: Protected "fingerprinting" APIs:
<ul> <ul>

View File

@ -1,17 +1,21 @@
This add-on allows users to prevent websites from using the Javascript &lt;canvas&gt; API to fingerprint them. Users can choose to block the &lt;canvas&gt; API entirely on some or all websites (which may break some websites) or just block or fake its fingerprinting-friendly readout API. More information on &lt;canvas&gt; fingerprinting can be found at http://www.browserleaks.com/canvas. This add-on allows users to prevent websites from using the some Javascript APIs to fingerprint them. Users can choose to block the APIs entirely on some or all websites (which may break some websites) or just block or fake its fingerprinting-friendly readout API.
More information on fingerprinting can be found at:
* &lt;canvas&gt;: http://www.browserleaks.com/canvas
* audio:
* https://audiofingerprint.openwpm.com/ (very poorly written = slow)
* https://webtransparency.cs.princeton.edu/webcensus/#audio-fp
* DOMRect:
* http://jcarlosnorte.com/security/2016/03/06/advanced-tor-browser-fingerprinting.html
* https://browserleaks.com/rects
The different block modes are: The different block modes are:
<ul> * fake: Canvas Blocker's default setting, and my favorite! All websites not on the white list or black list can use the protected APIs. But values obtained by the APIs are altered so that a consistent fingerprinting is not possible
<li>block readout API: All websites not on the white list or black list can use the &lt;canvas&gt; API to display something on the page, but the readout API is not allowed to return values to the website.</li> * ask for permission: If a website is not listed on the white list or black list, the user will be asked if the website should be allowed to use the protected APIs each time they are called.
<li>fake readout API: Canvas Blocker's default setting, and my favorite! All websites not on the white list or black list can use the &lt;canvas&gt; API to display something on the page, but the readout API is forced to return a new random value each time it is called.</li> * block everything: Ignore all lists and block the protected APIs on all websites.
<li>fake at input: on display of text the drawn pixels get modified slightly. This makes the detection of the add-on harder but is less secure. On WebGL-canvas the behaviour is identical to "fake readout API".</li> * allow only white list: Only websites in the white list are allowed to use the protected APIs.
<li>ask for readout API permission: All websites not on the white list or black list can use the &lt;canvas&gt; API to display something on the page, but the user will be asked if the website should be allowed to use the readout API each time it is called.</li> * block only black list: Block the protected APIs only for websites on the black list.
<li>block everything: Ignore all lists and block the &lt;canvas&gt; API on all websites.</li> * allow everything: Ignore all lists and allow the protected APIs on all websites.
<li>allow only white list: Only websites in the white list are allowed to use the &lt;canvas&gt; API.</li>
<li>ask for permission: If a website is not listed on the white list or black list, the user will be asked if the website should be allowed to use the &lt;canvas&gt; API each time it is called.</li>
<li>block only black list: Block the &lt;canvas&gt; API only for websites on the black list.</li>
<li>allow everything: Ignore all lists and allow the &lt;canvas&gt; API on all websites.</li>
</ul>
Protected "fingerprinting" APIs: Protected "fingerprinting" APIs:
* canvas 2d * canvas 2d

View File

@ -303,10 +303,6 @@
"message": "um Erlaubnis fragen", "message": "um Erlaubnis fragen",
"description": "" "description": ""
}, },
"blockMode_options.askReadout": {
"message": "bei Auslese-API um Erlaubnis fragen",
"description": ""
},
"blockMode_options.blockEverything": { "blockMode_options.blockEverything": {
"message": "alles blockieren", "message": "alles blockieren",
"description": "" "description": ""
@ -315,22 +311,34 @@
"message": "nur Einträge der Blacklist blockieren", "message": "nur Einträge der Blacklist blockieren",
"description": "" "description": ""
}, },
"blockMode_options.blockReadout": { "blockMode_options.fake": {
"message": "Auslese-API blockieren", "message": "vortäuschen",
"description": ""
},
"blockMode_options.fakeReadout": {
"message": "Auslese-API vortäuschen",
"description": ""
},
"blockMode_options.fakeInput": {
"message": "Bei Ausgabe vortäuschen",
"description": "" "description": ""
}, },
"blockMode_title": { "blockMode_title": {
"message": "Blockiermodus", "message": "Blockiermodus",
"description": "" "description": ""
}, },
"protectedCanvasPart_title": {
"message": "Beschützer Teil der Canvas-API",
"description": ""
},
"protectedCanvasPart_description": {
"message": "Auslese: die Auslese-Funktionen der Canvas-API werden beschützt.\n\nEingabe: die Ausgabe-Funktionen der Canvas-API werden beschützt. Mit dem Blockiermodus \"vortäuschen\" wird bei der Darstellung von Text die beschriebenen Pixel leicht verändert. Dies erschwert die Detektion des Add-ons, ist aber weniger sicher. Bei WebGL-Canvas ist das Verhalten identisch zu \"Auslese\".\n\nAlles: sowohl Auslese- als auch Eingabe-Funktionen werden beschützt. Es wird empfohlen diese nicht mit dem Blockiermodus \"vortäuschen\" zu verwenden, da es die Wahrscheinlichkeit erhöht detektiert zu werden.",
"description": ""
},
"protectedCanvasPart_options.input": {
"message": "Eingabe",
"description": ""
},
"protectedCanvasPart_options.readout": {
"message": "Auslese",
"description": ""
},
"protectedCanvasPart_options.everything": {
"message": "Alles",
"description": ""
},
"urlSettings_title": { "urlSettings_title": {
"message": "Seitenspezifische Werte", "message": "Seitenspezifische Werte",
"description": "" "description": ""
@ -1123,8 +1131,8 @@
"message": "Es werden die \"vortäuschen\" und \"fragen\" Blockiermodi empfohlen.", "message": "Es werden die \"vortäuschen\" und \"fragen\" Blockiermodi empfohlen.",
"description": "" "description": ""
}, },
"sanitation_resolution.switchToFakeReadout": { "sanitation_resolution.switchToFake": {
"message": "zu \"Auslese vortäuschen\" wechseln", "message": "zu \"vortäuschen\" wechseln",
"description": "" "description": ""
}, },
"sanitation_error.blockModeVsProtection": { "sanitation_error.blockModeVsProtection": {
@ -1136,13 +1144,25 @@
"description": "" "description": ""
}, },
"sanitation_error.fakeInputWithWhiteRng": { "sanitation_error.fakeInputWithWhiteRng": {
"message": "Der weiße Zufallszahlengenerator soll nicht mit \"{blockMode}\" verwendet werden.", "message": "Der weiße Zufallszahlengenerator soll nicht mit \"{blockMode}\" verwendet werden, wenn \"{protectedCanvasPart}\" beschützt wird.",
"description": ""
},
"sanitation_resolution.switchToProtectReadout": {
"message": "\"Auslese\" beschützen",
"description": "" "description": ""
}, },
"sanitation_resolution.switchToNonPersistentRng": { "sanitation_resolution.switchToNonPersistentRng": {
"message": "wechsle zu \"nicht persistent\"", "message": "wechsle zu \"nicht persistent\"",
"description": "" "description": ""
}, },
"sanitation_error.fakeEverythingInCanvas": {
"message": "Beschützen Sie nicht \"{protectedCanvasPart}\" im Blockiermodus \"{blockMode}\".",
"description": ""
},
"sanitation_resolution.switchToProtectInput": {
"message": "\"Eingabe\" beschützen",
"description": ""
},
"sanitation_error.valueTooLow": { "sanitation_error.valueTooLow": {
"message": "\"{setting}\" soll nicht niedriger als {value} sein.", "message": "\"{setting}\" soll nicht niedriger als {value} sein.",
"description": "" "description": ""

View File

@ -317,10 +317,6 @@
"message": "ask for permission", "message": "ask for permission",
"description": "" "description": ""
}, },
"blockMode_options.askReadout": {
"message": "ask for readout API permission",
"description": ""
},
"blockMode_options.blockEverything": { "blockMode_options.blockEverything": {
"message": "block everything", "message": "block everything",
"description": "" "description": ""
@ -329,16 +325,8 @@
"message": "block only blacklist", "message": "block only blacklist",
"description": "" "description": ""
}, },
"blockMode_options.blockReadout": { "blockMode_options.fake": {
"message": "block readout API", "message": "fake",
"description": ""
},
"blockMode_options.fakeReadout": {
"message": "fake readout API",
"description": ""
},
"blockMode_options.fakeInput": {
"message": "fake at input",
"description": "" "description": ""
}, },
"blockMode_title": { "blockMode_title": {
@ -346,6 +334,27 @@
"description": "" "description": ""
}, },
"protectedCanvasPart_title": {
"message": "Protected part of the canvas API",
"description": ""
},
"protectedCanvasPart_description": {
"message": "readout: the readout features of the canvas API are protected.\n\ninput: the input features of the canvas API are protected. With fake blocking mode on display of text the drawn pixels get modified slightly. This makes the detection of the add-on harder but is less secure. On WebGL-canvas the behaviour is identical to \"readout\".\n\neverything: both the readout and input features are protected. It's not recommended to use this with \"fake\" block mode as it increases the probability to be detected.",
"description": ""
},
"protectedCanvasPart_options.input": {
"message": "input",
"description": ""
},
"protectedCanvasPart_options.readout": {
"message": "readout",
"description": ""
},
"protectedCanvasPart_options.everything": {
"message": "everything",
"description": ""
},
"urlSettings_title": { "urlSettings_title": {
"message": "Site specific values", "message": "Site specific values",
"description": "" "description": ""
@ -1174,8 +1183,8 @@
"message": "It is recommended to use the \"fake\" or \"ask\" blocking modes.", "message": "It is recommended to use the \"fake\" or \"ask\" blocking modes.",
"description": "" "description": ""
}, },
"sanitation_resolution.switchToFakeReadout": { "sanitation_resolution.switchToFake": {
"message": "switch to \"fake readout\"", "message": "switch to \"fake\"",
"description": "" "description": ""
}, },
"sanitation_error.blockModeVsProtection": { "sanitation_error.blockModeVsProtection": {
@ -1187,13 +1196,25 @@
"description": "" "description": ""
}, },
"sanitation_error.fakeInputWithWhiteRng": { "sanitation_error.fakeInputWithWhiteRng": {
"message": "Do not use white random number generator with \"{blockMode}\".", "message": "Do not use white random number generator with \"{blockMode}\" and protecting \"{protectedCanvasPart}\".",
"description": ""
},
"sanitation_resolution.switchToProtectReadout": {
"message": "switch to protect readout",
"description": "" "description": ""
}, },
"sanitation_resolution.switchToNonPersistentRng": { "sanitation_resolution.switchToNonPersistentRng": {
"message": "switch to \"non persistent\" rng", "message": "switch to \"non persistent\" rng",
"description": "" "description": ""
}, },
"sanitation_error.fakeEverythingInCanvas": {
"message": "Do not use protect \"{protectedCanvasPart}\" in blocking mode \"{blockMode}\".",
"description": ""
},
"sanitation_resolution.switchToProtectInput": {
"message": "switch to protect input",
"description": ""
},
"sanitation_error.valueTooLow": { "sanitation_error.valueTooLow": {
"message": "\"{setting}\" should not be lower than {value}.", "message": "\"{setting}\" should not be lower than {value}.",
"description": "" "description": ""

View File

@ -21,21 +21,19 @@
scope.check = function check({url, errorStack}){ scope.check = function check({url, errorStack}){
url = new URL(url || "about:blank"); url = new URL(url || "about:blank");
var match = checkBoth(errorStack, url, settings.get("blockMode", url)).match( var match = checkBoth(errorStack, url, settings.get("blockMode", url)).match(
/^(block|allow|fake|ask)(|Readout|Everything|Context|Input|Internal)$/ /^(block|allow|fake|ask)(|Everything|Internal)$/
); );
if (match){ if (match){
return { return {
url: url, url: url,
type: (match[2] === "Everything" || match[2] === "")? internal: match[2] === "Internal",
["context", "readout", "input"]:
[match[2].toLowerCase()],
mode: match[1] mode: match[1]
}; };
} }
else { else {
return { return {
url: url, url: url,
type: ["context", "readout", "input"], internal: false,
mode: "block" mode: "block"
}; };
} }
@ -72,21 +70,9 @@
mode = "block"; mode = "block";
break; break;
case "block": case "block":
case "blockContext":
case "blockReadout":
case "blockInput":
case "ask": case "ask":
case "askContext":
case "askReadout":
case "askInput":
case "fake": case "fake":
case "fakeContext":
case "fakeReadout":
case "fakeInput":
case "allow": case "allow":
case "allowContext":
case "allowReadout":
case "allowInput":
if (url && lists.get("white").match(url)){ if (url && lists.get("white").match(url)){
mode = "allow"; mode = "allow";
} }

View File

@ -17,7 +17,7 @@
const logging = require("./logging"); const logging = require("./logging");
const {copyCanvasToWebgl} = require("./webgl"); const {copyCanvasToWebgl} = require("./webgl");
const getWrapped = require("sdk/getWrapped"); const getWrapped = require("sdk/getWrapped");
const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); const {checkerWrapper} = require("./modifiedAPIFunctions");
const modifiedAudioAPI = require("./modifiedAudioAPI"); const modifiedAudioAPI = require("./modifiedAudioAPI");
const modifiedDOMRectAPI = require("./modifiedDOMRectAPI"); const modifiedDOMRectAPI = require("./modifiedDOMRectAPI");
@ -153,6 +153,27 @@
} }
} }
function getProtectedPartChecker(pref, url){
const protectedPart = pref("protectedCanvasPart", url);
if (protectedPart === "everything"){
return function(){
return true;
};
}
else {
return function(parts){
if (Array.isArray(parts)){
return parts.some(function(part){
return part === protectedPart;
});
}
else {
return parts === protectedPart;
}
};
}
}
scope.setRandomSupply = function(supply){ scope.setRandomSupply = function(supply){
randomSupply = supply; randomSupply = supply;
modifiedAudioAPI.setRandomSupply(supply); modifiedAudioAPI.setRandomSupply(supply);
@ -163,15 +184,15 @@
scope.changedFunctions = { scope.changedFunctions = {
getContext: { getContext: {
type: "context", type: "context",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
if (hasType(status, "internal")){ if (status.internal){
return { return {
mode: "allow", mode: "allow",
type: status.type, type: status.type,
active: false active: false
}; };
} }
else if (hasType(status, "context") || hasType(status, "input")){ else if (getProtectedPartChecker(prefs, status.url)("input")){
return { return {
mode: status.mode, mode: status.mode,
type: status.type, type: status.type,
@ -197,10 +218,11 @@
}, },
toDataURL: { toDataURL: {
type: "readout", type: "readout",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "readout"); status.active = protectedPartChecker("readout");
if (!status.active && hasType(status, "input")){ if (!status.active && protectedPartChecker("input")){
var contextType = canvasContextType.get(obj); var contextType = canvasContextType.get(obj);
status.active = contextType !== "2d"; status.active = contextType !== "2d";
} }
@ -227,10 +249,11 @@
}, },
toBlob: { toBlob: {
type: "readout", type: "readout",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "readout"); status.active = protectedPartChecker("readout");
if (!status.active && hasType(status, "input")){ if (!status.active && protectedPartChecker("input")){
var contextType = canvasContextType.get(obj); var contextType = canvasContextType.get(obj);
status.active = contextType !== "2d"; status.active = contextType !== "2d";
} }
@ -258,10 +281,11 @@
}, },
mozGetAsFile: { mozGetAsFile: {
type: "readout", type: "readout",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "readout"); status.active = protectedPartChecker("readout");
if (!status.active && hasType(status, "input")){ if (!status.active && protectedPartChecker("input")){
var contextType = canvasContextType.get(obj); var contextType = canvasContextType.get(obj);
status.active = contextType !== "2d"; status.active = contextType !== "2d";
} }
@ -288,9 +312,10 @@
}, },
getImageData: { getImageData: {
type: "readout", type: "readout",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "readout"); status.active = protectedPartChecker("readout");
return status; return status;
}, },
object: "CanvasRenderingContext2D", object: "CanvasRenderingContext2D",
@ -322,9 +347,10 @@
}, },
isPointInPath: { isPointInPath: {
type: "readout", type: "readout",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "readout"); status.active = protectedPartChecker("readout");
return status; return status;
}, },
object: "CanvasRenderingContext2D", object: "CanvasRenderingContext2D",
@ -348,9 +374,10 @@
}, },
isPointInStroke: { isPointInStroke: {
type: "readout", type: "readout",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "readout"); status.active = protectedPartChecker("readout");
return status; return status;
}, },
object: "CanvasRenderingContext2D", object: "CanvasRenderingContext2D",
@ -374,9 +401,10 @@
}, },
fillText: { fillText: {
type: "input", type: "input",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "input"); status.active = protectedPartChecker("input");
return status; return status;
}, },
object: "CanvasRenderingContext2D", object: "CanvasRenderingContext2D",
@ -409,9 +437,10 @@
}, },
strokeText: { strokeText: {
type: "input", type: "input",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "input"); status.active = protectedPartChecker("input");
return status; return status;
}, },
object: "CanvasRenderingContext2D", object: "CanvasRenderingContext2D",
@ -444,9 +473,10 @@
}, },
readPixels: { readPixels: {
type: "readout", type: "readout",
getStatus: function(obj, status){ getStatus: function(obj, status, prefs){
const protectedPartChecker = getProtectedPartChecker(prefs, status.url);
status = Object.create(status); status = Object.create(status);
status.active = hasType(status, "readout") || hasType(status, "input"); status.active = protectedPartChecker(["readout", "input"]);
return status; return status;
}, },
object: ["WebGLRenderingContext", "WebGL2RenderingContext"], object: ["WebGLRenderingContext", "WebGL2RenderingContext"],

View File

@ -13,12 +13,6 @@
scope = window.scope.modifiedAPIFunctions; scope = window.scope.modifiedAPIFunctions;
} }
scope.hasType = function hasType(status, type){
return status.type.indexOf(type) !== -1;
};
scope.checkerWrapper = function checkerWrapper(checker, object, args, callback){ scope.checkerWrapper = function checkerWrapper(checker, object, args, callback){
const check = checker.call(object); const check = checker.call(object);
if (check.allow){ if (check.allow){

View File

@ -16,7 +16,7 @@
const logging = require("./logging"); const logging = require("./logging");
const {sha256String: hashing} = require("./hash"); const {sha256String: hashing} = require("./hash");
const getWrapped = require("sdk/getWrapped"); const getWrapped = require("sdk/getWrapped");
const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); const {checkerWrapper} = require("./modifiedAPIFunctions");
var randomSupply = null; var randomSupply = null;
@ -150,7 +150,7 @@
function getStatus(obj, status, prefs){ function getStatus(obj, status, prefs){
status = Object.create(status); status = Object.create(status);
status.active = prefs("protectAudio", status.url) && hasType(status, "readout"); status.active = prefs("protectAudio", status.url);
return status; return status;
} }

View File

@ -13,7 +13,7 @@
scope = window.scope.modifiedDOMRectAPI; scope = window.scope.modifiedDOMRectAPI;
} }
const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); const {checkerWrapper} = require("./modifiedAPIFunctions");
const {byteArrayToString: hash} = require("./hash"); const {byteArrayToString: hash} = require("./hash");
const getWrapped = require("sdk/getWrapped"); const getWrapped = require("sdk/getWrapped");
@ -424,7 +424,7 @@
function getStatus(obj, status, prefs){ function getStatus(obj, status, prefs){
status = Object.create(status); status = Object.create(status);
status.active = prefs("protectDOMRect", status.url) && hasType(status, "readout"); status.active = prefs("protectDOMRect", status.url);
return status; return status;
} }

View File

@ -13,7 +13,7 @@
scope = window.scope.modifiedHistoryAPI; scope = window.scope.modifiedHistoryAPI;
} }
const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); const {checkerWrapper} = require("./modifiedAPIFunctions");
scope.changedGetters = [ scope.changedGetters = [
{ {
@ -43,9 +43,7 @@
function getStatus(obj, status){ function getStatus(obj, status){
status = Object.create(status); return Object.create(status);
status.active = hasType(status, "readout");
return status;
} }
scope.changedGetters.forEach(function(changedGetter){ scope.changedGetters.forEach(function(changedGetter){

View File

@ -13,7 +13,7 @@
scope = window.scope.modifiedWindowAPI; scope = window.scope.modifiedWindowAPI;
} }
const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); const {checkerWrapper} = require("./modifiedAPIFunctions");
const windowNames = new WeakMap(); const windowNames = new WeakMap();
scope.changedGetters = [ scope.changedGetters = [
@ -75,7 +75,7 @@
function getStatus(obj, status, prefs){ function getStatus(obj, status, prefs){
status = Object.create(status); status = Object.create(status);
status.active = prefs("protectWindow", status.url) && hasType(status, "readout"); status.active = prefs("protectWindow", status.url);
return status; return status;
} }

View File

@ -58,11 +58,19 @@
}, },
{ {
name: "blockMode", name: "blockMode",
defaultValue: "fakeReadout", defaultValue: "fake",
urlSpecific: true, urlSpecific: true,
options: [ options: [
"blockReadout", "fakeReadout", "fakeInput", "askReadout", null, "fake", "ask", null,
"blockEverything", "block", "ask", "allow", "allowEverything" "blockEverything", "block", "allow", "allowEverything"
]
},
{
name: "protectedCanvasPart",
defaultValue: "readout",
urlSpecific: true,
options: [
"readout", "input", "everything"
] ]
}, },
{ {

View File

@ -17,7 +17,7 @@
scope.transitions = { scope.transitions = {
"": function(oldStorage){ "": function(oldStorage){
return { return {
storageVersion: 0.4 storageVersion: 0.5
}; };
}, },
0.1: function(oldStorage){ 0.1: function(oldStorage){
@ -97,6 +97,40 @@
} }
return newStorage; return newStorage;
}, },
0.4: function(oldStorage){
var newStorage = {
storageVersion: 0.5
};
if (oldStorage.hasOwnProperty("blockMode")){
switch (oldStorage.blockMode){
case "blockReadout":
newStorage.blockMode = "block";
newStorage.protectedCanvasPart = "readout";
break;
case "fakeReadout":
newStorage.blockMode = "fake";
newStorage.protectedCanvasPart = "readout";
break;
case "fakeInput":
newStorage.blockMode = "fake";
newStorage.protectedCanvasPart = "input";
break;
case "askReadout":
newStorage.blockMode = "ask";
newStorage.protectedCanvasPart = "readout";
break;
case "blockEverything":
case "block":
case "ask":
case "allow":
case "allowEverything":
newStorage.protectedCanvasPart = "everything";
break;
}
}
return newStorage;
}
}; };
scope.check = function(storage, {settings, logging, changeValue, urlContainer}){ scope.check = function(storage, {settings, logging, changeValue, urlContainer}){

View File

@ -117,55 +117,70 @@
{ {
name: "blockMode", name: "blockMode",
check: function(errorCallback){ check: function(errorCallback){
const switchMode = {
label: browser.i18n.getMessage("sanitation_resolution.switchToFakeReadout"),
callback: function(){
settings.blockMode = "fakeReadout";
}
};
const blockMode = settings.blockMode; const blockMode = settings.blockMode;
const blockModeName = browser.i18n.getMessage("blockMode_options." + blockMode); const protectedCanvasPart = settings.protectedCanvasPart;
if (!blockMode.match("^fake|^ask")){ if (!blockMode.match("^fake|^ask")){
errorCallback({ errorCallback({
message: browser.i18n.getMessage("sanitation_error.badBlockMode"), message: browser.i18n.getMessage("sanitation_error.badBlockMode"),
severity: "medium", severity: "medium",
resolutions: [switchMode] resolutions: [{
}); label: browser.i18n.getMessage("sanitation_resolution.switchToFake"),
}
["Audio", "Window", "DOMRect"].forEach(function(api){
const mainFlag = "protect" + api;
if (settings[mainFlag]){
if (["fakeInput"].indexOf(blockMode) !== -1){
const blockModeName = browser.i18n.getMessage("blockMode_options." + blockMode);
errorCallback({
message: browser.i18n.getMessage("sanitation_error.blockModeVsProtection")
.replace(/{blockMode}/g, blockModeName)
.replace(/{api}/g, browser.i18n.getMessage("section_" + api + "-api")),
severity: "high",
resolutions: [switchMode, {
label: browser.i18n.getMessage("sanitation_resolution.disableFlag")
.replace(/{flag}/g, browser.i18n.getMessage(mainFlag + "_title")),
callback: function(){
settings[mainFlag] = false;
}
}]
});
}
}
});
if (blockMode === "fakeInput" && settings.rng === "white"){
errorCallback({
message: browser.i18n.getMessage("sanitation_error.fakeInputWithWhiteRng")
.replace(/{blockMode}/g, blockModeName),
severity: "low",
resolutions: [switchMode, {
label: browser.i18n.getMessage("sanitation_resolution.switchToNonPersistentRng"),
callback: function(){ callback: function(){
settings.rng = "nonPersistent"; settings.blockMode = "fake";
} }
}] }]
}); });
} }
if (blockMode === "fake" && protectedCanvasPart === "input" && settings.rng === "white"){
errorCallback({
message: browser.i18n.getMessage("sanitation_error.fakeInputWithWhiteRng")
.replace(/{blockMode}/g, browser.i18n.getMessage("blockMode_options." + blockMode))
.replace(
/{protectedCanvasPart}/g,
browser.i18n.getMessage("protectedCanvasPart_options." + settings.protectedCanvasPart)
),
severity: "low",
resolutions: [
{
label: browser.i18n.getMessage("sanitation_resolution.switchToProtectReadout"),
callback: function(){
settings.protectedCanvasPart = "readout";
}
},
{
label: browser.i18n.getMessage("sanitation_resolution.switchToNonPersistentRng"),
callback: function(){
settings.rng = "nonPersistent";
}
}
]
});
}
if (blockMode === "fake" && protectedCanvasPart === "everything"){
errorCallback({
message: browser.i18n.getMessage("sanitation_error.fakeEverythingInCanvas")
.replace(/{blockMode}/g, browser.i18n.getMessage("blockMode_options." + blockMode))
.replace(
/{protectedCanvasPart}/g,
browser.i18n.getMessage("protectedCanvasPart_options." + settings.protectedCanvasPart)
),
severity: "low",
resolutions: [
{
label: browser.i18n.getMessage("sanitation_resolution.switchToProtectReadout"),
callback: function(){
settings.protectedCanvasPart = "readout";
}
},
{
label: browser.i18n.getMessage("sanitation_resolution.switchToProtectInput"),
callback: function(){
settings.protectedCanvasPart = "input";
}
}
]
});
}
} }
}, },
{ {

View File

@ -18,20 +18,20 @@
{ {
"name": "askOnlyOnce", "name": "askOnlyOnce",
"displayDependencies": { "displayDependencies": {
"blockMode": ["askReadout", "ask"] "blockMode": ["ask"]
} }
}, },
{ {
"name": "askDenyMode", "name": "askDenyMode",
"displayDependencies": { "displayDependencies": {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
}, },
{ {
"name": "showCanvasWhileAsking", "name": "showCanvasWhileAsking",
"displayDependencies": { "displayDependencies": {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
}, },
@ -40,11 +40,11 @@
"name": "rng", "name": "rng",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
@ -54,12 +54,12 @@
"name": "storePersistentRnd", "name": "storePersistentRnd",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"rng": ["persistent"], "rng": ["persistent"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"rng": ["persistent"], "rng": ["persistent"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
@ -71,12 +71,12 @@
"inputs": ["persistentRndClearIntervalValue", "persistentRndClearIntervalUnit"], "inputs": ["persistentRndClearIntervalValue", "persistentRndClearIntervalUnit"],
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"rng": ["persistent"], "rng": ["persistent"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"rng": ["persistent"], "rng": ["persistent"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
@ -87,12 +87,12 @@
"name": "clearPersistentRnd", "name": "clearPersistentRnd",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"rng": ["persistent"], "rng": ["persistent"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"rng": ["persistent"], "rng": ["persistent"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
@ -163,7 +163,7 @@
{ {
"name": "showCallingFile", "name": "showCallingFile",
"displayDependencies": { "displayDependencies": {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
}, },
@ -179,7 +179,7 @@
{ {
"name": "enableStackList", "name": "enableStackList",
"displayDependencies": { "displayDependencies": {
"blockMode": ["blockReadout", "fakeReadout", "fakeInput", "askReadout", "block", "ask"], "blockMode": ["fake", "block", "ask"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
}, },
@ -194,25 +194,28 @@
{ {
"name": "whiteList", "name": "whiteList",
"displayDependencies": { "displayDependencies": {
"blockMode": ["blockReadout", "fakeReadout", "fakeInput", "askReadout", "block", "ask"], "blockMode": ["fake", "block", "ask"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
}, },
{ {
"name": "sessionWhiteList", "name": "sessionWhiteList",
"displayDependencies": { "displayDependencies": {
"blockMode": ["blockReadout", "fakeReadout", "fakeInput", "askReadout", "block", "ask"], "blockMode": ["fake", "block", "ask"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
}, },
{ {
"name": "blackList", "name": "blackList",
"displayDependencies": { "displayDependencies": {
"blockMode": ["blockReadout", "fakeReadout", "fakeInput", "askReadout", "ask", "allow"], "blockMode": ["block", "fake", "ask", "allow"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
}, },
"Canvas-API", "Canvas-API",
{
"name": "protectedCanvasPart"
},
{ {
"name": "protectedAPIFeatures", "name": "protectedAPIFeatures",
"displayedSection": "Canvas-API", "displayedSection": "Canvas-API",
@ -226,11 +229,11 @@
"name": "minFakeSize", "name": "minFakeSize",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
@ -240,11 +243,11 @@
"name": "maxFakeSize", "name": "maxFakeSize",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
@ -254,11 +257,12 @@
"name": "ignoreFrequentColors", "name": "ignoreFrequentColors",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout"], "blockMode": ["fake"],
"protectedCanvasPart": ["readout"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
@ -268,11 +272,12 @@
"name": "minColors", "name": "minColors",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout"], "blockMode": ["fake"],
"protectedCanvasPart": ["readout"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
@ -282,11 +287,12 @@
"name": "fakeAlphaChannel", "name": "fakeAlphaChannel",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout"], "blockMode": ["fake"],
"protectedCanvasPart": ["readout"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
@ -296,11 +302,12 @@
"name": "useCanvasCache", "name": "useCanvasCache",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout"], "blockMode": ["fake"],
"protectedCanvasPart": ["readout"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
} }
@ -334,12 +341,12 @@
"name": "audioFakeRate", "name": "audioFakeRate",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask", "allow"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
@ -350,12 +357,12 @@
"name": "audioNoiseLevel", "name": "audioNoiseLevel",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask", "allow"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
@ -366,12 +373,12 @@
"name": "useAudioCache", "name": "useAudioCache",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask", "allow"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
@ -382,12 +389,12 @@
"name": "audioUseFixedIndices", "name": "audioUseFixedIndices",
"displayDependencies": [ "displayDependencies": [
{ {
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"blockMode": ["askReadout", "ask", "allow"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
@ -399,13 +406,13 @@
"displayDependencies": [ "displayDependencies": [
{ {
"audioUseFixedIndices": [true], "audioUseFixedIndices": [true],
"blockMode": ["fakeReadout", "fakeInput"], "blockMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]
}, },
{ {
"audioUseFixedIndices": [true], "audioUseFixedIndices": [true],
"blockMode": ["askReadout", "ask", "allow"], "blockMode": ["ask"],
"askDenyMode": ["fake"], "askDenyMode": ["fake"],
"protectAudio": [true], "protectAudio": [true],
"displayAdvancedSettings": [true] "displayAdvancedSettings": [true]

View File

@ -1,12 +1,13 @@
Version 0.5.6: Version 0.5.6:
changes: changes:
- - removed *Readout and *Input block modes (use protectedCanvasPart instead)
new features: new features:
- options gui improvements - options gui improvements
- url specific values can be added by hitting enter in the input - url specific values can be added by hitting enter in the input
- highlight "hide" icon when "tabing" to it - highlight "hide" icon when "tabing" to it
- made url specific values manageable with "tabing" - made url specific values manageable with "tabing"
- added setting "protected canvas part" to decouple block mode from part
fixes: fixes:
- -