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
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(lazy.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(lazy.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;
}
}
ChromeUtils.defineLazyGetter(lazy, "recipes", () => {
return {
// 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: async (window, canvas, ctx) => {
const imageB64 =
"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";
// createImageBitmap() call.
const byteCharacters = atob(imageB64);
const byteArray = new Uint8Array(
[...byteCharacters].map(c => c.charCodeAt(0))
);
const blob = new Blob([byteArray], { type: "image/jpeg" });
const bitmap = await window.createImageBitmap(blob);
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
},
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;
}