Bug 1081926 - Fallback on a simple image lookup when the normal lookup fails. r=mattwoodrow, a=lmandel
authorMichael Wu <mwu@mozilla.com>
Wed, 29 Oct 2014 16:17:00 +0100
changeset 225892 546105a6d5c0
parent 225891 06d2090db817
child 225893 8977f5061773
push id4059
push userryanvm@gmail.com
push date2014-10-31 21:13 +0000
treeherdermozilla-beta@631a73cdbc91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, lmandel
bugs1081926
milestone34.0
Bug 1081926 - Fallback on a simple image lookup when the normal lookup fails. r=mattwoodrow, a=lmandel
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
@@ -3269,16 +3269,66 @@ bool CanvasRenderingContext2D::IsPointIn
                               state.miterLimit,
                               state.dash.Length(),
                               state.dash.Elements(),
                               state.dashOffset);
 
   return tempPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform());
 }
 
+// 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
@@ -3329,18 +3379,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
@@ -658,16 +658,19 @@ protected:
   bool IsTargetValid() { return mTarget != sErrorTarget && mTarget != nullptr; }
 
   /**
     * Returns the surface format this canvas should be allocated using. Takes
     * into account mOpaque, platform requirements, etc.
     */
   mozilla::gfx::SurfaceFormat GetSurfaceFormat() const;
 
+  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,