diff --git a/lib/modifiedSVGAPI.js b/lib/modifiedSVGAPI.js
new file mode 100644
index 0000000..644b139
--- /dev/null
+++ b/lib/modifiedSVGAPI.js
@@ -0,0 +1,88 @@
+/* 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";
+
+ let scope;
+ if ((typeof exports) !== "undefined"){
+ scope = exports;
+ }
+ else {
+ scope = require.register("./modifiedSVGAPI", {});
+ }
+
+ const {checkerWrapper, setProperties, getStatusByFlag} = require("./modifiedAPIFunctions");
+ const {byteArrayToString: hash} = require("./hash");
+
+
+ let randomSupply = null;
+ scope.setRandomSupply = function(supply){
+ randomSupply = supply;
+ };
+
+ function getValueHash(value){
+ return hash(new Float32Array([value]));
+ }
+
+ const cache = {};
+ function getFakeValue(value, window){
+ const valueHash = getValueHash(value);
+ let cachedValue = cache[valueHash];
+ if (typeof cachedValue === "number"){
+ return cachedValue;
+ }
+ else {
+ const rng = randomSupply.getRng(1, window);
+ const fakedValue = value + 0.01 * (rng(0) / 0xffffffff - 0.5);
+ const fakedHash = getValueHash(fakedValue);
+ cache[valueHash] = fakedValue;
+ cache[fakedHash] = fakedValue;
+ return fakedValue;
+ }
+ }
+ scope.getFakeValue = getFakeValue;
+
+ function getFakeValueCallback(args, check){
+ const {notify, window, original} = check;
+ const ret = args.length? original.call(this, ...args): original.call(this);
+ notify("fakedSVGReadout");
+ return getFakeValue(ret, window);
+ }
+
+ scope.changedFunctions = {
+ getTotalLength: {
+ object: ["SVGGeometryElement"],
+ fakeGenerator: function(checker){
+ return function getTotalLength(){
+ return checkerWrapper(checker, this, arguments, getFakeValueCallback);
+ };
+ }
+ },
+ getComputedTextLength: {
+ object: ["SVGTextContentElement"],
+ fakeGenerator: function(checker){
+ return function getComputedTextLength(){
+ return checkerWrapper(checker, this, arguments, getFakeValueCallback);
+ };
+ }
+ },
+ getSubStringLength: {
+ object: ["SVGTextContentElement"],
+ fakeGenerator: function(checker){
+ return function getSubStringLength(charnum, nchars){
+ return checkerWrapper(checker, this, arguments, getFakeValueCallback);
+ };
+ }
+ },
+ };
+
+
+ scope.changedGetters = [];
+
+ setProperties(scope.changedFunctions, scope.changedGetters, {
+ type: "readout",
+ getStatus: getStatusByFlag("protectSVG"),
+ api: "svg"
+ });
+}());
\ No newline at end of file
diff --git a/test/domRectSVG.svg b/test/domRectSVG.svg
index a3db696..06c60ff 100644
--- a/test/domRectSVG.svg
+++ b/test/domRectSVG.svg
@@ -16,4 +16,5 @@
Q 90,60 50,90
Q 10,60 10,30 z"/>
Text with Unicode 𝞐
+ 顃ģԹ̔ࠕ
\ No newline at end of file
diff --git a/test/index.html b/test/index.html
index b6cdf24..4183c9c 100644
--- a/test/index.html
+++ b/test/index.html
@@ -18,6 +18,7 @@
Audio Fingerprint test
DOMRect Fingerprint test
TextMetrics test
+ SVG test
Detection test
Performance test
Support for webGL
diff --git a/test/svgTest.css b/test/svgTest.css
new file mode 100644
index 0000000..4d9c621
--- /dev/null
+++ b/test/svgTest.css
@@ -0,0 +1,43 @@
+
+#svg {
+ position: fixed;
+ top: -2000%;
+}
+
+#test {
+ display: inline-block;
+ margin: 1em;
+}
+#test .data table {
+ border-collapse: collapse;
+}
+#test .data th {
+ padding: 0.4em;
+}
+#test .data td{
+ border: 1px solid #c7c7c7;
+ padding: 0.4em;
+}
+#test .data td.value {
+ text-align: right;
+}
+.small {
+ font-size: 0.8em;
+ color: gray;
+}
+.rectHash {
+ font-size: 4px;
+}
+.rectHash:hover {
+ font-size: 100%;
+}
+
+.content-hidable.content-hidden .content, .content-hidable .anti-content {
+ display: none;
+}
+.content-hidable .content, .content-hidable.content-hidden .anti-content {
+ display: initial;
+}
+.content-hidable .toggle {
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/test/svgTest.html b/test/svgTest.html
new file mode 100644
index 0000000..3c80383
--- /dev/null
+++ b/test/svgTest.html
@@ -0,0 +1,36 @@
+
+
+
+ TextMetrics test
+
+
+
+
+
+
+
+
+SVG test
+Expected result
+
+ - the hashes are different to the hashes when CanvasBlocker is disabled
+ - if "refresh" is clicked nothing must change
+ - upon page reload the hash changes (depending on CanvasBlocker settings - e.g. not in the stealth preset)
+
+Tests
+
+
+
SVG
+ Hash:
+ Data: +−
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/svgTest.js b/test/svgTest.js
new file mode 100644
index 0000000..2f238a6
--- /dev/null
+++ b/test/svgTest.js
@@ -0,0 +1,114 @@
+/* globals testAPI */
+(function(){
+ "use strict";
+
+ function byteArrayToHex(arrayBuffer){
+ const chunks = [];
+ (new Uint32Array(arrayBuffer)).forEach(function(num){
+ chunks.push(num.toString(16));
+ });
+ return chunks.map(function(chunk){
+ return "0".repeat(8 - chunk.length) + chunk;
+ }).join("");
+ }
+
+ function formatNumber(number){
+ const str = number.toString();
+ return "" + str.substring(0, str.length - 2) + "" +
+ str.substring(str.length - 2);
+ }
+
+ const svg = document.getElementById("svg");
+ const output = document.getElementById("test");
+
+ function getElements(){
+ const doc = svg.contentDocument;
+
+ return Array.from(doc.querySelectorAll(".testRect"));
+ }
+
+ const tests = [];
+ function addTest(title, callback){
+ tests.push({title, callback});
+ }
+
+ async function performTests(){
+ const elements = getElements();
+ const results = await Promise.all(tests.map(async function(test){
+ return {
+ name: test.title,
+ data: await Promise.all(elements.map(async function(svgElement){
+ return await test.callback(svgElement);
+ }))
+ };
+ }));
+ const data = new Float64Array(elements.length * tests.length);
+ results.forEach(function(svgData, i){
+ svgData.data.forEach(function(testData, j){
+ if ((typeof testData) === "number"){
+ data[i * elements.length + j] = testData;
+ }
+ });
+ });
+
+ const hash = await crypto.subtle.digest("SHA-256", data);
+ output.querySelector(".hash").textContent = byteArrayToHex(hash);
+
+ const dataNode = output.querySelector(".data");
+ dataNode.innerHTML = " | " +
+ elements.map(function(svgElement){
+ return "" + svgElement.dataset.name + " | ";
+ }).join("") +
+ results.map(function(result){
+ return "
---|
" + result.name + " | " + result.data.map(function(value){
+ if ((typeof value) === "number"){
+ return "" +
+ formatNumber(value) +
+ " | ";
+ }
+ else {
+ return "-- | ";
+ }
+ }).join("") + "
";
+ }).join("") +
+ "
";
+ }
+
+ svg.addEventListener("load", function(){
+ addTest("getTotalLength", function(element){
+ if (!element.getTotalLength){
+ return null;
+ }
+ return element.getTotalLength();
+ });
+ addTest("getComputedTextLength", function(element){
+ if (!element.getComputedTextLength){
+ return null;
+ }
+ return element.getComputedTextLength();
+ });
+ [{start: 3, end: 7}, {start: 7, end: 11}, {start: 3, end: 11}].forEach(function(substringDefinition){
+ addTest(
+ `getSubStringLength(${substringDefinition.start}, ${substringDefinition.end})`,
+ function(element){
+ if (!element.getSubStringLength){
+ return null;
+ }
+ return element.getSubStringLength(substringDefinition.start, substringDefinition.end);
+ }
+ );
+ });
+
+ test.querySelector(".refresh").addEventListener("click", function(){
+ performTests();
+ });
+ performTests();
+
+
+ document.querySelectorAll(".content-hidable").forEach(function(parentNode){
+ parentNode.querySelector(".toggle").addEventListener("click", function(){
+ parentNode.classList.toggle("content-hidden");
+ });
+ });
+ });
+}());
\ No newline at end of file