From c23a81b1850bbc27c91b89561b9b9f5507638783 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Sun, 30 Apr 2023 15:07:58 +0100 Subject: [PATCH] Render to a new webgl canvas to ensure re-entrancy isn't an issue --- src/canvas.js | 35 +++++++++++++++++++++++++-- src/features/fingerprinting-canvas.js | 16 ++++++------ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index 97f780658..1cd09d94d 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -96,14 +96,23 @@ function createShader (gl, type, source) { return shader } +/** + * @typedef {Object} OffscreenCanvasInfo + * @property {HTMLCanvasElement | OffscreenCanvas} offScreenCanvas + * @property {CanvasRenderingContext2D | WebGL2RenderingContext | WebGLRenderingContext} offScreenCtx + * @property {WebGL2RenderingContext | WebGLRenderingContext} [offScreenWebGlCtx] + */ + /** * @param {HTMLCanvasElement | OffscreenCanvas} canvas * @param {string} domainKey * @param {string} sessionKey * @param {any} getImageDataProxy * @param {CanvasRenderingContext2D | WebGL2RenderingContext | WebGLRenderingContext} ctx? + * @param {boolean} [shouldCopy2dContextToWebGLContext] + * @returns {OffscreenCanvasInfo | null} */ -export function computeOffScreenCanvas (canvas, domainKey, sessionKey, getImageDataProxy, ctx) { +export function computeOffScreenCanvas (canvas, domainKey, sessionKey, getImageDataProxy, ctx, shouldCopy2dContextToWebGLContext = false) { if (!ctx) { // @ts-expect-error - Type 'null' is not assignable to type 'CanvasRenderingContext2D | WebGL2RenderingContext | WebGLRenderingContext'. ctx = canvas.getContext('2d') @@ -137,7 +146,29 @@ export function computeOffScreenCanvas (canvas, domainKey, sessionKey, getImageD offScreenCtx.putImageData(imageData, 0, 0) - return { offScreenCanvas, offScreenCtx } + /** @type {OffscreenCanvasInfo} */ + const output = { offScreenCanvas, offScreenCtx } + if (shouldCopy2dContextToWebGLContext) { + const offScreenWebGlCanvas = document.createElement('canvas') + offScreenWebGlCanvas.width = canvas.width + offScreenWebGlCanvas.height = canvas.height + let offScreenWebGlCtx + if (ctx instanceof WebGLRenderingContext) { + offScreenWebGlCtx = offScreenWebGlCanvas.getContext('webgl') + } else { + offScreenWebGlCtx = offScreenWebGlCanvas.getContext('webgl2') + } + if (offScreenWebGlCtx) { + try { + // Clone the 2d context back into the pages webgl context + copy2dContextToWebGLContext(offScreenCtx, offScreenWebGlCtx) + output.offScreenWebGlCtx = offScreenWebGlCtx + } catch (e) { + postDebugMessage('Failed to call readPixels on offscreen canvas', e) + } + } + } + return output } /** diff --git a/src/features/fingerprinting-canvas.js b/src/features/fingerprinting-canvas.js index a78ef2262..9367e5eea 100644 --- a/src/features/fingerprinting-canvas.js +++ b/src/features/fingerprinting-canvas.js @@ -1,4 +1,4 @@ -import { DDGProxy, DDGReflect } from '../utils' +import { DDGProxy, DDGReflect, postDebugMessage } from '../utils' import { computeOffScreenCanvas, copy2dContextToWebGLContext } from '../canvas' import ContentFeature from '../content-feature' @@ -133,12 +133,9 @@ export default class FingerprintingCanvas extends ContentFeature { const webGLReadMethodsProxy = new DDGProxy(featureName, context.prototype, methodName, { apply (target, thisArg, args) { if (thisArg) { - const { offScreenCanvas, offScreenCtx } = getCachedOffScreenCanvasOrCompute(thisArg.canvas, domainKey, sessionKey) - try { - // Clone the 2d context back into the pages webgl context - copy2dContextToWebGLContext(offScreenCtx, thisArg) - } catch (e) { - console.log('Failed to call readPixels on offscreen canvas', e, target, offScreenCanvas, offScreenCtx, args) + const { offScreenWebGlCtx } = getCachedOffScreenCanvasOrCompute(thisArg.canvas, domainKey, sessionKey, true) + if (offScreenWebGlCtx) { + return DDGReflect.apply(target, offScreenWebGlCtx, args) } } return DDGReflect.apply(target, thisArg, args) @@ -177,14 +174,15 @@ export default class FingerprintingCanvas extends ContentFeature { * @param {HTMLCanvasElement | OffscreenCanvas} canvas * @param {string} domainKey * @param {string} sessionKey + * @returns {import('../canvas').OffscreenCanvasInfo} */ - function getCachedOffScreenCanvasOrCompute (canvas, domainKey, sessionKey) { + function getCachedOffScreenCanvasOrCompute (canvas, domainKey, sessionKey, copy2dContextToWebGLContext) { let result if (canvasCache.has(canvas)) { result = canvasCache.get(canvas) } else { const ctx = canvasContexts.get(canvas) - result = computeOffScreenCanvas(canvas, domainKey, sessionKey, getImageDataProxy, ctx) + result = computeOffScreenCanvas(canvas, domainKey, sessionKey, getImageDataProxy, ctx, copy2dContextToWebGLContext) canvasCache.set(canvas, result) } return result