Bug 1453916 - Allow canvas extraction from webextension content-script even with resistFingerprinting turned on. r=kmag,bz
authorTom Schuster <evilpies@gmail.com>
Mon, 14 May 2018 20:49:32 +0200
changeset 419407 fe91056fdd27e6e117888883129aed554815ab2a
parent 419406 75122e95e3212dd14d0608fe4b06e951ba8c427b
child 419408 f0cc065d8a76cefc8ee68c0a5cff08667a6d63d4
push id34037
push userdluca@mozilla.com
push dateWed, 23 May 2018 09:51:55 +0000
treeherdermozilla-central@d36cd8bdbc5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag, bz
bugs1453916
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1453916 - Allow canvas extraction from webextension content-script even with resistFingerprinting turned on. r=kmag,bz
caps/BasePrincipal.cpp
caps/BasePrincipal.h
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
dom/canvas/CanvasUtils.cpp
dom/canvas/CanvasUtils.h
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLCanvasElement.h
dom/webidl/CanvasRenderingContext2D.webidl
dom/webidl/HTMLCanvasElement.webidl
js/xpconnect/src/XPCJSContext.cpp
js/xpconnect/src/XPCJSRuntime.cpp
toolkit/components/extensions/test/mochitest/mochitest-common.ini
toolkit/components/extensions/test/mochitest/test_ext_canvas_resistFingerprinting.html
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -456,16 +456,33 @@ BasePrincipal::CloneStrippingUserContext
 
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   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 */)
 {
   if (Is<ExpandedPrincipal>()) {
     return As<ExpandedPrincipal>()->AddonAllowsLoad(aURI, aExplicit);
   }
   if (auto policy = AddonPolicy()) {
     return policy->CanAccessURI(aURI, aExplicit);
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -108,16 +108,20 @@ public:
   uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
   uint32_t PrivateBrowsingId() const { return mOriginAttributes.mPrivateBrowsingId; }
   bool IsInIsolatedMozBrowserElement() const { return mOriginAttributes.mInIsolatedMozBrowser; }
 
   PrincipalKind Kind() const { return mKind; }
 
   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.
   bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);
 
   // Call these to avoid the cost of virtual dispatch.
   inline bool FastEquals(nsIPrincipal* aOther);
   inline bool FastEqualsConsideringDomain(nsIPrincipal* aOther);
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4660,69 +4660,76 @@ CanvasRenderingContext2D::SetLineDashOff
 }
 
 double
 CanvasRenderingContext2D::LineDashOffset() const {
   return CurrentState().dashOffset;
 }
 
 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;
   }
 
   // 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;
     }
   }
 
   EnsureUserSpacePath(aWinding);
   if (!mPath) {
     return false;
   }
 
   if (mPathTransformWillUpdate) {
     return mPath->ContainsPoint(Point(aX, aY), mPathToDS);
   }
 
   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;
   }
 
   EnsureTarget();
   if (!IsTargetValid()) {
     return false;
   }
 
   RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mTarget);
 
   return tempPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
 }
 
 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;
   }
 
   // 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;
     }
   }
 
   EnsureUserSpacePath();
   if (!mPath) {
     return false;
   }
@@ -4738,17 +4745,19 @@ CanvasRenderingContext2D::IsPointInStrok
                               state.dashOffset);
 
   if (mPathTransformWillUpdate) {
     return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS);
   }
   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;
   }
 
   EnsureTarget();
   if (!IsTargetValid()) {
     return false;
@@ -5430,18 +5439,19 @@ CanvasRenderingContext2D::DrawWindow(nsG
 }
 
 //
 // device pixel getting/setting
 //
 
 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);
   }
 
   if (!mCanvasElement && !mDocShell) {
     NS_ERROR("No canvas element and no docshell in GetImageData!!!");
     aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
@@ -5497,32 +5507,33 @@ CanvasRenderingContext2D::GetImageData(J
   if (w == 0) {
     w = 1;
   }
   if (h == 0) {
     h = 1;
   }
 
   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;
   }
   MOZ_ASSERT(array);
 
   RefPtr<ImageData> imageData = new ImageData(w, h, *array);
   return imageData.forget();
 }
 
 nsresult
 CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
                                             int32_t aX,
                                             int32_t aY,
                                             uint32_t aWidth,
                                             uint32_t aHeight,
+                                            nsIPrincipal& aSubjectPrincipal,
                                             JSObject** aRetval)
 {
   if (mDrawObserver) {
     mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
   }
 
   MOZ_ASSERT(aWidth && aHeight);
 
@@ -5585,17 +5596,17 @@ CanvasRenderingContext2D::GetImageDataAr
   dstWriteRect.MoveBy(-aX, -aY);
 
   // 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;
   if (mCanvasElement) {
     nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
-    usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx);
+    usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal);
   }
 
   do {
     JS::AutoCheckCannotGC nogc;
     bool isShared;
     uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
     MOZ_ASSERT(!isShared);        // Should not happen, data was created above
 
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -198,20 +198,26 @@ public:
   void Fill(const CanvasWindingRule& aWinding);
   void Fill(const CanvasPath& aPath, const CanvasWindingRule& aWinding);
   void Stroke();
   void Stroke(const CanvasPath& aPath);
   void DrawFocusIfNeeded(mozilla::dom::Element& aElement, ErrorResult& aRv);
   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);
   void StrokeText(const nsAString& aText, double aX, double aY,
                   const Optional<double>& aMaxWidth,
                   mozilla::ErrorResult& aError);
   TextMetrics*
     MeasureText(const nsAString& aRawText, mozilla::ErrorResult& aError);
@@ -243,17 +249,17 @@ public:
   already_AddRefed<ImageData>
     CreateImageData(JSContext* aCx, double aSw, double aSh,
                     mozilla::ErrorResult& aError);
   already_AddRefed<ImageData>
     CreateImageData(JSContext* aCx, ImageData& aImagedata,
                     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,
                     double aDx, double aDy, double aDirtyX, double aDirtyY,
                     double aDirtyWidth, double aDirtyHeight,
                     mozilla::ErrorResult& aError);
 
   double LineWidth() override
@@ -554,16 +560,17 @@ public:
   void OnShutdown();
 
   // Check the global setup, as well as the compositor type:
   bool AllowOpenGLCanvas() const;
 
 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,
                                  dom::Uint8ClampedArray* aArray,
                                  bool aHasDirtyRect, int32_t aDirtyX, int32_t aDirtyY,
                                  int32_t aDirtyWidth, int32_t aDirtyHeight);
 
   bool CopyBufferProvider(layers::PersistentBufferProvider& aOld,
--- a/dom/canvas/CanvasUtils.cpp
+++ b/dom/canvas/CanvasUtils.cpp
@@ -38,50 +38,39 @@
 #define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt"
 #define PERMISSION_CANVAS_EXTRACT_DATA "canvas"
 
 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()) {
         return true;
     }
 
     // Don't proceed if we don't have a document or JavaScript context.
     if (!aDocument || !aCx) {
         return false;
     }
 
     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;
     }
 
     // Get the document URI and its spec.
     nsIURI *docURI = aDocument->GetDocumentURI();
     nsCString docURISpec;
     docURI->GetSpec(docURISpec);
 
--- a/dom/canvas/CanvasUtils.h
+++ b/dom/canvas/CanvasUtils.h
@@ -45,17 +45,17 @@ void DoDrawImageSecurityCheck(dom::HTMLC
                               nsIPrincipal *aPrincipal,
                               bool forceWriteOnly,
                               bool CORSUsed);
 
 // Check if the context is chrome or has the permission to drawWindow
 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
 // succeeded.
 bool CoerceDouble(const JS::Value& v, double* d);
 
     /* Float validation stuff */
 #define VALIDATE(_f)  if (!IsFinite(_f)) return false
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -665,26 +665,27 @@ HTMLCanvasElement::ParseAttribute(int32_
 }
 
 
 
 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
   if (mWriteOnly &&
       !nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
-  aRv = ToDataURLImpl(aCx, aType, aParams, aDataURL);
+  aRv = ToDataURLImpl(aCx, aSubjectPrincipal, aType, aParams, aDataURL);
 }
 
 void
 HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback)
 {
   mPrintCallback = aCallback;
 }
 
@@ -745,16 +746,17 @@ NS_IMPL_RELEASE_INHERITED(CanvasCaptureT
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasCaptureTrackSource)
 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
 NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource,
                                    MediaStreamTrackSource,
                                    mCaptureStream)
 
 already_AddRefed<CanvasCaptureMediaStream>
 HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
+                                 nsIPrincipal& aSubjectPrincipal,
                                  ErrorResult& aRv)
 {
   if (IsWriteOnly()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
@@ -789,47 +791,51 @@ HTMLCanvasElement::CaptureStream(const O
                          new CanvasCaptureTrackSource(principal, stream));
   stream->AddTrackInternal(track);
 
   // Check site-specific permission and display prompt if appropriate.
   // If no permission, arrange for the frame capture listener to return
   // all-white, opaque image data.
   bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(
     OwnerDoc(),
-    nsContentUtils::GetCurrentJSContext());
+    nsContentUtils::GetCurrentJSContext(),
+    aSubjectPrincipal);
 
   rv = RegisterFrameCaptureListener(stream->FrameCaptureListener(), usePlaceholder);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   return stream.forget();
 }
 
 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(),
                                    usePlaceholder,
                                    mCurrentContext,
                                    mAsyncCanvasRenderer,
                                    aStream);
 }
 
 nsresult
 HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
+                                 nsIPrincipal& aSubjectPrincipal,
                                  const nsAString& aMimeType,
                                  const JS::Value& aEncoderOptions,
                                  nsAString& aDataURL)
 {
   nsIntSize size = GetWidthHeight();
   if (size.height == 0 || size.width == 0) {
     aDataURL = NS_LITERAL_STRING("data:,");
     return NS_OK;
@@ -842,22 +848,24 @@ HTMLCanvasElement::ToDataURLImpl(JSConte
   bool usingCustomParseOptions;
   nsresult rv =
     ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   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);
 
   // build data URL string
   aDataURL = NS_LITERAL_STRING("data:") + type + NS_LITERAL_STRING(";base64,");
 
   uint64_t count;
@@ -868,16 +876,17 @@ HTMLCanvasElement::ToDataURLImpl(JSConte
   return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count, aDataURL.Length());
 }
 
 void
 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
   if (mWriteOnly &&
       !nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
@@ -899,17 +908,18 @@ HTMLCanvasElement::ToBlob(JSContext* aCx
           &BlobCallback::Call),
         nullptr,
         nullptr));
     return;
   }
 
   // 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);
 
 }
 
 OffscreenCanvas*
 HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
 {
@@ -947,45 +957,47 @@ HTMLCanvasElement::TransferControlToOffs
   }
 
   return mOffscreenCanvas;
 }
 
 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;
   }
   return file.forget();
 }
 
 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;
   void* imgData = nullptr;
   rv = NS_ReadInputStreamToBuffer(stream, &imgData, -1, &imgSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(OwnerDoc()->GetScopeObject());
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -173,22 +173,24 @@ public:
   virtual already_AddRefed<nsISupports>
   GetContext(JSContext* aCx, const nsAString& aContextId,
              JS::Handle<JS::Value> aContextOptions,
              ErrorResult& aRv) override;
 
   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);
 
   bool MozOpaque() const
   {
     return GetBoolAttr(nsGkAtoms::moz_opaque);
   }
@@ -198,25 +200,25 @@ public:
       aRv.Throw(NS_ERROR_FAILURE);
       return;
     }
 
     SetHTMLBoolAttr(nsGkAtoms::moz_opaque, aValue, aRv);
   }
   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);
   PrintCallback* GetMozPrintCallback() const;
   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
    */
   nsIntSize GetSize();
 
   /**
    * Determine whether the canvas is write-only.
@@ -349,25 +351,28 @@ protected:
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual nsIntSize GetWidthHeight() override;
 
   virtual already_AddRefed<nsICanvasRenderingContextInternal>
   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();
 
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue,
                                 nsIPrincipal* aSubjectPrincipal,
                                 bool aNotify) override;
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -225,19 +225,23 @@ interface CanvasDrawPath {
   void beginPath();
   void fill(optional CanvasWindingRule winding = "nonzero");
   void fill(Path2D path, optional CanvasWindingRule winding = "nonzero");
   void stroke();
   void stroke(Path2D path);
   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);
 };
 
 [NoInterfaceObject]
 interface CanvasUserInterface {
   [Pref="canvas.focusring.enabled", Throws] void drawFocusIfNeeded(Element element);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Path path, HTMLElement element);
   [Pref="canvas.customfocusring.enabled"] boolean drawCustomFocusRing(Element element);
@@ -269,17 +273,17 @@ interface CanvasDrawImage {
 
 [NoInterfaceObject]
 interface CanvasImageData {
   // pixel manipulation
   [NewObject, Throws]
   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);
   [Throws]
   void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, double dirtyWidth, double dirtyHeight);
 };
 
 [NoInterfaceObject]
--- a/dom/webidl/HTMLCanvasElement.webidl
+++ b/dom/webidl/HTMLCanvasElement.webidl
@@ -18,39 +18,39 @@ interface HTMLCanvasElement : HTMLElemen
   [CEReactions, Pure, SetterThrows]
            attribute unsigned long width;
   [CEReactions, Pure, SetterThrows]
            attribute unsigned long height;
 
   [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);
 };
 
 // Mozilla specific bits
 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.
   [ChromeOnly, Throws]
   nsISupports? MozGetIPCContext(DOMString contextId);
 
            attribute PrintCallback? mozPrintCallback;
 
-  [Throws, Pref="canvas.capturestream.enabled"]
+  [Throws, Pref="canvas.capturestream.enabled", NeedsSubjectPrincipal]
   CanvasCaptureMediaStream captureStream(optional double frameRate);
 };
 
 // For OffscreenCanvas
 // Reference: https://wiki.whatwg.org/wiki/OffscreenCanvas
 partial interface HTMLCanvasElement {
   [Pref="gfx.offscreencanvas.enabled", Throws]
   OffscreenCanvas transferControlToOffscreen();
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -564,44 +564,16 @@ XPCJSContext::ActivityCallback(void* arg
     if (!active) {
         ProcessHangMonitor::ClearHang();
     }
 
     XPCJSContext* self = static_cast<XPCJSContext*>(arg);
     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)
 {
     XPCJSContext* self = XPCJSContext::Get();
 
     // Now is a good time to turn on profiling if it's pending.
     PROFILER_JS_INTERRUPT_CALLBACK();
@@ -631,17 +603,18 @@ XPCJSContext::InterruptCallback(JSContex
     nsString addonId;
     const char* prefName;
 
     auto principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
     bool chrome = principal->Is<SystemPrincipal>();
     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 {
         prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
         limit = Preferences::GetInt(prefName, 10);
     }
 
     // If there's no limit, or we're within the limit, let it go.
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -334,18 +334,17 @@ PrincipalImmuneToScriptPolicy(nsIPrincip
     auto principal = BasePrincipal::Cast(aPrincipal);
 
     // ExpandedPrincipal gets a free pass.
     if (principal->Is<ExpandedPrincipal>()) {
         return true;
     }
 
     // WebExtension principals get a free pass.
-    nsString addonId;
-    if (IsWebExtensionPrincipal(principal, addonId)) {
+    if (principal->AddonPolicy()) {
         return true;
     }
 
     // Check whether our URI is an "about:" URI that allows scripts.  If it is,
     // we need to allow JS to run.
     nsCOMPtr<nsIURI> principalURI;
     aPrincipal->GetURI(getter_AddRefs(principalURI));
     MOZ_ASSERT(principalURI);
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -52,16 +52,17 @@ support-files =
   !/toolkit/components/passwordmgr/test/authenticate.sjs
   file_redirect_data_uri.html
 prefs =
   security.mixed_content.upgrade_display_content=false
 
 [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
 [test_ext_contentscript_about_blank.html]
 skip-if = os == 'android' # bug 1369440
 [test_ext_contentscript_cache.html]
 skip-if = (os == 'linux' && debug) || (toolkit == 'android' && debug) # bug 1348241
 [test_ext_contentscript_canvas.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_canvas_resistFingerprinting.html
@@ -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>