tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CanvasImageCache.cpp (12359B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "CanvasImageCache.h"
      7 
      8 #include "gfx2DGlue.h"
      9 #include "imgIRequest.h"
     10 #include "mozilla/Preferences.h"
     11 #include "mozilla/UniquePtr.h"
     12 #include "mozilla/dom/Element.h"
     13 #include "mozilla/gfx/2D.h"
     14 #include "nsContentUtils.h"
     15 #include "nsExpirationTracker.h"
     16 #include "nsIImageLoadingContent.h"
     17 #include "nsTHashtable.h"
     18 
     19 namespace mozilla {
     20 
     21 using namespace dom;
     22 using namespace gfx;
     23 
     24 /**
     25 * Used for images specific to this one canvas. Required
     26 * due to CORS security.
     27 */
     28 struct ImageCacheKey {
     29  ImageCacheKey(imgIContainer* aImage, CanvasRenderingContext2D* aContext,
     30                BackendType aBackendType)
     31      : mImage(aImage), mContext(aContext), mBackendType(aBackendType) {}
     32  nsCOMPtr<imgIContainer> mImage;
     33  CanvasRenderingContext2D* mContext;
     34  BackendType mBackendType;
     35 };
     36 
     37 /**
     38 * Cache data needs to be separate from the entry
     39 * for nsExpirationTracker.
     40 */
     41 struct ImageCacheEntryData {
     42  ImageCacheEntryData(const ImageCacheEntryData& aOther)
     43      : mImage(aOther.mImage),
     44        mContext(aOther.mContext),
     45        mBackendType(aOther.mBackendType),
     46        mSourceSurface(aOther.mSourceSurface),
     47        mSize(aOther.mSize),
     48        mIntrinsicSize(aOther.mIntrinsicSize),
     49        mCropRect(aOther.mCropRect) {}
     50  explicit ImageCacheEntryData(const ImageCacheKey& aKey)
     51      : mImage(aKey.mImage),
     52        mContext(aKey.mContext),
     53        mBackendType(aKey.mBackendType) {}
     54 
     55  nsExpirationState* GetExpirationState() { return &mState; }
     56  size_t SizeInBytes() { return mSize.width * mSize.height * 4; }
     57 
     58  // Key
     59  nsCOMPtr<imgIContainer> mImage;
     60  CanvasRenderingContext2D* mContext;
     61  BackendType mBackendType;
     62  // Value
     63  RefPtr<SourceSurface> mSourceSurface;
     64  IntSize mSize;
     65  IntSize mIntrinsicSize;
     66  Maybe<IntRect> mCropRect;
     67  nsExpirationState mState;
     68 };
     69 
     70 class ImageCacheEntry : public PLDHashEntryHdr {
     71 public:
     72  using KeyType = ImageCacheKey;
     73  using KeyTypePointer = const ImageCacheKey*;
     74 
     75  explicit ImageCacheEntry(const KeyType* aKey)
     76      : mData(new ImageCacheEntryData(*aKey)) {}
     77  ImageCacheEntry(const ImageCacheEntry& toCopy)
     78      : mData(new ImageCacheEntryData(*toCopy.mData)) {}
     79  ~ImageCacheEntry() = default;
     80 
     81  bool KeyEquals(KeyTypePointer key) const {
     82    return mData->mImage == key->mImage && mData->mContext == key->mContext &&
     83           mData->mBackendType == key->mBackendType;
     84  }
     85 
     86  static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
     87  static PLDHashNumber HashKey(KeyTypePointer key) {
     88    return HashGeneric(key->mImage.get(), key->mContext, key->mBackendType);
     89  }
     90  enum { ALLOW_MEMMOVE = true };
     91 
     92  UniquePtr<ImageCacheEntryData> mData;
     93 };
     94 
     95 /**
     96 * Used for all images across all canvases.
     97 */
     98 struct AllCanvasImageCacheKey {
     99  explicit AllCanvasImageCacheKey(imgIContainer* aImage,
    100                                  BackendType aBackendType)
    101      : mImage(aImage), mBackendType(aBackendType) {}
    102 
    103  nsCOMPtr<imgIContainer> mImage;
    104  BackendType mBackendType;
    105 };
    106 
    107 class AllCanvasImageCacheEntry : public PLDHashEntryHdr {
    108 public:
    109  using KeyType = AllCanvasImageCacheKey;
    110  using KeyTypePointer = const AllCanvasImageCacheKey*;
    111 
    112  explicit AllCanvasImageCacheEntry(const KeyType* aKey)
    113      : mImage(aKey->mImage), mBackendType(aKey->mBackendType) {}
    114 
    115  AllCanvasImageCacheEntry(const AllCanvasImageCacheEntry& toCopy)
    116      : mImage(toCopy.mImage),
    117        mSourceSurface(toCopy.mSourceSurface),
    118        mBackendType(toCopy.mBackendType) {}
    119 
    120  ~AllCanvasImageCacheEntry() = default;
    121 
    122  bool KeyEquals(KeyTypePointer key) const {
    123    return mImage == key->mImage && mBackendType == key->mBackendType;
    124  }
    125 
    126  static KeyTypePointer KeyToPointer(KeyType& key) { return &key; }
    127  static PLDHashNumber HashKey(KeyTypePointer key) {
    128    return HashGeneric(key->mImage.get(), key->mBackendType);
    129  }
    130  enum { ALLOW_MEMMOVE = true };
    131 
    132  nsCOMPtr<imgIContainer> mImage;
    133  RefPtr<SourceSurface> mSourceSurface;
    134  BackendType mBackendType;
    135 };
    136 
    137 class ImageCacheObserver;
    138 
    139 class ImageCache final : public nsExpirationTracker<ImageCacheEntryData, 4> {
    140 public:
    141  // We use 3 generations of 1 second each to get a 2-3 seconds timeout.
    142  enum { GENERATION_MS = 1000 };
    143  ImageCache();
    144  ~ImageCache();
    145 
    146  virtual void NotifyExpired(ImageCacheEntryData* aObject) override {
    147    RemoveObject(aObject);
    148 
    149    // Remove from the all canvas cache entry first since nsExpirationTracker
    150    // will delete aObject.
    151    mAllCanvasCache.RemoveEntry(
    152        AllCanvasImageCacheKey(aObject->mImage, aObject->mBackendType));
    153 
    154    // Deleting the entry will delete aObject since the entry owns aObject.
    155    mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mContext,
    156                                     aObject->mBackendType));
    157  }
    158 
    159  nsTHashtable<ImageCacheEntry> mCache;
    160  nsTHashtable<AllCanvasImageCacheEntry> mAllCanvasCache;
    161  RefPtr<ImageCacheObserver> mImageCacheObserver;
    162 };
    163 
    164 static ImageCache* gImageCache = nullptr;
    165 
    166 // Listen memory-pressure event for image cache purge.
    167 class ImageCacheObserver final : public nsIObserver {
    168 public:
    169  NS_DECL_ISUPPORTS
    170 
    171  explicit ImageCacheObserver(ImageCache* aImageCache)
    172      : mImageCache(aImageCache) {
    173    RegisterObserverEvents();
    174  }
    175 
    176  void Destroy() {
    177    UnregisterObserverEvents();
    178    mImageCache = nullptr;
    179  }
    180 
    181  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
    182                     const char16_t* aSomeData) override {
    183    if (!mImageCache || (strcmp(aTopic, "memory-pressure") != 0 &&
    184                         strcmp(aTopic, "canvas-device-reset") != 0)) {
    185      return NS_OK;
    186    }
    187 
    188    mImageCache->AgeAllGenerations();
    189    return NS_OK;
    190  }
    191 
    192 private:
    193  virtual ~ImageCacheObserver() = default;
    194 
    195  void RegisterObserverEvents() {
    196    nsCOMPtr<nsIObserverService> observerService =
    197        mozilla::services::GetObserverService();
    198 
    199    MOZ_ASSERT(observerService);
    200 
    201    if (observerService) {
    202      observerService->AddObserver(this, "memory-pressure", false);
    203      observerService->AddObserver(this, "canvas-device-reset", false);
    204    }
    205  }
    206 
    207  void UnregisterObserverEvents() {
    208    nsCOMPtr<nsIObserverService> observerService =
    209        mozilla::services::GetObserverService();
    210 
    211    // Do not assert on observerService here. This might be triggered by
    212    // the cycle collector at a late enough time, that XPCOM services are
    213    // no longer available. See bug 1029504.
    214    if (observerService) {
    215      observerService->RemoveObserver(this, "memory-pressure");
    216      observerService->RemoveObserver(this, "canvas-device-reset");
    217    }
    218  }
    219 
    220  ImageCache* mImageCache;
    221 };
    222 
    223 NS_IMPL_ISUPPORTS(ImageCacheObserver, nsIObserver)
    224 
    225 class CanvasImageCacheShutdownObserver final : public nsIObserver {
    226  ~CanvasImageCacheShutdownObserver() = default;
    227 
    228 public:
    229  NS_DECL_ISUPPORTS
    230  NS_DECL_NSIOBSERVER
    231 };
    232 
    233 ImageCache::ImageCache()
    234    : nsExpirationTracker<ImageCacheEntryData, 4>(GENERATION_MS,
    235                                                  "ImageCache"_ns) {
    236  mImageCacheObserver = new ImageCacheObserver(this);
    237  MOZ_RELEASE_ASSERT(mImageCacheObserver,
    238                     "GFX: Can't alloc ImageCacheObserver");
    239 }
    240 
    241 ImageCache::~ImageCache() {
    242  AgeAllGenerations();
    243  mImageCacheObserver->Destroy();
    244 }
    245 
    246 static already_AddRefed<imgIContainer> GetImageContainer(dom::Element* aImage) {
    247  nsCOMPtr<imgIRequest> request;
    248  nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
    249  if (!ilc) {
    250    return nullptr;
    251  }
    252 
    253  ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
    254                  getter_AddRefs(request));
    255  if (!request) {
    256    return nullptr;
    257  }
    258 
    259  nsCOMPtr<imgIContainer> imgContainer;
    260  request->GetImage(getter_AddRefs(imgContainer));
    261  if (!imgContainer) {
    262    return nullptr;
    263  }
    264 
    265  return imgContainer.forget();
    266 }
    267 
    268 void CanvasImageCache::NotifyCanvasDestroyed(
    269    CanvasRenderingContext2D* aContext) {
    270  MOZ_ASSERT(aContext);
    271 
    272  if (!NS_IsMainThread() || !gImageCache) {
    273    return;
    274  }
    275 
    276  for (auto i = gImageCache->mCache.Iter(); !i.Done(); i.Next()) {
    277    ImageCacheEntryData* data = i.Get()->mData.get();
    278    if (data->mContext == aContext) {
    279      gImageCache->RemoveObject(data);
    280      gImageCache->mAllCanvasCache.RemoveEntry(
    281          AllCanvasImageCacheKey(data->mImage, data->mBackendType));
    282      i.Remove();
    283    }
    284  }
    285 }
    286 
    287 void CanvasImageCache::NotifyDrawImage(
    288    Element* aImage, CanvasRenderingContext2D* aContext, DrawTarget* aTarget,
    289    SourceSurface* aSource, const IntSize& aSize, const IntSize& aIntrinsicSize,
    290    const Maybe<IntRect>& aCropRect) {
    291  MOZ_ASSERT(NS_IsMainThread());
    292  MOZ_ASSERT(aContext);
    293 
    294  if (!aTarget || !aContext) {
    295    return;
    296  }
    297 
    298  if (!gImageCache) {
    299    gImageCache = new ImageCache();
    300    nsContentUtils::RegisterShutdownObserver(
    301        new CanvasImageCacheShutdownObserver());
    302  }
    303 
    304  nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
    305  if (!imgContainer) {
    306    return;
    307  }
    308 
    309  BackendType backendType = aTarget->GetBackendType();
    310  AllCanvasImageCacheKey allCanvasCacheKey(imgContainer, backendType);
    311  ImageCacheKey canvasCacheKey(imgContainer, aContext, backendType);
    312  ImageCacheEntry* entry = gImageCache->mCache.PutEntry(canvasCacheKey);
    313 
    314  if (entry) {
    315    if (entry->mData->mSourceSurface) {
    316      // We are overwriting an existing entry.
    317      gImageCache->RemoveObject(entry->mData.get());
    318      gImageCache->mAllCanvasCache.RemoveEntry(allCanvasCacheKey);
    319    }
    320 
    321    gImageCache->AddObject(entry->mData.get());
    322    entry->mData->mSourceSurface = aSource;
    323    entry->mData->mSize = aSize;
    324    entry->mData->mIntrinsicSize = aIntrinsicSize;
    325    entry->mData->mCropRect = aCropRect;
    326 
    327    AllCanvasImageCacheEntry* allEntry =
    328        gImageCache->mAllCanvasCache.PutEntry(allCanvasCacheKey);
    329    if (allEntry) {
    330      allEntry->mSourceSurface = aSource;
    331    }
    332  }
    333 }
    334 
    335 SourceSurface* CanvasImageCache::LookupAllCanvas(Element* aImage,
    336                                                 DrawTarget* aTarget) {
    337  MOZ_ASSERT(NS_IsMainThread());
    338 
    339  if (!gImageCache || !aTarget) {
    340    return nullptr;
    341  }
    342 
    343  nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
    344  if (!imgContainer) {
    345    return nullptr;
    346  }
    347 
    348  AllCanvasImageCacheEntry* entry = gImageCache->mAllCanvasCache.GetEntry(
    349      AllCanvasImageCacheKey(imgContainer, aTarget->GetBackendType()));
    350  if (!entry) {
    351    return nullptr;
    352  }
    353 
    354  return entry->mSourceSurface;
    355 }
    356 
    357 SourceSurface* CanvasImageCache::LookupCanvas(
    358    Element* aImage, CanvasRenderingContext2D* aContext, DrawTarget* aTarget,
    359    IntSize* aSizeOut, IntSize* aIntrinsicSizeOut,
    360    Maybe<IntRect>* aCropRectOut) {
    361  MOZ_ASSERT(NS_IsMainThread());
    362  MOZ_ASSERT(aContext);
    363 
    364  if (!gImageCache || !aTarget) {
    365    return nullptr;
    366  }
    367 
    368  nsCOMPtr<imgIContainer> imgContainer = GetImageContainer(aImage);
    369  if (!imgContainer) {
    370    return nullptr;
    371  }
    372 
    373  // We filter based on the backend type because we don't want a surface that
    374  // was optimized for one backend to be preferred for another backend, as this
    375  // could have performance implications. For example, a Skia optimized surface
    376  // given to a D2D backend would cause an upload each time, and similarly a D2D
    377  // optimized surface given to a Skia backend would cause a readback. For
    378  // details, see bug 1794442.
    379  ImageCacheEntry* entry = gImageCache->mCache.GetEntry(
    380      ImageCacheKey(imgContainer, aContext, aTarget->GetBackendType()));
    381  if (!entry) {
    382    return nullptr;
    383  }
    384 
    385  MOZ_ASSERT(aSizeOut);
    386 
    387  gImageCache->MarkUsed(entry->mData.get());
    388  *aSizeOut = entry->mData->mSize;
    389  *aIntrinsicSizeOut = entry->mData->mIntrinsicSize;
    390  *aCropRectOut = entry->mData->mCropRect;
    391  return entry->mData->mSourceSurface;
    392 }
    393 
    394 NS_IMPL_ISUPPORTS(CanvasImageCacheShutdownObserver, nsIObserver)
    395 
    396 NS_IMETHODIMP
    397 CanvasImageCacheShutdownObserver::Observe(nsISupports* aSubject,
    398                                          const char* aTopic,
    399                                          const char16_t* aData) {
    400  if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
    401    delete gImageCache;
    402    gImageCache = nullptr;
    403 
    404    nsContentUtils::UnregisterShutdownObserver(this);
    405  }
    406 
    407  return NS_OK;
    408 }
    409 
    410 }  // namespace mozilla