diff --git a/test/webGL-Test.html b/test/webGL-Test.html index 8eb131e..aaf29e3 100644 --- a/test/webGL-Test.html +++ b/test/webGL-Test.html @@ -5,6 +5,11 @@ +

webGL test

@@ -12,12 +17,16 @@

Support

+

Hashes

+

Parameters

+ \ No newline at end of file diff --git a/test/webGL-Test.js b/test/webGL-Test.js index 94b4ea1..6b87ea4 100644 --- a/test/webGL-Test.js +++ b/test/webGL-Test.js @@ -1,3 +1,4 @@ +/* globals testAPI */ (function(){ "use strict"; @@ -45,13 +46,61 @@ return parameters; } - ["webgl", "webgl2"].forEach(async function(context, index){ + function createHashRow(hashTable, rowName){ + const row = document.createElement("tr"); + hashTable.appendChild(row); + + const nameCell = document.createElement("th"); + nameCell.textContent = rowName; + row.appendChild(nameCell); + + const valueCell = document.createElement("td"); + valueCell.innerHTML = "computing"; + row.appendChild(valueCell); + + return valueCell; + } + + function fillWebGlContext(context){ + // taken from https://github.com/Valve/fingerprintjs2/blob/master/fingerprint2.js + const vertexShaderTemplate = "attribute vec2 attrVertex;varying vec2 varyingTexCoordinate;" + + "uniform vec2 uniformOffset;void main(){varyingTexCoordinate=attrVertex+uniformOffset;" + + "gl_Position=vec4(attrVertex,0,1);}"; + const fragmentShaderTemplate = "precision mediump float;varying vec2 varyingTexCoordinate;" + + "void main() {gl_FragColor=vec4(varyingTexCoordinate,0,1);}"; + const vertexPosBuffer = context.createBuffer(); + context.bindBuffer(context.ARRAY_BUFFER, vertexPosBuffer); + const vertices = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.732134444, 0]); + context.bufferData(context.ARRAY_BUFFER, vertices, context.STATIC_DRAW); + vertexPosBuffer.itemSize = 3; + vertexPosBuffer.numItems = 3; + const program = context.createProgram(); + const vertexShader = context.createShader(context.VERTEX_SHADER); + context.shaderSource(vertexShader, vertexShaderTemplate); + context.compileShader(vertexShader); + const fragmentShader = context.createShader(context.FRAGMENT_SHADER); + context.shaderSource(fragmentShader, fragmentShaderTemplate); + context.compileShader(fragmentShader); + context.attachShader(program, vertexShader); + context.attachShader(program, fragmentShader); + context.linkProgram(program); + context.useProgram(program); + program.vertexPosAttrib = context.getAttribLocation(program, "attrVertex"); + program.offsetUniform = context.getUniformLocation(program, "uniformOffset"); + context.enableVertexAttribArray(program.vertexPosArray); + context.vertexAttribPointer(program.vertexPosAttrib, vertexPosBuffer.itemSize, context.FLOAT, !1, 0, 0); + context.uniform2f(program.offsetUniform, 1, 1); + context.drawArrays(context.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems); + } + + async function testSupport(context, index){ + const canvas = document.createElement("canvas"); + canvas.width = 11; + canvas.height = 13; + const output = document.createElement("div"); document.getElementById("output").appendChild(output); try { - const canvas = document.createElement("canvas"); - canvas.width = 11; - canvas.height = 13; const gl = canvas.getContext(context) || canvas.getContext("experimental-" + context); // paint it completely black @@ -67,6 +116,26 @@ max = Math.max(max, values[pixels[i]]); } + output.textContent = context + ": " + + (max !== 3 * values[255]? "": "not ") + "supported "; + output.title = JSON.stringify(values); + } + catch (error){ + output.textContent = context + ": ERROR"; + output.title = error; + } + } + + async function testParameters(context, hashTable){ + const canvas = document.createElement("canvas"); + canvas.width = 11; + canvas.height = 13; + + const hashCell = createHashRow(hashTable, "parameter"); + + try { + const gl = canvas.getContext(context) || canvas.getContext("experimental-" + context); + const parameters = getParameters(gl); if (context === "webgl2"){ const parameterOutput = document.createElement("table"); @@ -77,27 +146,82 @@ parameterOutput.appendChild(parameterRow); }); } - const hashBytes = await crypto.subtle.digest("SHA-256", new TextEncoder("utf-8") - .encode(parameters.map(function(parameter){ - return parameter.name + ": " + parameter.value; - }).join(","))); - const chunks = []; - (new Uint32Array(hashBytes)).forEach(function(num){ - chunks.push(num.toString(16)); - }); - const hash = chunks.map(function(chunk){ - return "0".repeat(8 - chunk.length) + chunk; - }).join(""); - - output.textContent = context + ": " + - (max !== 3 * values[255]? "": "not ") + "supported " + - "(parameter hash: " + hash + ")"; - output.title = JSON.stringify(values); + hashCell.textContent = await testAPI.hash(parameters.map(function(parameter){ + return parameter.name + ": " + parameter.value; + }).join(",")); } catch (error){ - output.textContent = context + ": ERROR"; - output.title = error; + hashCell.textContent = "ERROR"; + hashCell.title = error; } + } + + async function createImageHash(context, hashTable){ + return Promise.all([function(){ + const canvas = document.createElement("canvas"); + canvas.width = 300; + canvas.height = 150; + return canvas; + }, function(){ + if (window.OffscreenCanvas){ + return new OffscreenCanvas(300, 150); + } + return null; + }].map(async function(getter){ + const canvas = getter(); + if (canvas){ + const hashCell = createHashRow(hashTable, "image" + (canvas.toDataURL? "": " (offscreen)")); + try { + const gl = canvas.getContext(context) || canvas.getContext("experimental-" + context); + fillWebGlContext(gl); + if (canvas.convertToBlob || canvas.toBlob){ + const blob = await (canvas.convertToBlob? canvas.convertToBlob(): (canvas.toBlob.length? + new Promise(function(resolve){canvas.toBlob(resolve);}): + canvas.toBlob() + )); + hashCell.textContent = await testAPI.hash(await testAPI.readBlob(blob)); + } + } + catch (error){ + console.log(error); + } + } + return false; + })); + } + + async function createWorkerImageHash(context, hashTable){ + if (window.Worker && window.OffscreenCanvas){ + const hashCell = createHashRow(hashTable, "image (offscreen worker)"); + const url = new URL("./testAPI.js", location); + hashCell.textContent = await testAPI.runInWorker(async function getHash(contextType){ + const canvas = new OffscreenCanvas(300, 150); + const context = canvas.getContext(contextType); + fillWebGlContext(context); + return await testAPI.hash( + await testAPI.readBlob( + canvas.convertToBlob? + await canvas.convertToBlob(): + await canvas.toBlob() + ) + ); + }, [context], [url, fillWebGlContext]); + } + } + + ["webgl", "webgl2"].forEach(async function(context, index){ + testSupport(context, index); + + const hashOutput = document.createElement("div"); + document.getElementById("hashes").appendChild(hashOutput); + + hashOutput.textContent = context + ": "; + const table = document.createElement("table"); + hashOutput.appendChild(table); + + testParameters(context, table); + createImageHash(context, table); + createWorkerImageHash(context, table); }); }()); \ No newline at end of file