Bug 1301863 - Tell the JS engine how much memory blob reflectors hold alive r=baku
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 12 Sep 2017 10:46:51 +0100
changeset 429769 6187a252c146962dbc6554c0406152fefef96313
parent 429768 485c8d248bfc4acc97f92cdc130210d7388bca32
child 429770 78cd991b074fff879264429397f6cb01d7ce8522
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1301863
milestone57.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 1301863 - Tell the JS engine how much memory blob reflectors hold alive r=baku
dom/bindings/BindingUtils.h
dom/canvas/CanvasRenderingContextHelper.cpp
dom/canvas/OffscreenCanvas.cpp
dom/file/BaseBlobImpl.h
dom/file/Blob.cpp
dom/file/Blob.h
dom/file/BlobImpl.h
dom/file/MemoryBlobImpl.h
dom/file/MultipartBlobImpl.cpp
dom/file/MultipartBlobImpl.h
dom/file/StreamBlobImpl.cpp
dom/file/StreamBlobImpl.h
dom/file/StringBlobImpl.h
dom/html/HTMLCanvasElement.cpp
dom/indexedDB/FileSnapshot.h
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2814,16 +2814,28 @@ ToSupportsIsCorrect(T* aObject)
 template<class T>
 bool
 ToSupportsIsOnPrimaryInheritanceChain(T* aObject, nsWrapperCache* aCache)
 {
   return CastingAssertions<T>::ToSupportsIsOnPrimaryInheritanceChain(aObject,
                                                                      aCache);
 }
 
+// Get the size of allocated memory to associate with a binding JSObject for a
+// native object. This is supplied to the JS engine to allow it to schedule GC
+// when necessary.
+//
+// This function supplies a default value and is overloaded for specific native
+// object types.
+inline uint64_t
+BindingJSObjectMallocBytes(void *aNativePtr)
+{
+  return 0;
+}
+
 // The BindingJSObjectCreator class is supposed to be used by a caller that
 // wants to create and initialise a binding JSObject. After initialisation has
 // been successfully completed it should call ForgetObject().
 // The BindingJSObjectCreator object will root the JSObject until ForgetObject()
 // is called on it. If the native object for the binding is refcounted it will
 // also hold a strong reference to it, that reference is transferred to the
 // JSObject (which holds the native in a slot) when ForgetObject() is called. If
 // the BindingJSObjectCreator object is destroyed and ForgetObject() was never
@@ -2856,29 +2868,37 @@ public:
     options.setClass(aClass);
     aReflector.set(js::NewProxyObject(aCx, aHandler, aExpandoValue, aProto,
                                       options));
     if (aReflector) {
       js::SetProxyReservedSlot(aReflector, DOM_OBJECT_SLOT, JS::PrivateValue(aNative));
       mNative = aNative;
       mReflector = aReflector;
     }
+
+    if (uint64_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
+      JS_updateMallocCounter(aCx, mallocBytes);
+    }
   }
 
   void
   CreateObject(JSContext* aCx, const JSClass* aClass,
                JS::Handle<JSObject*> aProto,
                T* aNative, JS::MutableHandle<JSObject*> aReflector)
   {
     aReflector.set(JS_NewObjectWithGivenProto(aCx, aClass, aProto));
     if (aReflector) {
       js::SetReservedSlot(aReflector, DOM_OBJECT_SLOT, JS::PrivateValue(aNative));
       mNative = aNative;
       mReflector = aReflector;
     }
+
+    if (uint64_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
+      JS_updateMallocCounter(aCx, mallocBytes);
+    }
   }
 
   void
   InitializationSucceeded()
   {
     void* dummy;
     mNative.forget(&dummy);
     mReflector = nullptr;
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -35,29 +35,19 @@ CanvasRenderingContextHelper::ToBlob(JSC
       : mGlobal(aGlobal)
       , mBlobCallback(aCallback) {}
 
     // This is called on main thread.
     nsresult ReceiveBlob(already_AddRefed<Blob> aBlob)
     {
       RefPtr<Blob> blob = aBlob;
 
-      ErrorResult rv;
-      uint64_t size = blob->GetSize(rv);
-      if (rv.Failed()) {
-        rv.SuppressException();
-      } else {
-        AutoJSAPI jsapi;
-        if (jsapi.Init(mGlobal)) {
-          JS_updateMallocCounter(jsapi.cx(), size);
-        }
-      }
-
       RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
 
+      ErrorResult rv;
       mBlobCallback->Call(newBlob, rv);
 
       mGlobal = nullptr;
       mBlobCallback = nullptr;
 
       return rv.StealNSResult();
     }
 
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -261,36 +261,25 @@ OffscreenCanvas::ToBlob(JSContext* aCx,
       : mGlobal(aGlobal)
       , mPromise(aPromise) {}
 
     // This is called on main thread.
     nsresult ReceiveBlob(already_AddRefed<Blob> aBlob)
     {
       RefPtr<Blob> blob = aBlob;
 
-      ErrorResult rv;
-      uint64_t size = blob->GetSize(rv);
-      if (rv.Failed()) {
-        rv.SuppressException();
-      } else {
-        AutoJSAPI jsapi;
-        if (jsapi.Init(mGlobal)) {
-          JS_updateMallocCounter(jsapi.cx(), size);
-        }
-      }
-
       if (mPromise) {
         RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
         mPromise->MaybeResolve(newBlob);
       }
 
       mGlobal = nullptr;
       mPromise = nullptr;
 
-      return rv.StealNSResult();
+      return NS_OK;
     }
 
     nsCOMPtr<nsIGlobalObject> mGlobal;
     RefPtr<Promise> mPromise;
   };
 
   RefPtr<EncodeCompleteCallback> callback =
     new EncodeCallback(global, promise);
--- a/dom/file/BaseBlobImpl.h
+++ b/dom/file/BaseBlobImpl.h
@@ -92,16 +92,21 @@ public:
 
   virtual uint64_t GetSize(ErrorResult& aRv) override
   {
     return mLength;
   }
 
   virtual void GetType(nsAString& aType) override;
 
+  size_t GetAllocationSize() const override
+  {
+    return 0;
+  }
+
   virtual uint64_t GetSerialNumber() const override { return mSerialNumber; }
 
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType, ErrorResult& aRv) override
   {
     return nullptr;
   }
--- a/dom/file/Blob.cpp
+++ b/dom/file/Blob.cpp
@@ -209,16 +209,22 @@ Blob::Slice(const Optional<int64_t>& aSt
   if (aRv.Failed()) {
     return nullptr;
   }
 
   RefPtr<Blob> blob = Blob::Create(mParent, impl);
   return blob.forget();
 }
 
+size_t
+Blob::GetAllocationSize() const
+{
+  return mImpl->GetAllocationSize();
+}
+
 NS_IMETHODIMP
 Blob::GetSendInfo(nsIInputStream** aBody,
                   uint64_t* aContentLength,
                   nsACString& aContentType,
                   nsACString& aCharset)
 {
   return mImpl->GetSendInfo(aBody, aContentLength, aContentType, aCharset);
 }
@@ -281,10 +287,17 @@ Blob::IsMemoryFile() const
 }
 
 void
 Blob::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv)
 {
   mImpl->GetInternalStream(aStream, aRv);
 }
 
+uint64_t
+BindingJSObjectMallocBytes(Blob* aBlob)
+{
+  MOZ_ASSERT(aBlob);
+  return aBlob->GetAllocationSize();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/file/Blob.h
+++ b/dom/file/Blob.h
@@ -125,16 +125,18 @@ public:
 
   void GetType(nsAString& aType);
 
   already_AddRefed<Blob> Slice(const Optional<int64_t>& aStart,
                                const Optional<int64_t>& aEnd,
                                const nsAString& aContentType,
                                ErrorResult& aRv);
 
+  size_t GetAllocationSize() const;
+
 protected:
   // File constructor should never be used directly. Use Blob::Create instead.
   Blob(nsISupports* aParent, BlobImpl* aImpl);
   virtual ~Blob();
 
   virtual bool HasFileInterface() const { return false; }
 
   // The member is the real backend implementation of this File/Blob.
@@ -142,12 +144,16 @@ protected:
   // between threads.
   // Note: we should not store any other state in this class!
   RefPtr<BlobImpl> mImpl;
 
 private:
   nsCOMPtr<nsISupports> mParent;
 };
 
+// Override BindingJSObjectMallocBytes for blobs to tell the JS GC how much
+// memory is held live by the binding object.
+uint64_t BindingJSObjectMallocBytes(Blob* aBlob);
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Blob_h
--- a/dom/file/BlobImpl.h
+++ b/dom/file/BlobImpl.h
@@ -46,16 +46,18 @@ public:
                               ErrorResult& aRv) const = 0;
 
   virtual void GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const = 0;
 
   virtual uint64_t GetSize(ErrorResult& aRv) = 0;
 
   virtual void GetType(nsAString& aType) = 0;
 
+  virtual size_t GetAllocationSize() const = 0;
+
   /**
    * An effectively-unique serial number identifying this instance of FileImpl.
    *
    * Implementations should obtain a serial number from
    * FileImplBase::NextSerialNumber().
    */
   virtual uint64_t GetSerialNumber() const = 0;
 
--- a/dom/file/MemoryBlobImpl.h
+++ b/dom/file/MemoryBlobImpl.h
@@ -48,16 +48,21 @@ public:
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType, ErrorResult& aRv) override;
 
   virtual bool IsMemoryFile() const override
   {
     return true;
   }
 
+  size_t GetAllocationSize() const override
+  {
+    return mLength;
+  }
+
   class DataOwner final : public mozilla::LinkedListElement<DataOwner>
   {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataOwner)
     DataOwner(void* aMemoryBuffer, uint64_t aLength)
       : mData(aMemoryBuffer)
       , mLength(aLength)
     {
--- a/dom/file/MultipartBlobImpl.cpp
+++ b/dom/file/MultipartBlobImpl.cpp
@@ -406,8 +406,18 @@ MultipartBlobImpl::MayBeClonedToOtherThr
   for (uint32_t i = 0; i < mBlobImpls.Length(); ++i) {
     if (!mBlobImpls[i]->MayBeClonedToOtherThreads()) {
       return false;
     }
   }
 
   return true;
 }
+
+size_t MultipartBlobImpl::GetAllocationSize() const
+{
+  size_t total = 0;
+  for (uint32_t i = 0; i < mBlobImpls.Length(); ++i) {
+    total += mBlobImpls[i]->GetAllocationSize();
+  }
+
+  return total;
+}
--- a/dom/file/MultipartBlobImpl.h
+++ b/dom/file/MultipartBlobImpl.h
@@ -87,16 +87,18 @@ public:
 
   void SetName(const nsAString& aName)
   {
     mName = aName;
   }
 
   virtual bool MayBeClonedToOtherThreads() const override;
 
+  size_t GetAllocationSize() const override;
+
 protected:
   MultipartBlobImpl(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
                     const nsAString& aName,
                     const nsAString& aContentType)
     : BaseBlobImpl(aName, aContentType, UINT64_MAX),
       mBlobImpls(Move(aBlobImpls)),
       mIsFromNsIFile(false)
   {
--- a/dom/file/StreamBlobImpl.cpp
+++ b/dom/file/StreamBlobImpl.cpp
@@ -151,10 +151,22 @@ StreamBlobImpl::CollectReports(nsIHandle
   MOZ_COLLECT_REPORT(
     "explicit/dom/memory-file-data/stream", KIND_HEAP, UNITS_BYTES,
     stringInputStream->SizeOfIncludingThis(MallocSizeOf),
     "Memory used to back a File/Blob based on an input stream.");
 
   return NS_OK;
 }
 
+size_t
+StreamBlobImpl::GetAllocationSize() const
+{
+  nsCOMPtr<nsIStringInputStream> stringInputStream =
+    do_QueryInterface(mInputStream);
+  if (!stringInputStream) {
+    return 0;
+  }
+
+  return stringInputStream->SizeOfIncludingThis(MallocSizeOf);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/file/StreamBlobImpl.h
+++ b/dom/file/StreamBlobImpl.h
@@ -72,16 +72,18 @@ public:
     mIsDirectory = aIsDirectory;
   }
 
   bool IsDirectory() const override
   {
     return mIsDirectory;
   }
 
+  size_t GetAllocationSize() const override;
+
 private:
   StreamBlobImpl(nsIInputStream* aInputStream,
                  const nsAString& aContentType,
                  uint64_t aLength);
 
   StreamBlobImpl(nsIInputStream* aInputStream,
                  const nsAString& aName,
                  const nsAString& aContentType,
--- a/dom/file/StringBlobImpl.h
+++ b/dom/file/StringBlobImpl.h
@@ -27,16 +27,21 @@ public:
 
   virtual void GetInternalStream(nsIInputStream** aStream,
                                  ErrorResult& aRv) override;
 
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType, ErrorResult& aRv) override;
 
+  size_t GetAllocationSize() const override
+  {
+    return mData.Length();
+  }
+
 private:
   StringBlobImpl(const nsACString& aData, const nsAString& aContentType);
 
   ~StringBlobImpl();
 
   nsCString mData;
 };
 
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -937,21 +937,16 @@ HTMLCanvasElement::MozGetAsFileImpl(cons
   rv = stream->Available(&imgSize);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
   void* imgData = nullptr;
   rv = NS_ReadInputStreamToBuffer(stream, &imgData, (uint32_t)imgSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  JSContext* cx = nsContentUtils::GetCurrentJSContext();
-  if (cx) {
-    JS_updateMallocCounter(cx, imgSize);
-  }
-
   nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(OwnerDoc()->GetScopeObject());
 
   // The File takes ownership of the buffer
   RefPtr<File> file =
     File::CreateMemoryFile(win, imgData, (uint32_t)imgSize, aName, type,
                            PR_Now());
 
   file.forget(aResult);
--- a/dom/indexedDB/FileSnapshot.h
+++ b/dom/indexedDB/FileSnapshot.h
@@ -103,16 +103,22 @@ private:
   }
 
   virtual void
   GetType(nsAString& aType) override
   {
     mBlobImpl->GetType(aType);
   }
 
+  size_t
+  GetAllocationSize() const override
+  {
+    return mBlobImpl->GetAllocationSize();
+  }
+
   virtual uint64_t
   GetSerialNumber() const override
   {
     return mBlobImpl->GetSerialNumber();
   }
 
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t aStart,