| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // 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_CORE_PAINT_OBJECT_PAINT_PROPERTIES_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINT_PROPERTIES_H_ |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h" |
| |
| namespace blink { |
| |
| // This class stores the paint property nodes created by a LayoutObject. The |
| // object owns each of the property nodes directly and RefPtrs are only used to |
| // harden against use-after-free bugs. These paint properties are built/updated |
| // by PaintPropertyTreeBuilder during the PrePaint lifecycle step. |
| // |
| // [update & clear implementation note] This class has Update[property](...) and |
| // Clear[property]() helper functions for efficiently creating and updating |
| // properties. The update functions returns a 3-state result to indicate whether |
| // the value or the existence of the node has changed. They use a create-or- |
| // update pattern of re-using existing properties for efficiency: |
| // 1. It avoids extra allocations. |
| // 2. It preserves existing child->parent pointers. |
| // The clear functions return true if an existing node is removed. Property |
| // nodes store parent pointers but not child pointers and these return values |
| // are important for catching property tree structure changes which require |
| // updating descendant's parent pointers. |
| class CORE_EXPORT ObjectPaintProperties { |
| USING_FAST_MALLOC(ObjectPaintProperties); |
| |
| public: |
| static std::unique_ptr<ObjectPaintProperties> Create() { |
| return base::WrapUnique(new ObjectPaintProperties()); |
| } |
| |
| ~ObjectPaintProperties() { DCHECK(!is_immutable_); } |
| |
| class UpdateResult { |
| public: |
| bool Unchanged() const { return result_ == kUnchanged; } |
| bool NewNodeCreated() const { return result_ == kNewNodeCreated; } |
| |
| private: |
| friend class ObjectPaintProperties; |
| enum Result { kUnchanged, kValueChanged, kNewNodeCreated }; |
| UpdateResult(Result r) : result_(r) {} |
| Result result_; |
| }; |
| |
| // The following defines 3 functions and one variable: |
| // - Foo(): a getter for the property. |
| // - UpdateFoo(): an update function. |
| // - ClearFoo(): a clear function |
| // - foo_: the variable itself. |
| // |
| // Note that clear* functions return true if the property tree structure |
| // changes (an existing node was deleted), and false otherwise. See the |
| // class-level comment ("update & clear implementation note") for details |
| // about why this is needed for efficient updates. |
| #define ADD_NODE(type, function, variable) \ |
| const type##PaintPropertyNode* function() const { return variable.get(); } \ |
| UpdateResult Update##function(const type##PaintPropertyNode& parent, \ |
| type##PaintPropertyNode::State&& state, \ |
| bool is_parent_alias = false) { \ |
| auto result = is_parent_alias \ |
| ? UpdateAlias(variable, parent) \ |
| : Update(variable, parent, std::move(state)); \ |
| DCHECK(!is_immutable_ || result.Unchanged()) \ |
| << "Value changed while immutable. New state:\n" \ |
| << *variable; \ |
| return result; \ |
| } \ |
| bool Clear##function() { \ |
| DCHECK(!is_immutable_ || !variable) \ |
| << "Value cleared while immutable. Old state:\n" \ |
| << *variable; \ |
| return Clear(variable); \ |
| } \ |
| \ |
| private: \ |
| scoped_refptr<type##PaintPropertyNode> variable; \ |
| \ |
| public: |
| // (End of ADD_NODE definition) |
| |
| #define ADD_TRANSFORM(function, variable) \ |
| ADD_NODE(Transform, function, variable) |
| #define ADD_EFFECT(function, variable) ADD_NODE(Effect, function, variable) |
| #define ADD_CLIP(function, variable) ADD_NODE(Clip, function, variable) |
| |
| // The hierarchy of the transform subtree created by a LayoutObject is as |
| // follows: |
| // [ PaintOffsetTranslation ] |
| // | Normally paint offset is accumulated without creating a node until |
| // | we see, for example, transform or position:fixed. |
| // +-[ StickyTranslation ] |
| // / This applies the sticky offset induced by position:sticky. |
| // | |
| // +-[ Transform ] |
| // | The space created by CSS transform. This is the local border box |
| // | space. |
| // +-[ Perspective ] |
| // | The space created by CSS perspective. |
| // +-[ ReplacedContentTransform ] |
| // Additional transform for replaced elements to implement object-fit. |
| // (Replaced elements don't scroll.) |
| // OR |
| // +-[ ScrollTranslation ] |
| // The space created by overflow clip. The translation equals the |
| // offset between the scrolling contents and the scrollable area of |
| // the container, both originated from the top-left corner, so it is |
| // the sum of scroll origin and scroll offset of the ScrollableArea. |
| // To use any content offset based on ScrollOrigin() (e.g. LayoutBox |
| // or InlineBox's PhysicalLocation()) in this space, we should add |
| // ScrollOrigin() to the offset. |
| // |
| // ... +-[ TransformIsolationNode ] |
| // This serves as a parent to subtree transforms on an element with |
| // paint containment. It induces a PaintOffsetTranslation node and |
| // is the deepest child of any transform tree on the contain: paint |
| // element. |
| ADD_TRANSFORM(PaintOffsetTranslation, paint_offset_translation_); |
| ADD_TRANSFORM(StickyTranslation, sticky_translation_); |
| ADD_TRANSFORM(Transform, transform_); |
| ADD_TRANSFORM(Perspective, perspective_); |
| ADD_TRANSFORM(ReplacedContentTransform, replaced_content_transform_); |
| ADD_TRANSFORM(ScrollTranslation, scroll_translation_); |
| ADD_NODE(Scroll, Scroll, scroll_); |
| ADD_TRANSFORM(TransformIsolationNode, transform_isolation_node_); |
| |
| // The hierarchy of the effect subtree created by a LayoutObject is as |
| // follows: |
| // [ effect ] |
| // | Isolated group to apply various CSS effects, including opacity, |
| // | mix-blend-mode, backdrop-filter, and for isolation if a mask needs |
| // | to be applied or backdrop-dependent children are present. |
| // +-[ filter ] |
| // | Isolated group for CSS filter. |
| // +-[ vertical/horizontal scrollbar effect ] |
| // | Overlay Scrollbars on Aura and Android need effect node for fade |
| // | animation. |
| // +-[ mask ] |
| // | Isolated group for painting the CSS mask. This node will have |
| // | SkBlendMode::kDstIn and shall paint last, i.e. after masked contents. |
| // +-[ clip path ] |
| // Isolated group for painting the CSS clip-path. This node will have |
| // SkBlendMode::kDstIn and shall paint last, i.e. after clipped |
| // contents. |
| // |
| // ... +-[ effectIsolationNode ] |
| // This serves as a parent to subtree effects on an element with paint |
| // containment, It is the deepest child of any effect tree on the |
| // contain: paint element. |
| ADD_EFFECT(Effect, effect_); |
| ADD_EFFECT(Filter, filter_); |
| ADD_EFFECT(VerticalScrollbarEffect, vertical_scrollbar_effect_); |
| ADD_EFFECT(HorizontalScrollbarEffect, horizontal_scrollbar_effect_); |
| ADD_EFFECT(Mask, mask_); |
| ADD_EFFECT(ClipPath, clip_path_); |
| ADD_EFFECT(EffectIsolationNode, effect_isolation_node_); |
| |
| // The hierarchy of the clip subtree created by a LayoutObject is as follows: |
| // [ fragment clip ] |
| // | Clips to a fragment's bounds. |
| // | This is only present for content under a fragmentation container. |
| // +-[ clip path clip ] |
| // | Clip created by path-based CSS clip-path. Only exists if the |
| // / clip-path is "simple" that can be applied geometrically. This and |
| // / the clip path effect node are mutually exclusive. |
| // | NOTE: for composited SPv1 clip path clips, we move clip path clip |
| // | below mask. |
| // +-[ mask clip ] |
| // | Clip created by CSS mask or CSS clip-path. It serves two purposes: |
| // | 1. Cull painting of the masked subtree. Because anything outside of |
| // | the mask is never visible, it is pointless to paint them. |
| // | 2. Raster clip of the masked subtree. Because the mask implemented |
| // | as SkBlendMode::kDstIn, pixels outside of mask's bound will be |
| // | intact when they shall be masked out. This clip ensures no pixels |
| // | leak out. |
| // +-[ css clip ] |
| // | Clip created by CSS clip. CSS clip applies to all descendants, this |
| // | node only applies to containing block descendants. For descendants |
| // | not contained by this object, use [ css clip fixed position ]. |
| // +-[ overflow controls clip ] |
| // | Clip created by overflow clip to clip overflow controls |
| // | (scrollbars, resizer, scroll corner) that would overflow the box. |
| // +-[ inner border radius clip] |
| // | Clip created by a rounded border with overflow clip. This clip is |
| // | not inset by scrollbars. |
| // +-[ overflow clip ] |
| // Clip created by overflow clip and is inset by the scrollbar. |
| // [ css clip fixed position ] |
| // Clip created by CSS clip. Only exists if the current clip includes |
| // some clip that doesn't apply to our fixed position descendants. |
| // |
| // ... +-[ clipIsolationNode ] |
| // This serves as a parent to subtree clips on an element with paint |
| // containment. It is the deepest child of any clip tree on the contain: |
| // paint element. |
| ADD_CLIP(FragmentClip, fragment_clip_); |
| ADD_CLIP(ClipPathClip, clip_path_clip_); |
| ADD_CLIP(MaskClip, mask_clip_); |
| ADD_CLIP(CssClip, css_clip_); |
| ADD_CLIP(CssClipFixedPosition, css_clip_fixed_position_); |
| ADD_CLIP(OverflowControlsClip, overflow_controls_clip_); |
| ADD_CLIP(InnerBorderRadiusClip, inner_border_radius_clip_); |
| ADD_CLIP(OverflowClip, overflow_clip_); |
| ADD_CLIP(ClipIsolationNode, clip_isolation_node_); |
| |
| #if DCHECK_IS_ON() |
| // Used by FindPropertiesNeedingUpdate.h for verifying state doesn't change. |
| void SetImmutable() const { is_immutable_ = true; } |
| bool IsImmutable() const { return is_immutable_; } |
| void SetMutable() const { is_immutable_ = false; } |
| |
| void Validate() { |
| DCHECK(!ScrollTranslation() || !ReplacedContentTransform()) |
| << "Replaced elements don't scroll so there should never be both a " |
| "scroll translation and a replaced content transform."; |
| DCHECK(!ClipPathClip() || !ClipPath()) |
| << "ClipPathClip and ClipPathshould be mutually exclusive."; |
| DCHECK((!TransformIsolationNode() && !ClipIsolationNode() && |
| !EffectIsolationNode()) || |
| (TransformIsolationNode() && ClipIsolationNode() && |
| EffectIsolationNode())) |
| << "Isolation nodes have to be created for all of transform, clip, and " |
| "effect trees."; |
| } |
| #endif |
| |
| private: |
| ObjectPaintProperties() = default; |
| |
| // Return true if the property tree structure changes (an existing node was |
| // deleted), and false otherwise. See the class-level comment ("update & clear |
| // implementation note") for details about why this is needed for efficiency. |
| template <typename PaintPropertyNode> |
| bool Clear(scoped_refptr<PaintPropertyNode>& field) { |
| if (field) { |
| field = nullptr; |
| return true; |
| } |
| return false; |
| } |
| |
| // Return true if the property tree structure changes (a new node was |
| // created), and false otherwise. See the class-level comment ("update & clear |
| // implementation note") for details about why this is needed for efficiency. |
| template <typename PaintPropertyNode> |
| UpdateResult Update(scoped_refptr<PaintPropertyNode>& field, |
| const PaintPropertyNode& parent, |
| typename PaintPropertyNode::State&& state) { |
| if (field) { |
| return field->Update(parent, std::move(state)) |
| ? UpdateResult::kValueChanged |
| : UpdateResult::kUnchanged; |
| } |
| field = PaintPropertyNode::Create(parent, std::move(state)); |
| return UpdateResult::kNewNodeCreated; |
| } |
| template <typename PaintPropertyNode> |
| UpdateResult UpdateAlias(scoped_refptr<PaintPropertyNode>& field, |
| const PaintPropertyNode& parent) { |
| if (field) { |
| DCHECK(field->IsParentAlias()); |
| return field->SetParent(&parent) ? UpdateResult::kValueChanged |
| : UpdateResult::kUnchanged; |
| } |
| field = PaintPropertyNode::CreateAlias(parent); |
| return UpdateResult::kNewNodeCreated; |
| } |
| |
| // This is used in DCHECKs only, but is not guarded by DCHECK_IS_ON() because |
| // we can't have a similar guard in a macro definition. |
| mutable bool is_immutable_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(ObjectPaintProperties); |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINT_PROPERTIES_H_ |