/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WebRenderCommandBuilder.h" #include "BasicLayers.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Types.h" #include "mozilla/gfx/DrawEventRecorder.h" #include "mozilla/layers/ImageClient.h" #include "mozilla/layers/WebRenderBridgeChild.h" #include "mozilla/layers/WebRenderLayerManager.h" #include "mozilla/layers/IpcResourceUpdateQueue.h" #include "mozilla/layers/ScrollingLayersHelper.h" #include "mozilla/layers/StackingContextHelper.h" #include "mozilla/layers/UpdateImageHelper.h" #include "gfxEnv.h" #include "nsDisplayListInvalidation.h" #include "WebRenderCanvasRenderer.h" #include "LayersLogging.h" #include "LayerTreeInvalidation.h" namespace mozilla { namespace layers { using namespace gfx; void WebRenderCommandBuilder::Destroy() { mLastCanvasDatas.Clear(); RemoveUnusedAndResetWebRenderUserData(); } void WebRenderCommandBuilder::EmptyTransaction() { // We need to update canvases that might have changed. for (auto iter = mLastCanvasDatas.Iter(); !iter.Done(); iter.Next()) { RefPtr canvasData = iter.Get()->GetKey(); WebRenderCanvasRendererAsync* canvas = canvasData->GetCanvasRenderer(); if (canvas) { canvas->UpdateCompositableClient(); } } } bool WebRenderCommandBuilder::NeedsEmptyTransaction() { return !mLastCanvasDatas.IsEmpty(); } void WebRenderCommandBuilder::BuildWebRenderCommands(wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResourceUpdates, nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder, WebRenderScrollData& aScrollData, wr::LayoutSize& aContentSize) { { // scoping for StackingContextHelper RAII StackingContextHelper sc; mParentCommands.Clear(); aScrollData = WebRenderScrollData(mManager); MOZ_ASSERT(mLayerScrollData.empty()); mLastCanvasDatas.Clear(); mLastAsr = nullptr; mScrollingHelper.BeginBuild(mManager, aBuilder); { StackingContextHelper pageRootSc(sc, aBuilder); CreateWebRenderCommandsFromDisplayList(aDisplayList, aDisplayListBuilder, pageRootSc, aBuilder, aResourceUpdates); } // Make a "root" layer data that has everything else as descendants mLayerScrollData.emplace_back(); mLayerScrollData.back().InitializeRoot(mLayerScrollData.size() - 1); if (aDisplayListBuilder->IsBuildingLayerEventRegions()) { nsIPresShell* shell = aDisplayListBuilder->RootReferenceFrame()->PresShell(); if (nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(shell)) { mLayerScrollData.back().SetEventRegionsOverride(EventRegionsOverride::ForceDispatchToContent); } } auto callback = [&aScrollData](FrameMetrics::ViewID aScrollId) -> bool { return aScrollData.HasMetadataFor(aScrollId); }; if (Maybe rootMetadata = nsLayoutUtils::GetRootMetadata( aDisplayListBuilder, mManager, ContainerLayerParameters(), callback)) { mLayerScrollData.back().AppendScrollMetadata(aScrollData, rootMetadata.ref()); } // Append the WebRenderLayerScrollData items into WebRenderScrollData // in reverse order, from topmost to bottommost. This is in keeping with // the semantics of WebRenderScrollData. for (auto i = mLayerScrollData.crbegin(); i != mLayerScrollData.crend(); i++) { aScrollData.AddLayerData(*i); } mLayerScrollData.clear(); mScrollingHelper.EndBuild(); // Remove the user data those are not displayed on the screen and // also reset the data to unused for next transaction. RemoveUnusedAndResetWebRenderUserData(); } mManager->WrBridge()->AddWebRenderParentCommands(mParentCommands); } void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList(nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc, wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources) { mScrollingHelper.BeginList(); bool apzEnabled = mManager->AsyncPanZoomEnabled(); EventRegions eventRegions; FlattenedDisplayItemIterator iter(aDisplayListBuilder, aDisplayList); while (nsDisplayItem* i = iter.GetNext()) { nsDisplayItem* item = i; DisplayItemType itemType = item->GetType(); // If the item is a event regions item, but is empty (has no regions in it) // then we should just throw it out if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) { nsDisplayLayerEventRegions* eventRegions = static_cast(item); if (eventRegions->IsEmpty()) { continue; } } // Peek ahead to the next item and try merging with it or swapping with it // if necessary. AutoTArray mergedItems; mergedItems.AppendElement(item); while (nsDisplayItem* peek = iter.PeekNext()) { if (!item->CanMerge(peek)) { break; } mergedItems.AppendElement(peek); // Move the iterator forward since we will merge this item. i = iter.GetNext(); } if (mergedItems.Length() > 1) { item = aDisplayListBuilder->MergeItems(mergedItems); MOZ_ASSERT(item && itemType == item->GetType()); } bool forceNewLayerData = false; size_t layerCountBeforeRecursing = mLayerScrollData.size(); if (apzEnabled) { // For some types of display items we want to force a new // WebRenderLayerScrollData object, to ensure we preserve the APZ-relevant // data that is in the display item. forceNewLayerData = item->UpdateScrollData(nullptr, nullptr); // Anytime the ASR changes we also want to force a new layer data because // the stack of scroll metadata is going to be different for this // display item than previously, so we can't squash the display items // into the same "layer". const ActiveScrolledRoot* asr = item->GetActiveScrolledRoot(); if (asr != mLastAsr) { mLastAsr = asr; forceNewLayerData = true; } // If we're creating a new layer data then flush whatever event regions // we've collected onto the old layer. if (forceNewLayerData && !eventRegions.IsEmpty()) { // If eventRegions is non-empty then we must have a layer data already, // because we (below) force one if we encounter an event regions item // with an empty layer data list. Additionally, the most recently // created layer data must have been created from an item whose ASR // is the same as the ASR on the event region items that were collapsed // into |eventRegions|. This is because any ASR change causes us to force // a new layer data which flushes the eventRegions. MOZ_ASSERT(!mLayerScrollData.empty()); mLayerScrollData.back().AddEventRegions(eventRegions); eventRegions.SetEmpty(); } // Collapse event region data into |eventRegions|, which will either be // empty, or filled with stuff from previous display items with the same // ASR. if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) { nsDisplayLayerEventRegions* regionsItem = static_cast(item); int32_t auPerDevPixel = item->Frame()->PresContext()->AppUnitsPerDevPixel(); EventRegions regions( regionsItem->HitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel), regionsItem->MaybeHitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel), regionsItem->DispatchToContentHitRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel), regionsItem->NoActionRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel), regionsItem->HorizontalPanRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel), regionsItem->VerticalPanRegion().ScaleToOutsidePixels(1.0f, 1.0f, auPerDevPixel)); eventRegions.OrWith(regions); if (mLayerScrollData.empty()) { // If we don't have a layer data yet then create one because we will // need it to store this event region information. forceNewLayerData = true; } } // If we're going to create a new layer data for this item, stash the // ASR so that if we recurse into a sublist they will know where to stop // walking up their ASR chain when building scroll metadata. if (forceNewLayerData) { mAsrStack.push_back(asr); } } mScrollingHelper.BeginItem(item, aSc); // Note: this call to CreateWebRenderCommands can recurse back into // this function if the |item| is a wrapper for a sublist. if (itemType != DisplayItemType::TYPE_LAYER_EVENT_REGIONS && !item->CreateWebRenderCommands(aBuilder, aResources, aSc, mManager, aDisplayListBuilder)) { PushItemAsImage(item, aBuilder, aResources, aSc, aDisplayListBuilder); } if (apzEnabled) { if (forceNewLayerData) { // Pop the thing we pushed before the recursion, so the topmost item on // the stack is enclosing display item's ASR (or the stack is empty) mAsrStack.pop_back(); const ActiveScrolledRoot* stopAtAsr = mAsrStack.empty() ? nullptr : mAsrStack.back(); int32_t descendants = mLayerScrollData.size() - layerCountBeforeRecursing; mLayerScrollData.emplace_back(); mLayerScrollData.back().Initialize(mManager->GetScrollData(), item, descendants, stopAtAsr); } else if (mLayerScrollData.size() != layerCountBeforeRecursing && !eventRegions.IsEmpty()) { // We are not forcing a new layer for |item|, but we did create some // layers while recursing. In this case, we need to make sure any // event regions that we were carrying end up on the right layer. So we // do an event region "flush" but retroactively; i.e. the event regions // end up on the layer that was mLayerScrollData.back() prior to the // recursion. MOZ_ASSERT(layerCountBeforeRecursing > 0); mLayerScrollData[layerCountBeforeRecursing - 1].AddEventRegions(eventRegions); eventRegions.SetEmpty(); } } } // If we have any event region info left over we need to flush it before we // return. Again, at this point the layer data list must be non-empty, and // the most recently created layer data will have been created by an item // with matching ASRs. if (!eventRegions.IsEmpty()) { MOZ_ASSERT(apzEnabled); MOZ_ASSERT(!mLayerScrollData.empty()); mLayerScrollData.back().AddEventRegions(eventRegions); } mScrollingHelper.EndList(); } Maybe WebRenderCommandBuilder::CreateImageKey(nsDisplayItem* aItem, ImageContainer* aContainer, mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, gfx::IntSize& aSize, const Maybe& aAsyncImageBounds) { RefPtr imageData = CreateOrRecycleWebRenderUserData(aItem); MOZ_ASSERT(imageData); if (aContainer->IsAsync()) { MOZ_ASSERT(aAsyncImageBounds); LayoutDeviceRect rect = aAsyncImageBounds.value(); LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), rect.Size()); gfx::MaybeIntSize scaleToSize; if (!aContainer->GetScaleHint().IsEmpty()) { scaleToSize = Some(aContainer->GetScaleHint()); } // TODO! // We appear to be using the image bridge for a lot (most/all?) of // layers-free image handling and that breaks frame consistency. imageData->CreateAsyncImageWebRenderCommands(aBuilder, aContainer, aSc, rect, scBounds, gfx::Matrix4x4(), scaleToSize, wr::ImageRendering::Auto, wr::MixBlendMode::Normal, !aItem->BackfaceIsHidden()); return Nothing(); } AutoLockImage autoLock(aContainer); if (!autoLock.HasImage()) { return Nothing(); } mozilla::layers::Image* image = autoLock.GetImage(); aSize = image->GetSize(); return imageData->UpdateImageKey(aContainer, aResources); } bool WebRenderCommandBuilder::PushImage(nsDisplayItem* aItem, ImageContainer* aContainer, mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, const LayoutDeviceRect& aRect) { gfx::IntSize size; Maybe key = CreateImageKey(aItem, aContainer, aBuilder, aResources, aSc, size, Some(aRect)); if (aContainer->IsAsync()) { // Async ImageContainer does not create ImageKey, instead it uses Pipeline. MOZ_ASSERT(key.isNothing()); return true; } if (!key) { return false; } auto r = aSc.ToRelativeLayoutRect(aRect); gfx::SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()); aBuilder.PushImage(r, r, !aItem->BackfaceIsHidden(), wr::ToImageRendering(sampleFilter), key.value()); return true; } static void PaintByLayer(nsDisplayItem* aItem, nsDisplayListBuilder* aDisplayListBuilder, RefPtr& aManager, gfxContext* aContext, const std::function& aPaintFunc) { if (aManager == nullptr) { aManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE); } FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); layerBuilder->Init(aDisplayListBuilder, aManager, nullptr, true); layerBuilder->DidBeginRetainedLayerTransaction(aManager); aManager->BeginTransactionWithTarget(aContext); ContainerLayerParameters param; RefPtr layer = aItem->BuildLayer(aDisplayListBuilder, aManager, param); if (layer) { UniquePtr props; props = Move(LayerProperties::CloneFrom(aManager->GetRoot())); aManager->SetRoot(layer); layerBuilder->WillEndTransaction(); aPaintFunc(); } #ifdef MOZ_DUMP_PAINTING if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) { fprintf_stderr(gfxUtils::sDumpPaintFile, "Basic layer tree for painting contents of display item %s(%p):\n", aItem->Name(), aItem->Frame()); std::stringstream stream; aManager->Dump(stream, "", gfxEnv::DumpPaintToFile()); fprint_stderr(gfxUtils::sDumpPaintFile, stream); // not a typo, fprint_stderr declared in LayersLogging.h } #endif if (aManager->InTransaction()) { aManager->AbortTransaction(); } aManager->SetTarget(nullptr); } static void PaintItemByDrawTarget(nsDisplayItem* aItem, gfx::DrawTarget* aDT, const LayerRect& aImageRect, const LayoutDevicePoint& aOffset, nsDisplayListBuilder* aDisplayListBuilder, RefPtr& aManager, WebRenderLayerManager* aWrManager, const gfx::Size& aScale) { MOZ_ASSERT(aDT); aDT->ClearRect(aImageRect.ToUnknownRect()); RefPtr context = gfxContext::CreateOrNull(aDT); MOZ_ASSERT(context); context->SetMatrix(context->CurrentMatrix().PreScale(aScale.width, aScale.height).PreTranslate(-aOffset.x, -aOffset.y)); switch (aItem->GetType()) { case DisplayItemType::TYPE_MASK: static_cast(aItem)->PaintMask(aDisplayListBuilder, context); break; case DisplayItemType::TYPE_SVG_WRAPPER: { PaintByLayer(aItem, aDisplayListBuilder, aManager, context, [&]() { aManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aDisplayListBuilder); }); break; } case DisplayItemType::TYPE_FILTER: { PaintByLayer(aItem, aDisplayListBuilder, aManager, context, [&]() { static_cast(aItem)->PaintAsLayer(aDisplayListBuilder, context, aManager); }); break; } default: aItem->Paint(aDisplayListBuilder, context); break; } if (gfxPrefs::WebRenderHighlightPaintedLayers()) { aDT->SetTransform(gfx::Matrix()); aDT->FillRect(gfx::Rect(0, 0, aImageRect.Width(), aImageRect.Height()), gfx::ColorPattern(gfx::Color(1.0, 0.0, 0.0, 0.5))); } if (aItem->Frame()->PresContext()->GetPaintFlashing()) { aDT->SetTransform(gfx::Matrix()); float r = float(rand()) / RAND_MAX; float g = float(rand()) / RAND_MAX; float b = float(rand()) / RAND_MAX; aDT->FillRect(gfx::Rect(0, 0, aImageRect.Width(), aImageRect.Height()), gfx::ColorPattern(gfx::Color(r, g, b, 0.5))); } } already_AddRefed WebRenderCommandBuilder::GenerateFallbackData(nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, nsDisplayListBuilder* aDisplayListBuilder, LayoutDeviceRect& aImageRect) { RefPtr fallbackData = CreateOrRecycleWebRenderUserData(aItem); bool snap; nsRect itemBounds = aItem->GetBounds(aDisplayListBuilder, &snap); // Blob images will only draw the visible area of the blob so we don't need to clip // them here and can just rely on the webrender clipping. bool useClipBounds = true; nsRect paintBounds = itemBounds; if (gfxPrefs::WebRenderBlobImages()) { paintBounds = itemBounds; useClipBounds = false; } else { paintBounds = aItem->GetClippedBounds(aDisplayListBuilder); } // nsDisplayItem::Paint() may refer the variables that come from ComputeVisibility(). // So we should call RecomputeVisibility() before painting. e.g.: nsDisplayBoxShadowInner // uses mVisibleRegion in Paint() and mVisibleRegion is computed in // nsDisplayBoxShadowInner::ComputeVisibility(). nsRegion visibleRegion(itemBounds); aItem->RecomputeVisibility(aDisplayListBuilder, &visibleRegion, useClipBounds); const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(paintBounds, appUnitsPerDevPixel); gfx::Size scale = aSc.GetInheritedScale(); // XXX not sure if paintSize should be in layer or layoutdevice pixels, it // has some sort of scaling applied. LayerIntSize paintSize = RoundedToInt(LayerSize(bounds.width * scale.width, bounds.height * scale.height)); if (paintSize.width == 0 || paintSize.height == 0) { return nullptr; } bool needPaint = true; LayoutDeviceIntPoint offset = RoundedToInt(bounds.TopLeft()); aImageRect = LayoutDeviceRect(offset, LayoutDeviceSize(RoundedToInt(bounds.Size()))); LayerRect paintRect = LayerRect(LayerPoint(0, 0), LayerSize(paintSize)); nsAutoPtr geometry = fallbackData->GetGeometry(); // nsDisplayFilter is rendered via BasicLayerManager which means the invalidate // region is unknown until we traverse the displaylist contained by it. if (geometry && !fallbackData->IsInvalid() && aItem->GetType() != DisplayItemType::TYPE_FILTER && aItem->GetType() != DisplayItemType::TYPE_SVG_WRAPPER && scale == fallbackData->GetScale()) { nsRect invalid; nsRegion invalidRegion; if (aItem->IsInvalid(invalid)) { invalidRegion.OrWith(paintBounds); } else { nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft(); geometry->MoveBy(shift); aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, &invalidRegion); nsRect lastBounds = fallbackData->GetBounds(); lastBounds.MoveBy(shift); if (!lastBounds.IsEqualInterior(paintBounds)) { invalidRegion.OrWith(lastBounds); invalidRegion.OrWith(paintBounds); } } needPaint = !invalidRegion.IsEmpty(); } if (needPaint || !fallbackData->GetKey()) { gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK ? gfx::SurfaceFormat::A8 : gfx::SurfaceFormat::B8G8R8A8; if (gfxPrefs::WebRenderBlobImages()) { bool snapped; bool isOpaque = aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).Contains(paintBounds); RefPtr recorder = MakeAndAddRef([&] (MemStream &aStream, std::vector> &aUnscaledFonts) { size_t count = aUnscaledFonts.size(); aStream.write((const char*)&count, sizeof(count)); for (auto unscaled : aUnscaledFonts) { wr::FontKey key = mManager->WrBridge()->GetFontKeyForUnscaledFont(unscaled); aStream.write((const char*)&key, sizeof(key)); } }); RefPtr dummyDt = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), format); RefPtr dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, paintSize.ToUnknownSize()); PaintItemByDrawTarget(aItem, dt, paintRect, offset, aDisplayListBuilder, fallbackData->mBasicLayerManager, mManager, scale); recorder->FlushItem(IntRect()); recorder->Finish(); Range bytes((uint8_t*)recorder->mOutputStream.mData, recorder->mOutputStream.mLength); wr::ImageKey key = mManager->WrBridge()->GetNextImageKey(); wr::ImageDescriptor descriptor(paintSize.ToUnknownSize(), 0, dt->GetFormat(), isOpaque); if (!aResources.AddBlobImage(key, descriptor, bytes)) { return nullptr; } fallbackData->SetKey(key); } else { fallbackData->CreateImageClientIfNeeded(); RefPtr imageClient = fallbackData->GetImageClient(); RefPtr imageContainer = LayerManager::CreateImageContainer(); { UpdateImageHelper helper(imageContainer, imageClient, paintSize.ToUnknownSize(), format); { RefPtr dt = helper.GetDrawTarget(); if (!dt) { return nullptr; } PaintItemByDrawTarget(aItem, dt, paintRect, offset, aDisplayListBuilder, fallbackData->mBasicLayerManager, mManager, scale); } if (!helper.UpdateImage()) { return nullptr; } } // Force update the key in fallback data since we repaint the image in this path. // If not force update, fallbackData may reuse the original key because it // doesn't know UpdateImageHelper already updated the image container. if (!fallbackData->UpdateImageKey(imageContainer, aResources, true)) { return nullptr; } } geometry = aItem->AllocateGeometry(aDisplayListBuilder); fallbackData->SetScale(scale); fallbackData->SetInvalid(false); } // Update current bounds to fallback data fallbackData->SetGeometry(Move(geometry)); fallbackData->SetBounds(paintBounds); MOZ_ASSERT(fallbackData->GetKey()); return fallbackData.forget(); } Maybe WebRenderCommandBuilder::BuildWrMaskImage(nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, nsDisplayListBuilder* aDisplayListBuilder, const LayoutDeviceRect& aBounds) { LayoutDeviceRect imageRect; RefPtr fallbackData = GenerateFallbackData(aItem, aBuilder, aResources, aSc, aDisplayListBuilder, imageRect); if (!fallbackData) { return Nothing(); } wr::WrImageMask imageMask; imageMask.image = fallbackData->GetKey().value(); imageMask.rect = aSc.ToRelativeLayoutRect(aBounds); imageMask.repeat = false; return Some(imageMask); } bool WebRenderCommandBuilder::PushItemAsImage(nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, nsDisplayListBuilder* aDisplayListBuilder) { LayoutDeviceRect imageRect; RefPtr fallbackData = GenerateFallbackData(aItem, aBuilder, aResources, aSc, aDisplayListBuilder, imageRect); if (!fallbackData) { return false; } wr::LayoutRect dest = aSc.ToRelativeLayoutRect(imageRect); gfx::SamplingFilter sampleFilter = nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()); aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), wr::ToImageRendering(sampleFilter), fallbackData->GetKey().value()); return true; } void WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData() { for (auto iter = mWebRenderUserDatas.Iter(); !iter.Done(); iter.Next()) { WebRenderUserData* data = iter.Get()->GetKey(); if (!data->IsUsed()) { nsIFrame* frame = data->GetFrame(); MOZ_ASSERT(frame->HasProperty(nsIFrame::WebRenderUserDataProperty())); nsIFrame::WebRenderUserDataTable* userDataTable = frame->GetProperty(nsIFrame::WebRenderUserDataProperty()); MOZ_ASSERT(userDataTable->Count()); userDataTable->Remove(data->GetDisplayItemKey()); if (!userDataTable->Count()) { frame->RemoveProperty(nsIFrame::WebRenderUserDataProperty()); } if (data->GetType() == WebRenderUserData::UserDataType::eCanvas) { mLastCanvasDatas.RemoveEntry(data->AsCanvasData()); } iter.Remove(); continue; } data->SetUsed(false); } } void WebRenderCommandBuilder::ClearCachedResources() { for (auto iter = mWebRenderUserDatas.Iter(); !iter.Done(); iter.Next()) { WebRenderUserData* data = iter.Get()->GetKey(); data->ClearCachedResources(); } } } // namespace layers } // namespace mozilla