blob: f22d717c752b30e7387d4209742673fa82b0215f [file] [log] [blame]
// 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.
#include "core/paint/PaintPropertyTreeBuilder.h"
#include <memory>
#include "core/dom/DOMNodeIds.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/Settings.h"
#include "core/layout/FragmentainerIterator.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutView.h"
#include "core/layout/svg/LayoutSVGResourceMasker.h"
#include "core/layout/svg/LayoutSVGRoot.h"
#include "core/layout/svg/SVGLayoutSupport.h"
#include "core/layout/svg/SVGResources.h"
#include "core/layout/svg/SVGResourcesCache.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/paint/FindPaintOffsetAndVisualRectNeedingUpdate.h"
#include "core/paint/FindPropertiesNeedingUpdate.h"
#include "core/paint/ObjectPaintProperties.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/SVGRootPainter.h"
#include "core/paint/compositing/CompositedLayerMapping.h"
#include "core/paint/compositing/CompositingReasonFinder.h"
#include "platform/geometry/TransformState.h"
#include "platform/transforms/TransformationMatrix.h"
#include "platform/wtf/PtrUtil.h"
namespace blink {
PaintPropertyTreeBuilderFragmentContext::
PaintPropertyTreeBuilderFragmentContext()
: current_effect(EffectPaintPropertyNode::Root()) {
current.clip = absolute_position.clip = fixed_position.clip =
ClipPaintPropertyNode::Root();
current.transform = absolute_position.transform = fixed_position.transform =
TransformPaintPropertyNode::Root();
current.scroll = absolute_position.scroll = fixed_position.scroll =
ScrollPaintPropertyNode::Root();
}
// True if a new property was created, false if an existing one was updated.
static bool UpdatePreTranslation(
LocalFrameView& frame_view,
scoped_refptr<const TransformPaintPropertyNode> parent,
const TransformationMatrix& matrix,
const FloatPoint3D& origin) {
DCHECK(!RuntimeEnabledFeatures::RootLayerScrollingEnabled());
if (auto* existing_pre_translation = frame_view.PreTranslation()) {
existing_pre_translation->Update(std::move(parent), matrix, origin);
return false;
}
frame_view.SetPreTranslation(
TransformPaintPropertyNode::Create(std::move(parent), matrix, origin));
return true;
}
// True if a new property was created, false if an existing one was updated.
static bool UpdateContentClip(
LocalFrameView& frame_view,
scoped_refptr<const ClipPaintPropertyNode> parent,
scoped_refptr<const TransformPaintPropertyNode> local_transform_space,
const FloatRoundedRect& clip_rect,
bool& clip_changed) {
DCHECK(!RuntimeEnabledFeatures::RootLayerScrollingEnabled());
if (auto* existing_content_clip = frame_view.ContentClip()) {
if (existing_content_clip->ClipRect() != clip_rect)
clip_changed = true;
existing_content_clip->Update(std::move(parent),
std::move(local_transform_space), clip_rect);
return false;
}
frame_view.SetContentClip(ClipPaintPropertyNode::Create(
std::move(parent), std::move(local_transform_space), clip_rect));
clip_changed = true;
return true;
}
static MainThreadScrollingReasons GetMainThreadScrollingReasons(
const LocalFrameView& frame_view,
MainThreadScrollingReasons ancestor_reasons) {
auto reasons = ancestor_reasons;
if (!frame_view.GetFrame().GetSettings()->GetThreadedScrollingEnabled())
reasons |= MainThreadScrollingReason::kThreadedScrollingDisabled;
if (frame_view.HasBackgroundAttachmentFixedObjects())
reasons |= MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
return reasons;
}
// True if a new property was created or a main thread scrolling reason changed
// (which can affect descendants), false if an existing one was updated.
static bool UpdateScroll(LocalFrameView& frame_view,
PaintPropertyTreeBuilderFragmentContext& context) {
DCHECK(!RuntimeEnabledFeatures::RootLayerScrollingEnabled());
IntRect container_rect(IntPoint(), frame_view.VisibleContentSize());
IntRect contents_rect(-frame_view.ScrollOrigin(), frame_view.ContentsSize());
bool user_scrollable_horizontal =
frame_view.UserInputScrollable(kHorizontalScrollbar);
bool user_scrollable_vertical =
frame_view.UserInputScrollable(kVerticalScrollbar);
auto ancestor_reasons =
context.current.scroll->GetMainThreadScrollingReasons();
auto main_thread_scrolling_reasons =
GetMainThreadScrollingReasons(frame_view, ancestor_reasons);
auto element_id = frame_view.GetCompositorElementId();
if (auto* existing_scroll = frame_view.ScrollNode()) {
auto existing_reasons = existing_scroll->GetMainThreadScrollingReasons();
existing_scroll->Update(context.current.scroll, container_rect,
contents_rect, user_scrollable_horizontal,
user_scrollable_vertical,
main_thread_scrolling_reasons, element_id);
return existing_reasons != main_thread_scrolling_reasons;
}
frame_view.SetScrollNode(ScrollPaintPropertyNode::Create(
context.current.scroll, container_rect, contents_rect,
user_scrollable_horizontal, user_scrollable_vertical,
main_thread_scrolling_reasons, element_id));
return true;
}
// True if a new property was created, false if an existing one was updated.
static bool UpdateScrollTranslation(
LocalFrameView& frame_view,
scoped_refptr<const TransformPaintPropertyNode> parent,
const TransformationMatrix& matrix,
scoped_refptr<ScrollPaintPropertyNode> scroll) {
DCHECK(!RuntimeEnabledFeatures::RootLayerScrollingEnabled());
// TODO(pdr): Set the correct compositing reasons here.
if (auto* existing_scroll_translation = frame_view.ScrollTranslation()) {
existing_scroll_translation->Update(
std::move(parent), matrix, FloatPoint3D(), false, 0,
kCompositingReasonNone, CompositorElementId(), std::move(scroll));
return false;
}
frame_view.SetScrollTranslation(TransformPaintPropertyNode::Create(
std::move(parent), matrix, FloatPoint3D(), false, 0,
kCompositingReasonNone, CompositorElementId(), std::move(scroll)));
return true;
}
void FrameViewPaintPropertyTreeBuilder::Update(
LocalFrameView& frame_view,
PaintPropertyTreeBuilderContext& full_context) {
if (full_context.fragments.IsEmpty())
full_context.fragments.push_back(PaintPropertyTreeBuilderFragmentContext());
PaintPropertyTreeBuilderFragmentContext& context = full_context.fragments[0];
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
// With root layer scrolling, the LayoutView (a LayoutObject) properties are
// updated like other objects (see updatePropertiesAndContextForSelf and
// updatePropertiesAndContextForChildren) instead of needing LayoutView-
// specific property updates here.
context.current.paint_offset.MoveBy(frame_view.Location());
context.current.rendering_context_id = 0;
context.current.should_flatten_inherited_transform = true;
context.absolute_position = context.current;
full_context.container_for_absolute_position = nullptr;
context.fixed_position = context.current;
context.fixed_position.fixed_position_children_fixed_to_root = true;
return;
} else {
context.current.paint_offset_root = frame_view.GetLayoutView();
}
#if DCHECK_IS_ON()
FindFrameViewPropertiesNeedingUpdateScope check_scope(
&frame_view, full_context.is_actually_needed);
#endif
if (frame_view.NeedsPaintPropertyUpdate() ||
full_context.force_subtree_update) {
TransformationMatrix frame_translate;
frame_translate.Translate(
frame_view.X() + context.current.paint_offset.X(),
frame_view.Y() + context.current.paint_offset.Y());
full_context.force_subtree_update |= UpdatePreTranslation(
frame_view, context.current.transform, frame_translate, FloatPoint3D());
FloatRoundedRect content_clip(
IntRect(IntPoint(), frame_view.VisibleContentSize()));
full_context.force_subtree_update |= UpdateContentClip(
frame_view, context.current.clip, frame_view.PreTranslation(),
content_clip, full_context.clip_changed);
if (frame_view.IsScrollable()) {
full_context.force_subtree_update |= UpdateScroll(frame_view, context);
} else if (frame_view.ScrollNode()) {
// Ensure pre-existing properties are cleared if there is no scrolling.
frame_view.SetScrollNode(nullptr);
// Rebuild all descendant properties because a property was removed.
full_context.force_subtree_update = true;
}
// A scroll translation node is created for static offset (e.g., overflow
// hidden with scroll offset) or cases that scroll and have a scroll node.
ScrollOffset scroll_offset = frame_view.GetScrollOffset();
if (frame_view.IsScrollable() || !scroll_offset.IsZero()) {
TransformationMatrix frame_scroll;
frame_scroll.Translate(-scroll_offset.Width(), -scroll_offset.Height());
full_context.force_subtree_update |=
UpdateScrollTranslation(frame_view, frame_view.PreTranslation(),
frame_scroll, frame_view.ScrollNode());
} else if (frame_view.ScrollTranslation()) {
// Ensure pre-existing properties are cleared if there is no scrolling.
frame_view.SetScrollTranslation(nullptr);
// Rebuild all descendant properties because a property was removed.
full_context.force_subtree_update = true;
}
full_context.painting_layer = frame_view.GetLayoutView()->Layer();
}
// Initialize the context for current, absolute and fixed position cases.
// They are the same, except that scroll translation does not apply to
// fixed position descendants.
const auto* fixed_transform_node = frame_view.PreTranslation()
? frame_view.PreTranslation()
: context.current.transform;
auto* fixed_scroll_node = context.current.scroll;
DCHECK(frame_view.PreTranslation());
context.current.transform = frame_view.PreTranslation();
DCHECK(frame_view.ContentClip());
context.current.clip = frame_view.ContentClip();
if (const auto* scroll_node = frame_view.ScrollNode())
context.current.scroll = scroll_node;
if (const auto* scroll_translation = frame_view.ScrollTranslation())
context.current.transform = scroll_translation;
context.current.paint_offset = LayoutPoint();
context.current.rendering_context_id = 0;
context.current.should_flatten_inherited_transform = true;
context.absolute_position = context.current;
full_context.container_for_absolute_position = nullptr;
context.fixed_position = context.current;
context.fixed_position.transform = fixed_transform_node;
context.fixed_position.scroll = fixed_scroll_node;
context.fixed_position.fixed_position_children_fixed_to_root = true;
std::unique_ptr<PropertyTreeState> contents_state(new PropertyTreeState(
context.current.transform, context.current.clip, context.current_effect));
frame_view.SetTotalPropertyTreeStateForContents(std::move(contents_state));
}
namespace {
class FragmentPaintPropertyTreeBuilder {
public:
FragmentPaintPropertyTreeBuilder(
const LayoutObject& object,
PaintPropertyTreeBuilderContext& full_context,
PaintPropertyTreeBuilderFragmentContext& context,
FragmentData& fragment_data)
: object_(object),
full_context_(full_context),
context_(context),
fragment_data_(fragment_data),
properties_(fragment_data.GetRarePaintData()
? fragment_data.GetRarePaintData()->PaintProperties()
: nullptr) {}
ALWAYS_INLINE void UpdateForSelf();
ALWAYS_INLINE void UpdateForChildren();
private:
ALWAYS_INLINE void UpdatePaintOffset();
ALWAYS_INLINE void UpdateForPaintOffsetTranslation(Optional<IntPoint>&);
ALWAYS_INLINE void UpdatePaintOffsetTranslation(const Optional<IntPoint>&);
ALWAYS_INLINE void UpdateForObjectLocationAndSize(
Optional<IntPoint>& paint_offset_translation);
ALWAYS_INLINE void UpdateTransform();
ALWAYS_INLINE void UpdateTransformForNonRootSVG();
ALWAYS_INLINE void UpdateEffect();
ALWAYS_INLINE void UpdateFilter();
ALWAYS_INLINE void UpdateFragmentClip(const PaintLayer&);
ALWAYS_INLINE void UpdateCssClip();
ALWAYS_INLINE void UpdateLocalBorderBoxContext();
ALWAYS_INLINE void UpdateOverflowClip();
ALWAYS_INLINE void UpdatePerspective();
ALWAYS_INLINE void UpdateSvgLocalToBorderBoxTransform();
ALWAYS_INLINE void UpdateScrollAndScrollTranslation();
ALWAYS_INLINE void UpdateOutOfFlowContext();
const LayoutObject& object_;
// The tree builder context for the whole object.
PaintPropertyTreeBuilderContext& full_context_;
// The tree builder context for the current fragment, which is one of the
// entries in |full_context.fragments|.
PaintPropertyTreeBuilderFragmentContext& context_;
FragmentData& fragment_data_;
ObjectPaintProperties* properties_;
};
static bool NeedsScrollNode(const LayoutObject& object) {
if (!object.HasOverflowClip())
return false;
return ToLayoutBox(object).GetScrollableArea()->ScrollsOverflow();
}
// True if a scroll translation is needed for static scroll offset (e.g.,
// overflow hidden with scroll), or if a scroll node is needed for composited
// scrolling.
static bool NeedsScrollOrScrollTranslation(const LayoutObject& object) {
if (!object.HasOverflowClip())
return false;
IntSize scroll_offset = ToLayoutBox(object).ScrolledContentOffset();
return !scroll_offset.IsZero() || NeedsScrollNode(object);
}
static bool NeedsSVGLocalToBorderBoxTransform(const LayoutObject& object) {
return object.IsSVGRoot();
}
static bool NeedsPaintOffsetTranslationForScrollbars(
const LayoutBoxModelObject& object) {
if (auto* area = object.GetScrollableArea()) {
if (area->HorizontalScrollbar() || area->VerticalScrollbar())
return true;
}
return false;
}
static bool NeedsPaintOffsetTranslation(const LayoutObject& object) {
if (!object.IsBoxModelObject())
return false;
const LayoutBoxModelObject& box_model = ToLayoutBoxModelObject(object);
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled() &&
box_model.IsLayoutView()) {
// Root layer scrolling always creates a translation node for LayoutView to
// ensure fixed and absolute contexts use the correct transform space.
return true;
}
if (box_model.HasLayer() && box_model.Layer()->PaintsWithTransform(
kGlobalPaintFlattenCompositingLayers)) {
return true;
}
if (NeedsScrollOrScrollTranslation(object))
return true;
if (NeedsPaintOffsetTranslationForScrollbars(box_model))
return true;
if (NeedsSVGLocalToBorderBoxTransform(object))
return true;
// Don't let paint offset cross composited layer boundaries, to avoid
// unnecessary full layer paint/raster invalidation when paint offset in
// ancestor transform node changes which should not affect the descendants
// of the composited layer.
// TODO(wangxianzhu): For SPv2, we also need a avoid unnecessary paint/raster
// invalidation in composited layers when their paint offset changes.
if (!RuntimeEnabledFeatures::SlimmingPaintV2Enabled() &&
// For only LayoutBlocks that won't be escaped by floating objects and
// column spans when finding their containing blocks.
// TODO(crbug.com/780242): This can be avoided if we have fully correct
// paint property tree states for floating objects and column spans.
object.IsLayoutBlock() && object.HasLayer() &&
!ToLayoutBoxModelObject(object).Layer()->EnclosingPaginationLayer() &&
object.GetCompositingState() == kPaintsIntoOwnBacking)
return true;
return false;
}
IntPoint ApplyPaintOffsetTranslation(const LayoutObject& object,
LayoutPoint& paint_offset) {
// We should use the same subpixel paint offset values for snapping
// regardless of whether a transform is present. If there is a transform
// we round the paint offset but keep around the residual fractional
// component for the transformed content to paint with. In spv1 this was
// called "subpixel accumulation". For more information, see
// PaintLayer::subpixelAccumulation() and
// PaintLayerPainter::paintFragmentByApplyingTransform.
IntPoint paint_offset_translation = RoundedIntPoint(paint_offset);
LayoutPoint fractional_paint_offset =
LayoutPoint(paint_offset - paint_offset_translation);
if (fractional_paint_offset != LayoutPoint()) {
// If the object has a non-translation transform, discard the fractional
// paint offset which can't be transformed by the transform.
TransformationMatrix matrix;
object.StyleRef().ApplyTransform(
matrix, LayoutSize(), ComputedStyle::kExcludeTransformOrigin,
ComputedStyle::kIncludeMotionPath,
ComputedStyle::kIncludeIndependentTransformProperties);
if (!matrix.IsIdentityOrTranslation())
fractional_paint_offset = LayoutPoint();
}
paint_offset = fractional_paint_offset;
return paint_offset_translation;
}
void FragmentPaintPropertyTreeBuilder::UpdateForPaintOffsetTranslation(
Optional<IntPoint>& paint_offset_translation) {
if (!NeedsPaintOffsetTranslation(object_))
return;
paint_offset_translation =
ApplyPaintOffsetTranslation(object_, context_.current.paint_offset);
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled() &&
object_.IsLayoutView()) {
context_.absolute_position.paint_offset = context_.current.paint_offset;
context_.fixed_position.paint_offset = context_.current.paint_offset;
}
}
void FragmentPaintPropertyTreeBuilder::UpdatePaintOffsetTranslation(
const Optional<IntPoint>& paint_offset_translation) {
DCHECK(properties_);
if (paint_offset_translation) {
auto result = properties_->UpdatePaintOffsetTranslation(
context_.current.transform,
TransformationMatrix().Translate(paint_offset_translation->X(),
paint_offset_translation->Y()),
FloatPoint3D(), context_.current.should_flatten_inherited_transform,
context_.current.rendering_context_id);
context_.current.transform = properties_->PaintOffsetTranslation();
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled() &&
object_.IsLayoutView()) {
context_.absolute_position.transform =
properties_->PaintOffsetTranslation();
context_.fixed_position.transform = properties_->PaintOffsetTranslation();
}
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
full_context_.force_subtree_update |=
properties_->ClearPaintOffsetTranslation();
}
}
static bool NeedsTransformForNonRootSVG(const LayoutObject& object) {
// TODO(pdr): Check for the presence of a transform instead of the value.
// Checking for an identity matrix will cause the property tree structure
// to change during animations if the animation passes through the
// identity matrix.
return object.IsSVGChild() &&
!object.LocalToSVGParentTransform().IsIdentity();
}
// SVG does not use the general transform update of |UpdateTransform|, instead
// creating a transform node for SVG-specific transforms without 3D.
void FragmentPaintPropertyTreeBuilder::UpdateTransformForNonRootSVG() {
DCHECK(properties_);
DCHECK(object_.IsSVGChild());
// SVG does not use paint offset internally, except for SVGForeignObject which
// has different SVG and HTML coordinate spaces.
DCHECK(object_.IsSVGForeignObject() ||
context_.current.paint_offset == LayoutPoint());
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
AffineTransform transform = object_.LocalToSVGParentTransform();
if (NeedsTransformForNonRootSVG(object_)) {
// The origin is included in the local transform, so leave origin empty.
auto result = properties_->UpdateTransform(
context_.current.transform, TransformationMatrix(transform),
FloatPoint3D());
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
full_context_.force_subtree_update |= properties_->ClearTransform();
}
}
if (properties_->Transform()) {
context_.current.transform = properties_->Transform();
context_.current.should_flatten_inherited_transform = false;
context_.current.rendering_context_id = 0;
}
}
static CompositingReasons CompositingReasonsForTransform(const LayoutBox& box) {
const ComputedStyle& style = box.StyleRef();
CompositingReasons compositing_reasons = kCompositingReasonNone;
if (CompositingReasonFinder::RequiresCompositingForTransform(box))
compositing_reasons |= kCompositingReason3DTransform;
if (CompositingReasonFinder::RequiresCompositingForTransformAnimation(style))
compositing_reasons |= kCompositingReasonActiveAnimation;
if (style.HasWillChangeCompositingHint() &&
!style.SubtreeWillChangeContents())
compositing_reasons |= kCompositingReasonWillChangeCompositingHint;
if (box.HasLayer() && box.Layer()->Has3DTransformedDescendant()) {
if (style.HasPerspective())
compositing_reasons |= kCompositingReasonPerspectiveWith3DDescendants;
if (style.UsedTransformStyle3D() == ETransformStyle3D::kPreserve3d)
compositing_reasons |= kCompositingReasonPreserve3DWith3DDescendants;
}
return compositing_reasons;
}
static FloatPoint3D TransformOrigin(const LayoutBox& box) {
const ComputedStyle& style = box.StyleRef();
// Transform origin has no effect without a transform or motion path.
if (!style.HasTransform())
return FloatPoint3D();
FloatSize border_box_size(box.Size());
return FloatPoint3D(
FloatValueForLength(style.TransformOriginX(), border_box_size.Width()),
FloatValueForLength(style.TransformOriginY(), border_box_size.Height()),
style.TransformOriginZ());
}
static bool NeedsTransform(const LayoutObject& object) {
if (!object.IsBox())
return false;
return object.StyleRef().HasTransform() || object.StyleRef().Preserves3D() ||
CompositingReasonsForTransform(ToLayoutBox(object)) !=
kCompositingReasonNone;
}
void FragmentPaintPropertyTreeBuilder::UpdateTransform() {
if (object_.IsSVGChild()) {
UpdateTransformForNonRootSVG();
return;
}
DCHECK(properties_);
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
const ComputedStyle& style = object_.StyleRef();
// A transform node is allocated for transforms, preserves-3d and any
// direct compositing reason. The latter is required because this is the
// only way to represent compositing both an element and its stacking
// descendants.
if (NeedsTransform(object_)) {
auto& box = ToLayoutBox(object_);
CompositingReasons compositing_reasons =
CompositingReasonsForTransform(box);
TransformationMatrix matrix;
style.ApplyTransform(
matrix, box.Size(), ComputedStyle::kExcludeTransformOrigin,
ComputedStyle::kIncludeMotionPath,
ComputedStyle::kIncludeIndependentTransformProperties);
// TODO(trchen): transform-style should only be respected if a
// PaintLayer is created. If a node with transform-style: preserve-3d
// does not exist in an existing rendering context, it establishes a new
// one.
unsigned rendering_context_id = context_.current.rendering_context_id;
if (style.Preserves3D() && !rendering_context_id)
rendering_context_id = PtrHash<const LayoutObject>::GetHash(&object_);
auto result = properties_->UpdateTransform(
context_.current.transform, matrix, TransformOrigin(box),
context_.current.should_flatten_inherited_transform,
rendering_context_id, compositing_reasons,
CompositorElementIdFromUniqueObjectId(
object_.UniqueId(), CompositorElementIdNamespace::kPrimary));
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
full_context_.force_subtree_update |= properties_->ClearTransform();
}
}
if (properties_->Transform()) {
context_.current.transform = properties_->Transform();
if (object_.StyleRef().Preserves3D()) {
context_.current.rendering_context_id =
properties_->Transform()->RenderingContextId();
context_.current.should_flatten_inherited_transform = false;
} else {
context_.current.rendering_context_id = 0;
context_.current.should_flatten_inherited_transform = true;
}
}
}
static bool ComputeMaskParameters(IntRect& mask_clip,
ColorFilter& mask_color_filter,
const LayoutObject& object,
const LayoutPoint& paint_offset) {
DCHECK(object.IsBoxModelObject() || object.IsSVGChild());
const ComputedStyle& style = object.StyleRef();
if (object.IsSVGChild()) {
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(&object);
LayoutSVGResourceMasker* masker = resources ? resources->Masker() : nullptr;
if (!masker)
return false;
mask_clip = EnclosingIntRect(object.ObjectBoundingBox());
mask_color_filter = masker->Style()->SvgStyle().MaskType() == MT_LUMINANCE
? kColorFilterLuminanceToAlpha
: kColorFilterNone;
return true;
}
if (!style.HasMask())
return false;
LayoutRect maximum_mask_region;
// For HTML/CSS objects, the extent of the mask is known as "mask
// painting area", which is determined by CSS mask-clip property.
// We don't implement mask-clip:margin-box or no-clip currently,
// so the maximum we can get is border-box.
if (object.IsBox()) {
maximum_mask_region = ToLayoutBox(object).BorderBoxRect();
} else {
// For inline elements, depends on the value of box-decoration-break
// there could be one box in multiple fragments or multiple boxes.
// Either way here we are only interested in the bounding box of them.
DCHECK(object.IsLayoutInline());
maximum_mask_region = ToLayoutInline(object).LinesBoundingBox();
}
if (style.HasMaskBoxImageOutsets())
maximum_mask_region.Expand(style.MaskBoxImageOutsets());
maximum_mask_region.MoveBy(paint_offset);
mask_clip = PixelSnappedIntRect(maximum_mask_region);
mask_color_filter = kColorFilterNone;
return true;
}
static bool NeedsEffect(const LayoutObject& object) {
const ComputedStyle& style = object.StyleRef();
const bool is_css_isolated_group =
object.IsBoxModelObject() && style.IsStackingContext();
if (!is_css_isolated_group && !object.IsSVGChild())
return false;
if (object.IsSVG()) {
// This handles SVGRoot objects which have PaintLayers.
if (object.IsSVGRoot() && object.HasNonIsolatedBlendingDescendants())
return true;
if (SVGLayoutSupport::IsIsolationRequired(&object))
return true;
} else if (object.IsBoxModelObject()) {
if (PaintLayer* layer = ToLayoutBoxModelObject(object).Layer()) {
if (layer->HasNonIsolatedDescendantWithBlendMode())
return true;
}
}
SkBlendMode blend_mode = object.IsBlendingAllowed()
? WebCoreCompositeToSkiaComposite(
kCompositeSourceOver, style.BlendMode())
: SkBlendMode::kSrcOver;
if (blend_mode != SkBlendMode::kSrcOver)
return true;
float opacity = style.Opacity();
if (opacity != 1.0f)
return true;
if (CompositingReasonFinder::RequiresCompositingForOpacityAnimation(style))
return true;
if (object.IsSVGChild()) {
if (SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(&object)) {
if (resources->Masker())
return true;
}
}
if (object.StyleRef().HasMask())
return true;
return false;
}
void FragmentPaintPropertyTreeBuilder::UpdateEffect() {
DCHECK(properties_);
const ComputedStyle& style = object_.StyleRef();
// TODO(trchen): Can't omit effect node if we have 3D children.
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
const ClipPaintPropertyNode* output_clip = nullptr;
bool local_clip_added_or_removed = false;
bool local_clip_changed = false;
if (NeedsEffect(object_)) {
// We may begin to composite our subtree prior to an animation starts,
// but a compositor element ID is only needed when an animation is
// current.
CompositingReasons compositing_reasons = kCompositingReasonNone;
if (CompositingReasonFinder::RequiresCompositingForOpacityAnimation(
style)) {
compositing_reasons = kCompositingReasonActiveAnimation;
}
IntRect mask_clip;
ColorFilter mask_color_filter;
bool has_mask = ComputeMaskParameters(
mask_clip, mask_color_filter, object_, context_.current.paint_offset);
if (has_mask &&
// TODO(crbug.com/768691): Remove the following condition after mask
// clip doesn't fail fast/borders/inline-mask-overlay-image-outset-
// vertical-rl.html.
RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) {
FloatRoundedRect rounded_mask_clip(mask_clip);
if (properties_->MaskClip() &&
rounded_mask_clip != properties_->MaskClip()->ClipRect())
local_clip_changed = true;
auto result = properties_->UpdateMaskClip(context_.current.clip,
context_.current.transform,
FloatRoundedRect(mask_clip));
local_clip_added_or_removed |= result.NewNodeCreated();
output_clip = properties_->MaskClip();
} else {
full_context_.force_subtree_update |= properties_->ClearMaskClip();
}
SkBlendMode blend_mode =
object_.IsBlendingAllowed()
? WebCoreCompositeToSkiaComposite(kCompositeSourceOver,
style.BlendMode())
: SkBlendMode::kSrcOver;
auto result = properties_->UpdateEffect(
context_.current_effect, context_.current.transform, output_clip,
kColorFilterNone, CompositorFilterOperations(), style.Opacity(),
blend_mode, compositing_reasons,
CompositorElementIdFromUniqueObjectId(
object_.UniqueId(), CompositorElementIdNamespace::kPrimary));
full_context_.force_subtree_update |= result.NewNodeCreated();
if (has_mask) {
auto result = properties_->UpdateMask(
properties_->Effect(), context_.current.transform, output_clip,
mask_color_filter, CompositorFilterOperations(), 1.f,
SkBlendMode::kDstIn, kCompositingReasonNone,
CompositorElementIdFromUniqueObjectId(
object_.UniqueId(), CompositorElementIdNamespace::kEffectMask));
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
full_context_.force_subtree_update |= properties_->ClearMask();
}
} else {
full_context_.force_subtree_update |= properties_->ClearEffect();
full_context_.force_subtree_update |= properties_->ClearMask();
local_clip_added_or_removed |= properties_->ClearMaskClip();
}
full_context_.force_subtree_update |= local_clip_added_or_removed;
full_context_.clip_changed |=
local_clip_changed || local_clip_added_or_removed;
}
if (properties_->Effect()) {
context_.current_effect = properties_->Effect();
if (properties_->MaskClip()) {
context_.current.clip = context_.absolute_position.clip =
context_.fixed_position.clip = properties_->MaskClip();
}
}
}
static bool NeedsFilter(const LayoutObject& object) {
// TODO(trchen): SVG caches filters in SVGResources. Implement it.
return object.IsBoxModelObject() && ToLayoutBoxModelObject(object).Layer() &&
(object.StyleRef().HasFilter() || object.HasReflection());
}
void FragmentPaintPropertyTreeBuilder::UpdateFilter() {
DCHECK(properties_);
const ComputedStyle& style = object_.StyleRef();
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
if (NeedsFilter(object_)) {
CompositorFilterOperations filter =
ToLayoutBoxModelObject(object_)
.Layer()
->CreateCompositorFilterOperationsForFilter(style);
// The CSS filter spec didn't specify how filters interact with overflow
// clips. The implementation here mimics the old Blink/WebKit behavior for
// backward compatibility.
// Basically the output of the filter will be affected by clips that
// applies to the current element. The descendants that paints into the
// input of the filter ignores any clips collected so far. For example:
// <div style="overflow:scroll">
// <div style="filter:blur(1px);">
// <div>A</div>
// <div style="position:absolute;">B</div>
// </div>
// </div>
// In this example "A" should be clipped if the filter was not present.
// With the filter, "A" will be rastered without clipping, but instead
// the blurred result will be clipped.
// On the other hand, "B" should not be clipped because the overflow clip
// is not in its containing block chain, but as the filter output will be
// clipped, so a blurred "B" may still be invisible.
const ClipPaintPropertyNode* output_clip = context_.current.clip;
// TODO(trchen): A filter may contain spatial operations such that an
// output pixel may depend on an input pixel outside of the output clip.
// We should generate a special clip node to represent this expansion.
// We may begin to composite our subtree prior to an animation starts,
// but a compositor element ID is only needed when an animation is
// current.
CompositingReasons compositing_reasons =
CompositingReasonFinder::RequiresCompositingForFilterAnimation(style)
? kCompositingReasonActiveAnimation
: kCompositingReasonNone;
DCHECK(!style.HasCurrentFilterAnimation() ||
compositing_reasons != kCompositingReasonNone);
auto result = properties_->UpdateFilter(
context_.current_effect, context_.current.transform, output_clip,
kColorFilterNone, std::move(filter), 1.f, SkBlendMode::kSrcOver,
compositing_reasons,
CompositorElementIdFromUniqueObjectId(
object_.UniqueId(), CompositorElementIdNamespace::kEffectFilter),
FloatPoint(context_.current.paint_offset));
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
full_context_.force_subtree_update |= properties_->ClearFilter();
}
}
if (properties_->Filter()) {
context_.current_effect = properties_->Filter();
// TODO(trchen): Change input clip to expansion hint once implemented.
const ClipPaintPropertyNode* input_clip =
properties_->Filter()->OutputClip();
context_.current.clip = context_.absolute_position.clip =
context_.fixed_position.clip = input_clip;
}
}
static bool NeedsFragmentation(const LayoutObject& object,
const PaintLayer& painting_layer) {
return painting_layer.ShouldFragmentCompositedBounds();
}
static bool NeedsFragmentationClip(const LayoutObject& object,
const PaintLayer& painting_layer) {
return object.HasLayer() && NeedsFragmentation(object, painting_layer);
}
void FragmentPaintPropertyTreeBuilder::UpdateFragmentClip(
const PaintLayer& painting_layer) {
DCHECK(properties_);
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
bool local_clip_added_or_removed = false;
bool local_clip_changed = false;
// It's possible to still have no clips even if NeedsFragmentationClip is
// true, in the case when the FragmentainerIterator returns none.
if (NeedsFragmentationClip(object_, painting_layer) &&
context_.fragment_clip) {
LayoutRect clip_rect(*context_.fragment_clip);
FloatRoundedRect rounded_clip_rect((FloatRect(clip_rect)));
if (properties_->FragmentClip() &&
properties_->FragmentClip()->ClipRect() != rounded_clip_rect)
local_clip_changed = true;
auto result = properties_->UpdateFragmentClip(
context_.current.clip, context_.current.transform, rounded_clip_rect);
local_clip_added_or_removed |= result.NewNodeCreated();
} else {
local_clip_added_or_removed |= properties_->ClearFragmentClip();
}
full_context_.force_subtree_update |= local_clip_added_or_removed;
full_context_.clip_changed |=
local_clip_changed || local_clip_added_or_removed;
}
}
static bool NeedsCssClip(const LayoutObject& object) {
return object.HasClip();
}
void FragmentPaintPropertyTreeBuilder::UpdateCssClip() {
DCHECK(properties_);
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
bool local_clip_added_or_removed = false;
bool local_clip_changed = false;
if (NeedsCssClip(object_)) {
// Create clip node for descendants that are not fixed position.
// We don't have to setup context.absolutePosition.clip here because this
// object must be a container for absolute position descendants, and will
// copy from in-flow context later at updateOutOfFlowContext() step.
DCHECK(object_.CanContainAbsolutePositionObjects());
LayoutRect clip_rect =
ToLayoutBox(object_).ClipRect(context_.current.paint_offset);
FloatRoundedRect rounded_clip_rect((FloatRect(clip_rect)));
if (properties_->CssClip() &&
properties_->CssClip()->ClipRect() != rounded_clip_rect)
local_clip_changed = true;
auto result = properties_->UpdateCssClip(
context_.current.clip, context_.current.transform,
FloatRoundedRect(FloatRect(clip_rect)));
local_clip_added_or_removed |= result.NewNodeCreated();
} else {
local_clip_added_or_removed |= properties_->ClearCssClip();
}
full_context_.force_subtree_update |= local_clip_added_or_removed;
full_context_.clip_changed |=
local_clip_changed || local_clip_added_or_removed;
}
if (properties_->CssClip())
context_.current.clip = properties_->CssClip();
}
void FragmentPaintPropertyTreeBuilder::UpdateLocalBorderBoxContext() {
if (!object_.NeedsPaintPropertyUpdate() &&
!full_context_.force_subtree_update)
return;
auto* rare_paint_data = fragment_data_.GetRarePaintData();
if (!object_.HasLayer() && !NeedsPaintOffsetTranslation(object_)) {
if (rare_paint_data)
rare_paint_data->ClearLocalBorderBoxProperties();
} else {
DCHECK(rare_paint_data);
const ClipPaintPropertyNode* clip = context_.current.clip;
if (rare_paint_data->PaintProperties() &&
rare_paint_data->PaintProperties()->FragmentClip()) {
clip = rare_paint_data->PaintProperties()->FragmentClip();
}
PropertyTreeState local_border_box = PropertyTreeState(
context_.current.transform, clip, context_.current_effect);
rare_paint_data->SetLocalBorderBoxProperties(local_border_box);
}
}
static bool NeedsOverflowClip(const LayoutObject& object) {
return object.IsBox() && ToLayoutBox(object).ShouldClipOverflow();
}
static bool NeedsControlClipFragmentationAdjustment(const LayoutBox& box) {
return box.HasControlClip() && !box.Layer() &&
box.PaintingLayer()->EnclosingPaginationLayer();
}
static LayoutPoint VisualOffsetFromPaintOffsetRoot(
const PaintPropertyTreeBuilderFragmentContext& context,
const PaintLayer* child) {
const LayoutObject* paint_offset_root = context.current.paint_offset_root;
PaintLayer* painting_layer = paint_offset_root->PaintingLayer();
LayoutPoint result = child->VisualOffsetFromAncestor(painting_layer);
if (!paint_offset_root->HasLayer()) {
result.Move(-paint_offset_root->OffsetFromAncestorContainer(
&painting_layer->GetLayoutObject()));
}
// Don't include scroll offset of paint_offset_root. Any scroll is
// already included in a separate transform node.
if (paint_offset_root->HasOverflowClip())
result += ToLayoutBox(paint_offset_root)->ScrolledContentOffset();
return result;
}
void FragmentPaintPropertyTreeBuilder::UpdateOverflowClip() {
DCHECK(properties_);
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
bool local_clip_added_or_removed = false;
bool local_clip_changed = false;
if (NeedsOverflowClip(object_)) {
const LayoutBox& box = ToLayoutBox(object_);
LayoutRect clip_rect;
clip_rect = box.OverflowClipRect(context_.current.paint_offset);
const auto* current_clip = context_.current.clip;
if (box.StyleRef().HasBorderRadius()) {
auto inner_border = box.StyleRef().GetRoundedInnerBorderFor(
LayoutRect(context_.current.paint_offset, box.Size()));
auto result = properties_->UpdateInnerBorderRadiusClip(
context_.current.clip, context_.current.transform, inner_border);
local_clip_added_or_removed |= result.NewNodeCreated();
current_clip = properties_->InnerBorderRadiusClip();
} else {
local_clip_added_or_removed |=
properties_->ClearInnerBorderRadiusClip();
}
FloatRoundedRect clipping_rect((FloatRect(clip_rect)));
if (properties_->OverflowClip() &&
clipping_rect != properties_->OverflowClip()->ClipRect()) {
local_clip_changed = true;
}
auto result = properties_->UpdateOverflowClip(
current_clip, context_.current.transform,
FloatRoundedRect(FloatRect(clip_rect)));
local_clip_added_or_removed |= result.NewNodeCreated();
} else {
local_clip_added_or_removed |= properties_->ClearInnerBorderRadiusClip();
local_clip_added_or_removed |= properties_->ClearOverflowClip();
}
full_context_.force_subtree_update |= local_clip_added_or_removed;
full_context_.clip_changed |=
local_clip_changed || local_clip_added_or_removed;
}
if (properties_->OverflowClip())
context_.current.clip = properties_->OverflowClip();
}
static FloatPoint PerspectiveOrigin(const LayoutBox& box) {
const ComputedStyle& style = box.StyleRef();
// Perspective origin has no effect without perspective.
DCHECK(style.HasPerspective());
FloatSize border_box_size(box.Size());
return FloatPoint(
FloatValueForLength(style.PerspectiveOriginX(), border_box_size.Width()),
FloatValueForLength(style.PerspectiveOriginY(),
border_box_size.Height()));
}
static bool NeedsPerspective(const LayoutObject& object) {
return object.IsBox() && object.StyleRef().HasPerspective();
}
void FragmentPaintPropertyTreeBuilder::UpdatePerspective() {
DCHECK(properties_);
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
if (NeedsPerspective(object_)) {
const ComputedStyle& style = object_.StyleRef();
// The perspective node must not flatten (else nothing will get
// perspective), but it should still extend the rendering context as
// most transform nodes do.
TransformationMatrix matrix =
TransformationMatrix().ApplyPerspective(style.Perspective());
FloatPoint3D origin = PerspectiveOrigin(ToLayoutBox(object_)) +
ToLayoutSize(context_.current.paint_offset);
auto result = properties_->UpdatePerspective(
context_.current.transform, matrix, origin,
context_.current.should_flatten_inherited_transform,
context_.current.rendering_context_id);
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
full_context_.force_subtree_update |= properties_->ClearPerspective();
}
}
if (properties_->Perspective()) {
context_.current.transform = properties_->Perspective();
context_.current.should_flatten_inherited_transform = false;
}
}
void FragmentPaintPropertyTreeBuilder::UpdateSvgLocalToBorderBoxTransform() {
DCHECK(properties_);
if (!object_.IsSVGRoot())
return;
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
AffineTransform transform_to_border_box =
SVGRootPainter(ToLayoutSVGRoot(object_))
.TransformToPixelSnappedBorderBox(context_.current.paint_offset);
if (!transform_to_border_box.IsIdentity() &&
NeedsSVGLocalToBorderBoxTransform(object_)) {
auto result = properties_->UpdateSvgLocalToBorderBoxTransform(
context_.current.transform, transform_to_border_box, FloatPoint3D());
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
full_context_.force_subtree_update |=
properties_->ClearSvgLocalToBorderBoxTransform();
}
}
if (properties_->SvgLocalToBorderBoxTransform()) {
context_.current.transform = properties_->SvgLocalToBorderBoxTransform();
context_.current.should_flatten_inherited_transform = false;
context_.current.rendering_context_id = 0;
}
// The paint offset is included in |transformToBorderBox| so SVG does not need
// to handle paint offset internally.
context_.current.paint_offset = LayoutPoint();
}
static MainThreadScrollingReasons GetMainThreadScrollingReasons(
const LayoutObject& object,
MainThreadScrollingReasons ancestor_reasons) {
// The current main thread scrolling reasons implementation only changes
// reasons at frame boundaries, so we can early-out when not at a LayoutView.
// TODO(pdr): Need to find a solution to the style-related main thread
// scrolling reasons such as opacity and transform which violate this.
if (!object.IsLayoutView())
return ancestor_reasons;
return GetMainThreadScrollingReasons(*object.GetFrameView(),
ancestor_reasons);
}
void FragmentPaintPropertyTreeBuilder::UpdateScrollAndScrollTranslation() {
DCHECK(properties_);
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
if (NeedsScrollNode(object_)) {
const LayoutBox& box = ToLayoutBox(object_);
auto* scrollable_area = box.GetScrollableArea();
// The container bounds are snapped to integers to match the equivalent
// bounds on cc::ScrollNode. The offset is snapped to match the current
// integer offsets used in CompositedLayerMapping.
auto container_rect = PixelSnappedIntRect(
box.OverflowClipRect(context_.current.paint_offset));
IntRect contents_rect(-scrollable_area->ScrollOrigin(),
scrollable_area->ContentsSize());
contents_rect.MoveBy(container_rect.Location());
// In flipped blocks writing mode, if there is scrollbar on the right,
// we move the contents to the left with extra amount of ScrollTranslation
// (-VerticalScrollbarWidth, 0). As contents_rect is in the space of
// ScrollTranslation, we need to compensate the extra ScrollTranslation
// to get correct contents_rect origin.
if (box.HasFlippedBlocksWritingMode())
contents_rect.Move(box.VerticalScrollbarWidth(), 0);
bool user_scrollable_horizontal =
scrollable_area->UserInputScrollable(kHorizontalScrollbar);
bool user_scrollable_vertical =
scrollable_area->UserInputScrollable(kVerticalScrollbar);
auto ancestor_reasons =
context_.current.scroll->GetMainThreadScrollingReasons();
auto reasons = GetMainThreadScrollingReasons(object_, ancestor_reasons);
// Main thread scrolling reasons depend on their ancestor's reasons
// so ensure the entire subtree is updated when reasons change.
if (auto* existing_scroll = properties_->Scroll()) {
if (existing_scroll->GetMainThreadScrollingReasons() != reasons)
full_context_.force_subtree_update = true;
}
auto element_id = scrollable_area->GetCompositorElementId();
// TODO(pdr): Set the correct compositing reasons here.
auto result = properties_->UpdateScroll(
context_.current.scroll, container_rect, contents_rect,
user_scrollable_horizontal, user_scrollable_vertical, reasons,
element_id);
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
// Ensure pre-existing properties are cleared.
full_context_.force_subtree_update |= properties_->ClearScroll();
}
// A scroll translation node is created for static offset (e.g., overflow
// hidden with scroll offset) or cases that scroll and have a scroll node.
if (NeedsScrollOrScrollTranslation(object_)) {
const LayoutBox& box = ToLayoutBox(object_);
IntSize scroll_offset = box.ScrolledContentOffset();
TransformationMatrix scroll_offset_matrix =
TransformationMatrix().Translate(-scroll_offset.Width(),
-scroll_offset.Height());
auto result = properties_->UpdateScrollTranslation(
context_.current.transform, scroll_offset_matrix, FloatPoint3D(),
context_.current.should_flatten_inherited_transform,
context_.current.rendering_context_id, kCompositingReasonNone,
CompositorElementId(), properties_->Scroll());
full_context_.force_subtree_update |= result.NewNodeCreated();
} else {
// Ensure pre-existing properties are cleared.
full_context_.force_subtree_update |=
properties_->ClearScrollTranslation();
}
}
if (properties_->Scroll())
context_.current.scroll = properties_->Scroll();
if (properties_->ScrollTranslation()) {
context_.current.transform = properties_->ScrollTranslation();
context_.current.should_flatten_inherited_transform = false;
}
}
void FragmentPaintPropertyTreeBuilder::UpdateOutOfFlowContext() {
if (!object_.IsBoxModelObject() && !properties_)
return;
if (object_.IsLayoutBlock())
context_.paint_offset_for_float = context_.current.paint_offset;
if (object_.CanContainAbsolutePositionObjects())
context_.absolute_position = context_.current;
if (object_.IsLayoutView()) {
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
const auto* initial_fixed_transform = context_.fixed_position.transform;
const auto* initial_fixed_scroll = context_.fixed_position.scroll;
context_.fixed_position = context_.current;
// Fixed position transform and scroll nodes should not be affected.
context_.fixed_position.transform = initial_fixed_transform;
context_.fixed_position.scroll = initial_fixed_scroll;
}
} else if (object_.CanContainFixedPositionObjects()) {
context_.fixed_position = context_.current;
context_.fixed_position.fixed_position_children_fixed_to_root = false;
} else if (properties_ && properties_->CssClip()) {
// CSS clip applies to all descendants, even if this object is not a
// containing block ancestor of the descendant. It is okay for
// absolute-position descendants because having CSS clip implies being
// absolute position container. However for fixed-position descendants we
// need to insert the clip here if we are not a containing block ancestor of
// them.
auto* css_clip = properties_->CssClip();
// Before we actually create anything, check whether in-flow context and
// fixed-position context has exactly the same clip. Reuse if possible.
if (context_.fixed_position.clip == css_clip->Parent()) {
context_.fixed_position.clip = css_clip;
} else {
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
auto result = properties_->UpdateCssClipFixedPosition(
context_.fixed_position.clip,
const_cast<TransformPaintPropertyNode*>(
css_clip->LocalTransformSpace()),
css_clip->ClipRect());
full_context_.force_subtree_update |= result.NewNodeCreated();
}
if (properties_->CssClipFixedPosition())
context_.fixed_position.clip = properties_->CssClipFixedPosition();
return;
}
}
if (object_.NeedsPaintPropertyUpdate() ||
full_context_.force_subtree_update) {
if (properties_) {
full_context_.force_subtree_update |=
properties_->ClearCssClipFixedPosition();
}
}
}
static LayoutRect BoundingBoxInPaginationContainer(
const LayoutObject& object,
const PaintLayer& enclosing_pagination_layer) {
// Non-boxes that have no layer paint in the space of their containing block.
if (!object.IsBox() && !object.HasLayer()) {
return BoundingBoxInPaginationContainer(*object.ContainingBlock(),
enclosing_pagination_layer);
}
LayoutRect object_bounding_box_in_flow_thread;
if (object.HasLayer()) {
PaintLayer* paint_layer = ToLayoutBoxModelObject(object).Layer();
object_bounding_box_in_flow_thread =
paint_layer->PhysicalBoundingBox(&enclosing_pagination_layer);
} else {
// Compute the bounding box without transforms.
// The object is guaranteed to be a box due to the logic above.
DCHECK(object.IsBox());
object_bounding_box_in_flow_thread = ToLayoutBox(object).BorderBoxRect();
TransformState transform_state(
TransformState::kApplyTransformDirection,
FloatPoint(object_bounding_box_in_flow_thread.Location()));
object.MapLocalToAncestor(&enclosing_pagination_layer.GetLayoutObject(),
transform_state, kApplyContainerFlip);
transform_state.Flatten();
object_bounding_box_in_flow_thread.SetLocation(
LayoutPoint(transform_state.LastPlanarPoint()));
}
return object_bounding_box_in_flow_thread;
}
void FragmentPaintPropertyTreeBuilder::UpdatePaintOffset() {
// Paint offsets for fragmented content are computed from scratch.
if (context_.fragment_clip &&
// Except if the paint_offset_root is below the pagination container,
// in which case fragmentation offsets are already baked into the paint
// offset transform for paint_offset_root.
!context_.current.paint_offset_root->PaintingLayer()
->EnclosingPaginationLayer()) {
PaintLayer* paint_layer = object_.PaintingLayer();
PaintLayer* enclosing_pagination_layer =
paint_layer->EnclosingPaginationLayer();
DCHECK(enclosing_pagination_layer);
// Set fragment visual paint offset.
LayoutPoint paint_offset =
BoundingBoxInPaginationContainer(object_, *enclosing_pagination_layer)
.Location();
paint_offset.MoveBy(fragment_data_.GetRarePaintData()->PaginationOffset());
paint_offset.MoveBy(
VisualOffsetFromPaintOffsetRoot(context_, enclosing_pagination_layer));
// The paint offset root can have a subpixel paint offset adjustment.
// The paint offset root always has one fragment.
paint_offset.MoveBy(
context_.current.paint_offset_root->FirstFragment().PaintOffset());
context_.current.paint_offset = paint_offset;
return;
}
if (object_.IsFloating())
context_.current.paint_offset = context_.paint_offset_for_float;
// Multicolumn spanners are painted starting at the multicolumn container (but
// still inherit properties in layout-tree order) so reset the paint offset.
if (object_.IsColumnSpanAll()) {
context_.current.paint_offset =
object_.Container()->FirstFragment().PaintOffset();
}
if (object_.IsBoxModelObject()) {
const LayoutBoxModelObject& box_model_object =
ToLayoutBoxModelObject(object_);
switch (box_model_object.StyleRef().GetPosition()) {
case EPosition::kStatic:
break;
case EPosition::kRelative:
context_.current.paint_offset +=
box_model_object.OffsetForInFlowPosition();
break;
case EPosition::kAbsolute: {
DCHECK(full_context_.container_for_absolute_position ==
box_model_object.Container());
context_.current = context_.absolute_position;
// Absolutely positioned content in an inline should be positioned
// relative to the inline.
const auto* container = full_context_.container_for_absolute_position;
if (container && container->IsInFlowPositioned() &&
container->IsLayoutInline()) {
DCHECK(box_model_object.IsBox());
context_.current.paint_offset +=
ToLayoutInline(container)->OffsetForInFlowPositionedInline(
ToLayoutBox(box_model_object));
}
break;
}
case EPosition::kSticky:
context_.current.paint_offset +=
box_model_object.OffsetForInFlowPosition();
break;
case EPosition::kFixed:
context_.current = context_.fixed_position;
// Fixed-position elements that are fixed to the vieport have a
// transform above the scroll of the LayoutView. Child content is
// relative to that transform, and hence the fixed-position element.
if (context_.fixed_position.fixed_position_children_fixed_to_root)
context_.current.paint_offset_root = &box_model_object;
break;
default:
NOTREACHED();
}
}
if (object_.IsBox()) {
// TODO(pdr): Several calls in this function walk back up the tree to
// calculate containers (e.g., physicalLocation, offsetForInFlowPosition*).
// The containing block and other containers can be stored on
// PaintPropertyTreeBuilderFragmentContext instead of recomputing them.
context_.current.paint_offset.MoveBy(
ToLayoutBox(object_).PhysicalLocation());
// This is a weird quirk that table cells paint as children of table rows,
// but their location have the row's location baked-in.
// Similar adjustment is done in LayoutTableCell::offsetFromContainer().
if (object_.IsTableCell()) {
LayoutObject* parent_row = object_.Parent();
DCHECK(parent_row && parent_row->IsTableRow());
context_.current.paint_offset.MoveBy(
-ToLayoutBox(parent_row)->PhysicalLocation());
}
}
}
static void SetNeedsPaintPropertyUpdateIfNeeded(const LayoutObject& object) {
if (!object.IsBoxModelObject())
return;
const LayoutBoxModelObject& box_model_object = ToLayoutBoxModelObject(object);
if (box_model_object.Layer() &&
box_model_object.Layer()->ShouldFragmentCompositedBounds()) {
// Always force-update properties for fragmented content.
// TODO(chrishtr): find ways to optimize this in the future.
// It may suffice to compare previous and current visual overflow,
// but we do not currenly cache that on the LayoutObject or PaintLayer.
object.GetMutableForPainting().SetNeedsPaintPropertyUpdate();
return;
}
if (!object.IsBox())
return;
const LayoutBox& box = ToLayoutBox(object);
// Always force-update properties for fragmented content. Boxes with
// control clip have a fragment-aware offset.
if (NeedsControlClipFragmentationAdjustment(box)) {
box.GetMutableForPainting().SetNeedsPaintPropertyUpdate();
return;
}
if (box.Size() == box.PreviousSize())
return;
// CSS mask and clip-path comes with an implicit clip to the border box.
// Currently only SPv2 generate and take advantage of those.
const bool box_generates_property_nodes_for_mask_and_clip_path =
RuntimeEnabledFeatures::SlimmingPaintV175Enabled() &&
(box.HasMask() || box.HasClipPath());
// The overflow clip paint property depends on the border box rect through
// overflowClipRect(). The border box rect's size equals the frame rect's
// size so we trigger a paint property update when the frame rect changes.
if (box.ShouldClipOverflow() ||
// The used value of CSS clip may depend on size of the box, e.g. for
// clip: rect(auto auto auto -5px).
box.HasClip() ||
// Relative lengths (e.g., percentage values) in transform, perspective,
// transform-origin, and perspective-origin can depend on the size of the
// frame rect, so force a property update if it changes. TODO(pdr): We
// only need to update properties if there are relative lengths.
box.StyleRef().HasTransform() || box.StyleRef().HasPerspective() ||
box_generates_property_nodes_for_mask_and_clip_path)
box.GetMutableForPainting().SetNeedsPaintPropertyUpdate();
}
void FragmentPaintPropertyTreeBuilder::UpdateForObjectLocationAndSize(
Optional<IntPoint>& paint_offset_translation) {
#if DCHECK_IS_ON()
FindPaintOffsetNeedingUpdateScope check_scope(
object_, fragment_data_, full_context_.is_actually_needed);
#endif
UpdatePaintOffset();
UpdateForPaintOffsetTranslation(paint_offset_translation);
if (fragment_data_.PaintOffset() != context_.current.paint_offset) {
// Many paint properties depend on paint offset so we force an update of
// the entire subtree on paint offset changes.
full_context_.force_subtree_update = true;
if (RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) {
object_.GetMutableForPainting().SetShouldDoFullPaintInvalidation(
PaintInvalidationReason::kGeometry);
}
fragment_data_.SetPaintOffset(context_.current.paint_offset);
}
if (paint_offset_translation)
context_.current.paint_offset_root = &ToLayoutBoxModelObject(object_);
SetNeedsPaintPropertyUpdateIfNeeded(object_);
}
void FragmentPaintPropertyTreeBuilder::UpdateForSelf() {
// This is not in FindObjectPropertiesNeedingUpdateScope because paint offset
// can change without NeedsPaintPropertyUpdate.
Optional<IntPoint> paint_offset_translation;
UpdateForObjectLocationAndSize(paint_offset_translation);
if (properties_) {
// TODO(wangxianzhu): Put these in FindObjectPropertiesNeedingUpdateScope.
UpdateFragmentClip(*full_context_.painting_layer);
UpdatePaintOffsetTranslation(paint_offset_translation);
}
#if DCHECK_IS_ON()
FindObjectPropertiesNeedingUpdateScope check_needs_update_scope(
object_, fragment_data_, full_context_.force_subtree_update);
#endif
if (properties_) {
UpdateTransform();
UpdateCssClip();
if (RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
UpdateEffect();
UpdateFilter();
}
UpdateLocalBorderBoxContext();
}
void FragmentPaintPropertyTreeBuilder::UpdateForChildren() {
#if DCHECK_IS_ON()
FindObjectPropertiesNeedingUpdateScope check_needs_update_scope(
object_, fragment_data_, full_context_.force_subtree_update);
#endif
if (properties_) {
UpdateOverflowClip();
UpdatePerspective();
UpdateSvgLocalToBorderBoxTransform();
UpdateScrollAndScrollTranslation();
}
UpdateOutOfFlowContext();
}
} // namespace
// Match |fragment_clip| against an intersecting one from the parent contexts,
// if any, to allow for correct transform and effect parenting of fragments.
PaintPropertyTreeBuilderFragmentContext ContextForFragment(
const LayoutRect& fragment_clip,
const Vector<PaintPropertyTreeBuilderFragmentContext, 1>&
parent_fragments) {
// Find a fragment whose clip intersects this one, if any.
for (auto& fragment_context : parent_fragments) {
if (!fragment_context.fragment_clip ||
fragment_clip.Intersects(*fragment_context.fragment_clip)) {
PaintPropertyTreeBuilderFragmentContext context(fragment_context);
context.fragment_clip = fragment_clip;
return context;
}
}
// Otherwise return a new fragment parented at the first parent fragment.
if (parent_fragments.IsEmpty()) {
PaintPropertyTreeBuilderFragmentContext context;
context.fragment_clip = LayoutRect();
return context;
}
PaintPropertyTreeBuilderFragmentContext context(parent_fragments[0]);
context.fragment_clip = fragment_clip;
return context;
}
void ObjectPaintPropertyTreeBuilder::InitSingleFragmentFromParent(
bool needs_paint_properties) {
FragmentData& first_fragment =
object_.GetMutableForPainting().FirstFragment();
first_fragment.ClearNextFragment();
if (needs_paint_properties)
first_fragment.EnsureRarePaintData().EnsurePaintProperties();
if (context_.fragments.IsEmpty()) {
context_.fragments.push_back(PaintPropertyTreeBuilderFragmentContext());
} else {
context_.fragments.resize(1);
context_.fragments[0].fragment_clip.reset();
}
}
// Limit the maximum number of fragments, to avoid pathological situations.
static const int kMaxNumFragments = 2000;
void ObjectPaintPropertyTreeBuilder::UpdateFragments() {
bool needs_paint_properties =
NeedsPaintOffsetTranslation(object_) || NeedsTransform(object_) ||
NeedsEffect(object_) || NeedsTransformForNonRootSVG(object_) ||
NeedsFilter(object_) || NeedsCssClip(object_) ||
NeedsOverflowClip(object_) || NeedsPerspective(object_) ||
NeedsSVGLocalToBorderBoxTransform(object_) ||
NeedsScrollOrScrollTranslation(object_) ||
NeedsFragmentationClip(object_, *context_.painting_layer);
bool needs_fragments = NeedsFragmentation(object_, *context_.painting_layer);
bool had_paint_properties = object_.FirstFragment().PaintProperties();
if (!needs_paint_properties && !needs_fragments && !object_.HasLayer()) {
if (had_paint_properties) {
context_.force_subtree_update = true;
object_.FirstFragment().GetRarePaintData()->ClearPaintProperties();
}
InitSingleFragmentFromParent(needs_paint_properties);
} else {
if (!needs_fragments) {
InitSingleFragmentFromParent(needs_paint_properties);
} else {
// We need at least the fragments for all fragmented objects, which store
// their local border box properties and paint invalidation data (such
// as paint offset and visual rect) on each fragment.
PaintLayer* paint_layer = object_.PaintingLayer();
PaintLayer* enclosing_pagination_layer =
paint_layer->EnclosingPaginationLayer();
LayoutRect object_bounding_box_in_flow_thread =
BoundingBoxInPaginationContainer(object_,
*enclosing_pagination_layer);
const LayoutFlowThread& flow_thread =
ToLayoutFlowThread(enclosing_pagination_layer->GetLayoutObject());
FragmentainerIterator iterator(flow_thread,
object_bounding_box_in_flow_thread);
Vector<PaintPropertyTreeBuilderFragmentContext> new_fragment_contexts;
FragmentData* current_fragment_data = nullptr;
int fragment_count = 0;
for (; !iterator.AtEnd() && fragment_count < kMaxNumFragments;
iterator.Advance(), fragment_count++) {
if (!current_fragment_data) {
current_fragment_data =
&object_.GetMutableForPainting().FirstFragment();
} else {
current_fragment_data = &current_fragment_data->EnsureNextFragment();
}
RarePaintData& rare_paint_data =
current_fragment_data->EnsureRarePaintData();
if (needs_paint_properties)
rare_paint_data.EnsurePaintProperties();
// 1. Compute clip in flow thread space of the containing flow thread.
LayoutRect fragment_clip(iterator.ClipRectInFlowThread());
// 2. Convert #1 to visual coordinates in the space of the flow
// thread.
fragment_clip.Move(iterator.PaginationOffset());
// 3. Adust #2 to visual coordinates in the containing "paint offset"
// space.
{
DCHECK(context_.fragments[0].current.paint_offset_root);
LayoutPoint pagination_visual_offset =
VisualOffsetFromPaintOffsetRoot(context_.fragments[0],
enclosing_pagination_layer);
// Adjust for paint offset of the root, which may have a subpixel
// component.
// The paint offset root never has more than one fragment.
pagination_visual_offset.MoveBy(
context_.fragments[0]
.current.paint_offset_root->FirstFragment()
.PaintOffset());
fragment_clip.MoveBy(pagination_visual_offset);
}
// 4. Match to parent fragments from the same containing flow
// thread.
new_fragment_contexts.push_back(
ContextForFragment(fragment_clip, context_.fragments));
// 5. Save off PaginationOffset (which allows us to adjust
// logical paint offsets into the space of the current fragment later.
current_fragment_data->GetRarePaintData()->SetPaginationOffset(
ToLayoutPoint(iterator.PaginationOffset()));
}
if (current_fragment_data) {
current_fragment_data->ClearNextFragment();
context_.fragments = new_fragment_contexts;
} else {
// This will be an empty fragment - get rid of it?
InitSingleFragmentFromParent(needs_paint_properties);
}
}
}
if (object_.IsSVGHiddenContainer()) {
// SVG resources are painted within one or more other locations in the
// SVG during paint, and hence have their own independent paint property
// trees, paint offset, etc.
context_.fragments.clear();
context_.fragments.Grow(1);
PaintPropertyTreeBuilderFragmentContext& fragment_context =
context_.fragments[0];
fragment_context.current.paint_offset_root =
fragment_context.absolute_position.paint_offset_root =
fragment_context.fixed_position.paint_offset_root = &object_;
object_.GetMutableForPainting().FirstFragment().ClearNextFragment();
}
}
static inline bool ObjectTypeMightNeedPaintProperties(
const LayoutObject& object) {
return object.IsBoxModelObject() || object.IsSVG() ||
object.PaintingLayer()->EnclosingPaginationLayer();
}
void ObjectPaintPropertyTreeBuilder::UpdatePaintingLayer() {
bool changed_painting_layer = false;
if (object_.HasLayer() &&
ToLayoutBoxModelObject(object_).HasSelfPaintingLayer()) {
context_.painting_layer = ToLayoutBoxModelObject(object_).Layer();
changed_painting_layer = true;
} else if (object_.IsColumnSpanAll() ||
object_.IsFloatingWithNonContainingBlockParent()) {
// See LayoutObject::paintingLayer() for the special-cases of floating under
// inline and multicolumn.
context_.painting_layer = object_.PaintingLayer();
changed_painting_layer = true;
}
DCHECK(context_.painting_layer == object_.PaintingLayer());
}
void ObjectPaintPropertyTreeBuilder::UpdateForSelf() {
UpdatePaintingLayer();
if (ObjectTypeMightNeedPaintProperties(object_))
UpdateFragments();
else
object_.GetMutableForPainting().FirstFragment().ClearNextFragment();
auto* fragment_data = &object_.GetMutableForPainting().FirstFragment();
for (auto& fragment_context : context_.fragments) {
FragmentPaintPropertyTreeBuilder(object_, context_, fragment_context,
*fragment_data)
.UpdateForSelf();
fragment_data = fragment_data->NextFragment();
}
DCHECK(!fragment_data);
}
void ObjectPaintPropertyTreeBuilder::UpdateForChildren() {
if (!ObjectTypeMightNeedPaintProperties(object_))
return;
auto* fragment_data = &object_.GetMutableForPainting().FirstFragment();
for (auto& fragment_context : context_.fragments) {
FragmentPaintPropertyTreeBuilder(object_, context_, fragment_context,
*fragment_data)
.UpdateForChildren();
context_.force_subtree_update |= object_.SubtreeNeedsPaintPropertyUpdate();
fragment_data = fragment_data->NextFragment();
}
DCHECK(!fragment_data);
if (object_.CanContainAbsolutePositionObjects())
context_.container_for_absolute_position = &object_;
}
} // namespace blink