Bug 1453916 - Allow canvas extraction from webextension content-script even with resistFingerprinting turned on. r=kmag,bz

--HG--
extra : rebase_source : d67c589e8819407bb5acc4378d029288dd9295be
This commit is contained in:
Tom Schuster 2018-05-14 20:49:32 +02:00
parent 98fa42844c
commit 937d9326cd
14 changed files with 169 additions and 82 deletions

View File

@ -461,6 +461,23 @@ BasePrincipal::CloneStrippingUserContextIdAndFirstPartyDomain()
return BasePrincipal::CreateCodebasePrincipal(uri, attrs);
}
extensions::WebExtensionPolicy*
BasePrincipal::ContentScriptAddonPolicy()
{
if (!Is<ExpandedPrincipal>()) {
return nullptr;
}
auto expanded = As<ExpandedPrincipal>();
for (auto& prin : expanded->WhiteList()) {
if (auto policy = BasePrincipal::Cast(prin)->AddonPolicy()) {
return policy;
}
}
return nullptr;
}
bool
BasePrincipal::AddonAllowsLoad(nsIURI* aURI, bool aExplicit /* = false */)
{

View File

@ -113,6 +113,10 @@ public:
already_AddRefed<BasePrincipal> CloneStrippingUserContextIdAndFirstPartyDomain();
// If this is an add-on content script principal, returns its AddonPolicy.
// Otherwise returns null.
extensions::WebExtensionPolicy* ContentScriptAddonPolicy();
// Helper to check whether this principal is associated with an addon that
// allows unprivileged code to load aURI. aExplicit == true will prevent
// use of all_urls permission, requiring the domain in its permissions.

View File

@ -4665,7 +4665,9 @@ CanvasRenderingContext2D::LineDashOffset() const {
}
bool
CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY, const CanvasWindingRule& aWinding)
CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY,
const CanvasWindingRule& aWinding,
nsIPrincipal& aSubjectPrincipal)
{
if (!FloatValidate(aX, aY)) {
return false;
@ -4674,7 +4676,7 @@ CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY, co
// Check for site-specific permission and return false if no permission.
if (mCanvasElement) {
nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) {
if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal)) {
return false;
}
}
@ -4691,7 +4693,11 @@ CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY, co
return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
}
bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding)
bool
CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& aPath,
double aX, double aY,
const CanvasWindingRule& aWinding,
nsIPrincipal&)
{
if (!FloatValidate(aX, aY)) {
return false;
@ -4708,7 +4714,8 @@ bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& a
}
bool
CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY)
CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY,
nsIPrincipal& aSubjectPrincipal)
{
if (!FloatValidate(aX, aY)) {
return false;
@ -4717,7 +4724,7 @@ CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY)
// Check for site-specific permission and return false if no permission.
if (mCanvasElement) {
nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) {
if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal)) {
return false;
}
}
@ -4743,7 +4750,9 @@ CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY)
return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
}
bool CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& aPath, double aX, double aY)
bool
CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& aPath,
double aX, double aY, nsIPrincipal&)
{
if (!FloatValidate(aX, aY)) {
return false;
@ -5435,8 +5444,9 @@ CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, double aX,
already_AddRefed<ImageData>
CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
double aSy, double aSw,
double aSh, ErrorResult& aError)
double aSy, double aSw, double aSh,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError)
{
if (mDrawObserver) {
mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
@ -5502,7 +5512,7 @@ CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
}
JS::Rooted<JSObject*> array(aCx);
aError = GetImageDataArray(aCx, x, y, w, h, array.address());
aError = GetImageDataArray(aCx, x, y, w, h, aSubjectPrincipal, array.address());
if (aError.Failed()) {
return nullptr;
}
@ -5518,6 +5528,7 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
int32_t aY,
uint32_t aWidth,
uint32_t aHeight,
nsIPrincipal& aSubjectPrincipal,
JSObject** aRetval)
{
if (mDrawObserver) {
@ -5590,7 +5601,7 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
bool usePlaceholder = false;
if (mCanvasElement) {
nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx);
usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal);
}
do {

View File

@ -203,10 +203,16 @@ public:
bool DrawCustomFocusRing(mozilla::dom::Element& aElement);
void Clip(const CanvasWindingRule& aWinding);
void Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding);
bool IsPointInPath(JSContext* aCx, double aX, double aY, const CanvasWindingRule& aWinding);
bool IsPointInPath(JSContext* aCx, const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding);
bool IsPointInStroke(JSContext* aCx, double aX, double aY);
bool IsPointInStroke(JSContext* aCx, const CanvasPath& aPath, double aX, double aY);
bool IsPointInPath(JSContext* aCx, double aX, double aY,
const CanvasWindingRule& aWinding,
nsIPrincipal& aSubjectPrincipal);
bool IsPointInPath(JSContext* aCx, const CanvasPath& aPath,
double aX, double aY,
const CanvasWindingRule& aWinding, nsIPrincipal&);
bool IsPointInStroke(JSContext* aCx, double aX, double aY,
nsIPrincipal& aSubjectPrincipal);
bool IsPointInStroke(JSContext* aCx, const CanvasPath& aPath,
double aX, double aY, nsIPrincipal&);
void FillText(const nsAString& aText, double aX, double aY,
const Optional<double>& aMaxWidth,
mozilla::ErrorResult& aError);
@ -248,7 +254,7 @@ public:
mozilla::ErrorResult& aError);
already_AddRefed<ImageData>
GetImageData(JSContext* aCx, double aSx, double aSy, double aSw, double aSh,
mozilla::ErrorResult& aError);
nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aError);
void PutImageData(ImageData& aImageData,
double aDx, double aDy, mozilla::ErrorResult& aError);
void PutImageData(ImageData& aImageData,
@ -559,6 +565,7 @@ public:
protected:
nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY,
uint32_t aWidth, uint32_t aHeight,
nsIPrincipal& aSubjectPrincipal,
JSObject** aRetval);
nsresult PutImageData_explicit(int32_t aX, int32_t aY, uint32_t aW, uint32_t aH,

View File

@ -43,7 +43,7 @@ using namespace mozilla::gfx;
namespace mozilla {
namespace CanvasUtils {
bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx)
bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx, nsIPrincipal& aPrincipal)
{
// Do the rest of the checks only if privacy.resistFingerprinting is on.
if (!nsContentUtils::ShouldResistFingerprinting()) {
@ -58,25 +58,14 @@ bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx)
nsPIDOMWindowOuter *win = aDocument->GetWindow();
nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(win));
if (sop) {
// Documents with system principal can always extract canvas data.
nsIPrincipal *principal = sop->GetPrincipal();
if (nsContentUtils::IsSystemPrincipal(principal)) {
return true;
}
if (principal) {
// Allow extension principals
nsAutoString addonId;
Unused << NS_WARN_IF(NS_FAILED(principal->GetAddonId(addonId)));
if (!addonId.IsEmpty()) {
return true;
}
}
// The system principal can always extract canvas data.
if (nsContentUtils::IsSystemPrincipal(&aPrincipal)) {
return true;
}
// Always give permission to chrome scripts (e.g. Page Inspector).
if (nsContentUtils::ThreadsafeIsCallerChrome()) {
// Allow extension principals.
auto principal = BasePrincipal::Cast(&aPrincipal);
if (principal->AddonPolicy() || principal->ContentScriptAddonPolicy()) {
return true;
}

View File

@ -50,7 +50,7 @@ void DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement,
bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* aObj);
// Check site-specific permission and display prompt if appropriate.
bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx);
bool IsImageExtractionAllowed(nsIDocument *aDocument, 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

View File

@ -670,6 +670,7 @@ void
HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
JS::Handle<JS::Value> aParams,
nsAString& aDataURL,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
// do a trust check if this is a write-only canvas
@ -679,7 +680,7 @@ HTMLCanvasElement::ToDataURL(JSContext* aCx, const nsAString& aType,
return;
}
aRv = ToDataURLImpl(aCx, aType, aParams, aDataURL);
aRv = ToDataURLImpl(aCx, aSubjectPrincipal, aType, aParams, aDataURL);
}
void
@ -750,6 +751,7 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource,
already_AddRefed<CanvasCaptureMediaStream>
HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
if (IsWriteOnly()) {
@ -794,7 +796,8 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
// all-white, opaque image data.
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(
OwnerDoc(),
nsContentUtils::GetCurrentJSContext());
nsContentUtils::GetCurrentJSContext(),
aSubjectPrincipal);
rv = RegisterFrameCaptureListener(stream->FrameCaptureListener(), usePlaceholder);
if (NS_FAILED(rv)) {
@ -807,13 +810,15 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
nsresult
HTMLCanvasElement::ExtractData(JSContext* aCx,
nsIPrincipal& aSubjectPrincipal,
nsAString& aType,
const nsAString& aOptions,
nsIInputStream** aStream)
{
// Check site-specific permission and display prompt if appropriate.
// If no permission, return all-white, opaque image data.
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(OwnerDoc(), aCx);
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(
OwnerDoc(), aCx, aSubjectPrincipal);
return ImageEncoder::ExtractData(aType,
aOptions,
GetSize(),
@ -825,6 +830,7 @@ HTMLCanvasElement::ExtractData(JSContext* aCx,
nsresult
HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
nsIPrincipal& aSubjectPrincipal,
const nsAString& aMimeType,
const JS::Value& aEncoderOptions,
nsAString& aDataURL)
@ -847,12 +853,14 @@ HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
}
nsCOMPtr<nsIInputStream> stream;
rv = ExtractData(aCx, type, params, getter_AddRefs(stream));
rv = ExtractData(aCx, aSubjectPrincipal, type, params,
getter_AddRefs(stream));
// If there are unrecognized custom parse options, we should fall back to
// the default values for the encoder without any options at all.
if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
rv = ExtractData(aCx, type, EmptyString(), getter_AddRefs(stream));
rv = ExtractData(aCx, aSubjectPrincipal, type, EmptyString(),
getter_AddRefs(stream));
}
NS_ENSURE_SUCCESS(rv, rv);
@ -873,6 +881,7 @@ HTMLCanvasElement::ToBlob(JSContext* aCx,
BlobCallback& aCallback,
const nsAString& aType,
JS::Handle<JS::Value> aParams,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
// do a trust check if this is a write-only canvas
@ -904,7 +913,8 @@ HTMLCanvasElement::ToBlob(JSContext* aCx,
// Check site-specific permission and display prompt if appropriate.
// If no permission, return all-white, opaque image data.
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(OwnerDoc(), aCx);
bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(
OwnerDoc(), aCx, aSubjectPrincipal);
CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType,
aParams, usePlaceholder, aRv);
@ -952,20 +962,20 @@ HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
already_AddRefed<File>
HTMLCanvasElement::MozGetAsFile(const nsAString& aName,
const nsAString& aType,
CallerType aCallerType,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
OwnerDoc()->WarnOnceAbout(nsIDocument::eMozGetAsFile);
// do a trust check if this is a write-only canvas
if (mWriteOnly && aCallerType != CallerType::System) {
if (mWriteOnly && !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
RefPtr<File> file;
aRv = MozGetAsFileImpl(aName, aType, getter_AddRefs(file));
aRv = MozGetAsFileImpl(aName, aType, aSubjectPrincipal, getter_AddRefs(file));
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -975,12 +985,14 @@ HTMLCanvasElement::MozGetAsFile(const nsAString& aName,
nsresult
HTMLCanvasElement::MozGetAsFileImpl(const nsAString& aName,
const nsAString& aType,
nsIPrincipal& aSubjectPrincipal,
File** aResult)
{
nsCOMPtr<nsIInputStream> stream;
nsAutoString type(aType);
nsresult rv = ExtractData(nsContentUtils::GetCurrentJSContext(),
type, EmptyString(), getter_AddRefs(stream));
aSubjectPrincipal, type, EmptyString(),
getter_AddRefs(stream));
NS_ENSURE_SUCCESS(rv, rv);
uint64_t imgSize;

View File

@ -178,12 +178,14 @@ public:
void ToDataURL(JSContext* aCx, const nsAString& aType,
JS::Handle<JS::Value> aParams,
nsAString& aDataURL,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv);
void ToBlob(JSContext* aCx,
BlobCallback& aCallback,
const nsAString& aType,
JS::Handle<JS::Value> aParams,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv);
OffscreenCanvas* TransferControlToOffscreen(ErrorResult& aRv);
@ -203,7 +205,7 @@ public:
}
already_AddRefed<File> MozGetAsFile(const nsAString& aName,
const nsAString& aType,
CallerType aCallerType,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv);
already_AddRefed<nsISupports> MozGetIPCContext(const nsAString& aContextId,
ErrorResult& aRv);
@ -211,7 +213,7 @@ public:
void SetMozPrintCallback(PrintCallback* aCallback);
already_AddRefed<CanvasCaptureMediaStream>
CaptureStream(const Optional<double>& aFrameRate, ErrorResult& aRv);
CaptureStream(const Optional<double>& aFrameRate, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
/**
* Get the size in pixels of this canvas element
@ -354,15 +356,18 @@ protected:
CreateContext(CanvasContextType aContextType) override;
nsresult ExtractData(JSContext* aCx,
nsIPrincipal& aSubjectPrincipal,
nsAString& aType,
const nsAString& aOptions,
nsIInputStream** aStream);
nsresult ToDataURLImpl(JSContext* aCx,
nsIPrincipal& aSubjectPrincipal,
const nsAString& aMimeType,
const JS::Value& aEncoderOptions,
nsAString& aDataURL);
nsresult MozGetAsFileImpl(const nsAString& aName,
const nsAString& aType,
nsIPrincipal& aSubjectPrincipal,
File** aResult);
void CallPrintCallback();

View File

@ -230,9 +230,13 @@ interface CanvasDrawPath {
void clip(optional CanvasWindingRule winding = "nonzero");
void clip(Path2D path, optional CanvasWindingRule winding = "nonzero");
// NOT IMPLEMENTED void resetClip();
[NeedsSubjectPrincipal]
boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasWindingRule winding = "nonzero");
[NeedsSubjectPrincipal] // Only required because overloads can't have different extended attributes.
boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, optional CanvasWindingRule winding = "nonzero");
[NeedsSubjectPrincipal]
boolean isPointInStroke(double x, double y);
[NeedsSubjectPrincipal] // Only required because overloads can't have different extended attributes.
boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
};
@ -274,7 +278,7 @@ interface CanvasImageData {
ImageData createImageData(double sw, double sh);
[NewObject, Throws]
ImageData createImageData(ImageData imagedata);
[NewObject, Throws]
[NewObject, Throws, NeedsSubjectPrincipal]
ImageData getImageData(double sx, double sy, double sw, double sh);
[Throws]
void putImageData(ImageData imagedata, double dx, double dy);

View File

@ -23,10 +23,10 @@ interface HTMLCanvasElement : HTMLElement {
[Throws]
nsISupports? getContext(DOMString contextId, optional any contextOptions = null);
[Throws]
[Throws, NeedsSubjectPrincipal]
DOMString toDataURL(optional DOMString type = "",
optional any encoderOptions);
[Throws]
[Throws, NeedsSubjectPrincipal]
void toBlob(BlobCallback _callback,
optional DOMString type = "",
optional any encoderOptions);
@ -36,7 +36,7 @@ interface HTMLCanvasElement : HTMLElement {
partial interface HTMLCanvasElement {
[Pure, SetterThrows]
attribute boolean mozOpaque;
[Throws, NeedsCallerType]
[Throws, NeedsSubjectPrincipal]
File mozGetAsFile(DOMString name, optional DOMString? type = null);
// A Mozilla-only extension to get a canvas context backed by double-buffered
// shared memory. Only privileged callers can call this.
@ -45,7 +45,7 @@ partial interface HTMLCanvasElement {
attribute PrintCallback? mozPrintCallback;
[Throws, Pref="canvas.capturestream.enabled"]
[Throws, Pref="canvas.capturestream.enabled", NeedsSubjectPrincipal]
CanvasCaptureMediaStream captureStream(optional double frameRate);
};

View File

@ -569,34 +569,6 @@ XPCJSContext::ActivityCallback(void* arg, bool active)
self->mWatchdogManager->RecordContextActivity(self, active);
}
static inline bool
IsWebExtensionPrincipal(nsIPrincipal* principal, nsAString& addonId)
{
if (auto policy = BasePrincipal::Cast(principal)->AddonPolicy()) {
policy->GetId(addonId);
return true;
}
return false;
}
static bool
IsWebExtensionContentScript(BasePrincipal* principal, nsAString& addonId)
{
if (!principal->Is<ExpandedPrincipal>()) {
return false;
}
auto expanded = principal->As<ExpandedPrincipal>();
for (auto& prin : expanded->WhiteList()) {
if (IsWebExtensionPrincipal(prin, addonId)) {
return true;
}
}
return false;
}
// static
bool
XPCJSContext::InterruptCallback(JSContext* cx)
@ -636,7 +608,8 @@ XPCJSContext::InterruptCallback(JSContext* cx)
if (chrome) {
prefName = PREF_MAX_SCRIPT_RUN_TIME_CHROME;
limit = Preferences::GetInt(prefName, 20);
} else if (IsWebExtensionContentScript(principal, addonId)) {
} else if (auto policy = principal->ContentScriptAddonPolicy()) {
policy->GetId(addonId);
prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT;
limit = Preferences::GetInt(prefName, 5);
} else {

View File

@ -339,8 +339,7 @@ PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal)
}
// WebExtension principals get a free pass.
nsString addonId;
if (IsWebExtensionPrincipal(principal, addonId)) {
if (principal->AddonPolicy()) {
return true;
}

View File

@ -57,6 +57,7 @@ prefs =
[test_ext_background_canvas.html]
[test_ext_background_page.html]
skip-if = (toolkit == 'android') # android doesn't have devtools
[test_ext_canvas_resistFingerprinting.html]
[test_ext_clipboard.html]
[test_ext_clipboard_image.html]
skip-if = headless # disabled test case with_permission_allow_copy, see inline comment. Headless: Bug 1405872

View File

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebExtension test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<script type="text/javascript" src="head_cookies.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["privacy.resistFingerprinting", true]],
});
});
add_task(async function test_contentscript() {
function contentScript() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = "100";
var ctx = canvas.getContext("2d");
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 100, 100);
var data = ctx.getImageData(0, 0, 100, 100);
browser.test.sendMessage("data-color", data.data[1]);
}
let extensionData = {
manifest: {
content_scripts: [
{
"matches": ["http://mochi.test/*/file_sample.html"],
"js": ["content_script.js"],
"run_at": "document_start",
},
],
},
files: {
"content_script.js": contentScript,
},
};
const url = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest/file_sample.html";
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
let win = window.open(url);
let color = await extension.awaitMessage("data-color");
is(color, 128, "Got correct pixel data for green");
win.close();
await extension.unload();
});
</script>
</body>
</html>