tor-browser

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

SharedSurfacesChild.cpp (20940B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "SharedSurfacesChild.h"
      8 #include "CompositorManagerChild.h"
      9 #include "mozilla/layers/IpcResourceUpdateQueue.h"
     10 #include "mozilla/layers/SourceSurfaceSharedData.h"
     11 #include "mozilla/layers/WebRenderBridgeChild.h"
     12 #include "mozilla/layers/RenderRootStateManager.h"
     13 #include "mozilla/layers/WebRenderLayerManager.h"
     14 #include "mozilla/layers/CompositorBridgeChild.h"
     15 #include "mozilla/layers/CompositorManagerParent.h"
     16 #include "mozilla/SchedulerGroup.h"
     17 #include "mozilla/StaticPrefs_image.h"
     18 #include "mozilla/PresShell.h"
     19 #include "nsRefreshDriver.h"
     20 
     21 namespace mozilla {
     22 namespace layers {
     23 
     24 using namespace mozilla::gfx;
     25 
     26 /* static */
     27 UserDataKey SharedSurfacesChild::sSharedKey;
     28 
     29 SharedSurfacesChild::ImageKeyData::ImageKeyData(
     30    RenderRootStateManager* aManager, const wr::ImageKey& aImageKey)
     31    : mManager(aManager), mImageKey(aImageKey) {}
     32 
     33 SharedSurfacesChild::ImageKeyData::ImageKeyData(
     34    SharedSurfacesChild::ImageKeyData&& aOther)
     35    : mManager(std::move(aOther.mManager)),
     36      mDirtyRect(std::move(aOther.mDirtyRect)),
     37      mImageKey(aOther.mImageKey) {}
     38 
     39 SharedSurfacesChild::ImageKeyData& SharedSurfacesChild::ImageKeyData::operator=(
     40    SharedSurfacesChild::ImageKeyData&& aOther) {
     41  mManager = std::move(aOther.mManager);
     42  mDirtyRect = std::move(aOther.mDirtyRect);
     43  mImageKey = aOther.mImageKey;
     44  return *this;
     45 }
     46 
     47 SharedSurfacesChild::ImageKeyData::~ImageKeyData() = default;
     48 
     49 void SharedSurfacesChild::ImageKeyData::MergeDirtyRect(
     50    const Maybe<IntRect>& aDirtyRect) {
     51  if (mDirtyRect) {
     52    if (aDirtyRect) {
     53      mDirtyRect->UnionRect(mDirtyRect.ref(), aDirtyRect.ref());
     54    }
     55  } else {
     56    mDirtyRect = aDirtyRect;
     57  }
     58 }
     59 
     60 SharedSurfacesChild::SharedUserData::SharedUserData()
     61    : Runnable("SharedSurfacesChild::SharedUserData"),
     62      mId({}),
     63      mShared(false) {}
     64 
     65 SharedSurfacesChild::SharedUserData::~SharedUserData() {
     66  // We may fail to dispatch during shutdown, and since it would be issued on
     67  // the main thread, it releases the runnable instead of leaking it.
     68  if (mShared || !mKeys.IsEmpty()) {
     69    if (NS_IsMainThread()) {
     70      SharedSurfacesChild::Unshare(mId, mShared, mKeys);
     71    } else {
     72      MOZ_ASSERT_UNREACHABLE("Shared resources not released!");
     73    }
     74  }
     75 }
     76 
     77 /* static */
     78 void SharedSurfacesChild::SharedUserData::Destroy(void* aClosure) {
     79  MOZ_ASSERT(aClosure);
     80  RefPtr<SharedUserData> data =
     81      dont_AddRef(static_cast<SharedUserData*>(aClosure));
     82  if (data->mShared || !data->mKeys.IsEmpty()) {
     83    SchedulerGroup::Dispatch(data.forget());
     84  }
     85 }
     86 
     87 NS_IMETHODIMP SharedSurfacesChild::SharedUserData::Run() {
     88  SharedSurfacesChild::Unshare(mId, mShared, mKeys);
     89  mShared = false;
     90  mKeys.Clear();
     91  return NS_OK;
     92 }
     93 
     94 wr::ImageKey SharedSurfacesChild::SharedUserData::UpdateKey(
     95    RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources,
     96    const Maybe<IntRect>& aDirtyRect) {
     97  MOZ_ASSERT(aManager);
     98  MOZ_ASSERT(!aManager->IsDestroyed());
     99 
    100  // We iterate through all of the items to ensure we clean up the old
    101  // RenderRootStateManager references. Most of the time there will be few
    102  // entries and this should not be particularly expensive compared to the
    103  // cost of duplicating image keys. In an ideal world, we would generate a
    104  // single key for the surface, and it would be usable on all of the
    105  // renderer instances. For now, we must allocate a key for each WR bridge.
    106  wr::ImageKey key;
    107  bool found = false;
    108  auto i = mKeys.Length();
    109  while (i > 0) {
    110    --i;
    111    ImageKeyData& entry = mKeys[i];
    112    if (entry.mManager->IsDestroyed()) {
    113      mKeys.RemoveElementAt(i);
    114    } else if (entry.mManager == aManager) {
    115      WebRenderBridgeChild* wrBridge = aManager->WrBridge();
    116      MOZ_ASSERT(wrBridge);
    117 
    118      // Even if the manager is the same, its underlying WebRenderBridgeChild
    119      // can change state. If our namespace differs, then our old key has
    120      // already been discarded.
    121      bool ownsKey = wrBridge->GetNamespace() == entry.mImageKey.mNamespace;
    122      if (!ownsKey) {
    123        entry.mImageKey = wrBridge->GetNextImageKey();
    124        entry.TakeDirtyRect();
    125        aResources.AddSharedExternalImage(mId, entry.mImageKey);
    126      } else {
    127        entry.MergeDirtyRect(aDirtyRect);
    128        Maybe<IntRect> dirtyRect = entry.TakeDirtyRect();
    129        if (dirtyRect) {
    130          MOZ_ASSERT(mShared);
    131          aResources.UpdateSharedExternalImage(
    132              mId, entry.mImageKey, ViewAs<ImagePixel>(dirtyRect.ref()));
    133        }
    134      }
    135 
    136      key = entry.mImageKey;
    137      found = true;
    138    } else {
    139      // We don't have the resource update queue for this manager, so just
    140      // accumulate the dirty rects until it is requested.
    141      entry.MergeDirtyRect(aDirtyRect);
    142    }
    143  }
    144 
    145  if (!found) {
    146    key = aManager->WrBridge()->GetNextImageKey();
    147    ImageKeyData data(aManager, key);
    148    mKeys.AppendElement(std::move(data));
    149    aResources.AddSharedExternalImage(mId, key);
    150  }
    151 
    152  return key;
    153 }
    154 
    155 /* static */
    156 SourceSurfaceSharedData* SharedSurfacesChild::AsSourceSurfaceSharedData(
    157    SourceSurface* aSurface) {
    158  MOZ_ASSERT(aSurface);
    159  switch (aSurface->GetType()) {
    160    case SurfaceType::DATA_SHARED:
    161    case SurfaceType::DATA_RECYCLING_SHARED:
    162      return static_cast<SourceSurfaceSharedData*>(aSurface);
    163    default:
    164      return nullptr;
    165  }
    166 }
    167 
    168 /* static */
    169 nsresult SharedSurfacesChild::ShareInternal(SourceSurfaceSharedData* aSurface,
    170                                            SharedUserData** aUserData) {
    171  MOZ_ASSERT(NS_IsMainThread());
    172  MOZ_ASSERT(aSurface);
    173  MOZ_ASSERT(aUserData);
    174 
    175  CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
    176  if (NS_WARN_IF(!manager || !manager->CanSend())) {
    177    // We cannot try to share the surface, most likely because the GPU process
    178    // crashed. Ideally, we would retry when it is ready, but the handles may be
    179    // a scarce resource, which can cause much more serious problems if we run
    180    // out. Better to copy into a fresh buffer later.
    181    aSurface->FinishedSharing();
    182    return NS_ERROR_NOT_INITIALIZED;
    183  }
    184 
    185  SharedUserData* data =
    186      static_cast<SharedUserData*>(aSurface->GetUserData(&sSharedKey));
    187  if (!data) {
    188    data = MakeAndAddRef<SharedUserData>().take();
    189    aSurface->AddUserData(&sSharedKey, data, SharedUserData::Destroy);
    190  } else if (data->IsShared()) {
    191    if (manager->OwnsExternalImageId(data->Id())) {
    192      // It has already been shared with the GPU process.
    193      *aUserData = data;
    194      return NS_OK;
    195    }
    196 
    197    // If the id isn't owned by us, that means the bridge was reinitialized, due
    198    // to the GPU process crashing. All previous mappings have been released.
    199    data->ClearShared();
    200  }
    201 
    202  // Ensure that the handle doesn't get released until after we have finished
    203  // sending the buffer to the GPU process and/or reallocating it.
    204  // FinishedSharing is not a sufficient condition because another thread may
    205  // decide we are done while we are in the processing of sharing our newly
    206  // reallocated handle. Once it goes out of scope, it may release the handle.
    207  SourceSurfaceSharedData::HandleLock lock(aSurface);
    208 
    209  // If we live in the same process, then it is a simple matter of directly
    210  // asking the parent instance to store a pointer to the same data, no need
    211  // to map the data into our memory space twice.
    212  if (manager->SameProcess()) {
    213    data->MarkShared(manager->GetNextExternalImageId());
    214    CompositorManagerParent::AddSharedSurface(data->Id(), aSurface);
    215    *aUserData = data;
    216    return NS_OK;
    217  }
    218 
    219  // Attempt to share a handle with the GPU process. The handle may or may not
    220  // be available -- it will only be available if it is either not yet finalized
    221  // and/or if it has been finalized but never used for drawing in process.
    222  ipc::ReadOnlySharedMemoryHandle handle;
    223  nsresult rv = aSurface->CloneHandle(handle);
    224  if (rv == NS_ERROR_NOT_AVAILABLE) {
    225    // It is at least as expensive to copy the image to the GPU process if we
    226    // have already closed the handle necessary to share, but if we reallocate
    227    // the shared buffer to get a new handle, we can save some memory.
    228    if (NS_WARN_IF(!aSurface->ReallocHandle())) {
    229      return NS_ERROR_OUT_OF_MEMORY;
    230    }
    231 
    232    // Reattempt the sharing of the handle to the GPU process.
    233    rv = aSurface->CloneHandle(handle);
    234  }
    235 
    236  if (NS_WARN_IF(NS_FAILED(rv))) {
    237    MOZ_ASSERT(rv != NS_ERROR_NOT_AVAILABLE);
    238    return rv;
    239  }
    240 
    241  SurfaceFormat format = aSurface->GetFormat();
    242  MOZ_RELEASE_ASSERT(
    243      format == SurfaceFormat::B8G8R8X8 || format == SurfaceFormat::B8G8R8A8,
    244      "bad format");
    245 
    246  data->MarkShared(manager->GetNextExternalImageId());
    247  manager->SendAddSharedSurface(
    248      data->Id(),
    249      SurfaceDescriptorShared(aSurface->GetSize(), aSurface->Stride(), format,
    250                              std::move(handle)));
    251  *aUserData = data;
    252  return NS_OK;
    253 }
    254 
    255 /* static */
    256 void SharedSurfacesChild::Share(SourceSurfaceSharedData* aSurface) {
    257  MOZ_ASSERT(aSurface);
    258 
    259  // The IPDL actor to do sharing can only be accessed on the main thread so we
    260  // need to dispatch if off the main thread. However there is no real danger if
    261  // we end up racing because if it is already shared, this method will do
    262  // nothing.
    263  if (!NS_IsMainThread()) {
    264    class ShareRunnable final : public Runnable {
    265     public:
    266      explicit ShareRunnable(SourceSurfaceSharedData* aSurface)
    267          : Runnable("SharedSurfacesChild::Share"), mSurface(aSurface) {}
    268 
    269      NS_IMETHOD Run() override {
    270        SharedUserData* unused = nullptr;
    271        SharedSurfacesChild::ShareInternal(mSurface, &unused);
    272        return NS_OK;
    273      }
    274 
    275     private:
    276      RefPtr<SourceSurfaceSharedData> mSurface;
    277    };
    278 
    279    SchedulerGroup::Dispatch(MakeAndAddRef<ShareRunnable>(aSurface));
    280    return;
    281  }
    282 
    283  SharedUserData* unused = nullptr;
    284  SharedSurfacesChild::ShareInternal(aSurface, &unused);
    285 }
    286 
    287 /* static */
    288 nsresult SharedSurfacesChild::Share(SourceSurfaceSharedData* aSurface,
    289                                    RenderRootStateManager* aManager,
    290                                    wr::IpcResourceUpdateQueue& aResources,
    291                                    wr::ImageKey& aKey) {
    292  MOZ_ASSERT(NS_IsMainThread());
    293  MOZ_ASSERT(aSurface);
    294  MOZ_ASSERT(aManager);
    295 
    296  // Each time the surface changes, the producers of SourceSurfaceSharedData
    297  // surfaces promise to increment the invalidation counter each time the
    298  // surface has changed. We can use this counter to determine whether or not
    299  // we should update our paired ImageKey.
    300  Maybe<IntRect> dirtyRect = aSurface->TakeDirtyRect();
    301  SharedUserData* data = nullptr;
    302  nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data);
    303  if (NS_SUCCEEDED(rv)) {
    304    MOZ_ASSERT(data);
    305    aKey = data->UpdateKey(aManager, aResources, dirtyRect);
    306  }
    307 
    308  return rv;
    309 }
    310 
    311 /* static */
    312 nsresult SharedSurfacesChild::Share(SourceSurface* aSurface,
    313                                    RenderRootStateManager* aManager,
    314                                    wr::IpcResourceUpdateQueue& aResources,
    315                                    wr::ImageKey& aKey) {
    316  MOZ_ASSERT(NS_IsMainThread());
    317  MOZ_ASSERT(aSurface);
    318  MOZ_ASSERT(aManager);
    319 
    320  auto sharedSurface = AsSourceSurfaceSharedData(aSurface);
    321  if (!sharedSurface) {
    322    return NS_ERROR_NOT_IMPLEMENTED;
    323  }
    324 
    325  return Share(sharedSurface, aManager, aResources, aKey);
    326 }
    327 
    328 /* static */
    329 nsresult SharedSurfacesChild::Share(SourceSurface* aSurface,
    330                                    wr::ExternalImageId& aId) {
    331  MOZ_ASSERT(NS_IsMainThread());
    332  MOZ_ASSERT(aSurface);
    333 
    334  auto sharedSurface = AsSourceSurfaceSharedData(aSurface);
    335  if (!sharedSurface) {
    336    return NS_ERROR_NOT_IMPLEMENTED;
    337  }
    338 
    339  // The external image ID does not change with the invalidation counter. The
    340  // caller of this should be aware of the invalidations of the surface through
    341  // another mechanism (e.g. imgRequestProxy listener notifications).
    342  SharedUserData* data = nullptr;
    343  nsresult rv = ShareInternal(sharedSurface, &data);
    344  if (NS_SUCCEEDED(rv)) {
    345    MOZ_ASSERT(data);
    346    aId = data->Id();
    347  }
    348 
    349  return rv;
    350 }
    351 
    352 /* static */ nsresult SharedSurfacesChild::Share(
    353    gfx::SourceSurface* aSurface, Maybe<SurfaceDescriptor>& aDesc) {
    354  if (!aSurface) {
    355    return NS_ERROR_INVALID_ARG;
    356  }
    357 
    358  // TODO(aosmond): With a refactor of how we store the external image ID, we
    359  // could probably make it safe to access off the main thread. This would be
    360  // useful for OffscreenCanvas on DOM workers.
    361  if (!NS_IsMainThread()) {
    362    return NS_ERROR_UNEXPECTED;
    363  }
    364 
    365  wr::ExternalImageId extId{};
    366  nsresult rv = Share(aSurface, extId);
    367  if (NS_FAILED(rv)) {
    368    return rv;
    369  }
    370 
    371  aDesc = Some(SurfaceDescriptorExternalImage(
    372      wr::ExternalImageSource::SharedSurfaces, extId));
    373  return NS_OK;
    374 }
    375 
    376 /* static */
    377 void SharedSurfacesChild::Unshare(const wr::ExternalImageId& aId,
    378                                  bool aReleaseId,
    379                                  nsTArray<ImageKeyData>& aKeys) {
    380  MOZ_ASSERT(NS_IsMainThread());
    381 
    382  for (const auto& entry : aKeys) {
    383    if (!entry.mManager->IsDestroyed()) {
    384      entry.mManager->AddImageKeyForDiscard(entry.mImageKey);
    385    }
    386  }
    387 
    388  if (!aReleaseId) {
    389    // We don't own the external image ID itself.
    390    return;
    391  }
    392 
    393  CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
    394  if (MOZ_UNLIKELY(!manager || !manager->CanSend())) {
    395    return;
    396  }
    397 
    398  if (manager->OwnsExternalImageId(aId)) {
    399    // Only attempt to release current mappings in the compositor process. It is
    400    // possible we had a surface that was previously shared, the compositor
    401    // process crashed / was restarted, and then we freed the surface. In that
    402    // case we know the mapping has already been freed.
    403    manager->SendRemoveSharedSurface(aId);
    404  }
    405 }
    406 
    407 /* static */ Maybe<wr::ExternalImageId> SharedSurfacesChild::GetExternalId(
    408    const SourceSurfaceSharedData* aSurface) {
    409  MOZ_ASSERT(NS_IsMainThread());
    410  MOZ_ASSERT(aSurface);
    411 
    412  SharedUserData* data =
    413      static_cast<SharedUserData*>(aSurface->GetUserData(&sSharedKey));
    414  if (!data || !data->IsShared()) {
    415    return Nothing();
    416  }
    417 
    418  return Some(data->Id());
    419 }
    420 
    421 AnimationImageKeyData::AnimationImageKeyData(RenderRootStateManager* aManager,
    422                                             const wr::ImageKey& aImageKey)
    423    : SharedSurfacesChild::ImageKeyData(aManager, aImageKey) {}
    424 
    425 AnimationImageKeyData::AnimationImageKeyData(AnimationImageKeyData&& aOther)
    426    : SharedSurfacesChild::ImageKeyData(std::move(aOther)),
    427      mPendingRelease(std::move(aOther.mPendingRelease)) {}
    428 
    429 AnimationImageKeyData& AnimationImageKeyData::operator=(
    430    AnimationImageKeyData&& aOther) {
    431  mPendingRelease = std::move(aOther.mPendingRelease);
    432  SharedSurfacesChild::ImageKeyData::operator=(std::move(aOther));
    433  return *this;
    434 }
    435 
    436 AnimationImageKeyData::~AnimationImageKeyData() = default;
    437 
    438 SharedSurfacesAnimation::~SharedSurfacesAnimation() {
    439  MOZ_ASSERT(mKeys.IsEmpty());
    440 }
    441 
    442 void SharedSurfacesAnimation::Destroy() {
    443  if (!NS_IsMainThread()) {
    444    nsCOMPtr<nsIRunnable> task =
    445        NewRunnableMethod("SharedSurfacesAnimation::Destroy", this,
    446                          &SharedSurfacesAnimation::Destroy);
    447    NS_DispatchToMainThread(task.forget());
    448    return;
    449  }
    450 
    451  if (mKeys.IsEmpty()) {
    452    return;
    453  }
    454 
    455  for (const auto& entry : mKeys) {
    456    MOZ_ASSERT(!entry.mManager->IsDestroyed());
    457    if (StaticPrefs::image_animated_decode_on_demand_recycle_AtStartup()) {
    458      entry.mManager->DeregisterAsyncAnimation(entry.mImageKey);
    459    }
    460    entry.mManager->AddImageKeyForDiscard(entry.mImageKey);
    461  }
    462 
    463  mKeys.Clear();
    464 }
    465 
    466 void SharedSurfacesAnimation::HoldSurfaceForRecycling(
    467    AnimationImageKeyData& aEntry, SourceSurfaceSharedData* aSurface) {
    468  if (aSurface->GetType() != SurfaceType::DATA_RECYCLING_SHARED) {
    469    return;
    470  }
    471 
    472  MOZ_ASSERT(StaticPrefs::image_animated_decode_on_demand_recycle_AtStartup());
    473  aEntry.mPendingRelease.AppendElement(aSurface);
    474 }
    475 
    476 nsresult SharedSurfacesAnimation::SetCurrentFrame(
    477    SourceSurfaceSharedData* aSurface, const gfx::IntRect& aDirtyRect) {
    478  MOZ_ASSERT(aSurface);
    479 
    480  SharedSurfacesChild::SharedUserData* data = nullptr;
    481  nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data);
    482  if (NS_FAILED(rv)) {
    483    return rv;
    484  }
    485 
    486  MOZ_ASSERT(data);
    487  mId = data->Id();
    488 
    489  auto i = mKeys.Length();
    490  while (i > 0) {
    491    --i;
    492    AnimationImageKeyData& entry = mKeys[i];
    493    MOZ_ASSERT(!entry.mManager->IsDestroyed());
    494 
    495    if (auto* cbc =
    496            entry.mManager->LayerManager()->GetCompositorBridgeChild()) {
    497      if (cbc->IsPaused()) {
    498        continue;
    499      }
    500    }
    501 
    502    // Only root compositor bridge childs record if they are paused, so check
    503    // the refresh driver.
    504    if (auto* widget = entry.mManager->LayerManager()->GetWidget()) {
    505      if (auto* ps = widget->GetPresShell()) {
    506        if (auto* rd = ps->GetRefreshDriver(); rd && rd->IsThrottled()) {
    507          continue;
    508        }
    509      }
    510    }
    511 
    512    entry.MergeDirtyRect(Some(aDirtyRect));
    513    Maybe<IntRect> dirtyRect = entry.TakeDirtyRect();
    514    if (dirtyRect) {
    515      HoldSurfaceForRecycling(entry, aSurface);
    516      auto& resourceUpdates = entry.mManager->AsyncResourceUpdates();
    517      resourceUpdates.UpdateSharedExternalImage(
    518          mId, entry.mImageKey, ViewAs<ImagePixel>(dirtyRect.ref()));
    519    }
    520  }
    521 
    522  return NS_OK;
    523 }
    524 
    525 nsresult SharedSurfacesAnimation::UpdateKey(
    526    SourceSurfaceSharedData* aSurface, RenderRootStateManager* aManager,
    527    wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) {
    528  SharedSurfacesChild::SharedUserData* data = nullptr;
    529  nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data);
    530  if (NS_FAILED(rv)) {
    531    return rv;
    532  }
    533 
    534  MOZ_ASSERT(data);
    535  if (wr::AsUint64(mId) != wr::AsUint64(data->Id())) {
    536    mKeys.Clear();
    537    mId = data->Id();
    538  }
    539 
    540  // We iterate through all of the items to ensure we clean up the old
    541  // RenderRootStateManager references. Most of the time there will be few
    542  // entries and this should not be particularly expensive compared to the
    543  // cost of duplicating image keys. In an ideal world, we would generate a
    544  // single key for the surface, and it would be usable on all of the
    545  // renderer instances. For now, we must allocate a key for each WR bridge.
    546  bool found = false;
    547  auto i = mKeys.Length();
    548  while (i > 0) {
    549    --i;
    550    AnimationImageKeyData& entry = mKeys[i];
    551    MOZ_ASSERT(!entry.mManager->IsDestroyed());
    552    if (entry.mManager == aManager) {
    553      WebRenderBridgeChild* wrBridge = aManager->WrBridge();
    554      MOZ_ASSERT(wrBridge);
    555 
    556      // Even if the manager is the same, its underlying WebRenderBridgeChild
    557      // can change state. If our namespace differs, then our old key has
    558      // already been discarded.
    559      bool ownsKey = wrBridge->GetNamespace() == entry.mImageKey.mNamespace;
    560      if (!ownsKey) {
    561        entry.mImageKey = wrBridge->GetNextImageKey();
    562        HoldSurfaceForRecycling(entry, aSurface);
    563        aResources.AddSharedExternalImage(mId, entry.mImageKey);
    564      } else {
    565        MOZ_ASSERT(entry.mDirtyRect.isNothing());
    566      }
    567 
    568      aKey = entry.mImageKey;
    569      found = true;
    570      break;
    571    }
    572  }
    573 
    574  if (!found) {
    575    aKey = aManager->WrBridge()->GetNextImageKey();
    576    if (StaticPrefs::image_animated_decode_on_demand_recycle_AtStartup()) {
    577      aManager->RegisterAsyncAnimation(aKey, this);
    578    }
    579 
    580    AnimationImageKeyData data(aManager, aKey);
    581    HoldSurfaceForRecycling(data, aSurface);
    582    mKeys.AppendElement(std::move(data));
    583    aResources.AddSharedExternalImage(mId, aKey);
    584  }
    585 
    586  return NS_OK;
    587 }
    588 
    589 void SharedSurfacesAnimation::ReleasePreviousFrame(
    590    RenderRootStateManager* aManager, const wr::ExternalImageId& aId) {
    591  MOZ_ASSERT(aManager);
    592 
    593  auto i = mKeys.Length();
    594  while (i > 0) {
    595    --i;
    596    AnimationImageKeyData& entry = mKeys[i];
    597    MOZ_ASSERT(!entry.mManager->IsDestroyed());
    598    if (entry.mManager == aManager) {
    599      size_t k;
    600      for (k = 0; k < entry.mPendingRelease.Length(); ++k) {
    601        Maybe<wr::ExternalImageId> extId =
    602            SharedSurfacesChild::GetExternalId(entry.mPendingRelease[k]);
    603        if (extId && extId.ref() == aId) {
    604          break;
    605        }
    606      }
    607 
    608      if (k == entry.mPendingRelease.Length()) {
    609        continue;
    610      }
    611 
    612      entry.mPendingRelease.RemoveElementsAt(0, k + 1);
    613      break;
    614    }
    615  }
    616 }
    617 
    618 void SharedSurfacesAnimation::Invalidate(RenderRootStateManager* aManager) {
    619  auto i = mKeys.Length();
    620  while (i > 0) {
    621    --i;
    622    AnimationImageKeyData& entry = mKeys[i];
    623    if (entry.mManager == aManager) {
    624      mKeys.RemoveElementAt(i);
    625      break;
    626    }
    627  }
    628 }
    629 
    630 }  // namespace layers
    631 }  // namespace mozilla