Bug 967895 - Add an option to return a placeholder when extracting image data. (Tor 6253) draft
authorJonathan Hao <jhao@mozilla.com>
Fri, 12 May 2017 14:36:04 +0800
changeset 593938 cc886c2ecffbc68a75cb8d6f49003b2087e8ed3c
parent 591428 09e60b4a6e70c57f927309b9497a0188feef38e8
child 593939 c9dc65b30198580384fdcd4251934b9323a4ae99
push id63879
push userbmo:jhao@mozilla.com
push dateWed, 14 Jun 2017 09:49:53 +0000
bugs967895, 6253, 12684, 21620
milestone55.0a1
Bug 967895 - Add an option to return a placeholder when extracting image data. (Tor 6253) Preventing canvas data extraction is an important part of the anti-fingerprinting work. When the pref privacy.resistFingerprinting is on and content is trying to extract canvas data, we will ask for user permission by a PopupNotification. The three patches of bug 967895 are uplifted from Tor Browser. This patch adds a parameter to ImageEncoder::ExtractData, which will be used when we only want a placeholder data. See the original commit message below. From 2c9aa3b1c278653c27a0339db89675ad89933ec9 Mon Sep 17 00:00:00 2001 From: Kathy Brade <brade@pearlcrescent.com> Date: Thu, 26 Sep 2013 17:11:19 -0400 Subject: Bug 6253: Add canvas image extraction prompt. (See also Bug #12684, Make "Not now" default for HTML5 canvas permission dialogue, patched by Isis Lovecruft.) This implements a `PopupNotification` [0] which notifies users that a website has attempted to access an HTML5 canvas. The default ordering for buttons is: Not Now Never for this site (recommended) Allow in the future * FIXES #12684 [1] by making "Not Now" the default in the HTML5 canvas fingerprinting permissions dialogue. * Palette icons included in HTML5 canvas permissions PopupNotification UI. The image is freely licensed and obtainable from: https://openclipart.org/image/300px/svg_to_png/21620/ben_palette.png * Includes a CSS whitespace hack from Pearl Crescent to the `CanvasPermissionPromptHelper_init()` function in `browser/base/content/browser.js` for causing the newlines in the `canvas.siteprompt` string (in torbutton.git, in `chrome/locale/en/torbutton.properties`) to render correctly in PopupNotification XUL <description> elements. [2] NOTE: Applying this patch requires an additional patch to TorButton, to store the additional UI strings before localisation. [3] [0]: https://mxr.mozilla.org/mozilla-esr24/source/toolkit/modules/PopupNotifications.jsm [1]: https://bugs.torproject.org/12684 [2]: https://trac.torproject.org/projects/tor/ticket/12684#comment:21 [3]: https://github.com/isislovecruft/torbutton/commit/368e74d62df349b27cf578525c3fa15da19ccdc2 Also includes: MozReview-Commit-ID: 74SudpGso3Y
dom/base/ImageEncoder.cpp
dom/base/ImageEncoder.h
--- a/dom/base/ImageEncoder.cpp
+++ b/dom/base/ImageEncoder.cpp
@@ -150,51 +150,55 @@ public:
   EncodingRunnable(const nsAString& aType,
                    const nsAString& aOptions,
                    UniquePtr<uint8_t[]> aImageBuffer,
                    layers::Image* aImage,
                    imgIEncoder* aEncoder,
                    EncodingCompleteEvent* aEncodingCompleteEvent,
                    int32_t aFormat,
                    const nsIntSize aSize,
+                   bool aUsePlaceholder,
                    bool aUsingCustomOptions)
     : Runnable("EncodingRunnable")
     , mType(aType)
     , mOptions(aOptions)
     , mImageBuffer(Move(aImageBuffer))
     , mImage(aImage)
     , mEncoder(aEncoder)
     , mEncodingCompleteEvent(aEncodingCompleteEvent)
     , mFormat(aFormat)
     , mSize(aSize)
+    , mUsePlaceholder(aUsePlaceholder)
     , mUsingCustomOptions(aUsingCustomOptions)
   {}
 
   nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData)
   {
     nsCOMPtr<nsIInputStream> stream;
     nsresult rv = ImageEncoder::ExtractDataInternal(mType,
                                                     mOptions,
                                                     mImageBuffer.get(),
                                                     mFormat,
                                                     mSize,
+                                                    mUsePlaceholder,
                                                     mImage,
                                                     nullptr,
                                                     nullptr,
                                                     getter_AddRefs(stream),
                                                     mEncoder);
 
     // 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 && mUsingCustomOptions) {
       rv = ImageEncoder::ExtractDataInternal(mType,
                                              EmptyString(),
                                              mImageBuffer.get(),
                                              mFormat,
                                              mSize,
+                                             mUsePlaceholder,
                                              mImage,
                                              nullptr,
                                              nullptr,
                                              getter_AddRefs(stream),
                                              mEncoder);
     }
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -234,47 +238,51 @@ private:
   nsAutoString mType;
   nsAutoString mOptions;
   UniquePtr<uint8_t[]> mImageBuffer;
   RefPtr<layers::Image> mImage;
   nsCOMPtr<imgIEncoder> mEncoder;
   RefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
   int32_t mFormat;
   const nsIntSize mSize;
+  bool mUsePlaceholder;
   bool mUsingCustomOptions;
 };
 
 NS_IMPL_ISUPPORTS_INHERITED0(EncodingRunnable, Runnable);
 
 StaticRefPtr<nsIThreadPool> ImageEncoder::sThreadPool;
 
 /* static */
 nsresult
 ImageEncoder::ExtractData(nsAString& aType,
                           const nsAString& aOptions,
                           const nsIntSize aSize,
+                          bool aUsePlaceholder,
                           nsICanvasRenderingContextInternal* aContext,
                           layers::AsyncCanvasRenderer* aRenderer,
                           nsIInputStream** aStream)
 {
   nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
   if (!encoder) {
     return NS_IMAGELIB_ERROR_NO_ENCODER;
   }
 
-  return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr,
+  return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize,
+                             aUsePlaceholder, nullptr,
                              aContext, aRenderer, aStream, encoder);
 }
 
 /* static */
 nsresult
 ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
                                               const nsAString& aOptions,
                                               bool aUsingCustomOptions,
                                               layers::Image* aImage,
+                                              bool aUsePlaceholder,
                                               EncodeCompleteCallback* aEncodeCallback)
 {
   nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
   if (!encoder) {
     return NS_IMAGELIB_ERROR_NO_ENCODER;
   }
 
   nsresult rv = EnsureThreadPool();
@@ -289,28 +297,30 @@ ImageEncoder::ExtractDataFromLayersImage
   nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
                                                      aOptions,
                                                      nullptr,
                                                      aImage,
                                                      encoder,
                                                      completeEvent,
                                                      imgIEncoder::INPUT_FORMAT_HOSTARGB,
                                                      size,
+                                                     aUsePlaceholder,
                                                      aUsingCustomOptions);
   return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
 }
 
 /* static */
 nsresult
 ImageEncoder::ExtractDataAsync(nsAString& aType,
                                const nsAString& aOptions,
                                bool aUsingCustomOptions,
                                UniquePtr<uint8_t[]> aImageBuffer,
                                int32_t aFormat,
                                const nsIntSize aSize,
+                               bool aUsePlaceholder,
                                EncodeCompleteCallback* aEncodeCallback)
 {
   nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
   if (!encoder) {
     return NS_IMAGELIB_ERROR_NO_ENCODER;
   }
 
   nsresult rv = EnsureThreadPool();
@@ -324,16 +334,17 @@ ImageEncoder::ExtractDataAsync(nsAString
   nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
                                                      aOptions,
                                                      Move(aImageBuffer),
                                                      nullptr,
                                                      encoder,
                                                      completeEvent,
                                                      aFormat,
                                                      aSize,
+                                                     aUsePlaceholder,
                                                      aUsingCustomOptions);
   return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
 }
 
 /*static*/ nsresult
 ImageEncoder::GetInputStream(int32_t aWidth,
                              int32_t aHeight,
                              uint8_t* aImageBuffer,
@@ -354,54 +365,55 @@ ImageEncoder::GetInputStream(int32_t aWi
 
 /* static */
 nsresult
 ImageEncoder::ExtractDataInternal(const nsAString& aType,
                                   const nsAString& aOptions,
                                   uint8_t* aImageBuffer,
                                   int32_t aFormat,
                                   const nsIntSize aSize,
+                                  bool aUsePlaceholder,
                                   layers::Image* aImage,
                                   nsICanvasRenderingContextInternal* aContext,
                                   layers::AsyncCanvasRenderer* aRenderer,
                                   nsIInputStream** aStream,
                                   imgIEncoder* aEncoder)
 {
   if (aSize.IsEmpty()) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsCOMPtr<nsIInputStream> imgStream;
 
   // get image bytes
   nsresult rv;
-  if (aImageBuffer) {
+  if (aImageBuffer && !aUsePlaceholder) {
     if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
       return NS_ERROR_INVALID_ARG;
     }
 
     rv = ImageEncoder::GetInputStream(
       aSize.width,
       aSize.height,
       aImageBuffer,
       aFormat,
       aEncoder,
       nsPromiseFlatString(aOptions).get(),
       getter_AddRefs(imgStream));
-  } else if (aContext) {
+  } else if (aContext && !aUsePlaceholder) {
     NS_ConvertUTF16toUTF8 encoderType(aType);
     rv = aContext->GetInputStream(encoderType.get(),
                                   nsPromiseFlatString(aOptions).get(),
                                   getter_AddRefs(imgStream));
-  } else if (aRenderer) {
+  } else if (aRenderer && !aUsePlaceholder) {
     NS_ConvertUTF16toUTF8 encoderType(aType);
     rv = aRenderer->GetInputStream(encoderType.get(),
                                    nsPromiseFlatString(aOptions).get(),
                                    getter_AddRefs(imgStream));
-  } else if (aImage) {
+  } else if (aImage && !aUsePlaceholder) {
     // It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread.
     // Other image formats could have problem to convert format off-main-thread.
     // So here it uses a help function GetBRGADataSourceSurfaceSync() to convert
     // format on main thread.
     if (aImage->GetFormat() == ImageFormat::PLANAR_YCBCR) {
       nsTArray<uint8_t> data;
       layers::PlanarYCbCrImage* ycbcrImage = static_cast<layers::PlanarYCbCrImage*> (aImage);
       gfxImageFormat format = SurfaceFormat::A8R8G8B8_UINT32;
@@ -467,16 +479,20 @@ ImageEncoder::ExtractDataInternal(const 
     if (NS_WARN_IF(!emptyCanvas)) {
       return NS_ERROR_INVALID_ARG;
     }
 
     DataSourceSurface::MappedSurface map;
     if (!emptyCanvas->Map(DataSourceSurface::MapType::WRITE, &map)) {
       return NS_ERROR_INVALID_ARG;
     }
+    if (aUsePlaceholder) {
+      // If placeholder data was requested, return all-white, opaque image data.
+      memset(map.mData, 0xFF, 4 * aSize.width * aSize.height);
+    }
     rv = aEncoder->InitFromData(map.mData,
                                 aSize.width * aSize.height * 4,
                                 aSize.width,
                                 aSize.height,
                                 aSize.width * 4,
                                 imgIEncoder::INPUT_FORMAT_HOSTARGB,
                                 aOptions);
     emptyCanvas->Unmap();
--- a/dom/base/ImageEncoder.h
+++ b/dom/base/ImageEncoder.h
@@ -37,16 +37,17 @@ public:
   // represented by aContext. aType may change to "image/png" if we had to fall
   // back to a PNG encoder. A return value of NS_OK implies successful data
   // extraction. If there are any unrecognized custom parse options in
   // aOptions, NS_ERROR_INVALID_ARG will be returned. When encountering this
   // error it is usual to call this function again without any options at all.
   static nsresult ExtractData(nsAString& aType,
                               const nsAString& aOptions,
                               const nsIntSize aSize,
+                              bool aUsePlaceholder,
                               nsICanvasRenderingContextInternal* aContext,
                               layers::AsyncCanvasRenderer* aRenderer,
                               nsIInputStream** aStream);
 
   // Extracts data asynchronously. aType may change to "image/png" if we had to
   // fall back to a PNG encoder. aOptions are the options to be passed to the
   // encoder and aUsingCustomOptions specifies whether custom parse options were
   // used (i.e. by using -moz-parse-options). If there are any unrecognized
@@ -58,27 +59,29 @@ public:
   // Note: The callback has to set a valid parent for content for the generated
   // Blob object.
   static nsresult ExtractDataAsync(nsAString& aType,
                                    const nsAString& aOptions,
                                    bool aUsingCustomOptions,
                                    UniquePtr<uint8_t[]> aImageBuffer,
                                    int32_t aFormat,
                                    const nsIntSize aSize,
+                                   bool aUsePlaceholder,
                                    EncodeCompleteCallback* aEncodeCallback);
 
   // Extract an Image asynchronously. Its function is same as ExtractDataAsync
   // except for the parameters. aImage is the uncompressed data. aEncodeCallback
   // will be called on main thread when encoding process is success.
   // Note: The callback has to set a valid parent for content for the generated
   // Blob object.
   static nsresult ExtractDataFromLayersImageAsync(nsAString& aType,
                                                   const nsAString& aOptions,
                                                   bool aUsingCustomOptions,
                                                   layers::Image* aImage,
+                                                  bool aUsePlaceholder,
                                                   EncodeCompleteCallback* aEncodeCallback);
 
   // Gives you a stream containing the image represented by aImageBuffer.
   // The format is given in aFormat, for example
   // imgIEncoder::INPUT_FORMAT_HOSTARGB.
   static nsresult GetInputStream(int32_t aWidth,
                                  int32_t aHeight,
                                  uint8_t* aImageBuffer,
@@ -90,16 +93,17 @@ public:
 private:
   // When called asynchronously, aContext and aRenderer are null.
   static nsresult
   ExtractDataInternal(const nsAString& aType,
                       const nsAString& aOptions,
                       uint8_t* aImageBuffer,
                       int32_t aFormat,
                       const nsIntSize aSize,
+                      bool aUsePlaceholder,
                       layers::Image* aImage,
                       nsICanvasRenderingContextInternal* aContext,
                       layers::AsyncCanvasRenderer* aRenderer,
                       nsIInputStream** aStream,
                       imgIEncoder* aEncoder);
 
   // Creates and returns an encoder instance of the type specified in aType.
   // aType may change to "image/png" if no instance of the original type could