diff options
Diffstat (limited to 'gfx/layers/composite/AsyncCompositionManager.cpp')
-rw-r--r-- | gfx/layers/composite/AsyncCompositionManager.cpp | 1495 |
1 files changed, 1495 insertions, 0 deletions
diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp new file mode 100644 index 000000000..69eafceca --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -0,0 +1,1495 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 et 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 "apz/src/AsyncPanZoomController.h" +#include "FrameMetrics.h" // for FrameMetrics +#include "LayerManagerComposite.h" // for LayerManagerComposite, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "gfxPoint.h" // for gfxPoint, gfxSize +#include "gfxPrefs.h" // for gfxPrefs +#include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc +#include "mozilla/WidgetUtils.h" // for ComputeTransformForRotation +#include "mozilla/dom/KeyframeEffectReadOnly.h" +#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for dom::FillMode +#include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite +#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/APZUtils.h" // for CompleteAsyncTransform +#include "mozilla/layers/Compositor.h" // for Compositor +#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 "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 InfallibleTArray +#include "UnitTransforms.h" // for TransformTo +#include "gfxPrefs.h" +#if defined(MOZ_WIDGET_ANDROID) +# include <android/log.h> +# include "mozilla/widget/AndroidCompositorWidget.h" +#endif +#include "GeckoProfiler.h" +#include "FrameUniformityData.h" +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch +#include "VsyncSource.h" + +struct nsCSSValueSharedList; + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +static bool +IsSameDimension(dom::ScreenOrientationInternal o1, dom::ScreenOrientationInternal o2) +{ + bool isO1portrait = (o1 == dom::eScreenOrientation_PortraitPrimary || o1 == dom::eScreenOrientation_PortraitSecondary); + bool isO2portrait = (o2 == dom::eScreenOrientation_PortraitPrimary || o2 == dom::eScreenOrientation_PortraitSecondary); + return !(isO1portrait ^ isO2portrait); +} + +static bool +ContentMightReflowOnOrientationChange(const IntRect& rect) +{ + return rect.width != rect.height; +} + +AsyncCompositionManager::AsyncCompositionManager(LayerManagerComposite* aManager) + : mLayerManager(aManager) + , mIsFirstPaint(true) + , mLayersUpdated(false) + , mPaintSyncId(0) + , mReadyForCompose(true) +{ +} + +AsyncCompositionManager::~AsyncCompositionManager() +{ +} + +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()) { + dom::ScreenOrientationInternal chromeOrientation = + mTargetConfig.orientation(); + dom::ScreenOrientationInternal 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()); + } +} + +#ifdef DEBUG +static void +GetBaseTransform(Layer* aLayer, Matrix4x4* aTransform) +{ + // Start with the animated transform if there is one + *aTransform = + (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation() + ? aLayer->GetLocalTransform() + : aLayer->GetTransform()); +} +#endif + +static void +TransformClipRect(Layer* aLayer, + const ParentLayerToParentLayerMatrix4x4& aTransform) +{ + MOZ_ASSERT(aTransform.Is2D()); + const Maybe<ParentLayerIntRect>& clipRect = aLayer->AsLayerComposite()->GetShadowClipRect(); + if (clipRect) { + ParentLayerIntRect transformed = TransformBy(aTransform, *clipRect); + aLayer->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->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); + } + } +} + +#ifdef DEBUG +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; + } +} +#endif + +static LayerPoint +GetLayerFixedMarginsOffset(Layer* aLayer, + const ScreenMargin& aFixedLayerMargins) +{ + // Work out the necessary translation, in root scrollable layer space. + // Because fixed layer margins are stored relative to the root scrollable + // layer, we can just take the difference between these values. + LayerPoint translation; + int32_t sides = aLayer->GetFixedPositionSides(); + + if ((sides & eSideBitsLeftRight) == eSideBitsLeftRight) { + translation.x += (aFixedLayerMargins.left - aFixedLayerMargins.right) / 2; + } else if (sides & eSideBitsRight) { + translation.x -= aFixedLayerMargins.right; + } else if (sides & eSideBitsLeft) { + translation.x += aFixedLayerMargins.left; + } + + if ((sides & eSideBitsTopBottom) == eSideBitsTopBottom) { + translation.y += (aFixedLayerMargins.top - aFixedLayerMargins.bottom) / 2; + } else if (sides & eSideBitsBottom) { + translation.y -= aFixedLayerMargins.bottom; + } else if (sides & eSideBitsTop) { + translation.y += aFixedLayerMargins.top; + } + + return translation; +} + +static gfxFloat +IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, gfxFloat aMax) +{ + // Determine the amount of overlap between the 1D vector |aTranslation| + // and the interval [aMin, aMax]. + if (aTranslation > 0) { + return std::max(0.0, std::min(aMax, aTranslation) - std::max(aMin, 0.0)); + } else { + return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0)); + } +} + +/** + * 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, FrameMetrics::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, + FrameMetrics::ViewID aFixedWithRespectTo, + Layer* aTransformedLayer, + FrameMetrics::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<FrameMetrics::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, + FrameMetrics::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, + ClipPartsCache* aClipPartsCache) +{ + // We're going to be inverting |aCurrentTransformForRoot|. + // If it's singular, there's nothing we can do. + if (aCurrentTransformForRoot.IsSingular()) { + return; + } + + Layer* layer = aStartTraversalAt; + bool needsAsyncTransformUnapplied = false; + if (Maybe<FrameMetrics::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, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, aClipPartsCache); + } + return; + } + + // Insert a translation so that the position of the anchor point is the same + // before and after the change to the transform of aTransformedSubtreeRoot. + + // 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. +#ifdef DEBUG + Matrix4x4 ancestorTransform; + if (layer != aTransformedSubtreeRoot) { + AccumulateLayerTransforms(layer->GetParent(), aTransformedSubtreeRoot, + ancestorTransform); + } + ancestorTransform.NudgeToIntegersFixedEpsilon(); + MOZ_ASSERT(ancestorTransform.IsIdentity()); +#endif + + // 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(); + + // Offset the layer's anchor point to make sure fixed position content + // respects content document fixed position margins. + LayerPoint offsetAnchor = anchor + GetLayerFixedMarginsOffset(layer, aFixedLayerMargins); + + // 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 = + aPreviousTransformForRoot.TransformPoint(offsetAnchor); + LayerPoint transformedAnchor = aCurrentTransformForRoot.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 LayerRect& stickyOuter = layer->GetStickyScrollRangeOuter(); + const LayerRect& 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 = + aPreviousTransformForRoot.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, aTransformScrollId, + aPreviousTransformForRoot, newTransform, aFixedLayerMargins, aClipPartsCache); + } + } + + return; +} + +static void +SampleValue(float aPortion, Animation& aAnimation, + const StyleAnimationValue& aStart, const StyleAnimationValue& aEnd, + const StyleAnimationValue& aLastValue, uint64_t aCurrentIteration, + Animatable* aValue, Layer* aLayer) +{ + NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() || + aStart.GetUnit() == StyleAnimationValue::eUnit_None || + aEnd.GetUnit() == StyleAnimationValue::eUnit_None, + "Must have same unit"); + + StyleAnimationValue startValue = aStart; + StyleAnimationValue endValue = aEnd; + // Iteration composition for accumulate + if (static_cast<dom::IterationCompositeOperation> + (aAnimation.iterationComposite()) == + dom::IterationCompositeOperation::Accumulate && + aCurrentIteration > 0) { + // FIXME: Bug 1293492: Add a utility function to calculate both of + // below StyleAnimationValues. + DebugOnly<bool> accumulateResult = + StyleAnimationValue::Accumulate(aAnimation.property(), + startValue, + aLastValue, + aCurrentIteration); + MOZ_ASSERT(accumulateResult, "could not accumulate value"); + accumulateResult = + StyleAnimationValue::Accumulate(aAnimation.property(), + endValue, + aLastValue, + aCurrentIteration); + MOZ_ASSERT(accumulateResult, "could not accumulate value"); + } + + StyleAnimationValue interpolatedValue; + // This should never fail because we only pass transform and opacity values + // to the compositor and they should never fail to interpolate. + DebugOnly<bool> uncomputeResult = + StyleAnimationValue::Interpolate(aAnimation.property(), + startValue, endValue, + aPortion, interpolatedValue); + MOZ_ASSERT(uncomputeResult, "could not uncompute value"); + + if (aAnimation.property() == eCSSProperty_opacity) { + *aValue = interpolatedValue.GetFloatValue(); + return; + } + + nsCSSValueSharedList* interpolatedList = + interpolatedValue.GetCSSValueSharedListValue(); + + TransformData& data = aAnimation.data().get_TransformData(); + nsPoint origin = data.origin(); + // we expect all our transform data to arrive in device pixels + Point3D transformOrigin = data.transformOrigin(); + nsDisplayTransform::FrameTransformProperties props(interpolatedList, + transformOrigin); + + // If our parent layer is a perspective layer, then the offset into reference + // frame coordinates is already on that layer. If not, then we need to ask + // for it to be added here. + uint32_t flags = 0; + if (!aLayer->GetParent() || !aLayer->GetParent()->GetTransformIsPerspective()) { + flags = nsDisplayTransform::OFFSET_BY_ORIGIN; + } + + Matrix4x4 transform = + nsDisplayTransform::GetResultingTransformMatrix(props, origin, + data.appUnitsPerDevPixel(), + flags, &data.bounds()); + + InfallibleTArray<TransformFunction> functions; + functions.AppendElement(TransformMatrix(transform)); + *aValue = functions; +} + +static bool +SampleAnimations(Layer* aLayer, TimeStamp aPoint) +{ + bool activeAnimations = false; + + ForEachNode<ForwardIterator>( + aLayer, + [&activeAnimations, &aPoint] (Layer* layer) + { + AnimationArray& animations = layer->GetAnimations(); + InfallibleTArray<AnimData>& animationData = layer->GetAnimationData(); + + // Process in order, since later animations override earlier ones. + for (size_t i = 0, iEnd = animations.Length(); i < iEnd; ++i) { + Animation& animation = animations[i]; + AnimData& animData = animationData[i]; + + activeAnimations = true; + + MOZ_ASSERT(!animation.startTime().IsNull(), + "Failed to resolve start time of pending animations"); + TimeDuration elapsedDuration = + (aPoint - animation.startTime()).MultDouble(animation.playbackRate()); + TimingParams timing; + timing.mDuration.emplace(animation.duration()); + timing.mDelay = animation.delay(); + timing.mIterations = animation.iterations(); + timing.mIterationStart = animation.iterationStart(); + timing.mDirection = + static_cast<dom::PlaybackDirection>(animation.direction()); + timing.mFill = static_cast<dom::FillMode>(animation.fillMode()); + timing.mFunction = + AnimationUtils::TimingFunctionToComputedTimingFunction( + animation.easingFunction()); + + ComputedTiming computedTiming = + dom::AnimationEffectReadOnly::GetComputedTimingAt( + Nullable<TimeDuration>(elapsedDuration), timing, + animation.playbackRate()); + + if (computedTiming.mProgress.IsNull()) { + continue; + } + + uint32_t segmentIndex = 0; + size_t segmentSize = animation.segments().Length(); + AnimationSegment* segment = animation.segments().Elements(); + while (segment->endPortion() < computedTiming.mProgress.Value() && + segmentIndex < segmentSize - 1) { + ++segment; + ++segmentIndex; + } + + double positionInSegment = + (computedTiming.mProgress.Value() - segment->startPortion()) / + (segment->endPortion() - segment->startPortion()); + + double portion = + ComputedTimingFunction::GetPortion(animData.mFunctions[segmentIndex], + positionInSegment, + computedTiming.mBeforeFlag); + + // interpolate the property + Animatable interpolatedValue; + SampleValue(portion, animation, + animData.mStartValues[segmentIndex], + animData.mEndValues[segmentIndex], + animData.mEndValues.LastElement(), + computedTiming.mCurrentIteration, + &interpolatedValue, layer); + LayerComposite* layerComposite = layer->AsLayerComposite(); + switch (animation.property()) { + case eCSSProperty_opacity: + { + layerComposite->SetShadowOpacity(interpolatedValue.get_float()); + layerComposite->SetShadowOpacitySetByAnimation(true); + break; + } + case eCSSProperty_transform: + { + Matrix4x4 matrix = interpolatedValue.get_ArrayOfTransformFunction()[0].get_TransformMatrix().value(); + if (ContainerLayer* c = layer->AsContainerLayer()) { + matrix.PostScale(c->GetInheritedXScale(), c->GetInheritedYScale(), 1); + } + layerComposite->SetShadowBaseTransform(matrix); + layerComposite->SetShadowTransformSetByAnimation(true); + break; + } + default: + NS_WARNING("Unhandled animated property"); + } + } + }); + return activeAnimations; +} + +static bool +SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aSampleTime) +{ + bool activeAnimations = false; + + ForEachNodePostOrder<ForwardIterator>(aLayer, + [&activeAnimations, &aSampleTime](LayerMetricsWrapper aLayerMetrics) + { + if (AsyncPanZoomController* apzc = aLayerMetrics.GetApzc()) { + apzc->ReportCheckerboard(aSampleTime); + activeAnimations |= apzc->AdvanceAnimations(aSampleTime); + } + } + ); + + return activeAnimations; +} + +void +AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer) +{ + MOZ_ASSERT(gfxPrefs::CollectScrollTransforms()); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + ForEachNodePostOrder<ForwardIterator>( + aLayer, + [this] (Layer* layer) + { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + AsyncPanZoomController* apzc = layer->GetAsyncPanZoomController(i); + if (!apzc) { + continue; + } + gfx::Matrix4x4 shadowTransform = layer->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->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->AsLayerComposite()->SetShadowClipRect(Some(RoundedOut(rect))); + } +} + +#ifdef MOZ_WIDGET_ANDROID +static void +MoveScrollbarForLayerMargin(Layer* aRoot, FrameMetrics::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->GetScrollbarDirection() == Layer::HORIZONTAL && + aNode->GetScrollbarTargetContainerId() == 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; + + // 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; + + ForEachNode<ForwardIterator>( + aLayer, + [&stackDeferredClips] (Layer* layer) + { + stackDeferredClips.push(Maybe<ParentLayerIntRect>()); + }, + [this, &aOutFoundRoot, &stackDeferredClips, &appliedTransform, &clipPartsCache] (Layer* layer) + { + Maybe<ParentLayerIntRect> clipDeferredFromChildren = stackDeferredClips.top(); + stackDeferredClips.pop(); + LayerToParentLayerMatrix4x4 oldTransform = layer->GetTransformTyped() * + AsyncTransformMatrix(); + + AsyncTransformComponentMatrix combinedAsyncTransform; + bool hasAsyncTransform = false; + 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())); + } + } + + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + AsyncPanZoomController* controller = layer->GetAsyncPanZoomController(i); + if (!controller) { + continue; + } + + hasAsyncTransform = true; + + AsyncTransform asyncTransformWithoutOverscroll = + controller->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + AsyncTransformComponentMatrix overscrollTransform = + controller->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + AsyncTransformComponentMatrix asyncTransform = + AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll) + * overscrollTransform; + + if (!layer->IsScrollInfoLayer()) { + controller->MarkAsyncTransformAppliedToContent(); + } + + const ScrollMetadata& scrollMetadata = layer->GetScrollMetadata(i); + const FrameMetrics& metrics = scrollMetadata.GetMetrics(); + +#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(); + CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel().ToScaleFactor(); + if (mIsFirstPaint) { + LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom); + mContentRect = metrics.GetScrollableRect(); + SetFirstPaintViewport(scrollOffsetLayerPixels, + geckoZoom, + mContentRect); + } else { + ParentLayerPoint scrollOffset = controller->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::RESPECT_FORCE_DISABLE); + // Compute the painted displayport in document-relative CSS pixels. + CSSRect displayPort(metrics.GetCriticalDisplayPort().IsEmpty() ? + metrics.GetDisplayPort() : + metrics.GetCriticalDisplayPort()); + displayPort += metrics.GetScrollOffset(); + SyncFrameMetrics(scrollOffset, + geckoZoom * asyncTransformWithoutOverscroll.mScale, + metrics.GetScrollableRect(), displayPort, geckoZoom, mLayersUpdated, + mPaintSyncId, fixedLayerMargins); + mFixedLayerMargins = fixedLayerMargins; + mLayersUpdated = false; + mPaintSyncId = 0; + } + mIsFirstPaint = false; + } + } +#else + // 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. If we're + // using containerful scrolling, then the clip is not part of the scrolled + // frame and should not be transformed. + if (!scrollMetadata.UsesContainerScrolling()) { + 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(asyncTransformWithoutOverscroll, layer)); + + AlignFixedAndStickyLayers(layer, layer, metrics.GetScrollId(), oldTransform, + transformWithoutOverscrollOrOmta, fixedLayerMargins, + &clipPartsCache); + + // 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); + } + } + } + + 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->AsLayerComposite()->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->GetScrollbarDirection() != Layer::NONE) { + ApplyAsyncTransformToScrollbar(layer); + } + }); + + return appliedTransform; +} + +static bool +LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, Layer* aScrollbar) +{ + AsyncPanZoomController* apzc = aTarget.GetApzc(); + if (!apzc) { + return false; + } + const FrameMetrics& metrics = aTarget.Metrics(); + if (metrics.GetScrollId() != aScrollbar->GetScrollbarTargetContainerId()) { + return false; + } + return !aTarget.IsScrollInfoLayer(); +} + +static void +ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar, + const LayerMetricsWrapper& aContent, + bool aScrollbarIsDescendant) +{ + // We only apply the transform if the scroll-target layer has non-container + // children (i.e. when it has some possibly-visible content). This is to + // avoid moving scroll-bars in the situation that only a scroll information + // layer has been built for a scroll frame, as this would result in a + // disparity between scrollbars and visible content. + if (aContent.IsScrollInfoLayer()) { + return; + } + + const FrameMetrics& metrics = aContent.Metrics(); + AsyncPanZoomController* apzc = aContent.GetApzc(); + MOZ_RELEASE_ASSERT(apzc); + + AsyncTransformComponentMatrix asyncTransform = + apzc->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + + // |asyncTransform| represents the amount by which we have scrolled and + // zoomed since the last paint. Because the scrollbar was sized and positioned based + // on the painted content, we need to adjust it based on asyncTransform so that + // it reflects what the user is actually seeing now. + AsyncTransformComponentMatrix scrollbarTransform; + if (aScrollbar->GetScrollbarDirection() == Layer::VERTICAL) { + const ParentLayerCoord asyncScrollY = asyncTransform._42; + const float asyncZoomY = asyncTransform._22; + + // The scroll thumb needs to be scaled in the direction of scrolling by the + // inverse of the async zoom. This is because zooming in decreases the + // fraction of the whole srollable rect that is in view. + const float yScale = 1.f / asyncZoomY; + + // Note: |metrics.GetZoom()| doesn't yet include the async zoom. + const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().yScale * asyncZoomY); + + // Here we convert the scrollbar thumb ratio into a true unitless ratio by + // dividing out the conversion factor from the scrollframe's parent's space + // to the scrollframe's space. + const float ratio = aScrollbar->GetScrollbarThumbRatio() / + (metrics.GetPresShellResolution() * asyncZoomY); + // The scroll thumb needs to be translated in opposite direction of the + // async scroll. This is because scrolling down, which translates the layer + // content up, should result in moving the scroll thumb down. + ParentLayerCoord yTranslation = -asyncScrollY * ratio; + + // The scroll thumb additionally needs to be translated to compensate for + // the scale applied above. The origin with respect to which the scale is + // applied is the origin of the entire scrollbar, rather than the origin of + // the scroll thumb (meaning, for a vertical scrollbar it's at the top of + // the composition bounds). This means that empty space above the thumb + // is scaled too, effectively translating the thumb. We undo that + // translation here. + // (One can think of the adjustment being done to the translation here as + // a change of basis. We have a method to help with that, + // Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code + // cleaner in this case). + const CSSCoord thumbOrigin = (metrics.GetScrollOffset().y * ratio); + const CSSCoord thumbOriginScaled = thumbOrigin * yScale; + const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin; + const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom; + yTranslation -= thumbOriginDeltaPL; + + if (metrics.IsRootContent()) { + // Scrollbar for the root are painted at the same resolution as the + // content. Since the coordinate space we apply this transform in includes + // the resolution, we need to adjust for it as well here. Note that in + // another metrics.IsRootContent() hunk below we apply a + // resolution-cancelling transform which ensures the scroll thumb isn't + // actually rendered at a larger scale. + yTranslation *= metrics.GetPresShellResolution(); + } + + scrollbarTransform.PostScale(1.f, yScale, 1.f); + scrollbarTransform.PostTranslate(0, yTranslation, 0); + } + if (aScrollbar->GetScrollbarDirection() == Layer::HORIZONTAL) { + // See detailed comments under the VERTICAL case. + + const ParentLayerCoord asyncScrollX = asyncTransform._41; + const float asyncZoomX = asyncTransform._11; + + const float xScale = 1.f / asyncZoomX; + + const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().xScale * asyncZoomX); + + const float ratio = aScrollbar->GetScrollbarThumbRatio() / + (metrics.GetPresShellResolution() * asyncZoomX); + ParentLayerCoord xTranslation = -asyncScrollX * ratio; + + const CSSCoord thumbOrigin = (metrics.GetScrollOffset().x * ratio); + const CSSCoord thumbOriginScaled = thumbOrigin * xScale; + const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin; + const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom; + xTranslation -= thumbOriginDeltaPL; + + if (metrics.IsRootContent()) { + xTranslation *= metrics.GetPresShellResolution(); + } + + scrollbarTransform.PostScale(xScale, 1.f, 1.f); + scrollbarTransform.PostTranslate(xTranslation, 0, 0); + } + + LayerToParentLayerMatrix4x4 transform = + aScrollbar->GetLocalTransformTyped() * scrollbarTransform; + + AsyncTransformComponentMatrix compensation; + // If the scrollbar layer is for the root then the content's resolution + // applies to the scrollbar as well. Since we don't actually want the scroll + // thumb's size to vary with the zoom (other than its length reflecting the + // fraction of the scrollable length that's in view, which is taken care of + // above), we apply a transform to cancel out this resolution. + if (metrics.IsRootContent()) { + compensation = + AsyncTransformComponentMatrix::Scaling( + metrics.GetPresShellResolution(), + metrics.GetPresShellResolution(), + 1.0f).Inverse(); + } + // If the scrollbar layer is a child of the content it is a scrollbar for, + // then we need to adjust for any async transform (including an overscroll + // transform) on the content. This needs to be cancelled out because layout + // positions and sizes the scrollbar on the assumption that there is no async + // transform, and without this adjustment the scrollbar will end up in the + // wrong place. + // + // Note that since the async transform is applied on top of the content's + // regular transform, we need to make sure to unapply the async transform in + // the same coordinate space. This requires applying the content transform + // and then unapplying it after unapplying the async transform. + if (aScrollbarIsDescendant) { + AsyncTransformComponentMatrix overscroll = + apzc->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE); + Matrix4x4 asyncUntransform = (asyncTransform * overscroll).Inverse().ToUnknownMatrix(); + Matrix4x4 contentTransform = aContent.GetTransform(); + Matrix4x4 contentUntransform = contentTransform.Inverse(); + + AsyncTransformComponentMatrix asyncCompensation = + ViewAs<AsyncTransformComponentMatrix>( + contentTransform + * asyncUntransform + * contentUntransform); + + compensation = compensation * asyncCompensation; + + // 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, asyncCompensation); + } + } + transform = transform * compensation; + + 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(aLayer, scrollTarget, isAncestor); + } +} + +void +AsyncCompositionManager::GetFrameUniformity(FrameUniformityData* aOutData) +{ + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mLayerTransformRecorder.EndTest(aOutData); +} + +bool +AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame, + TimeDuration aVsyncRate, + TransformsToSkip aSkip) +{ + PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree", + js::ProfileEntry::Category::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. + // Use a previous vsync time to make main thread animations and compositor + // more in sync with each other. + // On the initial frame we use aVsyncTimestamp here so the timestamp on the + // second frame are the same as the initial frame, but it does not matter. + bool wantNextFrame = SampleAnimations(root, + !mPreviousFrameTimeStamp.IsNull() ? + mPreviousFrameTimeStamp : aCurrentFrame); + + // 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 : TimeStamp(); + + if (!(aSkip & TransformsToSkip::APZ)) { + // FIXME/bug 775437: unify this interface with the ~native-fennec + // derived code + // + // Attempt to apply an async content transform to any layer that has + // an async pan zoom controller (which means that it is rendered + // async using Gecko). If this fails, fall back to transforming the + // primary scrollable layer. "Failing" here means that we don't + // find a frame that is async scrollable. Note that the fallback + // code also includes Fennec which is rendered async. Fennec uses + // its own platform-specific async rendering that is done partially + // in Gecko and partially in Java. + bool foundRoot = false; + if (ApplyAsyncContentTransformToTree(root, &foundRoot)) { +#if defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(foundRoot); + if (foundRoot && mFixedLayerMargins != ScreenMargin()) { + MoveScrollbarForLayerMargin(root, mRootScrollableId, mFixedLayerMargins); + } +#endif + } + + // Advance APZ animations to the next expected vsync timestamp, if we can + // get it. + TimeStamp nextFrame = aCurrentFrame; + + MOZ_ASSERT(aVsyncRate != TimeDuration::Forever()); + if (aVsyncRate != TimeDuration::Forever()) { + nextFrame += aVsyncRate; + } + + wantNextFrame |= SampleAPZAnimations(LayerMetricsWrapper(root), nextFrame); + } + + LayerComposite* rootComposite = root->AsLayerComposite(); + + gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform(); + trans *= gfx::Matrix4x4::From2D(mWorldTransform); + rootComposite->SetShadowBaseTransform(trans); + + if (gfxPrefs::CollectScrollTransforms()) { + RecordShadowTransforms(root); + } + + return wantNextFrame; +} + +void +AsyncCompositionManager::SetFirstPaintViewport(const LayerIntPoint& aOffset, + const CSSToLayerScale& aZoom, + const CSSRect& aCssPageRect) +{ +#ifdef MOZ_WIDGET_ANDROID + widget::AndroidCompositorWidget* widget = + mLayerManager->GetCompositor()->GetWidget()->AsAndroid(); + if (!widget) { + return; + } + widget->SetFirstPaintViewport(aOffset, aZoom, aCssPageRect); +#endif +} + +void +AsyncCompositionManager::SyncFrameMetrics(const ParentLayerPoint& aScrollOffset, + const CSSToParentLayerScale& aZoom, + const CSSRect& aCssPageRect, + const CSSRect& aDisplayPort, + const CSSToLayerScale& aPaintedResolution, + bool aLayersUpdated, + int32_t aPaintSyncId, + ScreenMargin& aFixedLayerMargins) +{ +#ifdef MOZ_WIDGET_ANDROID + widget::AndroidCompositorWidget* widget = + mLayerManager->GetCompositor()->GetWidget()->AsAndroid(); + if (!widget) { + return; + } + widget->SyncFrameMetrics( + aScrollOffset, aZoom, aCssPageRect, aDisplayPort, aPaintedResolution, + aLayersUpdated, aPaintSyncId, aFixedLayerMargins); +#endif +} + +} // namespace layers +} // namespace mozilla |