Bug 1312148 - report memory allocation while creating ImageBitmap; r?smaug, mtseng draft
authorKaku Kuo <kaku@mozilla.com>
Fri, 28 Oct 2016 18:18:48 +0800
changeset 441712 f7cf9531c8cd3c524fb5e296ed1d4efae60b1405
parent 441455 f09e137ead39230eaa94f47988ccce2cfcda4195
child 537623 5ad8478d405ebb4243d4b4e2d9220f456eced368
push id36506
push userbmo:kaku@mozilla.com
push dateMon, 21 Nov 2016 03:27:15 +0000
reviewerssmaug, mtseng
bugs1312148
milestone53.0a1
Bug 1312148 - report memory allocation while creating ImageBitmap; r?smaug, mtseng Creating ImageBitmap from the following sources includes allocating new memory: (1) from ImageData. (2) from Blob. (3) from HTMLCanvasElement. (4) from CanvasRenderingContext2D. (5) from Structured-clone. (6) from Transferring. (7) from OffscreenCanvas. (8) from ArrayBuffer/TypedArray. We need to report to DOM so that the GC would be triggered appropriately. MozReview-Commit-ID: 7rvNsjVNqpz
dom/canvas/ImageBitmap.cpp
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -30,16 +30,55 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Im
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmap)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmap)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmap)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 /*
+ * This helper function is used to notify DOM that aBytes memory is allocated
+ * here so that we could trigger GC appropriately.
+ */
+static void
+RegisterAllocation(nsIGlobalObject* aGlobal, size_t aBytes)
+{
+  AutoJSAPI jsapi;
+  if (jsapi.Init(aGlobal)) {
+    JS_updateMallocCounter(jsapi.cx(), aBytes);
+  }
+}
+
+static void
+RegisterAllocation(nsIGlobalObject* aGlobal, SourceSurface* aSurface)
+{
+  // Calculate how many bytes are used.
+  const int bytesPerPixel = BytesPerPixel(aSurface->GetFormat());
+  const size_t bytes =
+    aSurface->GetSize().height * aSurface->GetSize().width * bytesPerPixel;
+
+  // Register.
+  RegisterAllocation(aGlobal, bytes);
+}
+
+static void
+RegisterAllocation(nsIGlobalObject* aGlobal, layers::Image* aImage)
+{
+  // Calculate how many bytes are used.
+  if (aImage->GetFormat() == mozilla::ImageFormat::PLANAR_YCBCR) {
+    RegisterAllocation(aGlobal, aImage->AsPlanarYCbCrImage()->GetDataSize());
+  } else if (aImage->GetFormat() == mozilla::ImageFormat::NV_IMAGE) {
+    RegisterAllocation(aGlobal, aImage->AsNVImage()->GetBufferSize());
+  } else {
+    RefPtr<SourceSurface> surface = aImage->GetAsSourceSurface();
+    RegisterAllocation(aGlobal, surface);
+  }
+}
+
+/*
  * If either aRect.width or aRect.height are negative, then return a new IntRect
  * which represents the same rectangle as the aRect does but with positive width
  * and height.
  */
 static IntRect
 FixUpNegativeDimension(const IntRect& aRect, ErrorResult& aRv)
 {
   gfx::IntRect rect = aRect;
@@ -702,16 +741,19 @@ ImageBitmap::ToCloneData()
 ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal,
                                  ImageBitmapCloneData* aData)
 {
   RefPtr<layers::Image> data = CreateImageFromSurface(aData->mSurface);
 
   RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data,
                                             aData->mIsPremultipliedAlpha);
 
+  // Report memory allocation.
+  RegisterAllocation(aGlobal, aData->mSurface);
+
   ret->mIsCroppingAreaOutSideOfSourceImage =
     aData->mIsCroppingAreaOutSideOfSourceImage;
 
   ErrorResult rv;
   ret->SetPictureRect(aData->mPictureRect, rv);
   return ret.forget();
 }
 
@@ -736,16 +778,20 @@ ImageBitmap::CreateFromOffscreenCanvas(n
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<layers::Image> data =
     CreateImageFromSurface(surface);
 
   RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+  // Report memory allocation.
+  RegisterAllocation(aGlobal, surface);
+
   return ret.forget();
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
                             const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
 {
   // Check if the image element is completely available or not.
@@ -860,26 +906,28 @@ ImageBitmap::CreateInternal(nsIGlobalObj
 
   // Crop the source surface if needed.
   RefPtr<SourceSurface> croppedSurface;
   IntRect cropRect = aCropRect.valueOr(IntRect());
 
   // If the HTMLCanvasElement's rendering context is WebGL, then the snapshot
   // we got from the HTMLCanvasElement is a DataSourceSurface which is a copy
   // of the rendering context. We handle cropping in this case.
+  bool needToReportMemoryAllocation = false;
   if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 ||
        aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2) &&
       aCropRect.isSome()) {
     // The _surface_ must be a DataSourceSurface.
     MOZ_ASSERT(surface->GetType() == SurfaceType::DATA,
                "The snapshot SourceSurface from WebGL rendering contest is not \
                DataSourceSurface.");
     RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
     croppedSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect);
     cropRect.MoveTo(0, 0);
+    needToReportMemoryAllocation = true;
   }
   else {
     croppedSurface = surface;
   }
 
   if (NS_WARN_IF(!croppedSurface)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
@@ -890,16 +938,21 @@ ImageBitmap::CreateInternal(nsIGlobalObj
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
+  // Report memory allocation if needed.
+  if (needToReportMemoryAllocation) {
+    RegisterAllocation(aGlobal, croppedSurface);
+  }
+
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(cropRect, aRv);
   }
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
 
@@ -953,16 +1006,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   // Create an ImageBimtap.
   // ImageData's underlying data is not alpha-premultiplied.
   RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, false);
 
+  // Report memory allocation.
+  RegisterAllocation(aGlobal, data);
+
   // The cropping information has been handled in the CreateImageFromRawData()
   // function.
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   ret->SetIsCroppingAreaOutSideOfSourceImage(imageSize, aCropRect);
 
   return ret.forget();
 }
@@ -994,16 +1050,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
 
+  // Report memory allocation.
+  RegisterAllocation(aGlobal, surface);
+
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
 
@@ -1235,16 +1294,19 @@ protected:
       imageBitmap->SetPictureRect(mCropRect.ref(), rv);
 
       if (rv.Failed()) {
         mPromise->MaybeReject(rv);
         return false;
       }
     }
 
+    // Report memory allocation.
+    RegisterAllocation(mGlobalObject, imageBitmap->mData);
+
     mPromise->MaybeResolve(imageBitmap);
     return true;
   }
 
   // Will return null on failure.  In that case, mPromise will already
   // be rejected with the right thing.
   virtual already_AddRefed<ImageBitmap> CreateImageBitmap() = 0;
 
@@ -1519,16 +1581,19 @@ ImageBitmap::ReadStructuredClone(JSConte
     if (NS_WARN_IF(error.Failed())) {
       error.SuppressException();
       return nullptr;
     }
 
     if (!GetOrCreateDOMReflector(aCx, imageBitmap, &value)) {
       return nullptr;
     }
+
+    // Report memory allocation.
+    RegisterAllocation(aParent, aClonedSurfaces[aIndex]);
   }
 
   return &(value.toObject());
 }
 
 /*static*/ bool
 ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
                                   nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
@@ -2107,16 +2172,19 @@ ImageBitmap::Create(nsIGlobalObject* aGl
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return promise.forget();
   }
 
   // Create an ImageBimtap.
   // Assume the data from an external buffer is not alpha-premultiplied.
   RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data, false);
 
+  // Report memory allocation.
+  RegisterAllocation(aGlobal, data);
+
   // We don't need to call SetPictureRect() here because there is no cropping
   // supported and the ImageBitmap's mPictureRect is the size of the source
   // image in default
 
   // We don't need to set mIsCroppingAreaOutSideOfSourceImage here because there
   // is no cropping supported and the mIsCroppingAreaOutSideOfSourceImage is
   // false in default.