Bug 1856732 - WebExtensions should not be subject to random canvas noise for fingerprint resistance. r=timhuang,gfx-reviewers,lsalzman

Differential Revision: https://phabricator.services.mozilla.com/D195239
This commit is contained in:
Tom Schuster 2023-12-13 11:33:25 +00:00
parent 3104637fd7
commit 375c65e422
5 changed files with 332 additions and 343 deletions

View File

@ -5989,17 +5989,15 @@ nsresult CanvasRenderingContext2D::GetImageDataArray(
RefPtr<DataSourceSurface> readback = snapshot->GetDataSurface();
mBufferProvider->ReturnSnapshot(snapshot.forget());
// Check for site-specific permission. This check is not needed if the
// canvas was created with a docshell (that is only done for special
// internal uses).
bool usePlaceholder = false;
// Check for site-specific permission.
CanvasUtils::ImageExtraction permission =
CanvasUtils::ImageExtraction::Unrestricted;
if (mCanvasElement) {
nsCOMPtr<Document> ownerDoc = mCanvasElement->OwnerDoc();
usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx,
aSubjectPrincipal);
permission = CanvasUtils::ImageExtractionResult(mCanvasElement, aCx,
aSubjectPrincipal);
} else if (mOffscreenCanvas) {
usePlaceholder = mOffscreenCanvas->ShouldResistFingerprinting(
RFPTarget::CanvasImageExtractionPrompt);
permission = CanvasUtils::ImageExtractionResult(mOffscreenCanvas, aCx,
aSubjectPrincipal);
}
// Clone the data source surface if canvas randomization is enabled. We need
@ -6008,10 +6006,7 @@ nsresult CanvasRenderingContext2D::GetImageDataArray(
//
// Note that we don't need to clone if we will use the place holder because
// the place holder doesn't use actual image data.
bool needRandomizePixels = false;
if (!usePlaceholder &&
ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
needRandomizePixels = true;
if (permission == CanvasUtils::ImageExtraction::Randomize) {
if (readback) {
readback = CreateDataSourceSurfaceByCloning(readback);
}
@ -6024,12 +6019,12 @@ nsresult CanvasRenderingContext2D::GetImageDataArray(
do {
uint8_t* randomData;
if (usePlaceholder) {
if (permission == CanvasUtils::ImageExtraction::Placeholder) {
// Since we cannot call any GC-able functions (like requesting the RNG
// service) after we call JS_GetUint8ClampedArrayData, we will
// pre-generate the randomness required for GeneratePlaceholderCanvasData.
randomData = TryToGenerateRandomDataForPlaceholderCanvasData();
} else if (needRandomizePixels) {
} else if (permission == CanvasUtils::ImageExtraction::Randomize) {
// Apply the random noises if canvan randomization is enabled. We don't
// need to calculate random noises if we are going to use the place
// holder.
@ -6045,7 +6040,7 @@ nsresult CanvasRenderingContext2D::GetImageDataArray(
uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
MOZ_ASSERT(!isShared); // Should not happen, data was created above
if (usePlaceholder) {
if (permission == CanvasUtils::ImageExtraction::Placeholder) {
FillPlaceholderCanvas(randomData, len.value(), data);
break;
}

View File

@ -48,6 +48,16 @@
using namespace mozilla::gfx;
static bool IsUnrestrictedPrincipal(nsIPrincipal& aPrincipal) {
// The system principal can always extract canvas data.
if (aPrincipal.IsSystemPrincipal()) {
return true;
}
// Allow extension principals.
return aPrincipal.GetIsAddonOrExpandedAddonPrincipal();
}
namespace mozilla::CanvasUtils {
bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
@ -103,14 +113,8 @@ bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
return false;
}
// The system principal can always extract canvas data.
if (aPrincipal.IsSystemPrincipal()) {
return true;
}
// Allow extension principals.
auto* principal = BasePrincipal::Cast(&aPrincipal);
if (principal->AddonPolicy() || principal->ContentScriptAddonPolicy()) {
// The system and extension principals can always extract canvas data.
if (IsUnrestrictedPrincipal(aPrincipal)) {
return true;
}
@ -176,7 +180,7 @@ bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
// Either permit or block extraction if a stored permission setting exists.
uint32_t permission;
rv = permissionManager->TestPermissionFromPrincipal(
principal, PERMISSION_CANVAS_EXTRACT_DATA, &permission);
&aPrincipal, PERMISSION_CANVAS_EXTRACT_DATA, &permission);
NS_ENSURE_SUCCESS(rv, false);
switch (permission) {
case nsIPermissionManager::ALLOW_ACTION:
@ -238,7 +242,7 @@ bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
// maybe not
nsPIDOMWindowOuter* win = aDocument->GetWindow();
nsAutoCString origin;
rv = principal->GetOrigin(origin);
rv = aPrincipal.GetOrigin(origin);
NS_ENSURE_SUCCESS(rv, false);
if (XRE_IsContentProcess()) {
@ -262,6 +266,45 @@ bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
return false;
}
ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement,
JSContext* aCx,
nsIPrincipal& aPrincipal) {
if (IsUnrestrictedPrincipal(aPrincipal)) {
return ImageExtraction::Unrestricted;
}
nsCOMPtr<dom::Document> ownerDoc = aCanvasElement->OwnerDoc();
if (!IsImageExtractionAllowed(ownerDoc, aCx, aPrincipal)) {
return ImageExtraction::Placeholder;
}
if (ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
return ImageExtraction::Randomize;
}
return ImageExtraction::Unrestricted;
}
ImageExtraction ImageExtractionResult(dom::OffscreenCanvas* aOffscreenCanvas,
JSContext* aCx,
nsIPrincipal& aPrincipal) {
if (IsUnrestrictedPrincipal(aPrincipal)) {
return ImageExtraction::Unrestricted;
}
if (aOffscreenCanvas->ShouldResistFingerprinting(
RFPTarget::CanvasImageExtractionPrompt)) {
return ImageExtraction::Placeholder;
}
if (aOffscreenCanvas->ShouldResistFingerprinting(
RFPTarget::CanvasRandomization)) {
return ImageExtraction::Randomize;
}
return ImageExtraction::Unrestricted;
}
bool GetCanvasContextType(const nsAString& str,
dom::CanvasContextType* const out_type) {
if (str.EqualsLiteral("2d")) {

View File

@ -63,6 +63,19 @@ bool IsOffscreenCanvasEnabled(JSContext* aCx, JSObject* aObj);
bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx,
nsIPrincipal& aPrincipal);
enum class ImageExtraction {
Unrestricted,
Placeholder,
Randomize,
};
// Returns whether the result of an image extraction should be replaced
// by a placeholder or randomized.
ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement,
JSContext* aCx, nsIPrincipal& aPrincipal);
ImageExtraction ImageExtractionResult(dom::OffscreenCanvas* aOffscreenCanvas,
JSContext* aCx, nsIPrincipal& aPrincipal);
// Make a double out of |v|, treating undefined values as 0.0 (for
// the sake of sparse arrays). Return true iff coercion
// succeeded.

View File

@ -55,11 +55,7 @@ add_task(async function test_contentscript() {
let win = window.open(url);
let color = await extension.awaitMessage("data-color");
const expected = 128;
// TODO(dshin): Bug 1856732 - Currently resist fingerprinting induces canvas
// noise even for WebExtensions. Prevent this single bit noise from failing
// the test for now.
const fuzzy_is = color == (expected ^ 1 || expected ^ 2) ? todo_is : is;
fuzzy_is(color, expected, "Got correct pixel data for green");
is(color, expected, "Got correct pixel data for green");
win.close();
await extension.unload();
});

View File

@ -19,27 +19,25 @@ var TEST_CASES = [
{
name: "CanvasRenderingContext2D.getImageData() but constant color",
shouldBeRandomized: false,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], _ => {
const canvas = content.document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
extractCanvasData() {
const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext("2d");
const context = canvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
const imageData = context.getImageData(0, 0, 100, 100);
const imageData = context.getImageData(0, 0, 100, 100);
// Access the data again.
const imageDataSecond = context.getImageData(0, 0, 100, 100);
// Access the data again.
const imageDataSecond = context.getImageData(0, 0, 100, 100);
return [imageData.data, imageDataSecond.data];
});
return [imageData.data, imageDataSecond.data];
},
isDataRandomized(name, data1, data2, isCompareOriginal) {
let diffCnt = countDifferencesInUint8Arrays(data1, data2);
@ -64,29 +62,27 @@ var TEST_CASES = [
{
name: "CanvasRenderingContext2D.getImageData().",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], _ => {
const canvas = content.document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
extractCanvasData() {
const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext("2d");
const context = canvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
const imageData = context.getImageData(0, 0, 100, 100);
const imageData = context.getImageData(0, 0, 100, 100);
// Access the data again.
const imageDataSecond = context.getImageData(0, 0, 100, 100);
// Access the data again.
const imageDataSecond = context.getImageData(0, 0, 100, 100);
return [imageData.data, imageDataSecond.data];
});
return [imageData.data, imageDataSecond.data];
},
isDataRandomized(name, data1, data2, isCompareOriginal) {
let diffCnt = countDifferencesInUint8Arrays(data1, data2);
@ -111,29 +107,27 @@ var TEST_CASES = [
{
name: "HTMLCanvasElement.toDataURL() with a 2d context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], _ => {
const canvas = content.document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
extractCanvasData() {
const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext("2d");
const context = canvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
const dataURL = canvas.toDataURL();
const dataURL = canvas.toDataURL();
// Access the data again.
const dataURLSecond = canvas.toDataURL();
// Access the data again.
const dataURLSecond = canvas.toDataURL();
return [dataURL, dataURLSecond];
});
return [dataURL, dataURLSecond];
},
isDataRandomized(name, data1, data2) {
return data1 !== data2;
@ -142,33 +136,31 @@ var TEST_CASES = [
{
name: "HTMLCanvasElement.toDataURL() with a webgl context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], _ => {
const canvas = content.document.createElement("canvas");
extractCanvasData() {
const canvas = document.createElement("canvas");
const context = canvas.getContext("webgl");
const context = canvas.getContext("webgl");
context.enable(context.SCISSOR_TEST);
context.scissor(0, 0, 100, 100);
context.clearColor(1, 0.2, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(15, 15, 30, 15);
context.clearColor(0.2, 1, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(50, 50, 15, 15);
context.clearColor(0.2, 0.2, 1, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.enable(context.SCISSOR_TEST);
context.scissor(0, 0, 100, 100);
context.clearColor(1, 0.2, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(15, 15, 30, 15);
context.clearColor(0.2, 1, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(50, 50, 15, 15);
context.clearColor(0.2, 0.2, 1, 1);
context.clear(context.COLOR_BUFFER_BIT);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
const dataURL = canvas.toDataURL();
const dataURL = canvas.toDataURL();
// Access the data again.
const dataURLSecond = canvas.toDataURL();
// Access the data again.
const dataURLSecond = canvas.toDataURL();
return [dataURL, dataURLSecond];
});
return [dataURL, dataURLSecond];
},
isDataRandomized(name, data1, data2) {
return data1 !== data2;
@ -177,38 +169,36 @@ var TEST_CASES = [
{
name: "HTMLCanvasElement.toDataURL() with a bitmaprenderer context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
const canvas = content.document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
async extractCanvasData() {
const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext("2d");
const context = canvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
const bitmapCanvas = content.document.createElement("canvas");
bitmapCanvas.width = 100;
bitmapCanvas.heigh = 100;
content.document.body.appendChild(bitmapCanvas);
const bitmapCanvas = document.createElement("canvas");
bitmapCanvas.width = 100;
bitmapCanvas.heigh = 100;
document.body.appendChild(bitmapCanvas);
let bitmap = await content.createImageBitmap(canvas);
const bitmapContext = bitmapCanvas.getContext("bitmaprenderer");
bitmapContext.transferFromImageBitmap(bitmap);
let bitmap = await createImageBitmap(canvas);
const bitmapContext = bitmapCanvas.getContext("bitmaprenderer");
bitmapContext.transferFromImageBitmap(bitmap);
const dataURL = bitmapCanvas.toDataURL();
const dataURL = bitmapCanvas.toDataURL();
// Access the data again.
const dataURLSecond = bitmapCanvas.toDataURL();
// Access the data again.
const dataURLSecond = bitmapCanvas.toDataURL();
return [dataURL, dataURLSecond];
});
return [dataURL, dataURLSecond];
},
isDataRandomized(name, data1, data2) {
return data1 !== data2;
@ -217,45 +207,43 @@ var TEST_CASES = [
{
name: "HTMLCanvasElement.toBlob() with a 2d context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
const canvas = content.document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
async extractCanvasData() {
const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext("2d");
const context = canvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
let data = await new content.Promise(resolve => {
canvas.toBlob(blob => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
let data = await new Promise(resolve => {
canvas.toBlob(blob => {
let fileReader = new FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
// Access the data again.
let dataSecond = await new content.Promise(resolve => {
canvas.toBlob(blob => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
});
return [data, dataSecond];
});
// Access the data again.
let dataSecond = await new Promise(resolve => {
canvas.toBlob(blob => {
let fileReader = new FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
});
return [data, dataSecond];
},
isDataRandomized(name, data1, data2) {
let diffCnt = countDifferencesInArrayBuffers(data1, data2);
@ -266,43 +254,41 @@ var TEST_CASES = [
{
name: "HTMLCanvasElement.toBlob() with a webgl context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
const canvas = content.document.createElement("canvas");
async extractCanvasData() {
const canvas = document.createElement("canvas");
const context = canvas.getContext("webgl");
const context = canvas.getContext("webgl");
context.enable(context.SCISSOR_TEST);
context.scissor(0, 0, 100, 100);
context.clearColor(1, 0.2, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(15, 15, 30, 15);
context.clearColor(0.2, 1, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(50, 50, 15, 15);
context.clearColor(0.2, 0.2, 1, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.enable(context.SCISSOR_TEST);
context.scissor(0, 0, 100, 100);
context.clearColor(1, 0.2, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(15, 15, 30, 15);
context.clearColor(0.2, 1, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(50, 50, 15, 15);
context.clearColor(0.2, 0.2, 1, 1);
context.clear(context.COLOR_BUFFER_BIT);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
let data = await new content.Promise(resolve => {
canvas.toBlob(blob => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
let data = await new Promise(resolve => {
canvas.toBlob(blob => {
let fileReader = new FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
// We don't get the consistent blob data on second access with webgl
// context regardless of the canvas randomization. So, we report the
// same data here to not fail the test. Ideally, we should look into
// why this happens, but it's not caused by canvas randomization.
return [data, data];
});
// We don't get the consistent blob data on second access with webgl
// context regardless of the canvas randomization. So, we report the
// same data here to not fail the test. Ideally, we should look into
// why this happens, but it's not caused by canvas randomization.
return [data, data];
},
isDataRandomized(name, data1, data2) {
let diffCnt = countDifferencesInArrayBuffers(data1, data2);
@ -313,54 +299,52 @@ var TEST_CASES = [
{
name: "HTMLCanvasElement.toBlob() with a bitmaprenderer context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
const canvas = content.document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
async extractCanvasData() {
const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext("2d");
const context = canvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
// Add the canvas element to the document
content.document.body.appendChild(canvas);
// Add the canvas element to the document
document.body.appendChild(canvas);
const bitmapCanvas = content.document.createElement("canvas");
bitmapCanvas.width = 100;
bitmapCanvas.heigh = 100;
content.document.body.appendChild(bitmapCanvas);
const bitmapCanvas = document.createElement("canvas");
bitmapCanvas.width = 100;
bitmapCanvas.heigh = 100;
document.body.appendChild(bitmapCanvas);
let bitmap = await content.createImageBitmap(canvas);
const bitmapContext = bitmapCanvas.getContext("bitmaprenderer");
bitmapContext.transferFromImageBitmap(bitmap);
let bitmap = await createImageBitmap(canvas);
const bitmapContext = bitmapCanvas.getContext("bitmaprenderer");
bitmapContext.transferFromImageBitmap(bitmap);
let data = await new content.Promise(resolve => {
bitmapCanvas.toBlob(blob => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
let data = await new Promise(resolve => {
bitmapCanvas.toBlob(blob => {
let fileReader = new FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
// Access the data again.
let dataSecond = await new content.Promise(resolve => {
bitmapCanvas.toBlob(blob => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
});
return [data, dataSecond];
});
// Access the data again.
let dataSecond = await new Promise(resolve => {
bitmapCanvas.toBlob(blob => {
let fileReader = new FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
});
return [data, dataSecond];
},
isDataRandomized(name, data1, data2) {
let diffCnt = countDifferencesInArrayBuffers(data1, data2);
@ -371,40 +355,24 @@ var TEST_CASES = [
{
name: "OffscreenCanvas.convertToBlob() with a 2d context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
let offscreenCanvas = new content.OffscreenCanvas(100, 100);
async extractCanvasData() {
let offscreenCanvas = new OffscreenCanvas(100, 100);
const context = offscreenCanvas.getContext("2d");
const context = offscreenCanvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
let blob = await offscreenCanvas.convertToBlob();
let blob = await offscreenCanvas.convertToBlob();
let data = await blob.arrayBuffer();
let data = await new content.Promise(resolve => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
// Access the data again.
let blobSecond = await offscreenCanvas.convertToBlob();
let dataSecond = await blobSecond.arrayBuffer();
// Access the data again.
let blobSecond = await offscreenCanvas.convertToBlob();
let dataSecond = await new content.Promise(resolve => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blobSecond);
});
return [data, dataSecond];
});
return [data, dataSecond];
},
isDataRandomized(name, data1, data2) {
let diffCnt = countDifferencesInArrayBuffers(data1, data2);
@ -415,46 +383,30 @@ var TEST_CASES = [
{
name: "OffscreenCanvas.convertToBlob() with a webgl context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
let offscreenCanvas = new content.OffscreenCanvas(100, 100);
async extractCanvasData() {
let offscreenCanvas = new OffscreenCanvas(100, 100);
const context = offscreenCanvas.getContext("webgl");
const context = offscreenCanvas.getContext("webgl");
context.enable(context.SCISSOR_TEST);
context.scissor(0, 0, 100, 100);
context.clearColor(1, 0.2, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(15, 15, 30, 15);
context.clearColor(0.2, 1, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(50, 50, 15, 15);
context.clearColor(0.2, 0.2, 1, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.enable(context.SCISSOR_TEST);
context.scissor(0, 0, 100, 100);
context.clearColor(1, 0.2, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(15, 15, 30, 15);
context.clearColor(0.2, 1, 0.2, 1);
context.clear(context.COLOR_BUFFER_BIT);
context.scissor(50, 50, 15, 15);
context.clearColor(0.2, 0.2, 1, 1);
context.clear(context.COLOR_BUFFER_BIT);
let blob = await offscreenCanvas.convertToBlob();
let blob = await offscreenCanvas.convertToBlob();
let data = await blob.arrayBuffer();
let data = await new content.Promise(resolve => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
// Access the data again.
let blobSecond = await offscreenCanvas.convertToBlob();
let dataSecond = await blobSecond.arrayBuffer();
// Access the data again.
let blobSecond = await offscreenCanvas.convertToBlob();
let dataSecond = await new content.Promise(resolve => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blobSecond);
});
return [data, dataSecond];
});
return [data, dataSecond];
},
isDataRandomized(name, data1, data2) {
let diffCnt = countDifferencesInArrayBuffers(data1, data2);
@ -465,46 +417,30 @@ var TEST_CASES = [
{
name: "OffscreenCanvas.convertToBlob() with a bitmaprenderer context",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
let offscreenCanvas = new content.OffscreenCanvas(100, 100);
async extractCanvasData() {
let offscreenCanvas = new OffscreenCanvas(100, 100);
const context = offscreenCanvas.getContext("2d");
const context = offscreenCanvas.getContext("2d");
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
const bitmapCanvas = new content.OffscreenCanvas(100, 100);
const bitmapCanvas = new OffscreenCanvas(100, 100);
let bitmap = await content.createImageBitmap(offscreenCanvas);
const bitmapContext = bitmapCanvas.getContext("bitmaprenderer");
bitmapContext.transferFromImageBitmap(bitmap);
let bitmap = await createImageBitmap(offscreenCanvas);
const bitmapContext = bitmapCanvas.getContext("bitmaprenderer");
bitmapContext.transferFromImageBitmap(bitmap);
let blob = await bitmapCanvas.convertToBlob();
let blob = await bitmapCanvas.convertToBlob();
let data = await blob.arrayBuffer();
let data = await new content.Promise(resolve => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blob);
});
// Access the data again.
let blobSecond = await bitmapCanvas.convertToBlob();
let dataSecond = await blobSecond.arrayBuffer();
// Access the data again.
let blobSecond = await bitmapCanvas.convertToBlob();
let dataSecond = await new content.Promise(resolve => {
let fileReader = new content.FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.readAsArrayBuffer(blobSecond);
});
return [data, dataSecond];
});
return [data, dataSecond];
},
isDataRandomized(name, data1, data2) {
let diffCnt = countDifferencesInArrayBuffers(data1, data2);
@ -515,25 +451,23 @@ var TEST_CASES = [
{
name: "CanvasRenderingContext2D.getImageData() with a offscreen canvas",
shouldBeRandomized: true,
extractCanvasData(browser) {
return SpecialPowers.spawn(browser, [], async _ => {
let offscreenCanvas = new content.OffscreenCanvas(100, 100);
extractCanvasData() {
let offscreenCanvas = new OffscreenCanvas(100, 100);
const context = offscreenCanvas.getContext("2d");
const context = offscreenCanvas.getContext("2d");
// Draw a red rectangle
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
// Draw a red rectangle
context.fillStyle = "#EE2222";
context.fillRect(0, 0, 100, 100);
context.fillStyle = "#2222EE";
context.fillRect(20, 20, 100, 100);
const imageData = context.getImageData(0, 0, 100, 100);
const imageData = context.getImageData(0, 0, 100, 100);
// Access the data again.
const imageDataSecond = context.getImageData(0, 0, 100, 100);
// Access the data again.
const imageDataSecond = context.getImageData(0, 0, 100, 100);
return [imageData.data, imageDataSecond.data];
});
return [imageData.data, imageDataSecond.data];
},
isDataRandomized(name, data1, data2, isCompareOriginal) {
let diffCnt = countDifferencesInUint8Arrays(data1, data2);
@ -557,6 +491,14 @@ var TEST_CASES = [
},
];
function runExtractCanvasData(test, tab) {
let code = test.extractCanvasData.toString();
return SpecialPowers.spawn(tab.linkedBrowser, [code], async code => {
let result = await content.eval(`({${code}}).extractCanvasData()`);
return result;
});
}
async function runTest(enabled) {
// Enable/Disable CanvasRandomization by the RFP target overrides.
let RFPOverrides = enabled ? "+CanvasRandomization" : "-CanvasRandomization";
@ -588,7 +530,7 @@ async function runTest(enabled) {
// Clear telemetry before starting test.
Services.fog.testResetFOG();
let data = await test.extractCanvasData(tab.linkedBrowser);
let data = await runExtractCanvasData(test, tab);
let result = test.isDataRandomized(test.name, data[0], test.originalData);
if (test.shouldBeRandomized) {
@ -612,7 +554,7 @@ async function runTest(enabled) {
`The data of first and second access should be the same for ${test.name}.`
);
let privateData = await test.extractCanvasData(privateTab.linkedBrowser);
let privateData = await runExtractCanvasData(test, privateTab);
// Check if we add noise to canvas data in private windows.
result = test.isDataRandomized(
@ -687,7 +629,7 @@ add_setup(async function () {
// Extract the original canvas data without random noise.
for (let test of TEST_CASES) {
let data = await test.extractCanvasData(tab.linkedBrowser);
let data = await runExtractCanvasData(test, tab);
test.originalData = data[0];
}