Bug 1081926 - Fallback on a simple image lookup when the normal lookup fails. r=mattwoodrow
authorMichael Wu <mwu@mozilla.com>
Wed, 29 Oct 2014 16:17:00 +0100
changeset 213167 b365ed0223370bc2562f1ef3fedd1936bcadcdad
parent 213166 cd5e08af2e43ee726a4081b878b4e280b7f6e564
child 213168 07daf18bf013c5649eda03804e137ccb9a958ba2
push id27742
push userryanvm@gmail.com
push dateThu, 30 Oct 2014 20:15:35 +0000
treeherdermozilla-central@e0b505a37b1c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1081926
milestone36.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 1081926 - Fallback on a simple image lookup when the normal lookup fails. r=mattwoodrow
dom/canvas/CanvasImageCache.cpp
dom/canvas/CanvasImageCache.h
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
--- a/dom/canvas/CanvasImageCache.cpp
+++ b/dom/canvas/CanvasImageCache.cpp
@@ -77,16 +77,46 @@ public:
   {
     return HashGeneric(key->mImage, key->mCanvas);
   }
   enum { ALLOW_MEMMOVE = true };
 
   nsAutoPtr<ImageCacheEntryData> mData;
 };
 
+class SimpleImageCacheEntry : public PLDHashEntryHdr {
+public:
+  typedef imgIRequest& KeyType;
+  typedef const imgIRequest* KeyTypePointer;
+
+  explicit SimpleImageCacheEntry(KeyTypePointer aKey)
+    : mRequest(const_cast<imgIRequest*>(aKey))
+  {}
+  SimpleImageCacheEntry(const SimpleImageCacheEntry &toCopy)
+    : mRequest(toCopy.mRequest)
+    , mSourceSurface(toCopy.mSourceSurface)
+  {}
+  ~SimpleImageCacheEntry() {}
+
+  bool KeyEquals(KeyTypePointer key) const
+  {
+    return key == mRequest;
+  }
+
+  static KeyTypePointer KeyToPointer(KeyType key) { return &key; }
+  static PLDHashNumber HashKey(KeyTypePointer key)
+  {
+    return NS_PTR_TO_UINT32(key) >> 2;
+  }
+  enum { ALLOW_MEMMOVE = true };
+
+  nsCOMPtr<imgIRequest> mRequest;
+  RefPtr<SourceSurface> mSourceSurface;
+};
+
 static bool sPrefsInitialized = false;
 static int32_t sCanvasImageCacheLimit = 0;
 
 class ImageCacheObserver;
 
 class ImageCache MOZ_FINAL : public nsExpirationTracker<ImageCacheEntryData,4> {
 public:
   // We use 3 generations of 1 second each to get a 2-3 seconds timeout.
@@ -94,20 +124,22 @@ public:
   ImageCache();
   ~ImageCache();
 
   virtual void NotifyExpired(ImageCacheEntryData* aObject)
   {
     mTotal -= aObject->SizeInBytes();
     RemoveObject(aObject);
     // Deleting the entry will delete aObject since the entry owns aObject
+    mSimpleCache.RemoveEntry(*aObject->mRequest);
     mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas));
   }
 
   nsTHashtable<ImageCacheEntry> mCache;
+  nsTHashtable<SimpleImageCacheEntry> mSimpleCache;
   size_t mTotal;
   nsRefPtr<ImageCacheObserver> mImageCacheObserver;
 };
 
 static ImageCache* gImageCache = nullptr;
 
 // Listen memory-pressure event for image cache purge
 class ImageCacheObserver MOZ_FINAL : public nsIObserver
@@ -225,16 +257,22 @@ CanvasImageCache::NotifyDrawImage(Elemen
       ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                       getter_AddRefs(entry->mData->mRequest));
     }
     entry->mData->mILC = ilc;
     entry->mData->mSourceSurface = aSource;
     entry->mData->mSize = aSize;
 
     gImageCache->mTotal += entry->mData->SizeInBytes();
+
+    if (entry->mData->mRequest) {
+      SimpleImageCacheEntry* simpleentry =
+        gImageCache->mSimpleCache.PutEntry(*entry->mData->mRequest);
+      simpleentry->mSourceSurface = aSource;
+    }
   }
 
   if (!sCanvasImageCacheLimit)
     return;
 
   // Expire the image cache early if its larger than we want it to be.
   while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit))
     gImageCache->AgeOneGeneration();
@@ -258,16 +296,39 @@ CanvasImageCache::Lookup(Element* aImage
     return nullptr;
 
   gImageCache->MarkUsed(entry->mData);
 
   *aSize = entry->mData->mSize;
   return entry->mData->mSourceSurface;
 }
 
+SourceSurface*
+CanvasImageCache::SimpleLookup(Element* aImage)
+{
+  if (!gImageCache)
+    return nullptr;
+
+  nsCOMPtr<imgIRequest> request;
+  nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
+  if (!ilc)
+    return nullptr;
+
+  ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+                  getter_AddRefs(request));
+  if (!request)
+    return nullptr;
+
+  SimpleImageCacheEntry* entry = gImageCache->mSimpleCache.GetEntry(*request);
+  if (!entry)
+    return nullptr;
+
+  return entry->mSourceSurface;
+}
+
 NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver)
 
 NS_IMETHODIMP
 CanvasImageCacheShutdownObserver::Observe(nsISupports *aSubject,
                                           const char *aTopic,
                                           const char16_t *aData)
 {
   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
--- a/dom/canvas/CanvasImageCache.h
+++ b/dom/canvas/CanvasImageCache.h
@@ -39,13 +39,20 @@ public:
    * Check whether aImage has recently been drawn into aCanvas. If we return
    * a non-null surface, then the image was recently drawn into the canvas
    * (with the same image request) and the returned surface contains the image
    * data, and the image size will be returned in aSize.
    */
   static SourceSurface* Lookup(dom::Element* aImage,
                                dom::HTMLCanvasElement* aCanvas,
                                gfxIntSize* aSize);
+
+  /**
+   * This is the same as Lookup, except it works on any image recently drawn
+   * into any canvas. Security checks need to be done again if using the
+   * results from this.
+   */
+  static SourceSurface* SimpleLookup(dom::Element* aImage);
 };
 
 }
 
 #endif /* CANVASIMAGECACHE_H_ */
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3875,16 +3875,66 @@ ExtractSubrect(SourceSurface* aSurface, 
   }
 
   *aSourceRect -= roundedOutSourceRect.TopLeft();
 
   subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint());
   return subrectDT->Snapshot();
 }
 
+// Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt
+// to pull a SourceSurface from our cache. This allows us to avoid
+// reoptimizing surfaces if content and canvas backends are different.
+nsLayoutUtils::SurfaceFromElementResult
+CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement)
+{
+  nsLayoutUtils::SurfaceFromElementResult res;
+
+  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
+  if (!imageLoader) {
+    return res;
+  }
+
+  nsCOMPtr<imgIRequest> imgRequest;
+  imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+                          getter_AddRefs(imgRequest));
+  if (!imgRequest) {
+    return res;
+  }
+
+  uint32_t status;
+  if (NS_FAILED(imgRequest->GetImageStatus(&status)) ||
+      !(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+    return res;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal;
+  if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) ||
+      !principal) {
+    return res;
+  }
+
+  res.mSourceSurface = CanvasImageCache::SimpleLookup(aElement);
+  if (!res.mSourceSurface) {
+    return res;
+  }
+
+  int32_t corsmode = imgIRequest::CORS_NONE;
+  if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
+    res.mCORSUsed = corsmode != imgIRequest::CORS_NONE;
+  }
+
+  res.mSize = ThebesIntSize(res.mSourceSurface->GetSize());
+  res.mPrincipal = principal.forget();
+  res.mIsWriteOnly = false;
+  res.mImageRequest = imgRequest.forget();
+
+  return res;
+}
+
 //
 // image
 //
 
 // drawImage(in HTMLImageElement image, in float dx, in float dy);
 //   -- render image from 0,0 at dx,dy top-left coords
 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh);
 //   -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh
@@ -3935,18 +3985,25 @@ CanvasRenderingContext2D::DrawImage(cons
 
   nsLayoutUtils::DirectDrawInfo drawInfo;
 
   if (!srcSurf) {
     // The canvas spec says that drawImage should draw the first frame
     // of animated images. We also don't want to rasterize vector images.
     uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME |
                         nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS;
+    // The cache lookup can miss even if the image is already in the cache
+    // if the image is coming from a different element or cached for a
+    // different canvas. This covers the case when we miss due to caching
+    // for a different canvas, but CanvasImageCache should be fixed if we
+    // see misses due to different elements drawing the same image.
     nsLayoutUtils::SurfaceFromElementResult res =
-      nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
+      CachedSurfaceFromElement(element);
+    if (!res.mSourceSurface)
+      res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
 
     if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
       // Spec says to silently do nothing if the element is still loading.
       if (!res.mIsStillLoading) {
         error.Throw(NS_ERROR_NOT_AVAILABLE);
       }
       return;
     }
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -709,16 +709,19 @@ protected:
   mozilla::gfx::SurfaceFormat GetSurfaceFormat() const;
 
   /**
    * Update CurrentState().filter with the filter description for
    * CurrentState().filterChain.
    */
   void UpdateFilter();
 
+  nsLayoutUtils::SurfaceFromElementResult
+    CachedSurfaceFromElement(Element* aElement);
+
   void DrawImage(const HTMLImageOrCanvasOrVideoElement &imgElt,
                  double sx, double sy, double sw, double sh,
                  double dx, double dy, double dw, double dh,
                  uint8_t optional_argc, mozilla::ErrorResult& error);
 
   void DrawDirectlyToCanvas(const nsLayoutUtils::DirectDrawInfo& image,
                             mozilla::gfx::Rect* bounds,
                             mozilla::gfx::Rect dest,