Source code

Revision control

Copy as Markdown

Other Tools

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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 https://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "console", () => {
return console.createInstance({
prefix: "UserCharacteristicsPage",
maxLogLevelPref: "toolkit.telemetry.user_characteristics_ping.logLevel",
});
});
/* This actor is responsible for rendering the canvas elements defined in
* recipes. It renders with both hardware and software rendering.
* It also provides debug information about the canvas rendering
* capabilities of its window (not all windows get HW rendering).
*
* See the recipes object for the list of canvases to render.
* WebGL is still being rendered in toolkit/components/resistfingerprinting/content/usercharacteristics.js
*
*/
export class UserCharacteristicsCanvasRenderingChild extends JSWindowActorChild {
constructor() {
super();
this.destroyed = false;
}
async render(hwRenderingExpected) {
// I couldn't think of a good name. Recipes as in instructions to render.
const runRecipe = async (isAccelerated, recipe) => {
const canvas = this.document.createElement("canvas");
canvas.width = recipe.size[0];
canvas.height = recipe.size[1];
const ctx = canvas.getContext("2d", {
forceSoftwareRendering: !isAccelerated,
});
if (!ctx) {
lazy.console.error("Could not get 2d context");
return { error: "COULD_NOT_GET_CONTEXT" };
}
let debugInfo = null;
try {
debugInfo = ctx.getDebugInfo(true /* ensureTarget */);
} catch (e) {
lazy.console.error(
"Error getting canvas debug info during render: ",
await stringifyError(e)
);
return {
error: "COULD_NOT_GET_DEBUG_INFO",
originalError: await stringifyError(e),
};
}
if (debugInfo.isAccelerated !== isAccelerated) {
lazy.console.error(
`Canvas is not rendered with expected mode. Expected: ${isAccelerated}, got: ${debugInfo.isAccelerated}`
);
return { error: "WRONG_RENDERING_MODE" };
}
try {
await recipe.func(this.contentWindow, canvas, ctx);
} catch (e) {
lazy.console.error("Error rendering canvas: ", await stringifyError(e));
return {
error: "RENDERING_ERROR",
originalError: await stringifyError(e),
};
}
return sha1(canvas.toDataURL("image/png", 1)).catch(stringifyError);
};
const errors = [];
const renderings = new Map();
// Run HW renderings
// Attempt HW rendering regardless of the expected rendering mode.
for (const [name, recipe] of Object.entries(recipes)) {
lazy.console.debug("[HW] Rendering ", name);
const result = await runRecipe(true, recipe);
if (result.error) {
if (!hwRenderingExpected && result.error === "WRONG_RENDERING_MODE") {
// If the rendering mode is wrong, we can ignore the error.
lazy.console.debug(
"Ignoring error because HW rendering is not expected: ",
result.error
);
continue;
}
errors.push({
name,
error: result.error,
originalError: result.originalError,
});
continue;
}
renderings.set(name, result);
}
// Run SW renderings
for (const [name, recipe] of Object.entries(recipes)) {
lazy.console.debug("[SW] Rendering ", name);
const result = await runRecipe(false, recipe);
if (result.error) {
errors.push({
name: name + "software",
error: result.error,
originalError: result.originalError,
});
continue;
}
renderings.set(name + "software", result);
}
const data = new Map();
data.set("renderings", renderings);
data.set("errors", errors);
return data;
}
async getDebugInfo() {
const canvas = this.document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) {
return null;
}
try {
return ctx.getDebugInfo(true /* ensureTarget */);
} catch (e) {
lazy.console.error(
"Error getting canvas debug info: ",
await stringifyError(e)
);
return null;
}
}
sendMessage(name, obj, transferables) {
if (this.destroyed) {
return;
}
this.sendAsyncMessage(name, obj, transferables);
}
didDestroy() {
this.destroyed = true;
}
async receiveMessage(msg) {
lazy.console.debug("Actor Child: Got ", msg.name);
switch (msg.name) {
case "CanvasRendering:GetDebugInfo":
return this.getDebugInfo();
case "CanvasRendering:Render":
return this.render(msg.data.hwRenderingExpected);
}
return null;
}
}
const recipes = {
// Metric name => (optionally async) function to render
canvasdata1: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "orange";
ctx.fillRect(100, 100, 50, 50);
},
size: [250, 250],
},
canvasdata2: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 200);
ctx.lineTo(175, 100);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "red";
ctx.lineWidth = 5;
ctx.stroke();
},
size: [250, 250],
},
canvasdata3: {
func: (window, canvas, ctx) => {
const kImageBlob =
"data:image/jpeg;base64,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";
return new Promise((resolve, reject) => {
// Calling new Image() fails for some reason, that's why we use new window.Image()
// It is also the only reason we pass window as an argument to all the functions :)
const image = new window.Image();
image.src = kImageBlob;
image.onload = () => {
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
resolve();
};
image.onerror = e => {
reject(e);
};
});
},
size: [250, 250],
},
canvasdata4: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "orange";
ctx.globalAlpha = 0.5;
ctx.translate(100, 100);
ctx.rotate((45.0 * Math.PI) / 180.0);
ctx.fillRect(0, 0, 50, 50);
ctx.rotate((-15.0 * Math.PI) / 180.0);
ctx.fillRect(0, 0, 50, 50);
},
size: [250, 250],
},
canvasdata5: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "green";
ctx.font = "italic 30px Georgia";
ctx.fillText("The quick brown", 15, 100);
ctx.fillText("fox jumps over", 15, 150);
ctx.fillText("the lazy dog", 15, 200);
},
size: [250, 250],
},
canvasdata6: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "green";
ctx.translate(10, 100);
ctx.rotate((45.0 * Math.PI) / 180.0);
ctx.shadowColor = "blue";
ctx.shadowBlur = 50;
ctx.font = "italic 40px Georgia";
ctx.fillText("The quick", 0, 0);
},
size: [250, 250],
},
canvasdata7: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "green";
ctx.font = "italic 30px system-ui";
ctx.fillText("The quick brown", 15, 100);
ctx.fillText("fox jumps over", 15, 150);
ctx.fillText("the lazy dog", 15, 200);
},
size: [250, 250],
},
canvasdata8: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "green";
ctx.translate(10, 100);
ctx.rotate((45.0 * Math.PI) / 180.0);
ctx.shadowColor = "blue";
ctx.shadowBlur = 50;
ctx.font = "italic 40px system-ui";
ctx.fillText("The quick", 0, 0);
},
size: [250, 250],
},
canvasdata9: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "green";
ctx.font = "italic 30px LocalFiraSans";
ctx.fillText("The quick brown", 15, 100);
ctx.fillText("fox jumps over", 15, 150);
ctx.fillText("the lazy dog", 15, 200);
},
size: [250, 250],
},
canvasdata10: {
func: (window, canvas, ctx) => {
ctx.fillStyle = "green";
ctx.translate(10, 100);
ctx.rotate((45.0 * Math.PI) / 180.0);
ctx.shadowColor = "blue";
ctx.shadowBlur = 50;
ctx.font = "italic 40px LocalFiraSans";
ctx.fillText("The quick", 0, 0);
},
size: [250, 250],
},
// fingerprintjs
// Their fingerprinting code went to the BSL license from MIT in
// So use the version of the code in the parent commit which is still MIT
canvasdata12Fingerprintjs1: {
func: (window, canvas, ctx) => {
ctx.textBaseline = "alphabetic";
ctx.fillStyle = "#f60";
ctx.fillRect(100, 1, 62, 20);
ctx.fillStyle = "#069";
// It's important to use explicit built-in fonts in order to exclude the affect of font preferences
// (there is a separate entropy source for them).
ctx.font = '11pt "Times New Roman"';
// The choice of emojis has a gigantic impact on rendering performance (especially in FF).
// Some newer emojis cause it to slow down 50-200 times.
// There must be no text to the right of the emoji, see https://github.com/fingerprintjs/fingerprintjs/issues/574
// A bare emoji shouldn't be used because the canvas will change depending on the script encoding:
// Escape sequence shouldn't be used too because Terser will turn it into a bare unicode.
const printedText = `Cwm fjordbank gly ${
String.fromCharCode(55357, 56835) /* 😃 */
}`;
ctx.fillText(printedText, 2, 15);
ctx.fillStyle = "rgba(102, 204, 0, 0.2)";
ctx.font = "18pt Arial";
ctx.fillText(printedText, 4, 45);
},
// usercharacteristics.html uses 240x60, but we can't get HW acceleration
// if an axis is less than 128px
size: [240, 128],
},
canvasdata13Fingerprintjs2: {
func: (window, canvas, ctx) => {
// Canvas blending
ctx.globalCompositeOperation = "multiply";
for (const [color, x, y] of [
["#f2f", 40, 40],
["#2ff", 80, 40],
["#ff2", 60, 80],
]) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, 40, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
// Canvas winding
ctx.fillStyle = "#f9c";
ctx.arc(60, 60, 60, 0, Math.PI * 2, true);
ctx.arc(60, 60, 20, 0, Math.PI * 2, true);
ctx.fill("evenodd");
},
// usercharacteristics.html uses 122x110, but we can't get HW acceleration
// if an axis is less than 128px
size: [128, 128],
},
};
async function sha1(message) {
const msgUint8 = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest("SHA-1", msgUint8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
return hashHex;
}
async function stringifyError(error) {
if (error instanceof Error) {
const stack = error.stack ?? "";
return `${error.toString()} ${stack}`;
}
// A hacky attempt to extract as much as info from error
const errStr = await (async () => {
const asStr = await (async () => error.toString())().catch(() => "");
const asJson = await (async () => JSON.stringify(error))().catch(() => "");
return asStr.length > asJson.len ? asStr : asJson;
})();
return errStr;
}