| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_TRANSFORM_PAINT_PROPERTY_NODE_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_TRANSFORM_PAINT_PROPERTY_NODE_H_ |
| |
| #include <algorithm> |
| |
| #include "base/check_op.h" |
| #include "base/dcheck_is_on.h" |
| #include "cc/trees/sticky_position_constraint.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing_reasons.h" |
| #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper_transform_cache.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/platform_export.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" |
| #include "ui/gfx/geometry/point3_f.h" |
| #include "ui/gfx/geometry/transform.h" |
| |
| namespace blink { |
| |
| using CompositorStickyConstraint = cc::StickyPositionConstraint; |
| |
| // A transform (e.g., created by css "transform" or "perspective", or for |
| // internal positioning such as paint offset or scrolling) along with a |
| // reference to the parent TransformPaintPropertyNode. The scroll tree is |
| // referenced by transform nodes and a transform node with an associated scroll |
| // node will be a 2d transform for scroll offset. |
| // |
| // The transform tree is rooted at a node with no parent. This root node should |
| // not be modified. |
| class TransformPaintPropertyNode; |
| |
| class PLATFORM_EXPORT TransformPaintPropertyNodeOrAlias |
| : public PaintPropertyNodeBase<TransformPaintPropertyNodeOrAlias, |
| TransformPaintPropertyNode> { |
| public: |
| // If |relative_to_node| is an ancestor of |this|, returns true if any node is |
| // marked changed, at least significance of |change|, along the path from |
| // |this| to |relative_to_node| (not included). Otherwise returns the combined |
| // changed status of the paths from |this| and |relative_to_node| to the root. |
| bool Changed(PaintPropertyChangeType change, |
| const TransformPaintPropertyNodeOrAlias& relative_to_node) const; |
| |
| void AddChanged(PaintPropertyChangeType changed) final { |
| DCHECK_NE(PaintPropertyChangeType::kUnchanged, changed); |
| GeometryMapperTransformCache::ClearCache(); |
| GeometryMapperClipCache::ClearCache(); |
| PaintPropertyNode::AddChanged(changed); |
| } |
| |
| // See PaintPropertyNode::ChangedSequenceNumber(). |
| void ClearChangedToRoot(int sequence_number) const; |
| |
| protected: |
| using PaintPropertyNodeBase::PaintPropertyNodeBase; |
| }; |
| |
| class TransformPaintPropertyNodeAlias final |
| : public TransformPaintPropertyNodeOrAlias { |
| public: |
| static scoped_refptr<TransformPaintPropertyNodeAlias> Create( |
| const TransformPaintPropertyNodeOrAlias& parent) { |
| return base::AdoptRef(new TransformPaintPropertyNodeAlias(parent)); |
| } |
| |
| private: |
| explicit TransformPaintPropertyNodeAlias( |
| const TransformPaintPropertyNodeOrAlias& parent) |
| : TransformPaintPropertyNodeOrAlias(parent, kParentAlias) {} |
| }; |
| |
| class PLATFORM_EXPORT TransformPaintPropertyNode final |
| : public TransformPaintPropertyNodeOrAlias { |
| public: |
| enum class BackfaceVisibility : unsigned char { |
| // backface-visibility is not inherited per the css spec. However, for an |
| // element that don't create a new plane, we let the element inherit the |
| // parent backface-visibility and use the parent's transform to determine |
| // whether the backface is facing forward. |
| kInherited, |
| // backface-visibility: hidden for the new plane. |
| kHidden, |
| // backface-visibility: visible for the new plane. |
| kVisible, |
| }; |
| |
| struct PLATFORM_EXPORT TransformAndOrigin { |
| gfx::Transform matrix; |
| gfx::Point3F origin; |
| }; |
| |
| struct AnimationState { |
| AnimationState() {} |
| bool is_running_animation_on_compositor = false; |
| STACK_ALLOCATED(); |
| }; |
| |
| // To make it less verbose and more readable to construct and update a node, |
| // a struct with default values is used to represent the state. |
| struct PLATFORM_EXPORT State { |
| TransformAndOrigin transform_and_origin; |
| scoped_refptr<const ScrollPaintPropertyNode> scroll; |
| scoped_refptr<const TransformPaintPropertyNode> |
| scroll_translation_for_fixed; |
| |
| bool flattens_inherited_transform : 1 = false; |
| bool in_subtree_of_page_scale : 1 = true; |
| bool animation_is_axis_aligned : 1 = false; |
| // Set if a frame is rooted at this node. |
| bool is_frame_paint_offset_translation : 1 = false; |
| bool is_for_svg_child : 1 = false; |
| |
| BackfaceVisibility backface_visibility = BackfaceVisibility::kInherited; |
| unsigned rendering_context_id = 0; |
| CompositingReasons direct_compositing_reasons = CompositingReason::kNone; |
| CompositorElementId compositor_element_id; |
| std::unique_ptr<CompositorStickyConstraint> sticky_constraint; |
| std::unique_ptr<cc::AnchorPositionScrollData> anchor_position_scroll_data; |
| // If a visible frame is rooted at this node, this represents the element |
| // ID of the containing document. |
| CompositorElementId visible_frame_element_id; |
| |
| PaintPropertyChangeType ComputeTransformChange( |
| const TransformAndOrigin& other, |
| const AnimationState& animation_state) const; |
| PaintPropertyChangeType ComputeChange( |
| const State& other, |
| const AnimationState& animation_state) const; |
| |
| bool UsesCompositedScrolling() const { |
| return direct_compositing_reasons & CompositingReason::kOverflowScrolling; |
| } |
| bool RequiresCullRectExpansion() const { |
| return direct_compositing_reasons & |
| CompositingReason::kRequiresCullRectExpansion; |
| } |
| }; |
| |
| // This node is really a sentinel, and does not represent a real transform |
| // space. |
| static const TransformPaintPropertyNode& Root(); |
| |
| static scoped_refptr<TransformPaintPropertyNode> Create( |
| const TransformPaintPropertyNodeOrAlias& parent, |
| State&& state) { |
| return base::AdoptRef( |
| new TransformPaintPropertyNode(&parent, std::move(state))); |
| } |
| |
| const TransformPaintPropertyNode& Unalias() const = delete; |
| bool IsParentAlias() const = delete; |
| |
| PaintPropertyChangeType Update( |
| const TransformPaintPropertyNodeOrAlias& parent, |
| State&& state, |
| const AnimationState& animation_state = AnimationState()) { |
| auto parent_changed = SetParent(parent); |
| auto state_changed = state_.ComputeChange(state, animation_state); |
| if (state_changed != PaintPropertyChangeType::kUnchanged) { |
| state_ = std::move(state); |
| AddChanged(state_changed); |
| Validate(); |
| } |
| return std::max(parent_changed, state_changed); |
| } |
| |
| bool IsIdentityOr2dTranslation() const { |
| return state_.transform_and_origin.matrix.IsIdentityOr2dTranslation(); |
| } |
| bool IsIdentity() const { |
| return state_.transform_and_origin.matrix.IsIdentity(); |
| } |
| // Only available when IsIdentityOr2dTranslation() is true. |
| gfx::Vector2dF Get2dTranslation() const { |
| DCHECK(IsIdentityOr2dTranslation()); |
| return state_.transform_and_origin.matrix.To2dTranslation(); |
| } |
| const gfx::Transform& Matrix() const { |
| return state_.transform_and_origin.matrix; |
| } |
| |
| gfx::Transform MatrixWithOriginApplied() const { |
| gfx::Transform result = Matrix(); |
| result.ApplyTransformOrigin(Origin().x(), Origin().y(), Origin().z()); |
| return result; |
| } |
| |
| const gfx::Point3F& Origin() const { |
| return state_.transform_and_origin.origin; |
| } |
| |
| PaintPropertyChangeType DirectlyUpdateTransformAndOrigin( |
| TransformAndOrigin&& transform_and_origin, |
| const AnimationState& animation_state); |
| |
| // The associated scroll node, or nullptr otherwise. |
| const ScrollPaintPropertyNode* ScrollNode() const { |
| return state_.scroll.get(); |
| } |
| |
| const TransformPaintPropertyNode* ScrollTranslationForFixed() const { |
| return state_.scroll_translation_for_fixed.get(); |
| } |
| |
| // If true, this node is translated by the viewport bounds delta, which is |
| // used to keep bottom-fixed elements appear fixed to the bottom of the |
| // screen in the presence of URL bar movement. |
| bool IsAffectedByOuterViewportBoundsDelta() const { |
| return DirectCompositingReasons() & |
| CompositingReason::kAffectedByOuterViewportBoundsDelta; |
| } |
| |
| // If true, this node is a descendant of the page scale transform. This is |
| // important for avoiding raster during pinch-zoom (see: crbug.com/951861). |
| bool IsInSubtreeOfPageScale() const { |
| return state_.in_subtree_of_page_scale; |
| } |
| |
| const CompositorStickyConstraint* GetStickyConstraint() const { |
| return state_.sticky_constraint.get(); |
| } |
| |
| const cc::AnchorPositionScrollData* GetAnchorPositionScrollData() const { |
| return state_.anchor_position_scroll_data.get(); |
| } |
| |
| // If this is a scroll offset translation (i.e., has an associated scroll |
| // node), returns this. Otherwise, returns the transform node that this node |
| // scrolls with respect to. |
| const TransformPaintPropertyNode& NearestScrollTranslationNode() const { |
| return GetTransformCache().nearest_scroll_translation(); |
| } |
| |
| // This is different from NearestScrollTranslationNode in that for a |
| // fixed-position paint offset translation, this returns |
| // ScrollTranslationForFixed() instead of the ancestor scroll translation |
| // because a scroll gesture on a fixed-position element should scroll the |
| // containing view. |
| const TransformPaintPropertyNode& ScrollTranslationState() const { |
| return GetTransformCache().scroll_translation_state(); |
| } |
| |
| // Returns the nearest ancestor node (including |this|) that has direct |
| // compositing reasons. |
| const TransformPaintPropertyNode* NearestDirectlyCompositedAncestor() const { |
| return GetTransformCache().nearest_directly_composited_ancestor(); |
| } |
| |
| // If true, content with this transform node (or its descendant) appears in |
| // the plane of its parent. This is implemented by flattening the total |
| // accumulated transform from its ancestors. |
| bool FlattensInheritedTransform() const { |
| return state_.flattens_inherited_transform; |
| } |
| |
| // Returns the local BackfaceVisibility value set on this node. To be used |
| // for testing only; use |BackfaceVisibilitySameAsParent()| or |
| // |IsBackfaceHidden()| for production code. |
| BackfaceVisibility GetBackfaceVisibilityForTesting() const { |
| return state_.backface_visibility; |
| } |
| |
| bool IsBackfaceHidden() const { |
| return GetTransformCache().is_backface_hidden(); |
| } |
| |
| // Returns true if the backface visibility for this node is the same as that |
| // of its parent. This will be true for the Root node. |
| bool BackfaceVisibilitySameAsParent() const { |
| if (IsRoot()) |
| return true; |
| if (state_.backface_visibility == BackfaceVisibility::kInherited) |
| return true; |
| if (state_.backface_visibility == |
| Parent()->Unalias().state_.backface_visibility) |
| return true; |
| return IsBackfaceHidden() == Parent()->Unalias().IsBackfaceHidden(); |
| } |
| |
| // Returns true if the flattens inherited transform setting for this node is |
| // the same as that of its parent. This will be true for the Root node. |
| bool FlattensInheritedTransformSameAsParent() const { |
| if (IsRoot()) |
| return true; |
| return state_.flattens_inherited_transform == |
| Parent()->Unalias().state_.flattens_inherited_transform; |
| } |
| |
| bool HasDirectCompositingReasons() const { |
| return DirectCompositingReasons() != CompositingReason::kNone; |
| } |
| |
| bool HasDirectCompositingReasonsOtherThan3dTransform() const { |
| return DirectCompositingReasons() & |
| ~(CompositingReason::k3DTransform | CompositingReason::k3DScale | |
| CompositingReason::k3DRotate | CompositingReason::k3DTranslate | |
| CompositingReason::kTrivial3DTransform); |
| } |
| |
| bool HasActiveTransformAnimation() const { |
| return state_.direct_compositing_reasons & |
| (CompositingReason::kActiveTransformAnimation | |
| CompositingReason::kActiveScaleAnimation | |
| CompositingReason::kActiveRotateAnimation | |
| CompositingReason::kActiveTranslateAnimation); |
| } |
| |
| bool RequiresCompositingForFixedPosition() const { |
| return DirectCompositingReasons() & CompositingReason::kFixedPosition; |
| } |
| |
| bool RequiresCompositingForFixedToViewport() const { |
| return DirectCompositingReasons() & CompositingReason::kUndoOverscroll; |
| } |
| |
| bool RequiresCompositingForStickyPosition() const { |
| return DirectCompositingReasons() & CompositingReason::kStickyPosition; |
| } |
| |
| bool RequiresCompositingForAnchorPosition() const { |
| return DirectCompositingReasons() & CompositingReason::kAnchorPosition; |
| } |
| |
| CompositingReasons DirectCompositingReasonsForDebugging() const { |
| return DirectCompositingReasons(); |
| } |
| |
| bool TransformAnimationIsAxisAligned() const { |
| return state_.animation_is_axis_aligned; |
| } |
| |
| bool RequiresCompositingForRootScroller() const { |
| return state_.direct_compositing_reasons & CompositingReason::kRootScroller; |
| } |
| |
| bool RequiresCompositingForWillChangeTransform() const { |
| return state_.direct_compositing_reasons & |
| (CompositingReason::kWillChangeTransform | |
| CompositingReason::kWillChangeScale | |
| CompositingReason::kWillChangeRotate | |
| CompositingReason::kWillChangeTranslate); |
| } |
| |
| // Cull rect expansion is required if the compositing reasons hint requirement |
| // of high-performance movement, to avoid frequent change of cull rect. |
| bool RequiresCullRectExpansion() const { |
| return state_.RequiresCullRectExpansion(); |
| } |
| |
| const CompositorElementId& GetCompositorElementId() const { |
| return state_.compositor_element_id; |
| } |
| |
| const CompositorElementId& GetVisibleFrameElementId() const { |
| return state_.visible_frame_element_id; |
| } |
| |
| bool IsFramePaintOffsetTranslation() const { |
| return state_.is_frame_paint_offset_translation; |
| } |
| |
| bool DelegatesToParentForBackface() const { |
| return state_.backface_visibility == BackfaceVisibility::kInherited; |
| } |
| |
| // Content whose transform nodes have a common rendering context ID are 3D |
| // sorted. If this is 0, content will not be 3D sorted. |
| unsigned RenderingContextId() const { return state_.rendering_context_id; } |
| bool HasRenderingContext() const { return state_.rendering_context_id; } |
| |
| bool IsForSVGChild() const { return state_.is_for_svg_child; } |
| |
| std::unique_ptr<JSONObject> ToJSON() const final; |
| |
| private: |
| TransformPaintPropertyNode(const TransformPaintPropertyNodeOrAlias* parent, |
| State&& state) |
| : TransformPaintPropertyNodeOrAlias(parent), state_(std::move(state)) { |
| Validate(); |
| } |
| |
| CompositingReasons DirectCompositingReasons() const { |
| return state_.direct_compositing_reasons; |
| } |
| |
| bool IsBackfaceHiddenInternal(bool parent_backface_hidden) const { |
| if (state_.backface_visibility == BackfaceVisibility::kInherited) |
| return parent_backface_hidden; |
| return state_.backface_visibility == BackfaceVisibility::kHidden; |
| } |
| |
| void Validate() const { |
| #if DCHECK_IS_ON() |
| if (state_.scroll) { |
| // If there is an associated scroll node, this can only be a 2d |
| // translation for scroll offset. |
| DCHECK(IsIdentityOr2dTranslation()); |
| // The scroll compositor element id should be stored on the scroll node. |
| DCHECK(!state_.compositor_element_id); |
| } |
| #endif |
| } |
| |
| // For access to GetTransformCache() and SetCachedTransform. |
| friend class GeometryMapper; |
| friend class GeometryMapperTest; |
| friend class GeometryMapperTransformCache; |
| friend class GeometryMapperTransformCacheTest; |
| friend class PaintPropertyTreeBuilderTest; |
| |
| const GeometryMapperTransformCache& GetTransformCache() const { |
| if (!transform_cache_) |
| transform_cache_ = std::make_unique<GeometryMapperTransformCache>(); |
| transform_cache_->UpdateIfNeeded(*this); |
| return *transform_cache_; |
| } |
| void UpdateScreenTransform() const { |
| DCHECK(transform_cache_); |
| transform_cache_->UpdateScreenTransform(*this); |
| } |
| |
| State state_; |
| mutable std::unique_ptr<GeometryMapperTransformCache> transform_cache_; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_TRANSFORM_PAINT_PROPERTY_NODE_H_ |