diff options
Diffstat (limited to 'gfx/layers/composite/AsyncCompositionManager.cpp')
-rw-r--r-- | gfx/layers/composite/AsyncCompositionManager.cpp | 1314 |
1 files changed, 1314 insertions, 0 deletions
diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp new file mode 100644 index 0000000000..3c155d0df0 --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -0,0 +1,1314 @@ +/* -*- 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 "mozilla/layers/AsyncCompositionManager.h" +#include <stdint.h> // for uint32_t +#include "LayerManagerComposite.h" // for LayerManagerComposite, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "gfxPoint.h" // for gfxPoint, gfxSize +#include "mozilla/ServoBindings.h" // for Servo_AnimationValue_GetOpacity, etc +#include "mozilla/ScopeExit.h" // for MakeScopeExit +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/WidgetUtils.h" // for ComputeTransformForRotation +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Point.h" // for RoundedToInt, PointTyped +#include "mozilla/gfx/Rect.h" // for RoundedToInt, RectTyped +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/APZUtils.h" // for CompleteAsyncTransform +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorAnimationStorage.h" // for CompositorAnimationStorage +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/SampleTime.h" +#include "nsCoord.h" // for NSAppUnitsToFloatPixels, etc +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsDeviceContext.h" // for nsDeviceContext +#include "nsDisplayList.h" // for nsDisplayTransform, etc +#include "nsMathUtils.h" // for NS_round +#include "nsPoint.h" // for nsPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "nsTArrayForwardDeclare.h" // for nsTArray +#include "UnitTransforms.h" // for TransformTo +#if defined(MOZ_WIDGET_ANDROID) +# include <android/log.h> +# include "mozilla/layers/UiCompositorControllerParent.h" +# include "mozilla/widget/AndroidCompositorWidget.h" +#endif +#include "GeckoProfiler.h" +#include "FrameUniformityData.h" +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch +#include "VsyncSource.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +static bool IsSameDimension(hal::ScreenOrientation o1, + hal::ScreenOrientation o2) { + bool isO1portrait = (o1 == hal::eScreenOrientation_PortraitPrimary || + o1 == hal::eScreenOrientation_PortraitSecondary); + bool isO2portrait = (o2 == hal::eScreenOrientation_PortraitPrimary || + o2 == hal::eScreenOrientation_PortraitSecondary); + return !(isO1portrait ^ isO2portrait); +} + +static bool ContentMightReflowOnOrientationChange(const IntRect& rect) { + return rect.Width() != rect.Height(); +} + +AsyncCompositionManager::AsyncCompositionManager( + CompositorBridgeParent* aParent, HostLayerManager* aManager) + : mLayerManager(aManager), + mIsFirstPaint(true), + mLayersUpdated(false), + mReadyForCompose(true), + mCompositorBridge(aParent) { + MOZ_ASSERT(mCompositorBridge); +} + +AsyncCompositionManager::~AsyncCompositionManager() = default; + +void AsyncCompositionManager::ResolveRefLayers( + CompositorBridgeParent* aCompositor, bool* aHasRemoteContent, + bool* aResolvePlugins) { + if (aHasRemoteContent) { + *aHasRemoteContent = false; + } + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // If valid *aResolvePlugins indicates if we need to update plugin geometry + // when we walk the tree. + bool resolvePlugins = (aCompositor && aResolvePlugins && *aResolvePlugins); +#endif + + if (!mLayerManager->GetRoot()) { + // Updated the return value since this result controls completing + // composition. + if (aResolvePlugins) { + *aResolvePlugins = false; + } + return; + } + + mReadyForCompose = true; + bool hasRemoteContent = false; + bool didResolvePlugins = false; + + ForEachNode<ForwardIterator>(mLayerManager->GetRoot(), [&](Layer* layer) { + RefLayer* refLayer = layer->AsRefLayer(); + if (!refLayer) { + return; + } + + hasRemoteContent = true; + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree( + refLayer->GetReferentId()); + if (!state) { + return; + } + + Layer* referent = state->mRoot; + if (!referent) { + return; + } + + if (!refLayer->GetLocalVisibleRegion().IsEmpty()) { + hal::ScreenOrientation chromeOrientation = mTargetConfig.orientation(); + hal::ScreenOrientation contentOrientation = + state->mTargetConfig.orientation(); + if (!IsSameDimension(chromeOrientation, contentOrientation) && + ContentMightReflowOnOrientationChange( + mTargetConfig.naturalBounds())) { + mReadyForCompose = false; + } + } + + refLayer->ConnectReferentLayer(referent); + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + if (resolvePlugins) { + didResolvePlugins |= + aCompositor->UpdatePluginWindowState(refLayer->GetReferentId()); + } +#endif + }); + + if (aHasRemoteContent) { + *aHasRemoteContent = hasRemoteContent; + } + if (aResolvePlugins) { + *aResolvePlugins = didResolvePlugins; + } +} + +void AsyncCompositionManager::DetachRefLayers() { + if (!mLayerManager->GetRoot()) { + return; + } + + mReadyForCompose = false; + + ForEachNodePostOrder<ForwardIterator>( + mLayerManager->GetRoot(), [&](Layer* layer) { + RefLayer* refLayer = layer->AsRefLayer(); + if (!refLayer) { + return; + } + + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree( + refLayer->GetReferentId()); + if (!state) { + return; + } + + Layer* referent = state->mRoot; + if (referent) { + refLayer->DetachReferentLayer(referent); + } + }); +} + +void AsyncCompositionManager::ComputeRotation() { + if (!mTargetConfig.naturalBounds().IsEmpty()) { + mWorldTransform = ComputeTransformForRotation(mTargetConfig.naturalBounds(), + mTargetConfig.rotation()); + } +} + +static void GetBaseTransform(Layer* aLayer, Matrix4x4* aTransform) { + // Start with the animated transform if there is one + *aTransform = (aLayer->AsHostLayer()->GetShadowTransformSetByAnimation() + ? aLayer->GetLocalTransform() + : aLayer->GetTransform()); +} + +static void TransformClipRect( + Layer* aLayer, const ParentLayerToParentLayerMatrix4x4& aTransform) { + MOZ_ASSERT(aTransform.Is2D()); + const Maybe<ParentLayerIntRect>& clipRect = + aLayer->AsHostLayer()->GetShadowClipRect(); + if (clipRect) { + ParentLayerIntRect transformed = TransformBy(aTransform, *clipRect); + aLayer->AsHostLayer()->SetShadowClipRect(Some(transformed)); + } +} + +// Similar to TransformFixedClip(), but only transforms the fixed part of the +// clip. +static void TransformFixedClip( + Layer* aLayer, const ParentLayerToParentLayerMatrix4x4& aTransform, + AsyncCompositionManager::ClipParts& aClipParts) { + MOZ_ASSERT(aTransform.Is2D()); + if (aClipParts.mFixedClip) { + *aClipParts.mFixedClip = TransformBy(aTransform, *aClipParts.mFixedClip); + aLayer->AsHostLayer()->SetShadowClipRect(aClipParts.Intersect()); + } +} + +/** + * Set the given transform as the shadow transform on the layer, assuming + * that the given transform already has the pre- and post-scales applied. + * That is, this function cancels out the pre- and post-scales from aTransform + * before setting it as the shadow transform on the layer, so that when + * the layer's effective transform is computed, the pre- and post-scales will + * only be applied once. + */ +static void SetShadowTransform(Layer* aLayer, + LayerToParentLayerMatrix4x4 aTransform) { + if (ContainerLayer* c = aLayer->AsContainerLayer()) { + aTransform.PreScale(1.0f / c->GetPreXScale(), 1.0f / c->GetPreYScale(), 1); + } + aTransform.PostScale(1.0f / aLayer->GetPostXScale(), + 1.0f / aLayer->GetPostYScale(), 1); + aLayer->AsHostLayer()->SetShadowBaseTransform(aTransform.ToUnknownMatrix()); +} + +static void TranslateShadowLayer( + Layer* aLayer, const ParentLayerPoint& aTranslation, bool aAdjustClipRect, + AsyncCompositionManager::ClipPartsCache* aClipPartsCache) { + // This layer might also be a scrollable layer and have an async transform. + // To make sure we don't clobber that, we start with the shadow transform. + // (i.e. GetLocalTransform() instead of GetTransform()). + // Note that the shadow transform is reset on every frame of composition so + // we don't have to worry about the adjustments compounding over successive + // frames. + LayerToParentLayerMatrix4x4 layerTransform = aLayer->GetLocalTransformTyped(); + + // Apply the translation to the layer transform. + layerTransform.PostTranslate(aTranslation); + + SetShadowTransform(aLayer, layerTransform); + aLayer->AsHostLayer()->SetShadowTransformSetByAnimation(false); + + if (aAdjustClipRect) { + auto transform = + ParentLayerToParentLayerMatrix4x4::Translation(aTranslation); + // If we're passed a clip parts cache, only transform the fixed part of + // the clip. + if (aClipPartsCache) { + auto iter = aClipPartsCache->find(aLayer); + MOZ_ASSERT(iter != aClipPartsCache->end()); + TransformFixedClip(aLayer, transform, iter->second); + } else { + TransformClipRect(aLayer, transform); + } + + // If a fixed- or sticky-position layer has a mask layer, that mask should + // move along with the layer, so apply the translation to the mask layer + // too. + if (Layer* maskLayer = aLayer->GetMaskLayer()) { + TranslateShadowLayer(maskLayer, aTranslation, false, aClipPartsCache); + } + } +} + +static void AccumulateLayerTransforms(Layer* aLayer, Layer* aAncestor, + Matrix4x4& aMatrix) { + // Accumulate the transforms between this layer and the subtree root layer. + for (Layer* l = aLayer; l && l != aAncestor; l = l->GetParent()) { + Matrix4x4 transform; + GetBaseTransform(l, &transform); + aMatrix *= transform; + } +} + +/** + * Finds the metrics on |aLayer| with scroll id |aScrollId|, and returns a + * LayerMetricsWrapper representing the (layer, metrics) pair, or the null + * LayerMetricsWrapper if no matching metrics could be found. + */ +static LayerMetricsWrapper FindMetricsWithScrollId( + Layer* aLayer, ScrollableLayerGuid::ViewID aScrollId) { + for (uint64_t i = 0; i < aLayer->GetScrollMetadataCount(); ++i) { + if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollId) { + return LayerMetricsWrapper(aLayer, i); + } + } + return LayerMetricsWrapper(); +} + +/** + * Checks whether the (layer, metrics) pair (aTransformedLayer, + * aTransformedMetrics) is on the path from |aFixedLayer| to the metrics with + * scroll id |aFixedWithRespectTo|, inclusive. + */ +static bool AsyncTransformShouldBeUnapplied( + Layer* aFixedLayer, ScrollableLayerGuid::ViewID aFixedWithRespectTo, + Layer* aTransformedLayer, ScrollableLayerGuid::ViewID aTransformedMetrics) { + LayerMetricsWrapper transformed = + FindMetricsWithScrollId(aTransformedLayer, aTransformedMetrics); + if (!transformed.IsValid()) { + return false; + } + // It's important to start at the bottom, because the fixed layer itself + // could have the transformed metrics, and they can be at the bottom. + LayerMetricsWrapper current(aFixedLayer, + LayerMetricsWrapper::StartAt::BOTTOM); + bool encounteredTransformedLayer = false; + // The transformed layer is on the path from |aFixedLayer| to the fixed-to + // layer if as we walk up the (layer, metrics) tree starting from + // |aFixedLayer|, we *first* encounter the transformed layer, and *then* (or + // at the same time) the fixed-to layer. + while (current) { + if (!encounteredTransformedLayer && current == transformed) { + encounteredTransformedLayer = true; + } + if (current.Metrics().GetScrollId() == aFixedWithRespectTo) { + return encounteredTransformedLayer; + } + current = current.GetParent(); + // It's possible that we reach a layers id boundary before we reach an + // ancestor with the scroll id |aFixedWithRespectTo| (this could happen + // e.g. if the scroll frame with that scroll id uses containerless + // scrolling). In such a case, stop the walk, as a new layers id could + // have a different layer with scroll id |aFixedWithRespectTo| which we + // don't intend to match. + if (current && current.AsRefLayer() != nullptr) { + break; + } + } + return false; +} + +// If |aLayer| is fixed or sticky, returns the scroll id of the scroll frame +// that it's fixed or sticky to. Otherwise, returns Nothing(). +static Maybe<ScrollableLayerGuid::ViewID> IsFixedOrSticky(Layer* aLayer) { + bool isRootOfFixedSubtree = aLayer->GetIsFixedPosition() && + !aLayer->GetParent()->GetIsFixedPosition(); + if (isRootOfFixedSubtree) { + return Some(aLayer->GetFixedPositionScrollContainerId()); + } + if (aLayer->GetIsStickyPosition()) { + return Some(aLayer->GetStickyScrollContainerId()); + } + return Nothing(); +} + +void AsyncCompositionManager::AlignFixedAndStickyLayers( + Layer* aTransformedSubtreeRoot, Layer* aStartTraversalAt, + SideBits aStuckSides, ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins) { + Layer* layer = aStartTraversalAt; + bool needsAsyncTransformUnapplied = false; + if (Maybe<ScrollableLayerGuid::ViewID> fixedTo = IsFixedOrSticky(layer)) { + needsAsyncTransformUnapplied = AsyncTransformShouldBeUnapplied( + layer, *fixedTo, aTransformedSubtreeRoot, aTransformScrollId); + } + + // We want to process all the fixed and sticky descendants of + // aTransformedSubtreeRoot. Once we do encounter such a descendant, we don't + // need to recurse any deeper because the adjustment to the fixed or sticky + // layer will apply to its subtree. + if (!needsAsyncTransformUnapplied) { + for (Layer* child = layer->GetFirstChild(); child; + child = child->GetNextSibling()) { + AlignFixedAndStickyLayers(aTransformedSubtreeRoot, child, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); + } + return; + } + + AdjustFixedOrStickyLayer(aTransformedSubtreeRoot, layer, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); +} + +// Determine the amount of overlap between the 1D vector |aTranslation| +// and the interval [aMin, aMax]. +static gfxFloat IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, + gfxFloat aMax) { + if (aTranslation > 0) { + return std::max(0.0, std::min(aMax, aTranslation) - std::max(aMin, 0.0)); + } + + return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0)); +} + +void AsyncCompositionManager::AdjustFixedOrStickyLayer( + Layer* aTransformedSubtreeRoot, Layer* aFixedOrSticky, SideBits aStuckSides, + ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins) { + Layer* layer = aFixedOrSticky; + + // Insert a translation so that the position of the anchor point is the same + // before and after the change to the transform of aTransformedSubtreeRoot. + + // Accumulate the transforms between this layer and the subtree root layer. + Matrix4x4 ancestorTransform; + if (layer != aTransformedSubtreeRoot) { + AccumulateLayerTransforms(layer->GetParent(), aTransformedSubtreeRoot, + ancestorTransform); + } + ancestorTransform.NudgeToIntegersFixedEpsilon(); + + // A transform creates a containing block for fixed-position descendants, + // so there shouldn't be a transform in between the fixed layer and + // the subtree root layer. + if (layer->GetIsFixedPosition()) { + MOZ_ASSERT(ancestorTransform.IsIdentity()); + } + + // Calculate the cumulative transforms between the subtree root with the + // old transform and the current transform. + // For coordinate purposes, we'll treat the subtree root layer as the + // "parent" layer, even though it could be a farther ancestor. + auto oldCumulativeTransform = ViewAs<LayerToParentLayerMatrix4x4>( + ancestorTransform * aPreviousTransformForRoot.ToUnknownMatrix()); + auto newCumulativeTransform = ViewAs<LayerToParentLayerMatrix4x4>( + ancestorTransform * aCurrentTransformForRoot.ToUnknownMatrix()); + + // We're going to be inverting |newCumulativeTransform|. If it's singular, + // there's nothing we can do. + if (newCumulativeTransform.IsSingular()) { + return; + } + + // Since we create container layers for fixed layers, there shouldn't + // a local CSS or OMTA transform on the fixed layer, either (any local + // transform would go onto a descendant layer inside the container + // layer). +#ifdef DEBUG + Matrix4x4 localTransform; + GetBaseTransform(layer, &localTransform); + localTransform.NudgeToIntegersFixedEpsilon(); + MOZ_ASSERT(localTransform.IsIdentity()); +#endif + + // Now work out the translation necessary to make sure the layer doesn't + // move given the new sub-tree root transform. + + // Get the layer's fixed anchor point, in the layer's local coordinate space + // (before any transform is applied). + LayerPoint anchor = layer->GetFixedPositionAnchor(); + + SideBits sideBits = layer->GetFixedPositionSides(); + if (layer->GetIsStickyPosition()) { + // For sticky items, it may be that only some of the sides are actively + // stuck. Only take into account those sides. + sideBits &= aStuckSides; + } + + // Offset the layer's anchor point to make sure fixed position content + // respects content document fixed position margins. + ScreenPoint offset = apz::ComputeFixedMarginsOffset( + aFixedLayerMargins, sideBits, + // For sticky layers, we don't need to factor aGeckoFixedLayerMargins + // because Gecko doesn't shift the position of sticky elements for dynamic + // toolbar movements. + layer->GetIsStickyPosition() ? ScreenMargin() : aGeckoFixedLayerMargins); + + // Fixed margins only apply to layers fixed to the root, so we can view + // the offset in layer space. + LayerPoint offsetAnchor = + anchor + ViewAs<LayerPixel>( + offset, PixelCastJustification::ScreenIsParentLayerForRoot); + + // Additionally transform the anchor to compensate for the change + // from the old transform to the new transform. We do + // this by using the old transform to take the offset anchor back into + // subtree root space, and then the inverse of the new transform + // to bring it back to layer space. + ParentLayerPoint offsetAnchorInSubtreeRootSpace = + oldCumulativeTransform.TransformPoint(offsetAnchor); + LayerPoint transformedAnchor = + newCumulativeTransform.Inverse().TransformPoint( + offsetAnchorInSubtreeRootSpace); + + // We want to translate the layer by the difference between + // |transformedAnchor| and |anchor|. + LayerPoint translation = transformedAnchor - anchor; + + // A fixed layer will "consume" (be unadjusted by) the entire translation + // calculated above. A sticky layer may consume all, part, or none of it, + // depending on where we are relative to its sticky scroll range. + // The remainder of the translation (the unconsumed portion) needs to + // be propagated to descendant fixed/sticky layers. + LayerPoint unconsumedTranslation; + + if (layer->GetIsStickyPosition()) { + // For sticky positioned layers, the difference between the two rectangles + // defines a pair of translation intervals in each dimension through which + // the layer should not move relative to the scroll container. To + // accomplish this, we limit each dimension of the |translation| to that + // part of it which overlaps those intervals. + const LayerRectAbsolute& stickyOuter = layer->GetStickyScrollRangeOuter(); + const LayerRectAbsolute& stickyInner = layer->GetStickyScrollRangeInner(); + + LayerPoint originalTranslation = translation; + translation.y = + IntervalOverlap(translation.y, stickyOuter.Y(), stickyOuter.YMost()) - + IntervalOverlap(translation.y, stickyInner.Y(), stickyInner.YMost()); + translation.x = + IntervalOverlap(translation.x, stickyOuter.X(), stickyOuter.XMost()) - + IntervalOverlap(translation.x, stickyInner.X(), stickyInner.XMost()); + unconsumedTranslation = translation - originalTranslation; + } + + // Finally, apply the translation to the layer transform. Note that in cases + // where the async transform on |aTransformedSubtreeRoot| affects this layer's + // clip rect, we need to apply the same translation to said clip rect, so + // that the effective transform on the clip rect takes it back to where it was + // originally, had there been no async scroll. + TranslateShadowLayer( + layer, + ViewAs<ParentLayerPixel>(translation, + PixelCastJustification::NoTransformOnLayer), + true, &aClipPartsCache); + + // Propragate the unconsumed portion of the translation to descendant + // fixed/sticky layers. + if (unconsumedTranslation != LayerPoint()) { + // Take the computations we performed to derive |translation| from + // |aCurrentTransformForRoot|, and perform them in reverse, keeping other + // quantities fixed, to come up with a new transform |newTransform| that + // would produce |unconsumedTranslation|. + LayerPoint newTransformedAnchor = unconsumedTranslation + anchor; + ParentLayerPoint newTransformedAnchorInSubtreeRootSpace = + oldCumulativeTransform.TransformPoint(newTransformedAnchor); + LayerToParentLayerMatrix4x4 newTransform = aPreviousTransformForRoot; + newTransform.PostTranslate(newTransformedAnchorInSubtreeRootSpace - + offsetAnchorInSubtreeRootSpace); + + // Propagate this new transform to our descendants as the new value of + // |aCurrentTransformForRoot|. This allows them to consume the unconsumed + // translation. + for (Layer* child = layer->GetFirstChild(); child; + child = child->GetNextSibling()) { + AlignFixedAndStickyLayers(aTransformedSubtreeRoot, child, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + newTransform, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); + } + } +} + +bool AsyncCompositionManager::SampleAnimations(Layer* aLayer, + TimeStamp aCurrentFrameTime) { + CompositorAnimationStorage* storage = + mCompositorBridge->GetAnimationStorage(); + MOZ_ASSERT(storage); + + return storage->SampleAnimations(aLayer, mCompositorBridge, + mPreviousFrameTimeStamp, aCurrentFrameTime); +} + +void AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer) { + MOZ_ASSERT(StaticPrefs::gfx_vsync_collect_scroll_transforms()); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + ForEachNodePostOrder<ForwardIterator>(aLayer, [this](Layer* layer) { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + if (!layer->GetFrameMetrics(i).IsScrollable()) { + continue; + } + gfx::Matrix4x4 shadowTransform = + layer->AsHostLayer()->GetShadowBaseTransform(); + if (!shadowTransform.Is2D()) { + continue; + } + + Matrix transform = shadowTransform.As2D(); + if (transform.IsTranslation() && !shadowTransform.IsIdentity()) { + Point translation = transform.GetTranslation(); + mLayerTransformRecorder.RecordTransform(layer, translation); + return; + } + } + }); +} + +static AsyncTransformComponentMatrix AdjustForClip( + const AsyncTransformComponentMatrix& asyncTransform, Layer* aLayer) { + AsyncTransformComponentMatrix result = asyncTransform; + + // Container layers start at the origin, but they are clipped to where they + // actually have content on the screen. The tree transform is meant to apply + // to the clipped area. If the tree transform includes a scale component, + // then applying it to container as-is will produce incorrect results. To + // avoid this, translate the layer so that the clip rect starts at the origin, + // apply the tree transform, and translate back. + if (const Maybe<ParentLayerIntRect>& shadowClipRect = + aLayer->AsHostLayer()->GetShadowClipRect()) { + if (shadowClipRect->TopLeft() != + ParentLayerIntPoint()) { // avoid a gratuitous change of basis + result.ChangeBasis(shadowClipRect->X(), shadowClipRect->Y(), 0); + } + } + return result; +} + +static void ExpandRootClipRect(Layer* aLayer, + const ScreenMargin& aFixedLayerMargins) { + // For Fennec we want to expand the root scrollable layer clip rect based on + // the fixed position margins. In particular, we want this while the dynamic + // toolbar is in the process of sliding offscreen and the area of the + // LayerView visible to the user is larger than the viewport size that Gecko + // knows about (and therefore larger than the clip rect). We could also just + // clear the clip rect on aLayer entirely but this seems more precise. + Maybe<ParentLayerIntRect> rootClipRect = + aLayer->AsHostLayer()->GetShadowClipRect(); + if (rootClipRect && aFixedLayerMargins != ScreenMargin()) { +#ifndef MOZ_WIDGET_ANDROID + // We should never enter here on anything other than Fennec, since + // aFixedLayerMargins should be empty everywhere else. + MOZ_ASSERT(false); +#endif + ParentLayerRect rect(rootClipRect.value()); + rect.Deflate(ViewAs<ParentLayerPixel>( + aFixedLayerMargins, + PixelCastJustification::ScreenIsParentLayerForRoot)); + aLayer->AsHostLayer()->SetShadowClipRect(Some(RoundedOut(rect))); + } +} + +#ifdef MOZ_WIDGET_ANDROID +static void MoveScrollbarForLayerMargin( + Layer* aRoot, ScrollableLayerGuid::ViewID aRootScrollId, + const ScreenMargin& aFixedLayerMargins) { + // See bug 1223928 comment 9 - once we can detect the RCD with just the + // isRootContent flag on the metrics, we can probably move this code into + // ApplyAsyncTransformToScrollbar rather than having it as a separate + // adjustment on the layer tree. + Layer* scrollbar = + BreadthFirstSearch<ReverseIterator>(aRoot, [aRootScrollId](Layer* aNode) { + return (aNode->GetScrollbarData().IsThumb() && + aNode->GetScrollbarData().mDirection.isSome() && + *aNode->GetScrollbarData().mDirection == + ScrollDirection::eHorizontal && + aNode->GetScrollbarData().mTargetViewId == aRootScrollId); + }); + if (scrollbar) { + // Shift the horizontal scrollbar down into the new space exposed by the + // dynamic toolbar hiding. Technically we should also scale the vertical + // scrollbar a bit to expand into the new space but it's not as noticeable + // and it would add a lot more complexity, so we're going with the "it's not + // worth it" justification. + TranslateShadowLayer(scrollbar, + ParentLayerPoint(0, -aFixedLayerMargins.bottom), true, + nullptr); + if (scrollbar->GetParent()) { + // The layer that has the HORIZONTAL direction sits inside another + // ContainerLayer. This ContainerLayer also has a clip rect that causes + // the scrollbar to get clipped. We need to expand that clip rect to + // prevent that from happening. This is kind of ugly in that we're + // assuming a particular layer tree structure but short of adding more + // flags to the layer there doesn't appear to be a good way to do this. + ExpandRootClipRect(scrollbar->GetParent(), aFixedLayerMargins); + } + } +} +#endif + +bool AsyncCompositionManager::ApplyAsyncContentTransformToTree( + Layer* aLayer, bool* aOutFoundRoot) { + bool appliedTransform = false; + std::stack<Maybe<ParentLayerIntRect>> stackDeferredClips; + std::stack<LayersId> layersIds; + layersIds.push(mCompositorBridge->RootLayerTreeId()); + + // Maps layers to their ClipParts. The parts are not stored individually + // on the layer, but during AlignFixedAndStickyLayers we need access to + // the individual parts for descendant layers. + ClipPartsCache clipPartsCache; + + Layer* zoomContainer = nullptr; + Maybe<LayerMetricsWrapper> zoomedMetrics; + + ForEachNode<ForwardIterator>( + aLayer, + [&](Layer* layer) { + if (layer->AsRefLayer()) { + layersIds.push(layer->AsRefLayer()->GetReferentId()); + } + + stackDeferredClips.push(Maybe<ParentLayerIntRect>()); + + // If we encounter the async zoom container, find the corresponding + // APZC and stash it into |zoomedMetrics|. + // (We stash it in the form of a LayerMetricsWrapper because + // APZSampler requires going through that rather than using the APZC + // directly.) + // We do this on the way down the tree (i.e. here in the pre-action) + // so that by the time we encounter the layers with the RCD-RSF's + // scroll metadata (which will be descendants of the async zoom + // container), we can check for it and know we should only apply the + // scroll portion of the async transform to those layers (as the zoom + // portion will go on the async zoom container). + if (Maybe<ScrollableLayerGuid::ViewID> zoomedScrollId = + layer->IsAsyncZoomContainer()) { + zoomContainer = layer; + ForEachNode<ForwardIterator>( + LayerMetricsWrapper(layer), + [zoomedScrollId, &zoomedMetrics](LayerMetricsWrapper aWrapper) { + // Do not descend into layer subtrees with a different layers + // id. + if (aWrapper.AsRefLayer()) { + return TraversalFlag::Skip; + } + + if (aWrapper.Metrics().GetScrollId() == *zoomedScrollId) { + zoomedMetrics = Some(aWrapper); + MOZ_ASSERT(zoomedMetrics->GetApzc()); + return TraversalFlag::Abort; + } + + return TraversalFlag::Continue; + }); + } + }, + [&](Layer* layer) { + Maybe<ParentLayerIntRect> clipDeferredFromChildren = + stackDeferredClips.top(); + stackDeferredClips.pop(); + MOZ_ASSERT(!layersIds.empty()); + LayersId currentLayersId = layersIds.top(); + LayerToParentLayerMatrix4x4 oldTransform = + layer->GetTransformTyped() * AsyncTransformMatrix(); + + AsyncTransformComponentMatrix combinedAsyncTransform; + bool hasAsyncTransform = false; + // Only set on the root layer for Android. + ScreenMargin fixedLayerMargins; + + // Each layer has multiple clips: + // - Its local clip, which is fixed to the layer contents, i.e. it + // moves with those async transforms which the layer contents move + // with. + // - Its scrolled clip, which moves with all async transforms. + // - For each ScrollMetadata on the layer, a scroll clip. This + // includes the composition bounds and any other clips induced by + // layout. This moves with async transforms from ScrollMetadatas + // above it. + // In this function, these clips are combined into two shadow clip + // parts: + // - The fixed clip, which consists of the local clip only, initially + // transformed by all async transforms. + // - The scrolled clip, which consists of the other clips, transformed + // by the appropriate transforms. + // These two parts are kept separate for now, because for fixed layers, + // we need to adjust the fixed clip (to cancel out some async + // transforms). The parts are kept in a cache which is cleared at the + // beginning of every composite. The final shadow clip for the layer is + // the intersection of the (possibly adjusted) fixed clip and the + // scrolled clip. + ClipParts& clipParts = clipPartsCache[layer]; + clipParts.mFixedClip = layer->GetClipRect(); + clipParts.mScrolledClip = layer->GetScrolledClipRect(); + + // If we are a perspective transform ContainerLayer, apply the clip + // deferred from our child (if there is any) before we iterate over our + // frame metrics, because this clip is subject to all async transforms + // of this layer. Since this clip came from the a scroll clip on the + // child, it becomes part of our scrolled clip. + clipParts.mScrolledClip = IntersectMaybeRects(clipDeferredFromChildren, + clipParts.mScrolledClip); + + // The transform of a mask layer is relative to the masked layer's + // parent layer. So whenever we apply an async transform to a layer, we + // need to apply that same transform to the layer's own mask layer. A + // layer can also have "ancestor" mask layers for any rounded clips from + // its ancestor scroll frames. A scroll frame mask layer only needs to + // be async transformed for async scrolls of this scroll frame's + // ancestor scroll frames, not for async scrolls of this scroll frame + // itself. In the loop below, we iterate over scroll frames from inside + // to outside. At each iteration, this array contains the layer's + // ancestor mask layers of all scroll frames inside the current one. + nsTArray<Layer*> ancestorMaskLayers; + + // The layer's scrolled clip can have an ancestor mask layer as well, + // which is moved by all async scrolls on this layer. + if (const Maybe<LayerClip>& scrolledClip = layer->GetScrolledClip()) { + if (scrolledClip->GetMaskLayerIndex()) { + ancestorMaskLayers.AppendElement(layer->GetAncestorMaskLayerAt( + *scrolledClip->GetMaskLayerIndex())); + } + } + + if (RefPtr<APZSampler> sampler = mCompositorBridge->GetAPZSampler()) { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + LayerMetricsWrapper wrapper(layer, i); + if (!wrapper.GetApzc()) { + continue; + } + + const FrameMetrics& metrics = wrapper.Metrics(); + MOZ_ASSERT(metrics.IsScrollable()); + + hasAsyncTransform = true; + + AsyncTransformComponents asyncTransformComponents = + (zoomedMetrics && + sampler->GetGuid(*zoomedMetrics) == sampler->GetGuid(wrapper)) + ? AsyncTransformComponents{AsyncTransformComponent::eLayout} + : LayoutAndVisual; + AsyncTransform asyncTransformWithoutOverscroll = + sampler->GetCurrentAsyncTransform(wrapper, + asyncTransformComponents); + Maybe<CompositionPayload> payload = + sampler->NotifyScrollSampling(wrapper); + // The scroll latency should be measured between composition and the + // first scrolling event. Otherwise we observe metrics with <16ms + // latency even when frame.delay is enabled. + if (payload.isSome()) { + mLayerManager->RegisterPayload(*payload); + } + + AsyncTransformComponentMatrix overscrollTransform = + sampler->GetOverscrollTransform(wrapper); + AsyncTransformComponentMatrix asyncTransform = + AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll) * + overscrollTransform; + + if (!layer->IsScrollableWithoutContent()) { + sampler->MarkAsyncTransformAppliedToContent(wrapper); + } + + const ScrollMetadata& scrollMetadata = wrapper.Metadata(); + +#if defined(MOZ_WIDGET_ANDROID) + // If we find a metrics which is the root content doc, use that. If + // not, use the root layer. Since this function recurses on children + // first we should only end up using the root layer if the entire + // tree was devoid of a root content metrics. This is a temporary + // solution; in the long term we should not need the root content + // metrics at all. See bug 1201529 comment 6 for details. + if (!(*aOutFoundRoot)) { + *aOutFoundRoot = + metrics.IsRootContent() || /* RCD */ + (layer->GetParent() == nullptr && /* rootmost metrics */ + i + 1 >= layer->GetScrollMetadataCount()); + if (*aOutFoundRoot) { + mRootScrollableId = metrics.GetScrollId(); + Compositor* compositor = mLayerManager->GetCompositor(); + if (CompositorBridgeParent* bridge = + compositor->GetCompositorBridgeParent()) { + LayersId rootLayerTreeId = bridge->RootLayerTreeId(); + GeckoViewMetrics gvMetrics = + sampler->GetGeckoViewMetrics(wrapper); + if (mIsFirstPaint || GeckoViewMetricsHaveUpdated(gvMetrics)) { + if (RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyUpdateScreenMetrics(gvMetrics); + } + mLastMetrics = gvMetrics; + } + if (mIsFirstPaint) { + if (RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyFirstPaint(); + } + mIsFirstPaint = false; + } + if (mLayersUpdated) { + LayersId rootLayerTreeId = bridge->RootLayerTreeId(); + if (RefPtr<UiCompositorControllerParent> uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyLayersUpdated(); + } + mLayersUpdated = false; + } + } + fixedLayerMargins = GetFixedLayerMargins(); + } + } +#else + *aOutFoundRoot = false; + // Non-Android platforms still care about this flag being cleared + // after the first call to TransformShadowTree(). + mIsFirstPaint = false; +#endif + + // Transform the current local clips by this APZC's async transform. + MOZ_ASSERT(asyncTransform.Is2D()); + if (clipParts.mFixedClip) { + *clipParts.mFixedClip = + TransformBy(asyncTransform, *clipParts.mFixedClip); + } + if (clipParts.mScrolledClip) { + *clipParts.mScrolledClip = + TransformBy(asyncTransform, *clipParts.mScrolledClip); + } + // Note: we don't set the layer's shadow clip rect property yet; + // AlignFixedAndStickyLayers will use the clip parts from the clip + // parts cache. + + combinedAsyncTransform *= asyncTransform; + + // For the purpose of aligning fixed and sticky layers, we disregard + // the overscroll transform as well as any OMTA transform when + // computing the 'aCurrentTransformForRoot' parameter. This ensures + // that the overscroll and OMTA transforms are not unapplied, and + // therefore that the visual effects apply to fixed and sticky + // layers. We do this by using GetTransform() as the base transform + // rather than GetLocalTransform(), which would include those + // factors. + LayerToParentLayerMatrix4x4 transformWithoutOverscrollOrOmta = + layer->GetTransformTyped() * + CompleteAsyncTransform(AdjustForClip(asyncTransform, layer)); + // See bug 1630274 for why we pass eNone here; fixing that bug will + // probably end up changing this to be more correct. + AlignFixedAndStickyLayers(layer, layer, SideBits::eNone, + metrics.GetScrollId(), oldTransform, + transformWithoutOverscrollOrOmta, + fixedLayerMargins, clipPartsCache, + sampler->GetGeckoFixedLayerMargins()); + + // Combine the local clip with the ancestor scrollframe clip. This + // is not included in the async transform above, since the ancestor + // clip should not move with this APZC. + if (scrollMetadata.HasScrollClip()) { + ParentLayerIntRect clip = + scrollMetadata.ScrollClip().GetClipRect(); + if (layer->GetParent() && + layer->GetParent()->GetTransformIsPerspective()) { + // If our parent layer has a perspective transform, we want to + // apply our scroll clip to it instead of to this layer (see bug + // 1168263). A layer with a perspective transform shouldn't have + // multiple children with FrameMetrics, nor a child with + // multiple FrameMetrics. (A child with multiple FrameMetrics + // would mean that there's *another* scrollable element between + // the one with the CSS perspective and the transformed element. + // But you'd have to use preserve-3d on the inner scrollable + // element in order to have the perspective apply to the + // transformed child, and preserve-3d is not supported on + // scrollable elements, so this case can't occur.) + MOZ_ASSERT(!stackDeferredClips.top()); + stackDeferredClips.top().emplace(clip); + } else { + clipParts.mScrolledClip = + IntersectMaybeRects(Some(clip), clipParts.mScrolledClip); + } + } + + // Do the same for the ancestor mask layers: ancestorMaskLayers + // contains the ancestor mask layers for scroll frames *inside* the + // current scroll frame, so these are the ones we need to shift by + // our async transform. + for (Layer* ancestorMaskLayer : ancestorMaskLayers) { + SetShadowTransform( + ancestorMaskLayer, + ancestorMaskLayer->GetLocalTransformTyped() * asyncTransform); + } + + // Append the ancestor mask layer for this scroll frame to + // ancestorMaskLayers. + if (scrollMetadata.HasScrollClip()) { + const LayerClip& scrollClip = scrollMetadata.ScrollClip(); + if (scrollClip.GetMaskLayerIndex()) { + size_t maskLayerIndex = scrollClip.GetMaskLayerIndex().value(); + Layer* ancestorMaskLayer = + layer->GetAncestorMaskLayerAt(maskLayerIndex); + ancestorMaskLayers.AppendElement(ancestorMaskLayer); + } + } + } + + if (Maybe<ScrollableLayerGuid::ViewID> zoomedScrollId = + layer->IsAsyncZoomContainer()) { + if (zoomedMetrics) { + AsyncTransform zoomTransform = sampler->GetCurrentAsyncTransform( + *zoomedMetrics, {AsyncTransformComponent::eVisual}); + hasAsyncTransform = true; + combinedAsyncTransform *= + AsyncTransformComponentMatrix(zoomTransform); + } else { + // TODO: Is this normal? It happens on some pages, such as + // about:config on mobile, for just one frame or so, before the + // scroll metadata for zoomedScrollId appears in the layer tree. + } + } + + auto IsFixedToZoomContainer = [&](Layer* aFixedLayer) { + if (!zoomedMetrics) { + return false; + } + ScrollableLayerGuid::ViewID targetId = + aFixedLayer->GetFixedPositionScrollContainerId(); + MOZ_ASSERT(targetId != ScrollableLayerGuid::NULL_SCROLL_ID); + ScrollableLayerGuid rootContent = sampler->GetGuid(*zoomedMetrics); + return rootContent.mScrollId == targetId && + rootContent.mLayersId == currentLayersId; + }; + + auto SidesStuckToZoomContainer = [&](Layer* aLayer) -> SideBits { + SideBits result = SideBits::eNone; + if (!zoomedMetrics) { + return result; + } + if (!aLayer->GetIsStickyPosition()) { + return result; + } + + ScrollableLayerGuid::ViewID targetId = + aLayer->GetStickyScrollContainerId(); + if (targetId == ScrollableLayerGuid::NULL_SCROLL_ID) { + return result; + } + + ScrollableLayerGuid rootContent = sampler->GetGuid(*zoomedMetrics); + if (rootContent.mScrollId != targetId || + rootContent.mLayersId != currentLayersId) { + return result; + } + + ParentLayerPoint translation = + sampler + ->GetCurrentAsyncTransform( + *zoomedMetrics, {AsyncTransformComponent::eLayout}) + .mTranslation; + + if (apz::IsStuckAtBottom(translation.y, + aLayer->GetStickyScrollRangeInner(), + aLayer->GetStickyScrollRangeOuter())) { + result |= SideBits::eBottom; + } + if (apz::IsStuckAtTop(translation.y, + aLayer->GetStickyScrollRangeInner(), + aLayer->GetStickyScrollRangeOuter())) { + result |= SideBits::eTop; + } + return result; + }; + + // Layers fixed to the RCD-RSF no longer need + // AdjustFixedOrStickyLayer() to scroll them by the eVisual transform, + // as that's now applied to the async zoom container itself. However, + // we still need to adjust them by the fixed layer margins to + // account for dynamic toolbar transitions. This is also handled by + // AdjustFixedOrStickyLayer(), so we now call it with empty transforms + // to get it to perform just the fixed margins adjustment. + SideBits stuckSides = SidesStuckToZoomContainer(layer); + if (zoomedMetrics && ((layer->GetIsFixedPosition() && + !layer->GetParent()->GetIsFixedPosition() && + IsFixedToZoomContainer(layer)) || + stuckSides != SideBits::eNone)) { + LayerToParentLayerMatrix4x4 emptyTransform; + ScreenMargin marginsForFixedLayer = GetFixedLayerMargins(); + AdjustFixedOrStickyLayer(zoomContainer, layer, stuckSides, + sampler->GetGuid(*zoomedMetrics).mScrollId, + emptyTransform, emptyTransform, + marginsForFixedLayer, clipPartsCache, + sampler->GetGeckoFixedLayerMargins()); + } + } + + bool clipChanged = (hasAsyncTransform || clipDeferredFromChildren || + layer->GetScrolledClipRect()); + if (clipChanged) { + // Intersect the two clip parts and apply them to the layer. + // During ApplyAsyncContentTransformTree on an ancestor layer, + // AlignFixedAndStickyLayers may overwrite this with a new clip it + // computes from the clip parts, but if that doesn't happen, this + // is the layer's final clip rect. + layer->AsHostLayer()->SetShadowClipRect(clipParts.Intersect()); + } + + if (hasAsyncTransform) { + // Apply the APZ transform on top of GetLocalTransform() here (rather + // than GetTransform()) in case the OMTA code in SampleAnimations + // already set a shadow transform; in that case we want to apply ours + // on top of that one rather than clobber it. + SetShadowTransform(layer, + layer->GetLocalTransformTyped() * + AdjustForClip(combinedAsyncTransform, layer)); + + // Do the same for the layer's own mask layer, if it has one. + if (Layer* maskLayer = layer->GetMaskLayer()) { + SetShadowTransform(maskLayer, maskLayer->GetLocalTransformTyped() * + combinedAsyncTransform); + } + + appliedTransform = true; + } + + ExpandRootClipRect(layer, fixedLayerMargins); + + if (layer->GetScrollbarData().mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb) { + ApplyAsyncTransformToScrollbar(layer); + } + + if (layer->AsRefLayer()) { + MOZ_ASSERT(layersIds.size() > 1); + layersIds.pop(); + } + }); + + return appliedTransform; +} + +#if defined(MOZ_WIDGET_ANDROID) +bool AsyncCompositionManager::GeckoViewMetricsHaveUpdated( + const GeckoViewMetrics& aMetrics) { + return RoundedToInt(mLastMetrics.mVisualScrollOffset) != + RoundedToInt(aMetrics.mVisualScrollOffset) || + mLastMetrics.mZoom != aMetrics.mZoom; +} +#endif + +static bool LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, + Layer* aScrollbar) { + if (!aTarget.GetApzc()) { + return false; + } + const FrameMetrics& metrics = aTarget.Metrics(); + MOZ_ASSERT(metrics.IsScrollable()); + if (metrics.GetScrollId() != aScrollbar->GetScrollbarData().mTargetViewId) { + return false; + } + return !metrics.IsScrollInfoLayer(); +} + +static void ApplyAsyncTransformToScrollbarForContent( + const RefPtr<APZSampler>& aSampler, Layer* aScrollbar, + const LayerMetricsWrapper& aContent, bool aScrollbarIsDescendant) { + AsyncTransformComponentMatrix clipTransform; + + MOZ_ASSERT(aSampler); + LayerToParentLayerMatrix4x4 transform = + aSampler->ComputeTransformForScrollThumb( + aScrollbar->GetLocalTransformTyped(), aContent, + aScrollbar->GetScrollbarData(), aScrollbarIsDescendant, + &clipTransform); + + if (aScrollbarIsDescendant) { + // We also need to make a corresponding change on the clip rect of all the + // layers on the ancestor chain from the scrollbar layer up to but not + // including the layer with the async transform. Otherwise the scrollbar + // shifts but gets clipped and so appears to flicker. + for (Layer* ancestor = aScrollbar; ancestor != aContent.GetLayer(); + ancestor = ancestor->GetParent()) { + TransformClipRect(ancestor, clipTransform); + } + } + + SetShadowTransform(aScrollbar, transform); +} + +static LayerMetricsWrapper FindScrolledLayerForScrollbar(Layer* aScrollbar, + bool* aOutIsAncestor) { + // First check if the scrolled layer is an ancestor of the scrollbar layer. + LayerMetricsWrapper root(aScrollbar->Manager()->GetRoot()); + LayerMetricsWrapper prevAncestor(aScrollbar); + LayerMetricsWrapper scrolledLayer; + + for (LayerMetricsWrapper ancestor(aScrollbar); ancestor; + ancestor = ancestor.GetParent()) { + // Don't walk into remote layer trees; the scrollbar will always be in + // the same layer space. + if (ancestor.AsRefLayer()) { + root = prevAncestor; + break; + } + prevAncestor = ancestor; + + if (LayerIsScrollbarTarget(ancestor, aScrollbar)) { + *aOutIsAncestor = true; + return ancestor; + } + } + + // Search the entire layer space of the scrollbar. + ForEachNode<ForwardIterator>(root, [&root, &scrolledLayer, &aScrollbar]( + LayerMetricsWrapper aLayerMetrics) { + // Do not recurse into RefLayers, since our initial aSubtreeRoot is the + // root (or RefLayer root) of a single layer space to search. + if (root != aLayerMetrics && aLayerMetrics.AsRefLayer()) { + return TraversalFlag::Skip; + } + if (LayerIsScrollbarTarget(aLayerMetrics, aScrollbar)) { + scrolledLayer = aLayerMetrics; + return TraversalFlag::Abort; + } + return TraversalFlag::Continue; + }); + return scrolledLayer; +} + +void AsyncCompositionManager::ApplyAsyncTransformToScrollbar(Layer* aLayer) { + // If this layer corresponds to a scrollbar, then there should be a layer that + // is a previous sibling or a parent that has a matching ViewID on its + // FrameMetrics. That is the content that this scrollbar is for. We pick up + // the transient async transform from that layer and use it to update the + // scrollbar position. Note that it is possible that the content layer is no + // longer there; in this case we don't need to do anything because there can't + // be an async transform on the content. + bool isAncestor = false; + const LayerMetricsWrapper& scrollTarget = + FindScrolledLayerForScrollbar(aLayer, &isAncestor); + if (scrollTarget) { + ApplyAsyncTransformToScrollbarForContent(mCompositorBridge->GetAPZSampler(), + aLayer, scrollTarget, isAncestor); + } +} + +void AsyncCompositionManager::GetFrameUniformity( + FrameUniformityData* aOutData) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mLayerTransformRecorder.EndTest(aOutData); +} + +bool AsyncCompositionManager::TransformShadowTree( + const SampleTime& aCurrentFrame, TimeDuration aVsyncRate, + CompositorBridgeParentBase::TransformsToSkip aSkip) { + AUTO_PROFILER_LABEL("AsyncCompositionManager::TransformShadowTree", GRAPHICS); + + Layer* root = mLayerManager->GetRoot(); + if (!root) { + return false; + } + + // First, compute and set the shadow transforms from OMT animations. + // NB: we must sample animations *before* sampling pan/zoom + // transforms. + bool wantNextFrame = SampleAnimations(root, aCurrentFrame.Time()); + + // Advance animations to the next expected vsync timestamp, if we can + // get it. + SampleTime nextFrame = aCurrentFrame; + + MOZ_ASSERT(aVsyncRate != TimeDuration::Forever()); + if (aVsyncRate != TimeDuration::Forever()) { + nextFrame = nextFrame + aVsyncRate; + } + + // Reset the previous time stamp if we don't already have any running + // animations to avoid using the time which is far behind for newly + // started animations. + mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame.Time() : TimeStamp(); + + if (!(aSkip & CompositorBridgeParentBase::TransformsToSkip::APZ)) { + bool apzAnimating = false; + if (RefPtr<APZSampler> apz = mCompositorBridge->GetAPZSampler()) { + apzAnimating = apz->AdvanceAnimations(nextFrame); + } + wantNextFrame |= apzAnimating; + + // Apply an async content transform to any layer that has + // an async pan zoom controller. + bool foundRoot = false; + if (ApplyAsyncContentTransformToTree(root, &foundRoot)) { +#if defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(foundRoot); + if (foundRoot && GetFixedLayerMargins() != ScreenMargin()) { + MoveScrollbarForLayerMargin(root, mRootScrollableId, + GetFixedLayerMargins()); + } +#endif + } + } + + HostLayer* rootComposite = root->AsHostLayer(); + + gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform(); + trans *= gfx::Matrix4x4::From2D(mWorldTransform); + rootComposite->SetShadowBaseTransform(trans); + + if (StaticPrefs::gfx_vsync_collect_scroll_transforms()) { + RecordShadowTransforms(root); + } + + return wantNextFrame; +} + +void AsyncCompositionManager::SetFixedLayerMargins(ScreenIntCoord aTop, + ScreenIntCoord aBottom) { + mFixedLayerMargins.top = aTop; + mFixedLayerMargins.bottom = aBottom; +} +ScreenMargin AsyncCompositionManager::GetFixedLayerMargins() const { + ScreenMargin result = mFixedLayerMargins; + if (StaticPrefs::apz_fixed_margin_override_enabled()) { + result.top = StaticPrefs::apz_fixed_margin_override_top(); + result.bottom = StaticPrefs::apz_fixed_margin_override_bottom(); + } + return result; +} + +} // namespace layers +} // namespace mozilla |