mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 01:35:35 +00:00
36384db656
This results in fixed position elements being attached to the layout viewport when being async-scrolled by APZ (when the layout viewport is larger than the visual viewport). MozReview-Commit-ID: 2YYIDnTWgVn --HG-- extra : rebase_source : 58b77b2e9c8ed35bdc2d24dd8ca9494e8d23a391
1381 lines
57 KiB
C++
1381 lines
57 KiB
C++
/* -*- 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 "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/ServoBindings.h" // for Servo_AnimationValue_GetOpacity, etc
|
|
#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/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/layers/UiCompositorControllerParent.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(CompositorBridgeParent* aParent,
|
|
HostLayerManager* aManager)
|
|
: mLayerManager(aManager)
|
|
, mIsFirstPaint(true)
|
|
, mLayersUpdated(false)
|
|
, mReadyForCompose(true)
|
|
, mCompositorBridge(aParent)
|
|
{
|
|
MOZ_ASSERT(mCompositorBridge);
|
|
}
|
|
|
|
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->AsHostLayer()->GetShadowTransformSetByAnimation()
|
|
? aLayer->GetLocalTransform()
|
|
: aLayer->GetTransform());
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#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 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 =
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static Matrix4x4
|
|
ServoAnimationValueToMatrix4x4(const RefPtr<RawServoAnimationValue>& aValue,
|
|
const TransformData& aTransformData)
|
|
{
|
|
// FIXME: Bug 1457033: We should convert servo's animation value to matrix
|
|
// directly without nsCSSValueSharedList.
|
|
RefPtr<nsCSSValueSharedList> list;
|
|
Servo_AnimationValue_GetTransform(aValue, &list);
|
|
// we expect all our transform data to arrive in device pixels
|
|
Point3D transformOrigin = aTransformData.transformOrigin();
|
|
nsDisplayTransform::FrameTransformProperties props(std::move(list),
|
|
transformOrigin);
|
|
|
|
return nsDisplayTransform::GetResultingTransformMatrix(
|
|
props, aTransformData.origin(),
|
|
aTransformData.appUnitsPerDevPixel(),
|
|
0, &aTransformData.bounds());
|
|
}
|
|
|
|
|
|
static Matrix4x4
|
|
FrameTransformToTransformInDevice(const Matrix4x4& aFrameTransform,
|
|
Layer* aLayer,
|
|
const TransformData& aTransformData)
|
|
{
|
|
Matrix4x4 transformInDevice = aFrameTransform;
|
|
// 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.
|
|
if (!aLayer->GetParent() ||
|
|
!aLayer->GetParent()->GetTransformIsPerspective()) {
|
|
nsLayoutUtils::PostTranslate(transformInDevice, aTransformData.origin(),
|
|
aTransformData.appUnitsPerDevPixel(),
|
|
true);
|
|
}
|
|
|
|
if (ContainerLayer* c = aLayer->AsContainerLayer()) {
|
|
transformInDevice.PostScale(c->GetInheritedXScale(),
|
|
c->GetInheritedYScale(),
|
|
1);
|
|
}
|
|
|
|
return transformInDevice;
|
|
}
|
|
|
|
static void
|
|
ApplyAnimatedValue(Layer* aLayer,
|
|
CompositorAnimationStorage* aStorage,
|
|
nsCSSPropertyID aProperty,
|
|
const AnimationData& aAnimationData,
|
|
const RefPtr<RawServoAnimationValue>& aValue)
|
|
{
|
|
if (!aValue) {
|
|
// Return gracefully if we have no valid AnimationValue.
|
|
return;
|
|
}
|
|
|
|
HostLayer* layerCompositor = aLayer->AsHostLayer();
|
|
switch (aProperty) {
|
|
case eCSSProperty_opacity: {
|
|
float opacity = Servo_AnimationValue_GetOpacity(aValue);
|
|
layerCompositor->SetShadowOpacity(opacity);
|
|
layerCompositor->SetShadowOpacitySetByAnimation(true);
|
|
aStorage->SetAnimatedValue(aLayer->GetCompositorAnimationsId(), opacity);
|
|
|
|
layerCompositor->SetShadowBaseTransform(aLayer->GetBaseTransform());
|
|
layerCompositor->SetShadowTransformSetByAnimation(false);
|
|
break;
|
|
}
|
|
case eCSSProperty_transform: {
|
|
const TransformData& transformData = aAnimationData.get_TransformData();
|
|
|
|
Matrix4x4 frameTransform =
|
|
ServoAnimationValueToMatrix4x4(aValue, transformData);
|
|
|
|
Matrix4x4 transform =
|
|
FrameTransformToTransformInDevice(frameTransform,
|
|
aLayer,
|
|
transformData);
|
|
|
|
layerCompositor->SetShadowBaseTransform(transform);
|
|
layerCompositor->SetShadowTransformSetByAnimation(true);
|
|
aStorage->SetAnimatedValue(aLayer->GetCompositorAnimationsId(),
|
|
std::move(transform), std::move(frameTransform),
|
|
transformData);
|
|
|
|
layerCompositor->SetShadowOpacity(aLayer->GetOpacity());
|
|
layerCompositor->SetShadowOpacitySetByAnimation(false);
|
|
break;
|
|
}
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
|
|
}
|
|
}
|
|
|
|
static bool
|
|
SampleAnimations(Layer* aLayer,
|
|
CompositorAnimationStorage* aStorage,
|
|
TimeStamp aPreviousFrameTime,
|
|
TimeStamp aCurrentFrameTime)
|
|
{
|
|
bool isAnimating = false;
|
|
|
|
ForEachNode<ForwardIterator>(
|
|
aLayer,
|
|
[&] (Layer* layer)
|
|
{
|
|
AnimationArray& animations = layer->GetAnimations();
|
|
if (animations.IsEmpty()) {
|
|
return;
|
|
}
|
|
isAnimating = true;
|
|
AnimatedValue* previousValue =
|
|
aStorage->GetAnimatedValue(layer->GetCompositorAnimationsId());
|
|
RefPtr<RawServoAnimationValue> animationValue =
|
|
layer->GetBaseAnimationStyle();
|
|
AnimationHelper::SampleResult sampleResult =
|
|
AnimationHelper::SampleAnimationForEachNode(aPreviousFrameTime,
|
|
aCurrentFrameTime,
|
|
animations,
|
|
layer->GetAnimationData(),
|
|
animationValue,
|
|
previousValue);
|
|
switch (sampleResult) {
|
|
case AnimationHelper::SampleResult::Sampled: {
|
|
Animation& animation = animations.LastElement();
|
|
ApplyAnimatedValue(layer,
|
|
aStorage,
|
|
animation.property(),
|
|
animation.data(),
|
|
animationValue);
|
|
break;
|
|
}
|
|
case AnimationHelper::SampleResult::Skipped:
|
|
switch (animations[0].property()) {
|
|
case eCSSProperty_opacity: {
|
|
MOZ_ASSERT(
|
|
layer->AsHostLayer()->GetShadowOpacitySetByAnimation());
|
|
#ifdef DEBUG
|
|
// Disable this assertion until the root cause is fixed in bug
|
|
// 1459775.
|
|
// MOZ_ASSERT(FuzzyEqualsMultiplicative(
|
|
// Servo_AnimationValue_GetOpacity(animationValue),
|
|
// *(aStorage->GetAnimationOpacity(layer->GetCompositorAnimationsId()))));
|
|
#endif
|
|
// Even if opacity animation value has unchanged, we have to set
|
|
// the shadow base transform value here since the value might
|
|
// have been changed by APZC.
|
|
HostLayer* layerCompositor = layer->AsHostLayer();
|
|
layerCompositor->SetShadowBaseTransform(
|
|
layer->GetBaseTransform());
|
|
layerCompositor->SetShadowTransformSetByAnimation(false);
|
|
break;
|
|
}
|
|
case eCSSProperty_transform: {
|
|
MOZ_ASSERT(
|
|
layer->AsHostLayer()->GetShadowTransformSetByAnimation());
|
|
MOZ_ASSERT(previousValue);
|
|
#ifdef DEBUG
|
|
const TransformData& transformData =
|
|
animations[0].data().get_TransformData();
|
|
Matrix4x4 frameTransform =
|
|
ServoAnimationValueToMatrix4x4(animationValue, transformData);
|
|
Matrix4x4 transformInDevice =
|
|
FrameTransformToTransformInDevice(frameTransform,
|
|
layer,
|
|
transformData);
|
|
MOZ_ASSERT(
|
|
previousValue->mTransform.mTransformInDevSpace.FuzzyEqualsMultiplicative(
|
|
transformInDevice));
|
|
#endif
|
|
// In the case of transform we have to set the unchanged
|
|
// transform value again becasue APZC might have modified the
|
|
// previous shadow base transform value.
|
|
HostLayer* layerCompositor = layer->AsHostLayer();
|
|
layerCompositor->SetShadowBaseTransform(
|
|
// FIXME: Bug 1459775: It seems possible that we somehow try
|
|
// to sample animations and skip it even if the previous value
|
|
// has been discarded from the animation storage when we enable
|
|
// layer tree cache. So for the safety, in the case where we
|
|
// have no previous animation value, we set non-animating value
|
|
// instead.
|
|
previousValue
|
|
? previousValue->mTransform.mTransformInDevSpace
|
|
: layer->GetBaseTransform());
|
|
break;
|
|
}
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unsupported properties");
|
|
break;
|
|
}
|
|
break;
|
|
case AnimationHelper::SampleResult::None: {
|
|
HostLayer* layerCompositor = layer->AsHostLayer();
|
|
layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform());
|
|
layerCompositor->SetShadowTransformSetByAnimation(false);
|
|
layerCompositor->SetShadowOpacity(layer->GetOpacity());
|
|
layerCompositor->SetShadowOpacitySetByAnimation(false);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
|
|
return isAnimating;
|
|
}
|
|
|
|
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++) {
|
|
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, 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->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;
|
|
|
|
// 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;
|
|
// 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;
|
|
}
|
|
|
|
// Apply any additional async scrolling for testing purposes (used
|
|
// for reftest-async-scroll and reftest-async-zoom).
|
|
auto _ = sampler->ApplyAsyncTestAttributes(wrapper);
|
|
|
|
const FrameMetrics& metrics = wrapper.Metrics();
|
|
MOZ_ASSERT(metrics.IsScrollable());
|
|
|
|
hasAsyncTransform = true;
|
|
|
|
AsyncTransform asyncTransformWithoutOverscroll =
|
|
sampler->GetCurrentAsyncTransform(wrapper);
|
|
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()) {
|
|
AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator();
|
|
MOZ_ASSERT(animator);
|
|
if (mIsFirstPaint) {
|
|
animator->UpdateRootFrameMetrics(metrics);
|
|
animator->FirstPaint();
|
|
mIsFirstPaint = false;
|
|
}
|
|
if (mLayersUpdated) {
|
|
animator->NotifyLayersUpdated();
|
|
mLayersUpdated = false;
|
|
}
|
|
// If this is not actually the root content then the animator is not getting updated in AsyncPanZoomController::NotifyLayersUpdated
|
|
// because the root content document is not scrollable. So update it here so it knows if the root composition size has changed.
|
|
if (!metrics.IsRootContent()) {
|
|
animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(metrics);
|
|
}
|
|
}
|
|
fixedLayerMargins = mFixedLayerMargins;
|
|
}
|
|
}
|
|
#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. 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.
|
|
AsyncTransform asyncTransformForFixedAdjustment
|
|
= sampler->GetCurrentAsyncTransformForFixedAdjustment(wrapper);
|
|
LayerToParentLayerMatrix4x4 transformWithoutOverscrollOrOmta
|
|
= layer->GetTransformTyped()
|
|
* CompleteAsyncTransform(AdjustForClip(asyncTransformForFixedAdjustment, 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->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);
|
|
}
|
|
});
|
|
|
|
return appliedTransform;
|
|
}
|
|
|
|
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(
|
|
TimeStamp aCurrentFrame,
|
|
TimeDuration aVsyncRate,
|
|
CompositorBridgeParentBase::TransformsToSkip aSkip)
|
|
{
|
|
AUTO_PROFILER_LABEL("AsyncCompositionManager::TransformShadowTree", GRAPHICS);
|
|
|
|
Layer* root = mLayerManager->GetRoot();
|
|
if (!root) {
|
|
return false;
|
|
}
|
|
|
|
CompositorAnimationStorage* storage =
|
|
mCompositorBridge->GetAnimationStorage();
|
|
// First, compute and set the shadow transforms from OMT animations.
|
|
// NB: we must sample animations *before* sampling pan/zoom
|
|
// transforms.
|
|
bool wantNextFrame =
|
|
SampleAnimations(root,
|
|
storage,
|
|
mPreviousFrameTimeStamp,
|
|
aCurrentFrame);
|
|
|
|
if (!wantNextFrame) {
|
|
// Clean up the CompositorAnimationStorage because
|
|
// there are no active animations running
|
|
storage->Clear();
|
|
}
|
|
|
|
// Advance 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;
|
|
}
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
Compositor* compositor = mLayerManager->GetCompositor();
|
|
if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
|
|
AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator();
|
|
MOZ_ASSERT(animator);
|
|
wantNextFrame |= animator->UpdateAnimation(nextFrame);
|
|
}
|
|
#endif // defined(MOZ_WIDGET_ANDROID)
|
|
|
|
// 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 & CompositorBridgeParentBase::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
|
|
}
|
|
|
|
bool apzAnimating = false;
|
|
if (RefPtr<APZSampler> apz = mCompositorBridge->GetAPZSampler()) {
|
|
apzAnimating = apz->SampleAnimations(LayerMetricsWrapper(root), nextFrame);
|
|
}
|
|
wantNextFrame |= apzAnimating;
|
|
}
|
|
|
|
HostLayer* rootComposite = root->AsHostLayer();
|
|
|
|
gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform();
|
|
trans *= gfx::Matrix4x4::From2D(mWorldTransform);
|
|
rootComposite->SetShadowBaseTransform(trans);
|
|
|
|
if (gfxPrefs::CollectScrollTransforms()) {
|
|
RecordShadowTransforms(root);
|
|
}
|
|
|
|
return wantNextFrame;
|
|
}
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
void
|
|
AsyncCompositionManager::SetFixedLayerMargins(ScreenIntCoord aTop, ScreenIntCoord aBottom)
|
|
{
|
|
mFixedLayerMargins.top = aTop;
|
|
mFixedLayerMargins.bottom = aBottom;
|
|
}
|
|
#endif // defined(MOZ_WIDGET_ANDROID)
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|