/* -*- 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 "ClientTiledPaintedLayer.h" #include "FrameMetrics.h" // for FrameMetrics #include "Units.h" // for ScreenIntRect, CSSPoint, etc #include "UnitTransforms.h" // for TransformTo #include "ClientLayerManager.h" // for ClientLayerManager, etc #include "gfxPlatform.h" // for gfxPlatform #include "gfxRect.h" // for gfxRect #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc #include "mozilla/StaticPrefs_layers.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/gfx/BaseSize.h" // for BaseSize #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/Rect.h" // for Rect, RectTyped #include "mozilla/layers/CompositorBridgeChild.h" #include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper #include "mozilla/layers/LayersMessages.h" #include "mozilla/layers/PaintThread.h" #include "mozilla/mozalloc.h" // for operator delete, etc #include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc #include "mozilla/layers/MultiTiledContentClient.h" #include "mozilla/layers/SingleTiledContentClient.h" namespace mozilla { namespace layers { using gfx::IntRect; using gfx::IntSize; using gfx::Rect; ClientTiledPaintedLayer::ClientTiledPaintedLayer( ClientLayerManager* const aManager, ClientLayerManager::PaintedLayerCreationHint aCreationHint) : PaintedLayer(aManager, static_cast(this), aCreationHint), mContentClient(), mHaveSingleTiledContentClient(false) { MOZ_COUNT_CTOR(ClientTiledPaintedLayer); mPaintData.mLastScrollOffset = ParentLayerPoint(0, 0); mPaintData.mFirstPaint = true; } ClientTiledPaintedLayer::~ClientTiledPaintedLayer() { MOZ_COUNT_DTOR(ClientTiledPaintedLayer); } void ClientTiledPaintedLayer::ClearCachedResources() { if (mContentClient) { mContentClient->ClearCachedResources(); } ClearValidRegion(); mContentClient = nullptr; } void ClientTiledPaintedLayer::FillSpecificAttributes( SpecificLayerAttributes& aAttrs) { aAttrs = PaintedLayerAttributes(GetValidRegion()); } static Maybe ApplyParentLayerToLayerTransform( const ParentLayerToLayerMatrix4x4& aTransform, const ParentLayerRect& aParentLayerRect, const LayerRect& aClip) { return UntransformBy(aTransform, aParentLayerRect, aClip); } static LayerToParentLayerMatrix4x4 GetTransformToAncestorsParentLayer( Layer* aStart, const LayerMetricsWrapper& aAncestor) { // If the ancestor layer Combines3DTransformWithAncestors, then the // scroll offset is contained in the transform of the layer at the // root of the 3D context. So we must first find that layer, then // calcuate the transform to its parent. LayerMetricsWrapper root3dAncestor = aAncestor; while (root3dAncestor.Combines3DTransformWithAncestors()) { root3dAncestor = root3dAncestor.GetParent(); } gfx::Matrix4x4 transform; const LayerMetricsWrapper& ancestorParent = root3dAncestor.GetParent(); for (LayerMetricsWrapper iter(aStart, LayerMetricsWrapper::StartAt::BOTTOM); ancestorParent ? iter != ancestorParent : iter.IsValid(); iter = iter.GetParent()) { transform = transform * iter.GetTransform(); } return ViewAs(transform); } void ClientTiledPaintedLayer::GetAncestorLayers( LayerMetricsWrapper* aOutScrollAncestor, LayerMetricsWrapper* aOutDisplayPortAncestor, bool* aOutHasTransformAnimation) { LayerMetricsWrapper scrollAncestor; LayerMetricsWrapper displayPortAncestor; bool hasTransformAnimation = false; for (LayerMetricsWrapper ancestor(this, LayerMetricsWrapper::StartAt::BOTTOM); ancestor; ancestor = ancestor.GetParent()) { hasTransformAnimation |= ancestor.HasTransformAnimation(); const FrameMetrics& metrics = ancestor.Metrics(); if (!scrollAncestor && metrics.GetScrollId() != ScrollableLayerGuid::NULL_SCROLL_ID) { scrollAncestor = ancestor; } if (!metrics.GetDisplayPort().IsEmpty()) { displayPortAncestor = ancestor; // Any layer that has a displayport must be scrollable, so we can break // here. break; } } if (aOutScrollAncestor) { *aOutScrollAncestor = scrollAncestor; } if (aOutDisplayPortAncestor) { *aOutDisplayPortAncestor = displayPortAncestor; } if (aOutHasTransformAnimation) { *aOutHasTransformAnimation = hasTransformAnimation; } } void ClientTiledPaintedLayer::BeginPaint() { mPaintData.ResetPaintData(); if (!GetBaseTransform().Is2D()) { // Give up if there is a complex CSS transform on the layer. We might // eventually support these but for now it's too complicated to handle // given that it's a pretty rare scenario. return; } // Get the metrics of the nearest scrollable layer and the nearest layer // with a displayport. LayerMetricsWrapper scrollAncestor; LayerMetricsWrapper displayPortAncestor; bool hasTransformAnimation; GetAncestorLayers(&scrollAncestor, &displayPortAncestor, &hasTransformAnimation); if (!displayPortAncestor || !scrollAncestor) { // No displayport or scroll ancestor, so we can't do progressive rendering. #if defined(MOZ_WIDGET_ANDROID) // Android are guaranteed to have a displayport set, so this // should never happen. NS_WARNING("Tiled PaintedLayer with no scrollable container ancestor"); #endif return; } TILING_LOG( "TILING %p: Found scrollAncestor %p, displayPortAncestor %p, transform " "%d\n", this, scrollAncestor.GetLayer(), displayPortAncestor.GetLayer(), hasTransformAnimation); const FrameMetrics& scrollMetrics = scrollAncestor.Metrics(); const FrameMetrics& displayportMetrics = displayPortAncestor.Metrics(); // Calculate the transform required to convert ParentLayer space of our // display port ancestor to the Layer space of this layer. ParentLayerToLayerMatrix4x4 transformDisplayPortToLayer = GetTransformToAncestorsParentLayer(this, displayPortAncestor).Inverse(); LayerRect layerBounds(GetVisibleRegion().GetBounds()); // Compute the critical display port that applies to this layer in the // LayoutDevice space of this layer, but only if there is no OMT animation // on this layer. If there is an OMT animation then we need to draw the whole // visible region of this layer as determined by layout, because we don't know // what parts of it might move into view in the compositor. mPaintData.mHasTransformAnimation = hasTransformAnimation; if (!mPaintData.mHasTransformAnimation && mContentClient->GetLowPrecisionTiledBuffer()) { ParentLayerRect criticalDisplayPort = (displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom()) + displayportMetrics.GetCompositionBounds().TopLeft(); Maybe criticalDisplayPortTransformed = ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort, layerBounds); if (criticalDisplayPortTransformed) { mPaintData.mCriticalDisplayPort = Some(RoundedToInt(*criticalDisplayPortTransformed)); } else { mPaintData.mCriticalDisplayPort = Some(LayerIntRect(0, 0, 0, 0)); } } TILING_LOG("TILING %p: Critical displayport %s\n", this, mPaintData.mCriticalDisplayPort ? Stringify(*mPaintData.mCriticalDisplayPort).c_str() : "not set"); // Store the resolution from the displayport ancestor layer. Because this is // Gecko-side, before any async transforms have occurred, we can use the zoom // for this. mPaintData.mResolution = displayportMetrics.GetZoom(); TILING_LOG("TILING %p: Resolution %s\n", this, Stringify(mPaintData.mResolution).c_str()); // Store the applicable composition bounds in this layer's Layer units. mPaintData.mTransformToCompBounds = GetTransformToAncestorsParentLayer(this, scrollAncestor); ParentLayerToLayerMatrix4x4 transformToBounds = mPaintData.mTransformToCompBounds.Inverse(); Maybe compositionBoundsTransformed = ApplyParentLayerToLayerTransform( transformToBounds, scrollMetrics.GetCompositionBounds(), layerBounds); if (compositionBoundsTransformed) { mPaintData.mCompositionBounds = *compositionBoundsTransformed; } else { mPaintData.mCompositionBounds.SetEmpty(); } TILING_LOG("TILING %p: Composition bounds %s\n", this, Stringify(mPaintData.mCompositionBounds).c_str()); // Calculate the scroll offset since the last transaction mPaintData.mScrollOffset = displayportMetrics.GetLayoutScrollOffset() * displayportMetrics.GetZoom(); TILING_LOG("TILING %p: Scroll offset %s\n", this, Stringify(mPaintData.mScrollOffset).c_str()); } bool ClientTiledPaintedLayer::IsScrollingOnCompositor( const FrameMetrics& aParentMetrics) { CompositorBridgeChild* compositor = nullptr; if (Manager() && Manager()->AsClientLayerManager()) { compositor = Manager()->AsClientLayerManager()->GetCompositorBridgeChild(); } if (!compositor) { return false; } FrameMetrics compositorMetrics; if (!compositor->LookupCompositorFrameMetrics(aParentMetrics.GetScrollId(), compositorMetrics)) { return false; } // 1 is a tad high for a fuzzy equals epsilon however if our scroll delta // is so small then we have nothing to gain from using paint heuristics. float COORDINATE_EPSILON = 1.f; return !FuzzyEqualsAdditive(compositorMetrics.GetVisualScrollOffset().x, aParentMetrics.GetVisualScrollOffset().x, COORDINATE_EPSILON) || !FuzzyEqualsAdditive(compositorMetrics.GetVisualScrollOffset().y, aParentMetrics.GetVisualScrollOffset().y, COORDINATE_EPSILON); } bool ClientTiledPaintedLayer::UseProgressiveDraw() { if (!StaticPrefs::layers_progressive_paint()) { // pref is disabled, so never do progressive return false; } if (!mContentClient->GetTiledBuffer()->SupportsProgressiveUpdate()) { return false; } if (ClientManager()->HasShadowTarget()) { // This condition is true when we are in a reftest scenario. We don't want // to draw progressively here because it can cause intermittent reftest // failures because the harness won't wait for all the tiles to be drawn. return false; } if (GetIsFixedPosition() || GetParent()->GetIsFixedPosition()) { // This layer is fixed-position and so even if it does have a scrolling // ancestor it will likely be entirely on-screen all the time, so we // should draw it all at once return false; } if (mPaintData.mHasTransformAnimation) { // The compositor is going to animate this somehow, so we want it all // on the screen at once. return false; } if (ClientManager()->AsyncPanZoomEnabled()) { LayerMetricsWrapper scrollAncestor; GetAncestorLayers(&scrollAncestor, nullptr, nullptr); MOZ_ASSERT( scrollAncestor); // because mPaintData.mCriticalDisplayPort is set if (!scrollAncestor) { return false; } const FrameMetrics& parentMetrics = scrollAncestor.Metrics(); if (!IsScrollingOnCompositor(parentMetrics)) { return false; } } return true; } bool ClientTiledPaintedLayer::RenderHighPrecision( const nsIntRegion& aInvalidRegion, const nsIntRegion& aVisibleRegion, LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { // If we have started drawing low-precision already, then we // shouldn't do anything there. if (mPaintData.mLowPrecisionPaintCount != 0) { return false; } // Only draw progressively when there is something to paint and the // resolution is unchanged if (!aInvalidRegion.IsEmpty() && UseProgressiveDraw() && mContentClient->GetTiledBuffer()->GetFrameResolution() == mPaintData.mResolution) { // Store the old valid region, then clear it before painting. // We clip the old valid region to the visible region, as it only gets // used to decide stale content (currently valid and previously visible) nsIntRegion oldValidRegion = mContentClient->GetTiledBuffer()->GetValidRegion(); oldValidRegion.And(oldValidRegion, aVisibleRegion); if (mPaintData.mCriticalDisplayPort) { oldValidRegion.And(oldValidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); } TILING_LOG("TILING %p: Progressive update with old valid region %s\n", this, Stringify(oldValidRegion).c_str()); nsIntRegion drawnRegion; bool updatedBuffer = mContentClient->GetTiledBuffer()->ProgressiveUpdate( GetValidRegion(), aInvalidRegion, oldValidRegion, drawnRegion, &mPaintData, aCallback, aCallbackData); AddToValidRegion(drawnRegion); return updatedBuffer; } // Otherwise do a non-progressive paint. We must do this even when // the region to paint is empty as the valid region may have shrunk. nsIntRegion validRegion = aVisibleRegion; if (mPaintData.mCriticalDisplayPort) { validRegion.AndWith(mPaintData.mCriticalDisplayPort->ToUnknownRect()); } SetValidRegion(validRegion); TILING_LOG("TILING %p: Non-progressive paint invalid region %s\n", this, Stringify(aInvalidRegion).c_str()); TILING_LOG("TILING %p: Non-progressive paint new valid region %s\n", this, Stringify(GetValidRegion()).c_str()); TilePaintFlags flags = PaintThread::Get() ? TilePaintFlags::Async : TilePaintFlags::None; mContentClient->GetTiledBuffer()->SetFrameResolution(mPaintData.mResolution); mContentClient->GetTiledBuffer()->PaintThebes( GetValidRegion(), aInvalidRegion, aInvalidRegion, aCallback, aCallbackData, flags); mPaintData.mPaintFinished = true; return true; } bool ClientTiledPaintedLayer::RenderLowPrecision( const nsIntRegion& aInvalidRegion, const nsIntRegion& aVisibleRegion, LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { nsIntRegion invalidRegion = aInvalidRegion; // Render the low precision buffer, if the visible region is larger than the // critical display port. if (!mPaintData.mCriticalDisplayPort || !nsIntRegion(mPaintData.mCriticalDisplayPort->ToUnknownRect()) .Contains(aVisibleRegion)) { nsIntRegion oldValidRegion = mContentClient->GetLowPrecisionTiledBuffer()->GetValidRegion(); oldValidRegion.And(oldValidRegion, aVisibleRegion); bool updatedBuffer = false; // If the frame resolution or format have changed, invalidate the buffer if (mContentClient->GetLowPrecisionTiledBuffer()->GetFrameResolution() != mPaintData.mResolution || mContentClient->GetLowPrecisionTiledBuffer()->HasFormatChanged()) { if (!mLowPrecisionValidRegion.IsEmpty()) { updatedBuffer = true; } oldValidRegion.SetEmpty(); mLowPrecisionValidRegion.SetEmpty(); mContentClient->GetLowPrecisionTiledBuffer()->ResetPaintedAndValidState(); mContentClient->GetLowPrecisionTiledBuffer()->SetFrameResolution( mPaintData.mResolution); invalidRegion = aVisibleRegion; } // Invalidate previously valid content that is no longer visible if (mPaintData.mLowPrecisionPaintCount == 1) { mLowPrecisionValidRegion.And(mLowPrecisionValidRegion, aVisibleRegion); } mPaintData.mLowPrecisionPaintCount++; // Remove the valid high-precision region from the invalid low-precision // region. We don't want to spend time drawing things twice. invalidRegion.SubOut(GetValidRegion()); TILING_LOG( "TILING %p: Progressive paint: low-precision invalid region is %s\n", this, Stringify(invalidRegion).c_str()); TILING_LOG( "TILING %p: Progressive paint: low-precision old valid region is %s\n", this, Stringify(oldValidRegion).c_str()); if (!invalidRegion.IsEmpty()) { nsIntRegion drawnRegion; updatedBuffer = mContentClient->GetLowPrecisionTiledBuffer()->ProgressiveUpdate( mLowPrecisionValidRegion, invalidRegion, oldValidRegion, drawnRegion, &mPaintData, aCallback, aCallbackData); mLowPrecisionValidRegion.OrWith(drawnRegion); } TILING_LOG( "TILING %p: Progressive paint: low-precision new valid region is %s\n", this, Stringify(mLowPrecisionValidRegion).c_str()); return updatedBuffer; } if (!mLowPrecisionValidRegion.IsEmpty()) { TILING_LOG("TILING %p: Clearing low-precision buffer\n", this); // Clear the low precision tiled buffer. mLowPrecisionValidRegion.SetEmpty(); mContentClient->GetLowPrecisionTiledBuffer()->ResetPaintedAndValidState(); // Return true here so we send a Painted callback after clearing the valid // region of the low precision buffer. This allows the shadow buffer's valid // region to be updated and the associated resources to be freed. return true; } return false; } void ClientTiledPaintedLayer::EndPaint() { mPaintData.mLastScrollOffset = mPaintData.mScrollOffset; mPaintData.mPaintFinished = true; mPaintData.mFirstPaint = false; TILING_LOG("TILING %p: Paint finished\n", this); } void ClientTiledPaintedLayer::RenderLayer() { if (!ClientManager()->IsRepeatTransaction()) { // Only paint the mask layers on the first transaction. RenderMaskLayers(this); } LayerManager::DrawPaintedLayerCallback callback = ClientManager()->GetPaintedLayerCallback(); void* data = ClientManager()->GetPaintedLayerCallbackData(); IntSize layerSize = mVisibleRegion.GetBounds().ToUnknownRect().Size(); IntSize tileSize = gfx::gfxVars::TileSize(); bool isHalfTileWidthOrHeight = layerSize.width <= tileSize.width / 2 || layerSize.height <= tileSize.height / 2; // Use single tile when layer is not scrollable, is smaller than one // tile, or when more than half of the tiles' pixels in either // dimension would be wasted. bool wantSingleTiledContentClient = (mCreationHint == LayerManager::NONE || layerSize <= tileSize || isHalfTileWidthOrHeight) && SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager()) && StaticPrefs::layers_single_tile_enabled(); if (mContentClient && mHaveSingleTiledContentClient && !wantSingleTiledContentClient) { mContentClient = nullptr; ClearValidRegion(); } if (!mContentClient) { if (wantSingleTiledContentClient) { mContentClient = new SingleTiledContentClient(*this, ClientManager()); mHaveSingleTiledContentClient = true; } else { mContentClient = new MultiTiledContentClient(*this, ClientManager()); mHaveSingleTiledContentClient = false; } mContentClient->Connect(); ClientManager()->AsShadowForwarder()->Attach(mContentClient, this); MOZ_ASSERT(mContentClient->GetForwarder()); } if (mContentClient->GetTiledBuffer()->HasFormatChanged()) { ClearValidRegion(); mContentClient->GetTiledBuffer()->ResetPaintedAndValidState(); } TILING_LOG("TILING %p: Initial visible region %s\n", this, Stringify(mVisibleRegion).c_str()); TILING_LOG("TILING %p: Initial valid region %s\n", this, Stringify(GetValidRegion()).c_str()); TILING_LOG("TILING %p: Initial low-precision valid region %s\n", this, Stringify(mLowPrecisionValidRegion).c_str()); nsIntRegion neededRegion = mVisibleRegion.ToUnknownRegion(); #ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE // This is handled by PadDrawTargetOutFromRegion in TiledContentClient for // mobile if (MayResample()) { // If we're resampling then bilinear filtering can read up to 1 pixel // outside of our texture coords. Make the visible region a single rect, // and pad it out by 1 pixel (restricted to tile boundaries) so that // we always have valid content or transparent pixels to sample from. IntRect bounds = neededRegion.GetBounds(); IntRect wholeTiles = bounds; wholeTiles.InflateToMultiple(gfx::gfxVars::TileSize()); IntRect padded = bounds; padded.Inflate(1); padded.IntersectRect(padded, wholeTiles); neededRegion = padded; } #endif nsIntRegion invalidRegion; invalidRegion.Sub(neededRegion, GetValidRegion()); if (invalidRegion.IsEmpty()) { EndPaint(); return; } if (!callback) { ClientManager()->SetTransactionIncomplete(); return; } if (!ClientManager()->IsRepeatTransaction()) { // For more complex cases we need to calculate a bunch of metrics before we // can do the paint. BeginPaint(); if (mPaintData.mPaintFinished) { return; } // Make sure that tiles that fall outside of the visible region or outside // of the critical displayport are discarded on the first update. Also make // sure that we only draw stuff inside the critical displayport on the first // update. nsIntRegion validRegion; validRegion.And(GetValidRegion(), neededRegion); if (mPaintData.mCriticalDisplayPort) { validRegion.AndWith(mPaintData.mCriticalDisplayPort->ToUnknownRect()); invalidRegion.And(invalidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); } SetValidRegion(validRegion); TILING_LOG("TILING %p: First-transaction valid region %s\n", this, Stringify(validRegion).c_str()); TILING_LOG("TILING %p: First-transaction invalid region %s\n", this, Stringify(invalidRegion).c_str()); } else { if (mPaintData.mCriticalDisplayPort) { invalidRegion.And(invalidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); } TILING_LOG("TILING %p: Repeat-transaction invalid region %s\n", this, Stringify(invalidRegion).c_str()); } nsIntRegion lowPrecisionInvalidRegion; if (mContentClient->GetLowPrecisionTiledBuffer()) { // Calculate the invalid region for the low precision buffer. Make sure // to remove the valid high-precision area so we don't double-paint it. lowPrecisionInvalidRegion.Sub(neededRegion, mLowPrecisionValidRegion); lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, GetValidRegion()); } TILING_LOG("TILING %p: Low-precision invalid region %s\n", this, Stringify(lowPrecisionInvalidRegion).c_str()); bool updatedHighPrecision = RenderHighPrecision(invalidRegion, neededRegion, callback, data); if (updatedHighPrecision) { ClientManager()->Hold(this); mContentClient->UpdatedBuffer(TiledContentClient::TILED_BUFFER); if (!mPaintData.mPaintFinished) { // There is still more high-res stuff to paint, so we're not // done yet. A subsequent transaction will take care of this. ClientManager()->SetRepeatTransaction(); return; } } // If there is nothing to draw in low-precision, then we're done. if (lowPrecisionInvalidRegion.IsEmpty()) { EndPaint(); return; } if (updatedHighPrecision) { // If there are low precision updates, but we just did some high-precision // updates, then mark the paint as unfinished and request a repeat // transaction. This is so that we don't perform low-precision updates in // the same transaction as high-precision updates. TILING_LOG( "TILING %p: Scheduling repeat transaction for low-precision painting\n", this); ClientManager()->SetRepeatTransaction(); mPaintData.mLowPrecisionPaintCount = 1; mPaintData.mPaintFinished = false; return; } bool updatedLowPrecision = RenderLowPrecision(lowPrecisionInvalidRegion, neededRegion, callback, data); if (updatedLowPrecision) { ClientManager()->Hold(this); mContentClient->UpdatedBuffer( TiledContentClient::LOW_PRECISION_TILED_BUFFER); if (!mPaintData.mPaintFinished) { // There is still more low-res stuff to paint, so we're not // done yet. A subsequent transaction will take care of this. ClientManager()->SetRepeatTransaction(); return; } } // If we get here, we've done all the high- and low-precision // paints we wanted to do, so we can finish the paint and chill. EndPaint(); } bool ClientTiledPaintedLayer::IsOptimizedFor( LayerManager::PaintedLayerCreationHint aHint) { // The only creation hint is whether the layer is scrollable or not, and this // is only respected on OSX, where it's used to determine whether to // use a tiled content client or not. // There are pretty nasty performance consequences for not using tiles on // large, scrollable layers, so we want the layer to be recreated in this // situation. return aHint == GetCreationHint(); } void ClientTiledPaintedLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { PaintedLayer::PrintInfo(aStream, aPrefix); if (mContentClient) { aStream << "\n"; nsAutoCString pfx(aPrefix); pfx += " "; mContentClient->PrintInfo(aStream, pfx.get()); } } } // namespace layers } // namespace mozilla