summaryrefslogtreecommitdiff
path: root/data/extensions/jsr@javascriptrestrictor/wrappingS-H-C.js
diff options
context:
space:
mode:
authorRuben Rodriguez <ruben@trisquel.info>2022-09-08 20:18:54 -0400
committerRuben Rodriguez <ruben@trisquel.info>2022-09-08 20:18:54 -0400
commit5da28b0f8771834ae208d61431d632875e9f8e7d (patch)
tree688ecaff26197bad8abde617b4947b11d617309e /data/extensions/jsr@javascriptrestrictor/wrappingS-H-C.js
parent4a87716686104266a9cccc2d83cc249e312f3673 (diff)
Updated extensions:
* Upgraded Privacy Redirect to 1.1.49 and configured to use the 10 most reliable invidious instances * Removed ViewTube * Added torproxy@icecat.gnu based on 'Proxy toggle' extension * Added jShelter 0.11.1 * Upgraded LibreJS to 7.21.0 * Upgraded HTTPS Everywhere to 2021.7.13 * Upgraded SubmitMe to 1.9
Diffstat (limited to 'data/extensions/jsr@javascriptrestrictor/wrappingS-H-C.js')
-rw-r--r--data/extensions/jsr@javascriptrestrictor/wrappingS-H-C.js310
1 files changed, 310 insertions, 0 deletions
diff --git a/data/extensions/jsr@javascriptrestrictor/wrappingS-H-C.js b/data/extensions/jsr@javascriptrestrictor/wrappingS-H-C.js
new file mode 100644
index 0000000..4519c87
--- /dev/null
+++ b/data/extensions/jsr@javascriptrestrictor/wrappingS-H-C.js
@@ -0,0 +1,310 @@
+/** \file
+ * \brief This file contains wrappers for Canvas-related calls
+ *
+ * \see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
+ * \see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
+ * \see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
+ *
+ * \author Copyright (C) 2019 Libor Polcak
+ * \author Copyright (C) 2021 Matus Svancar
+ *
+ * \license SPDX-License-Identifier: GPL-3.0-or-later
+ * \license SPDX-License-Identifier: MPL-2.0
+ */
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+// Alternatively, the contents of this file may be used under the terms
+// of the Mozilla Public License, v. 2.0, as described below:
+//
+// 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/.
+//
+// \copyright Copyright (c) 2020 The Brave Authors.
+
+/** \file
+ * \ingroup wrappers
+ * This file contains wrappers for calls related to the Canvas API, about which you can read more at MDN:
+ * * [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
+ * * [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D)
+ * * [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas)
+ *
+ * The goal is to prevent fingerprinting by modifying the values that can be read from the canvas.
+ * So the visual content of wrapped canvases as displayed on the screen is the same as intended.
+ *
+ * The modified content can be either an empty image or a fake image that is modified according to
+ * session and domain keys to be different than the original albeit very similar (i.e. the approach
+ * inspired by the algorithms created by [Brave Software](https://brave.com) available [here](https://github.com/brave/brave-core/blob/master/chromium_src/third_party/blink/renderer/core/execution_context/execution_context.cc).
+ *
+ * Note that both approaches are detectable by a fingerprinter that checks if a predetermined image
+ * inserted to the canvas is the same as the read one, see [here](https://arkenfox.github.io/TZP/tests/canvasnoise.html) for an example,
+ * Nevertheless, the aim of the wrappers is to limit the finerprintability.
+ *
+ * Also note that a determined fingerprinter can reveal the modifications and consequently uncover
+ * the original image. This can be avoided with the approach that completely clears the data stored
+ * in the canvas. Use the modifications based on session and domain keys if you want to provide an
+ * image that is similar to the original or if you want to produce a fake image that is not
+ * obviously spoofed to a naked eye. Otherwise, use the clearing approach.
+ */
+
+/*
+ * Create private namespace
+ */
+(function() {
+
+ const DEF_CANVAS_COPY = `
+ let canvasCopy = ctx => {
+ let {width, height} = ctx.canvas;
+ let fake = document.createElement("canvas");
+ fake.setAttribute("width", width);
+ fake.setAttribute("height", height);
+ let stx = fake.getContext("2d");
+ let imageData = window.CanvasRenderingContext2D.prototype.getImageData.call(ctx, 0, 0, width, height);
+ stx.putImageData(imageData, 0, 0);
+ return fake;
+ };
+ `;
+
+ /** @var String helping_code.
+ * Selects if the canvas should be cleared (1) or a fake image should be created based on session
+ * and domain keys (0).
+ */
+ var helping_code = `var approach = args[0];`;
+ var wrappers = [{
+ parent_object: "HTMLCanvasElement.prototype",
+ parent_object_property: "toDataURL",
+ wrapped_objects: [{
+ original_name: "HTMLCanvasElement.prototype.toDataURL",
+ wrapped_name: "origToDataURL",
+ }],
+ helping_code: helping_code,
+ wrapping_code_function_name: "wrapping",
+ wrapping_code_function_params: "parent",
+ wrapping_code_function_call_window: true,
+ original_function: "parent.HTMLCanvasElement.prototype.toDataURL",
+ replace_original_function: true,
+ wrapping_function_args: "...args",
+ /** \fn fake HTMLCanvasElement.prototype.toDataURL
+ * \brief Returns fake canvas content, see CanvasRenderingContext2D.prototype for more details.
+ *
+ * Internally creates a fake canvas of the same height and width as the original and calls
+ * CanvasRenderingContext2D.getImageData() that determines the result. If canvas uses WebGLRenderingContext
+ * the content is copied to new canvas using CanvasRenderingContext2D and function toDataURL is called on it.
+ */
+ wrapping_function_body: `
+ var ctx = this.getContext("2d");
+ if(ctx){
+ ${DEF_CANVAS_COPY}
+ return origToDataURL.call(canvasCopy(ctx), ...args);
+ }
+ else {
+ var ctx = this.getContext("webgl2", {preserveDrawingBuffer: true}) ||
+ this.getContext("experimental-webgl2", {preserveDrawingBuffer: true}) ||
+ this.getContext("webgl", {preserveDrawingBuffer: true}) ||
+ this.getContext("experimental-webgl", {preserveDrawingBuffer: true}) ||
+ this.getContext("moz-webgl", {preserveDrawingBuffer: true});
+ if(ctx){
+ var fake = document.createElement("canvas");
+ fake.setAttribute("width", this.width);
+ fake.setAttribute("height", this.height);
+ var stx = fake.getContext("2d");
+ stx.drawImage(ctx.canvas, 0, 0);
+ return HTMLCanvasElement.prototype.toDataURL.call(fake);
+ }
+ }
+ `,
+ },
+ {
+ parent_object: "CanvasRenderingContext2D.prototype",
+ parent_object_property: "getImageData",
+ wrapped_objects: [{
+ original_name: "CanvasRenderingContext2D.prototype.getImageData",
+ wrapped_name: "origGetImageData",
+ }],
+ helping_code: helping_code + strToUint + farbleCanvasDataBrave.toString() + `
+ var farble = function(context, fake) {
+ if(approach === 1){
+ fake.fillStyle = "white";
+ fake.fillRect(0, 0, context.canvas.width, context.canvas.height);
+ return;
+ }
+ else if(approach === 0){
+ const width = context.canvas.width;
+ const height = context.canvas.height;
+ var imageData = origGetImageData.call(context, 0, 0, width, height);
+ const BYTES_PER_ROW = width * 4;
+ farbleCanvasDataBrave(function*() {
+ let data = imageData.data;
+ let offset = 0;
+ while (offset < data.length) {
+ yield imageData.data.subarray(offset, offset + BYTES_PER_ROW);
+ offset += BYTES_PER_ROW;
+ }
+ }, width);
+ // Do not modify the original canvas, always modify the fake canvas.
+ // Always farble the whole image so that the farbled data do not depend
+ // on the page-specified extraction data rectangle.
+ fake.putImageData(imageData, 0, 0);
+ }
+ };`,
+ wrapping_code_function_name: "wrapping",
+ wrapping_code_function_params: "parent",
+ wrapping_code_function_call_window: true,
+ original_function: "parent.CanvasRenderingContext2D.prototype.getImageData",
+ replace_original_function: true,
+ wrapping_function_args: "...args",
+ /** \fn fake CanvasRenderingContext2D.prototype.getImageData
+ * \brief Returns a fake image data of the same height and width as stored in the original canvas.
+ *
+ * Internally calls the farbling that select the output which can be either an empty image or
+ * a fake image that is modified according to session and domain keys to be different than the
+ * original albeit very similar.
+ */
+ wrapping_function_body: `
+ var fake = document.createElement("canvas");
+ fake.setAttribute("width", this.canvas.width);
+ fake.setAttribute("height", this.canvas.height);
+ var stx = fake.getContext("2d");
+ farble(this,stx);
+ return origGetImageData.call(stx, ...args);
+ `,
+ },
+ {
+ parent_object: "HTMLCanvasElement.prototype",
+ parent_object_property: "toBlob",
+ wrapped_objects: [{
+ original_name: "HTMLCanvasElement.prototype.toBlob",
+ wrapped_name: "origToBlob",
+ }],
+ helping_code: ``,
+ wrapping_code_function_name: "wrapping",
+ wrapping_code_function_params: "parent",
+ wrapping_code_function_call_window: true,
+ original_function: "parent.HTMLCanvasElement.prototype.toBlob",
+ replace_original_function: true,
+ wrapping_function_args: "...args",
+ /** \fn fake HTMLCanvasElement.prototype.toBlob
+ * \brief Returns fake canvas content, see CanvasRenderingContext2D.prototype for more details.
+ *
+ * Internally creates a fake canvas of the same height and width as the original and calls
+ * CanvasRenderingContext2D.getImageData() that detemines the result.
+ */
+ wrapping_function_body: `
+ ${DEF_CANVAS_COPY}
+ return origToBlob.call(canvasCopy(this.getContext("2d")), ...args);
+ `,
+ },
+ {
+ parent_object: "OffscreenCanvas.prototype",
+ parent_object_property: "convertToBlob",
+ wrapped_objects: [{
+ original_name: "OffscreenCanvas.prototype.convertToBlob",
+ wrapped_name: "origConvertToBlob",
+ }],
+ helping_code: ``,
+ wrapping_code_function_name: "wrapping",
+ wrapping_code_function_params: "parent",
+ wrapping_code_function_call_window: true,
+ original_function: "parent.OffscreenCanvas.prototype.convertToBlob",
+ replace_original_function: true,
+ wrapping_function_args: "...args",
+ /** \fn fake OffscreenCanvas.prototype.convertToBlob
+ * \brief Returns fake canvas content, see CanvasRenderingContext2D.prototype for more details.
+ *
+ * Internally creates a fake canvas of the same height and width as the original and calls
+ * CanvasRenderingContext2D.getImageData() that detemines the result.
+ */
+ wrapping_function_body: `
+ ${DEF_CANVAS_COPY}
+ return origConvertToBlob.call(canvasCopy(this.getContext("2d")), ...args);
+ `,
+ },
+ {
+ parent_object: "CanvasRenderingContext2D.prototype",
+ parent_object_property: "isPointInPath",
+ wrapped_objects: [{
+ original_name: "CanvasRenderingContext2D.prototype.isPointInPath",
+ wrapped_name: "origIsPointInPath",
+ }],
+ helping_code: helping_code + `
+ function farbleIsPointInPath(ctx, ...args){
+ if(approach === 0){
+ var ret = origIsPointInPath.call(ctx, ...args);
+ return (ret && ((prng()*20) > 1));
+ }
+ else if(approach === 1){
+ return false;
+ }
+ };
+ `,
+ wrapping_code_function_name: "wrapping",
+ wrapping_code_function_params: "parent",
+ wrapping_code_function_call_window: true,
+ original_function: "parent.CanvasRenderingContext2D.prototype.isPointInPath",
+ replace_original_function: true,
+ wrapping_function_args: "...args",
+ /** \fn fake CanvasRenderingContext2D.prototype.isPointInPath
+ * \brief Returns modified result
+ *
+ * Either returns false or original function return value which is changed to false with 1/20 probability
+ *
+ * \bug Changing value with probability has some issues:
+ * * multiple calls with the same pixel can return different values
+ * * inconsistencies among adjacent pixels
+ */
+ wrapping_function_body: `
+ return farbleIsPointInPath(this, ...args);
+ `,
+ },
+ {
+ parent_object: "CanvasRenderingContext2D.prototype",
+ parent_object_property: "isPointInStroke",
+ wrapped_objects: [{
+ original_name: "CanvasRenderingContext2D.prototype.isPointInStroke",
+ wrapped_name: "origIsPointInStroke",
+ }],
+ helping_code: helping_code + `
+ function farbleIsPointInStroke(ctx, ...args){
+ if(approach === 0){
+ var ret = origIsPointInStroke.call(ctx, ...args);
+ return (ret && ((prng()*20) > 1));
+ }
+ else if(approach === 1){
+ return false;
+ }
+ };
+ `,
+ wrapping_code_function_name: "wrapping",
+ wrapping_code_function_params: "parent",
+ wrapping_code_function_call_window: true,
+ original_function: "parent.CanvasRenderingContext2D.prototype.isPointInStroke",
+ replace_original_function: true,
+ wrapping_function_args: "...args",
+ /** \fn fake CanvasRenderingContext2D.prototype.isPointInStroke
+ * \brief Returns modified result
+ *
+ * Either returns false or original function return value which is changed to false with 1/20 probability
+ *
+ * \bug Changing value with probability has some issues:
+ * * multiple calls with the same pixel can return different values
+ * * inconsistencies among adjacent pixels
+ */
+ wrapping_function_body: `
+ return farbleIsPointInStroke(this, ...args);
+ `,
+ },
+ ]
+ add_wrappers(wrappers);
+})();