Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* 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 emptyPage =
getRootDirectory(gTestPath).replace(
) + "empty.html";
/**
* Testing canvas randomization for a canvas whose control has been transferred
* to an OffscreenCanvas via transferControlToOffscreen(). Both toDataURL() and
* toBlob() should produce consistently randomized output, and the two methods
* should agree with each other (same key, same noise).
*/
async function extractCanvasData() {
const W = 64;
const H = 64;
let canvas = document.createElement("canvas");
canvas.width = W;
canvas.height = H;
document.body.appendChild(canvas);
let offscreen = canvas.transferControlToOffscreen();
let workerCode = `
self.onmessage = function(e) {
var c = e.data.canvas;
var ctx = c.getContext('2d');
ctx.fillStyle = '#EE2222';
ctx.fillRect(0, 0, ${W}, ${H});
ctx.fillStyle = '#2222EE';
ctx.fillRect(20, 20, ${W - 20}, ${H - 20});
ctx.fillStyle = '#22EE22';
ctx.fillRect(40, 40, ${W - 40}, ${H - 40});
self.postMessage('done');
};
`;
let worker = new Worker(
URL.createObjectURL(new Blob([workerCode], { type: "text/javascript" }))
);
await new Promise(resolve => {
worker.onmessage = () => resolve();
worker.postMessage({ canvas: offscreen }, [offscreen]);
});
await new Promise(r => requestAnimationFrame(r));
// toDataURL readback
let dataUrl = canvas.toDataURL("image/png");
let img = new Image();
await new Promise((ok, fail) => {
img.onload = ok;
img.onerror = fail;
img.src = dataUrl;
});
let tmpA = document.createElement("canvas");
tmpA.width = W;
tmpA.height = H;
tmpA.getContext("2d", { willReadFrequently: true }).drawImage(img, 0, 0);
let dataUrlPixels = Array.from(
tmpA.getContext("2d").getImageData(0, 0, W, H).data
);
// toBlob readback
let blob = await new Promise((ok, fail) => {
canvas.toBlob(
b => (b ? ok(b) : fail(new Error("toBlob null"))),
"image/png"
);
});
let bmp = await createImageBitmap(blob);
let tmpB = document.createElement("canvas");
tmpB.width = W;
tmpB.height = H;
tmpB.getContext("2d", { willReadFrequently: true }).drawImage(bmp, 0, 0);
let blobPixels = Array.from(
tmpB.getContext("2d").getImageData(0, 0, W, H).data
);
worker.terminate();
return [dataUrlPixels, blobPixels];
}
async function getPixelsFromTransferredCanvas(browser) {
let code = extractCanvasData.toString();
return SpecialPowers.spawn(browser, [code], async code => {
return content.eval(`(${code})()`);
});
}
let originalData;
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.baselineFingerprintingProtection", false],
["privacy.fingerprintingProtection", false],
["privacy.resistFingerprinting", false],
],
});
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, emptyPage);
let [dataUrlPixels] = await getPixelsFromTransferredCanvas(tab.linkedBrowser);
originalData = new Uint8Array(dataUrlPixels);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
async function runTest(enabled) {
let RFPOverrides = enabled
? "+CanvasRandomization,-EfficientCanvasRandomization"
: "-CanvasRandomization,-EfficientCanvasRandomization";
await SpecialPowers.pushPrefEnv({
set: [
["privacy.baselineFingerprintingProtection", false],
["privacy.fingerprintingProtection", true],
["privacy.fingerprintingProtection.pbmode", true],
["privacy.fingerprintingProtection.overrides", RFPOverrides],
["privacy.resistFingerprinting", false],
],
});
let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
private: true,
});
const tab = await BrowserTestUtils.openNewForegroundTab(
privateWindow.gBrowser,
emptyPage
);
let [dataUrlPixels, blobPixels] = await getPixelsFromTransferredCanvas(
tab.linkedBrowser
);
let dataUrlArr = new Uint8Array(dataUrlPixels);
let blobArr = new Uint8Array(blobPixels);
let name = "transferControlToOffscreen canvas";
if (enabled) {
ok(
isDataRandomizedFuzzy(name + " toDataURL", dataUrlArr, originalData),
`${name}: toDataURL() should have randomized pixels when CanvasRandomization is enabled.`
);
ok(
isDataRandomizedFuzzy(name + " toBlob", blobArr, originalData),
`${name}: toBlob() should have randomized pixels when CanvasRandomization is enabled.`
);
// toDataURL and toBlob must agree: same canvas key => same noise.
let diffBits = countDifferencesInUint8Arrays(dataUrlArr, blobArr);
info(
`${name}: toDataURL vs toBlob difference: ${diffBits} bits (should be 0).`
);
is(
diffBits,
0,
`${name}: toDataURL() and toBlob() must produce identical randomized output.`
);
} else {
ok(
!isDataRandomizedFuzzy(name + " toDataURL", dataUrlArr, originalData),
`${name}: toDataURL() should return unmodified pixels when CanvasRandomization is disabled.`
);
ok(
!isDataRandomizedFuzzy(name + " toBlob", blobArr, originalData),
`${name}: toBlob() should return unmodified pixels when CanvasRandomization is disabled.`
);
}
BrowserTestUtils.removeTab(tab);
await BrowserTestUtils.closeWindow(privateWindow);
await SpecialPowers.popPrefEnv();
}
add_task(async function run_tests_with_randomization_enabled() {
await runTest(true);
});
add_task(async function run_tests_with_randomization_disabled() {
await runTest(false);
});