blob: d6de62cbb866886ad2d2cd377eea546380a41af7 [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 "third_party/blink/renderer/core/paint/pre_paint_tree_walk.h"
#include "base/auto_reset.h"
#include "cc/base/features.h"
#include "third_party/blink/renderer/core/dom/document_lifecycle.h"
#include "third_party/blink/renderer/core/frame/event_handler_registry.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_fieldset.h"
#include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h"
#include "third_party/blink/renderer/core/layout/layout_shift_tracker.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment_child_iterator.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/cull_rect_updater.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
namespace {
// Locate or/and set up the current FragmentData object. This may involve
// creating it, or resetting an existing one. If |allow_reset| is set, we're
// allowed to clear old FragmentData objects.
NGPrePaintInfo SetupFragmentData(const NGFragmentChildIterator& iterator,
bool allow_reset) {
// TODO(crbug.com/1043787): What's here is mostly gross, and we need to come
// up with something better. The way FragmentData works (and is stored)
// vs. the way NGPhysicalFragment works is less than ideal.
//
// There's essentially a 1:1 correspondence between a block-level
// NGPhysicalBoxFragment and FragmentData, but there's no direct link between
// them, so we have to do some work. In the future we might want to make
// FragmentData part of NGPhysicalBoxFragment objects, to simplify this, and
// to get rid of O(n^2) performance complexity (where n is the number of
// fragments generated by a node). Note that this performance complexity also
// exists in the legacy engine.
//
// For inline-level nodes, it gets a bit more complicated. There's one
// FragmentData object per fragmentainer said node occurs in. The offset and
// invalidation rectangle in each FragmentData will be a union of all the
// fragments generated by the node (one per line box, typically) in that
// fragmentainer. This also matches how we do it in legacy layout. It's
// considered too expensive to have one FragmentData object per line for each
// text node or non-atomic inline.
DCHECK(iterator->GetLayoutObject());
const LayoutObject& object = *iterator->GetLayoutObject();
FragmentData* fragment_data = &object.GetMutableForPainting().FirstFragment();
const auto* incoming_break_token = iterator->BlockBreakToken();
const NGPhysicalBoxFragment* box_fragment = iterator->BoxFragment();
// The need for paint properties is the same across all fragments, so if the
// first FragmentData needs it, so do all the others.
bool needs_paint_properties = fragment_data->PaintProperties();
if (const NGFragmentItem* fragment_item = iterator->FragmentItem()) {
// We're in an inline formatting context. The consumed block-size stored in
// the incoming break token will be stored in FragmentData objects to
// identify each portion for a given fragmentainer.
LayoutUnit consumed_block_size;
if (incoming_break_token)
consumed_block_size = incoming_break_token->ConsumedBlockSize();
if (fragment_item->IsFirstForNode()) {
// This is the first fragment generated for the node (i.e. we're on the
// first line and first fragmentainer (column) that this node occurs
// in). Now is our chance to reset everything (the number or size of
// fragments may have changed since last time). All the other fragments
// will be visited in due course.
if (allow_reset && !object.IsBox()) {
// For text and non-atomic inlines, we now remove additional
// FragmentData objects, and reset the visual rect. The visual rect will
// be set and expanded, as we visit each individual fragment.
fragment_data->ClearNextFragment();
}
fragment_data->SetLogicalTopInFlowThread(consumed_block_size);
} else {
// This is not the first fragment. Now see if we can find a FragmentData
// with the right consumed block-size (or flow thread logical top). If
// not, we'll have to create one now.
while (consumed_block_size > fragment_data->LogicalTopInFlowThread()) {
FragmentData* next_fragment_data = fragment_data->NextFragment();
if (!next_fragment_data) {
fragment_data = &fragment_data->EnsureNextFragment();
fragment_data->SetLogicalTopInFlowThread(consumed_block_size);
break;
}
fragment_data = next_fragment_data;
}
DCHECK_EQ(fragment_data->LogicalTopInFlowThread(), consumed_block_size);
}
} else {
// The fragment is block-level.
if (IsResumingLayout(incoming_break_token)) {
// This isn't the first fragment for the node. We now need to walk past
// all preceding fragments to figure out which FragmentData to return (or
// create, if it doesn't already exist).
const auto& layout_box = To<LayoutBox>(object);
for (wtf_size_t idx = 0;; idx++) {
DCHECK_LT(idx, layout_box.PhysicalFragmentCount());
if (layout_box.GetPhysicalFragment(idx) == box_fragment)
break;
FragmentData* next = fragment_data->NextFragment();
if (!next) {
DCHECK_EQ(layout_box.GetPhysicalFragment(idx + 1), box_fragment);
fragment_data = &fragment_data->EnsureNextFragment();
break;
}
fragment_data = next;
}
fragment_data->SetLogicalTopInFlowThread(
incoming_break_token->ConsumedBlockSize());
}
if (!box_fragment->BreakToken()) {
// We have reached the end. There may be more data entries that were
// needed in the previous layout, but not any more. Clear them.
fragment_data->ClearNextFragment();
}
}
if (needs_paint_properties)
fragment_data->EnsurePaintProperties();
return NGPrePaintInfo(iterator, *fragment_data);
}
} // anonymous namespace
static void SetNeedsCompositingLayerPropertyUpdate(const LayoutObject& object) {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
if (!object.HasLayer())
return;
auto* compositor = object.View()->Compositor();
if (!compositor)
return;
PaintLayer* paint_layer = To<LayoutBoxModelObject>(object).Layer();
DisableCompositingQueryAsserts disabler;
// This ensures that CompositingLayerPropertyUpdater::Update will
// be called and update LayerState for the LayoutView.
auto* mapping = paint_layer->GetCompositedLayerMapping();
if (!mapping)
mapping = paint_layer->GroupedMapping();
if (!mapping)
return;
// These two calls will cause GraphicsLayerUpdater to run on |paint_layer|
// from with PLC::UpdateIfNeeded.
compositor->SetNeedsCompositingUpdate(
kCompositingUpdateAfterCompositingInputChange);
mapping->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateLocal);
}
void PrePaintTreeWalk::WalkTree(LocalFrameView& root_frame_view) {
if (root_frame_view.ShouldThrottleRendering()) {
// Skip the throttled frame. Will update it when it becomes unthrottled.
return;
}
DCHECK_EQ(root_frame_view.GetFrame().GetDocument()->Lifecycle().GetState(),
DocumentLifecycle::kInPrePaint);
// Reserve 50 elements for a really deep DOM. If the nesting is deeper than
// this, then the vector will reallocate, but it shouldn't be a big deal. This
// is also temporary within this function.
DCHECK_EQ(context_storage_.size(), 0u);
context_storage_.ReserveCapacity(50);
context_storage_.emplace_back();
// GeometryMapper depends on paint properties.
bool needs_tree_builder_context_update =
NeedsTreeBuilderContextUpdate(root_frame_view, context_storage_.back());
if (needs_tree_builder_context_update)
GeometryMapper::ClearCache();
if (root_frame_view.GetFrame().IsMainFrame()) {
auto property_changed = VisualViewportPaintPropertyTreeBuilder::Update(
root_frame_view.GetPage()->GetVisualViewport(),
*context_storage_.back().tree_builder_context);
if (property_changed >
PaintPropertyChangeType::kChangedOnlyCompositedValues) {
root_frame_view.SetPaintArtifactCompositorNeedsUpdate();
if (auto* layout_view = root_frame_view.GetLayoutView())
SetNeedsCompositingLayerPropertyUpdate(*layout_view);
}
}
Walk(root_frame_view);
paint_invalidator_.ProcessPendingDelayedPaintInvalidations();
context_storage_.pop_back();
if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
if (auto* layout_view = root_frame_view.GetLayoutView())
CullRectUpdater(*layout_view->Layer()).Update();
}
#if DCHECK_IS_ON()
if (needs_tree_builder_context_update) {
if (VLOG_IS_ON(2) && root_frame_view.GetLayoutView()) {
VLOG(2) << "PrePaintTreeWalk::Walk(root_frame_view=" << &root_frame_view
<< ")\nPaintLayer tree:";
showLayerTree(root_frame_view.GetLayoutView()->Layer());
}
if (VLOG_IS_ON(1))
showAllPropertyTrees(root_frame_view);
}
#endif
// If the frame is invalidated, we need to inform the frame's chrome client
// so that the client will initiate repaint of the contents.
if (needs_invalidate_chrome_client_) {
if (auto* client = root_frame_view.GetChromeClient())
client->InvalidateRect(IntRect(IntPoint(), root_frame_view.Size()));
}
}
void PrePaintTreeWalk::Walk(LocalFrameView& frame_view) {
// We need to be careful not to have a reference to the parent context, since
// this reference will be to the context_storage_ memory which may be
// reallocated during this function call.
wtf_size_t parent_context_index = context_storage_.size() - 1;
auto parent_context = [this,
parent_context_index]() -> PrePaintTreeWalkContext& {
return context_storage_[parent_context_index];
};
bool needs_tree_builder_context_update =
NeedsTreeBuilderContextUpdate(frame_view, parent_context());
if (frame_view.ShouldThrottleRendering()) {
// Skip the throttled frame, and set dirty bits that will be applied when it
// becomes unthrottled.
if (LayoutView* layout_view = frame_view.GetLayoutView()) {
if (needs_tree_builder_context_update) {
layout_view->AddSubtreePaintPropertyUpdateReason(
SubtreePaintPropertyUpdateReason::kPreviouslySkipped);
}
if (parent_context().paint_invalidator_context.NeedsSubtreeWalk())
layout_view->SetSubtreeShouldDoFullPaintInvalidation();
if (parent_context().effective_allowed_touch_action_changed)
layout_view->MarkEffectiveAllowedTouchActionChanged();
if (parent_context().blocking_wheel_event_handler_changed)
layout_view->MarkBlockingWheelEventHandlerChanged();
}
return;
}
// Note that because we're emplacing an object constructed from
// parent_context() (which is a reference to the vector itself), it's
// important to first ensure that there's sufficient capacity in the vector.
// Otherwise, it may reallocate causing parent_context() to point to invalid
// memory.
ResizeContextStorageIfNeeded();
context_storage_.emplace_back(parent_context(),
PaintInvalidatorContext::ParentContextAccessor(
this, parent_context_index),
needs_tree_builder_context_update);
auto context = [this]() -> PrePaintTreeWalkContext& {
return context_storage_.back();
};
// ancestor_scroll_container_paint_layer does not cross frame boundaries.
context().ancestor_scroll_container_paint_layer = nullptr;
if (context().tree_builder_context) {
PaintPropertyTreeBuilder::SetupContextForFrame(
frame_view, *context().tree_builder_context);
context().tree_builder_context->supports_composited_raster_invalidation =
frame_view.GetFrame().GetSettings()->GetAcceleratedCompositingEnabled();
}
if (LayoutView* view = frame_view.GetLayoutView()) {
#if DCHECK_IS_ON()
if (VLOG_IS_ON(3) && needs_tree_builder_context_update) {
VLOG(3) << "PrePaintTreeWalk::Walk(frame_view=" << &frame_view
<< ")\nLayout tree:";
showLayoutTree(view);
}
#endif
is_wheel_event_regions_enabled_ =
base::FeatureList::IsEnabled(::features::kWheelEventRegions);
Walk(*view, /* iterator */ nullptr);
#if DCHECK_IS_ON()
view->AssertSubtreeClearedPaintInvalidationFlags();
#endif
}
frame_view.GetLayoutShiftTracker().NotifyPrePaintFinished();
frame_view.GetMobileFriendlinessChecker().NotifyPrePaintFinished();
context_storage_.pop_back();
}
namespace {
enum class BlockingEventHandlerType {
kNone,
kTouchStartOrMoveBlockingEventHandler,
kWheelBlockingEventHandler,
};
bool HasBlockingEventHandlerHelper(const LocalFrame& frame,
EventTarget& target,
BlockingEventHandlerType event_type) {
if (!target.HasEventListeners())
return false;
const auto& registry = frame.GetEventHandlerRegistry();
if (BlockingEventHandlerType::kTouchStartOrMoveBlockingEventHandler ==
event_type) {
const auto* blocking = registry.EventHandlerTargets(
EventHandlerRegistry::kTouchStartOrMoveEventBlocking);
const auto* blocking_low_latency = registry.EventHandlerTargets(
EventHandlerRegistry::kTouchStartOrMoveEventBlockingLowLatency);
return blocking->Contains(&target) ||
blocking_low_latency->Contains(&target);
} else if (BlockingEventHandlerType::kWheelBlockingEventHandler ==
event_type) {
const auto* blocking =
registry.EventHandlerTargets(EventHandlerRegistry::kWheelEventBlocking);
return blocking->Contains(&target);
}
NOTREACHED();
return false;
}
bool HasBlockingEventHandlerHelper(const LayoutObject& object,
BlockingEventHandlerType event_type) {
if (IsA<LayoutView>(object)) {
auto* frame = object.GetFrame();
if (HasBlockingEventHandlerHelper(*frame, *frame->DomWindow(), event_type))
return true;
}
auto* node = object.GetNode();
auto* layout_block_flow = DynamicTo<LayoutBlockFlow>(object);
if (!node && layout_block_flow &&
layout_block_flow->IsAnonymousBlockContinuation()) {
// An anonymous continuation does not have handlers so we need to check the
// DOM ancestor for handlers using |NodeForHitTest|.
node = object.NodeForHitTest();
}
if (!node)
return false;
return HasBlockingEventHandlerHelper(*object.GetFrame(), *node, event_type);
}
bool HasBlockingTouchEventHandler(const LayoutObject& object) {
return HasBlockingEventHandlerHelper(
object, BlockingEventHandlerType::kTouchStartOrMoveBlockingEventHandler);
}
bool HasBlockingWheelEventHandler(const LayoutObject& object) {
return HasBlockingEventHandlerHelper(
object, BlockingEventHandlerType::kWheelBlockingEventHandler);
}
} // namespace
void PrePaintTreeWalk::UpdateEffectiveAllowedTouchAction(
const LayoutObject& object,
PrePaintTreeWalk::PrePaintTreeWalkContext& context) {
if (object.EffectiveAllowedTouchActionChanged())
context.effective_allowed_touch_action_changed = true;
if (context.effective_allowed_touch_action_changed) {
object.GetMutableForPainting().UpdateInsideBlockingTouchEventHandler(
context.inside_blocking_touch_event_handler ||
HasBlockingTouchEventHandler(object));
}
if (object.InsideBlockingTouchEventHandler())
context.inside_blocking_touch_event_handler = true;
}
void PrePaintTreeWalk::UpdateBlockingWheelEventHandler(
const LayoutObject& object,
PrePaintTreeWalk::PrePaintTreeWalkContext& context) {
if (object.BlockingWheelEventHandlerChanged())
context.blocking_wheel_event_handler_changed = true;
if (context.blocking_wheel_event_handler_changed) {
object.GetMutableForPainting().UpdateInsideBlockingWheelEventHandler(
context.inside_blocking_wheel_event_handler ||
HasBlockingWheelEventHandler(object));
}
if (object.InsideBlockingWheelEventHandler())
context.inside_blocking_wheel_event_handler = true;
}
void PrePaintTreeWalk::InvalidatePaintForHitTesting(
const LayoutObject& object,
PrePaintTreeWalk::PrePaintTreeWalkContext& context) {
if (context.paint_invalidator_context.subtree_flags &
PaintInvalidatorContext::kSubtreeNoInvalidation)
return;
if (!context.effective_allowed_touch_action_changed &&
!context.blocking_wheel_event_handler_changed)
return;
context.paint_invalidator_context.painting_layer->SetNeedsRepaint();
ObjectPaintInvalidator(object).InvalidateDisplayItemClient(
object, PaintInvalidationReason::kHitTest);
SetNeedsCompositingLayerPropertyUpdate(object);
}
void PrePaintTreeWalk::UpdateAuxiliaryObjectProperties(
const LayoutObject& object,
PrePaintTreeWalk::PrePaintTreeWalkContext& context) {
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
if (!object.HasLayer())
return;
PaintLayer* paint_layer = To<LayoutBoxModelObject>(object).Layer();
paint_layer->UpdateAncestorScrollContainerLayer(
context.ancestor_scroll_container_paint_layer);
if (object.IsScrollContainer())
context.ancestor_scroll_container_paint_layer = paint_layer;
}
bool PrePaintTreeWalk::NeedsTreeBuilderContextUpdate(
const LocalFrameView& frame_view,
const PrePaintTreeWalkContext& context) {
if (frame_view.GetFrame().IsMainFrame() &&
frame_view.GetPage()->GetVisualViewport().NeedsPaintPropertyUpdate()) {
return true;
}
return frame_view.GetLayoutView() &&
(ObjectRequiresTreeBuilderContext(*frame_view.GetLayoutView()) ||
ContextRequiresChildTreeBuilderContext(context));
}
bool PrePaintTreeWalk::ObjectRequiresPrePaint(const LayoutObject& object) {
return object.ShouldCheckForPaintInvalidation() ||
object.EffectiveAllowedTouchActionChanged() ||
object.DescendantEffectiveAllowedTouchActionChanged() ||
object.BlockingWheelEventHandlerChanged() ||
object.DescendantBlockingWheelEventHandlerChanged();
;
}
bool PrePaintTreeWalk::ContextRequiresChildPrePaint(
const PrePaintTreeWalkContext& context) {
return context.paint_invalidator_context.NeedsSubtreeWalk() ||
context.effective_allowed_touch_action_changed ||
context.blocking_wheel_event_handler_changed || context.clip_changed;
}
bool PrePaintTreeWalk::ObjectRequiresTreeBuilderContext(
const LayoutObject& object) {
return object.NeedsPaintPropertyUpdate() ||
object.ShouldCheckGeometryForPaintInvalidation() ||
(!object.ChildPrePaintBlockedByDisplayLock() &&
(object.DescendantNeedsPaintPropertyUpdate() ||
object.DescendantShouldCheckGeometryForPaintInvalidation()));
}
bool PrePaintTreeWalk::ContextRequiresChildTreeBuilderContext(
const PrePaintTreeWalkContext& context) {
if (!context.NeedsTreeBuilderContext()) {
DCHECK(!context.tree_builder_context->force_subtree_update_reasons);
DCHECK(!context.paint_invalidator_context.NeedsSubtreeWalk());
return false;
}
return context.tree_builder_context->force_subtree_update_reasons ||
// PaintInvalidator forced subtree walk implies geometry update.
context.paint_invalidator_context.NeedsSubtreeWalk();
}
#if DCHECK_IS_ON()
void PrePaintTreeWalk::CheckTreeBuilderContextState(
const LayoutObject& object,
const PrePaintTreeWalkContext& parent_context) {
if (parent_context.tree_builder_context ||
(!ObjectRequiresTreeBuilderContext(object) &&
!ContextRequiresChildTreeBuilderContext(parent_context))) {
return;
}
DCHECK(!object.NeedsPaintPropertyUpdate());
DCHECK(!object.DescendantNeedsPaintPropertyUpdate());
DCHECK(!object.DescendantShouldCheckGeometryForPaintInvalidation());
DCHECK(!object.ShouldCheckGeometryForPaintInvalidation());
NOTREACHED() << "Unknown reason.";
}
#endif
static LayoutBoxModelObject* ContainerForPaintInvalidation(
const PaintLayer* painting_layer) {
if (!painting_layer)
return nullptr;
if (auto* containing_paint_layer =
painting_layer
->EnclosingLayerForPaintInvalidationCrossingFrameBoundaries())
return &containing_paint_layer->GetLayoutObject();
return nullptr;
}
void PrePaintTreeWalk::UpdatePaintInvalidationContainer(
const LayoutObject& object,
const PaintLayer* painting_layer,
PrePaintTreeWalkContext& context,
bool is_ng_painting) {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
DisableCompositingQueryAsserts disabler;
if (object.IsPaintInvalidationContainer()) {
context.paint_invalidation_container = To<LayoutBoxModelObject>(&object);
if (object.IsStackingContext() || object.IsSVGRoot()) {
context.paint_invalidation_container_for_stacked_contents =
To<LayoutBoxModelObject>(&object);
}
} else if (IsA<LayoutView>(object)) {
// paint_invalidation_container_for_stacked_contents is only for stacked
// descendants in its own frame, because it doesn't establish stacking
// context for stacked contents in sub-frames.
// Contents stacked in the root stacking context in this frame should use
// this frame's PaintInvalidationContainer.
context.paint_invalidation_container_for_stacked_contents =
ContainerForPaintInvalidation(painting_layer);
} else if (!is_ng_painting &&
(object.IsColumnSpanAll() ||
object.IsFloatingWithNonContainingBlockParent())) {
// In these cases, the object may belong to an ancestor of the current
// paint invalidation container, in paint order.
// Post LayoutNG the |LayoutObject::IsFloatingWithNonContainingBlockParent|
// check can be removed as floats will be painted by the correct layer.
context.paint_invalidation_container =
ContainerForPaintInvalidation(painting_layer);
} else if (object.IsStacked() &&
// This is to exclude some objects (e.g. LayoutText) inheriting
// stacked style from parent but aren't actually stacked.
object.HasLayer() &&
!To<LayoutBoxModelObject>(object)
.Layer()
->IsReplacedNormalFlowStacking() &&
context.paint_invalidation_container !=
context.paint_invalidation_container_for_stacked_contents) {
// The current object is stacked, so we should use
// m_paintInvalidationContainerForStackedContents as its paint invalidation
// container on which the current object is painted.
context.paint_invalidation_container =
context.paint_invalidation_container_for_stacked_contents;
}
}
void PrePaintTreeWalk::WalkInternal(const LayoutObject& object,
const NGFragmentChildIterator* iterator,
PrePaintTreeWalkContext& context) {
PaintInvalidatorContext& paint_invalidator_context =
context.paint_invalidator_context;
base::Optional<NGPrePaintInfo> pre_paint_info_storage;
NGPrePaintInfo* pre_paint_info = nullptr;
if (iterator) {
bool allow_reset = context.NeedsTreeBuilderContext();
pre_paint_info_storage.emplace(SetupFragmentData(*iterator, allow_reset));
pre_paint_info = &pre_paint_info_storage.value();
}
// This must happen before updatePropertiesForSelf, because the latter reads
// some of the state computed here.
UpdateAuxiliaryObjectProperties(object, context);
base::Optional<PaintPropertyTreeBuilder> property_tree_builder;
PaintPropertyChangeType property_changed =
PaintPropertyChangeType::kUnchanged;
if (context.tree_builder_context) {
property_tree_builder.emplace(object, pre_paint_info,
*context.tree_builder_context);
property_changed =
std::max(property_changed, property_tree_builder->UpdateForSelf());
if ((property_changed > PaintPropertyChangeType::kUnchanged) &&
!context.tree_builder_context
->supports_composited_raster_invalidation) {
paint_invalidator_context.subtree_flags |=
PaintInvalidatorContext::kSubtreeFullInvalidation;
}
}
// This must happen before paint invalidation because background painting
// depends on the effective allowed touch action and blocking wheel event
// handlers.
UpdateEffectiveAllowedTouchAction(object, context);
if (is_wheel_event_regions_enabled_)
UpdateBlockingWheelEventHandler(object, context);
if (paint_invalidator_.InvalidatePaint(
object, pre_paint_info,
base::OptionalOrNullptr(context.tree_builder_context),
paint_invalidator_context))
needs_invalidate_chrome_client_ = true;
InvalidatePaintForHitTesting(object, context);
UpdatePaintInvalidationContainer(object,
paint_invalidator_context.painting_layer,
context, !!pre_paint_info);
if (context.tree_builder_context) {
property_changed =
std::max(property_changed, property_tree_builder->UpdateForChildren());
if (!RuntimeEnabledFeatures::CullRectUpdateEnabled() &&
context.tree_builder_context->clip_changed) {
// Save clip_changed flag in |context| so that all descendants will see it
// even if we don't create tree_builder_context.
context.clip_changed = true;
}
if (property_changed != PaintPropertyChangeType::kUnchanged) {
if (property_changed >
PaintPropertyChangeType::kChangedOnlyCompositedValues) {
object.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate();
}
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
if ((property_changed >
PaintPropertyChangeType::kChangedOnlyCompositedValues) &&
context.paint_invalidation_container) {
// Mark the previous paint invalidation container as needing
// raster invalidation. This handles cases where raster invalidation
// needs to happen but no compositing layers were added or removed.
DisableCompositingQueryAsserts disabler;
const auto* paint_invalidation_container =
context.paint_invalidation_container->Layer();
if (!paint_invalidation_container->SelfNeedsRepaint()) {
auto* mapping =
paint_invalidation_container->GetCompositedLayerMapping();
if (!mapping)
mapping = paint_invalidation_container->GroupedMapping();
if (mapping)
mapping->SetNeedsCheckRasterInvalidation();
}
SetNeedsCompositingLayerPropertyUpdate(object);
}
} else if (!context.tree_builder_context
->supports_composited_raster_invalidation) {
paint_invalidator_context.subtree_flags |=
PaintInvalidatorContext::kSubtreeFullInvalidation;
}
}
}
if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
if (property_changed != PaintPropertyChangeType::kUnchanged ||
// CullRectUpdater proactively update cull rect if the layer or
// descendant will repaint, but in pre-CAP the repaint flag stops
// propagation at compositing boundaries, while cull rect update
// ancestor flag should not stop at compositing boundaries.
(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
context.paint_invalidator_context.painting_layer
->SelfOrDescendantNeedsRepaint())) {
if (object.HasLayer()) {
To<LayoutBoxModelObject>(object).Layer()->SetNeedsCullRectUpdate();
} else if (object.SlowFirstChild()) {
// This ensures cull rect update of the child PaintLayers affected by
// the paint property change on a non-PaintLayer. Though this may
// unnecessarily force update of unrelated children, the situation is
// rare and this is much easier.
context.paint_invalidator_context.painting_layer
->SetForcesChildrenCullRectUpdate();
}
}
} else if (context.clip_changed && object.HasLayer()) {
// When this or ancestor clip changed, the layer needs repaint because it
// may paint more or less results according to the changed clip.
To<LayoutBoxModelObject>(object).Layer()->SetNeedsRepaint();
}
}
LocalFrameView* FindWebViewPluginContentFrameView(
const LayoutEmbeddedContent& embedded_content) {
for (Frame* frame = embedded_content.GetFrame()->Tree().FirstChild(); frame;
frame = frame->Tree().NextSibling()) {
if (frame->IsLocalFrame() &&
To<LocalFrame>(frame)->OwnerLayoutObject() == &embedded_content)
return To<LocalFrame>(frame)->View();
}
return nullptr;
}
void PrePaintTreeWalk::WalkNGChildren(const LayoutObject* parent,
NGFragmentChildIterator* iterator) {
FragmentData* fragmentainer_fragment_data = nullptr;
#if DCHECK_IS_ON()
const LayoutObject* fragmentainer_owner_box = nullptr;
#endif
for (; !iterator->IsAtEnd(); iterator->Advance()) {
const LayoutObject* object = (*iterator)->GetLayoutObject();
if (const auto* fragment_item = (*iterator)->FragmentItem()) {
// Line boxes are not interesting. They have no paint effects. Descend
// directly into children.
if (fragment_item->Type() == NGFragmentItem::kLine) {
WalkChildren(/* parent */ nullptr, iterator);
continue;
}
} else if (!object) {
const NGPhysicalBoxFragment* box_fragment = (*iterator)->BoxFragment();
if (UNLIKELY(box_fragment->IsLayoutObjectDestroyedOrMoved()))
continue;
// Check |box_fragment| and the |LayoutBox| that produced it are in sync.
// |OwnerLayoutBox()| has a few DCHECKs for this purpose.
DCHECK(box_fragment->OwnerLayoutBox());
// A fragmentainer doesn't paint anything itself. Just include its offset
// and descend into children.
DCHECK((*iterator)->BoxFragment()->IsFragmentainerBox());
if (UNLIKELY(!context_storage_.back().tree_builder_context)) {
WalkChildren(/* parent */ nullptr, iterator);
continue;
}
PaintPropertyTreeBuilderContext& tree_builder_context =
context_storage_.back().tree_builder_context.value();
PaintPropertyTreeBuilderFragmentContext& context =
tree_builder_context.fragments[0];
PaintPropertyTreeBuilderFragmentContext::ContainingBlockContext*
containing_block_context = &context.current;
const PhysicalOffset offset = (*iterator)->Link().offset;
containing_block_context->paint_offset += offset;
const PhysicalOffset paint_offset =
containing_block_context->paint_offset;
// Create corresponding |FragmentData|. Hit-testing needs
// |FragmentData.PaintOffset|.
if (fragmentainer_fragment_data) {
DCHECK(!box_fragment->IsFirstForNode());
#if DCHECK_IS_ON()
DCHECK_EQ(fragmentainer_owner_box, box_fragment->OwnerLayoutBox());
#endif
fragmentainer_fragment_data =
&fragmentainer_fragment_data->EnsureNextFragment();
} else {
const LayoutBox* owner_box = box_fragment->OwnerLayoutBox();
#if DCHECK_IS_ON()
DCHECK(!fragmentainer_owner_box);
fragmentainer_owner_box = owner_box;
#endif
fragmentainer_fragment_data =
&owner_box->GetMutableForPainting().FirstFragment();
if (box_fragment->IsFirstForNode()) {
fragmentainer_fragment_data->ClearNextFragment();
} else {
// |box_fragment| is nested in another fragmentainer, and that it is
// the first one in this loop, but not the first one for the
// |LayoutObject|. Append a new |FragmentData| to the last one.
fragmentainer_fragment_data =
&fragmentainer_fragment_data->LastFragment().EnsureNextFragment();
}
}
fragmentainer_fragment_data->SetPaintOffset(paint_offset);
WalkChildren(/* parent */ nullptr, iterator);
containing_block_context->paint_offset -= offset;
continue;
}
Walk(*object, iterator);
}
const LayoutBlockFlow* parent_block = DynamicTo<LayoutBlockFlow>(parent);
if (!parent_block || !parent_block->MultiColumnFlowThread())
return;
// Multicol containers only contain special legacy children invisible to
// LayoutNG, so we need to clean them manually.
for (const LayoutObject* child = parent->SlowFirstChild(); child;
child = child->NextSibling()) {
DCHECK(child->IsLayoutFlowThread() || child->IsLayoutMultiColumnSet() ||
child->IsLayoutMultiColumnSpannerPlaceholder());
child->GetMutableForPainting().ClearPaintFlags();
}
}
void PrePaintTreeWalk::WalkLegacyChildren(const LayoutObject& object) {
if (const auto* layout_box = DynamicTo<LayoutBox>(&object)) {
if (layout_box->CanTraversePhysicalFragments()) {
// Enter NG child fragment traversal. We'll stay in this mode for all
// descendants that support fragment traversal. We'll re-enter
// LayoutObject traversal for descendants that don't support it. This only
// works correctly if we're not block-fragmented, though, so DCHECK for
// that.
DCHECK_EQ(layout_box->PhysicalFragmentCount(), 1u);
const NGPhysicalBoxFragment& fragment =
To<NGPhysicalBoxFragment>(*layout_box->GetPhysicalFragment(0));
DCHECK(!fragment.BreakToken());
NGFragmentChildIterator child_iterator(fragment);
WalkNGChildren(&object, &child_iterator);
return;
}
}
if (UNLIKELY(object.IsFieldsetIncludingNG())) {
// Handle the rendered legend of the fieldset right away. It may not be a
// direct child in the layout object tree (there may be an anonymous
// fieldset content wrapper in-between, and even a flow thread), but it is
// to be treated as such (similarly to out-of-flow positioned elements in a
// way).
if (const LayoutBox* legend =
LayoutFieldset::FindInFlowLegend(To<LayoutBlock>(object)))
Walk(*legend, /* iterator */ nullptr);
}
for (const LayoutObject* child = object.SlowFirstChild(); child;
child = child->NextSibling()) {
if (child->IsLayoutMultiColumnSpannerPlaceholder()) {
child->GetMutableForPainting().ClearPaintFlags();
continue;
}
// Skip out-of-flow positioned children lest they be walked twice. If |this|
// is an NG object, but it still walks its children the legacy way (this may
// happen to table-cells; see LayoutObject::CanTraversePhysicalFragments()),
// we're either going to handle it in the code below after the loop - if
// |this| is actually the containing block. Otherwise it will be handled by
// some ancestor - either in the same code below (if it's a legacy-walking
// object) or via regular child fragment traversal. If we walk it here as
// well, we'd end up walking it twice. This is both bad for performance, and
// also correctness, as fragment items are sensitive to how they're walked
// (see SetupFragmentData()).
if (UNLIKELY(RuntimeEnabledFeatures::LayoutNGFragmentTraversalEnabled() &&
child->IsOutOfFlowPositioned() && object.IsLayoutNGObject()))
continue;
// The rendered legend was handled above, before processing the children of
// the fieldset. So skip it when found during normal child traversal.
if (UNLIKELY(child->IsRenderedLegend()))
continue;
Walk(*child, /* iterator */ nullptr);
}
if (!RuntimeEnabledFeatures::LayoutNGFragmentTraversalEnabled())
return;
const LayoutBlock* block = DynamicTo<LayoutBlock>(&object);
if (!block)
return;
const auto* positioned_objects = block->PositionedObjects();
if (!positioned_objects)
return;
// If we have performed NG fragment traversal in any part of the subtree, we
// may have missed certain out-of-flow positioned objects. LayoutNG fragments
// are always children of their containing block, while the structure of the
// LayoutObject tree corresponds more closely to that of the DOM tree.
//
// Example: if we assume that flexbox isn't natively supported in LayoutNG:
//
// <div id="flex" style="display:flex; position:relative;">
// <div id="flexitem">
// <div id="abs" style="position:absolute;"></div>
// <div id="regular"></div>
//
// If we let |object| be #flex, it will be handled by legacy LayoutObject
// traversal, while #flexitem, on the other hand, can traverse its NG child
// fragments. However, #regular will be the only child fragment of #flexitem,
// since the containing block for #abs is #flex. So we'd miss it, unless we
// walk it now.
for (const LayoutBox* box : *positioned_objects) {
// It's important that objects that have already been walked be left alone.
// Otherwise, we might calculate the wrong offsets (and overwrite the
// correct ones) in case of out-of-flow positioned objects whose containing
// block is a relatively positioned non-atomic inline (such objects will
// already have been properly walked, since we don't switch engines within
// an inline formatting context). Put differently, the code here will only
// do the right thing if |object| is truly the containing block of the
// positioned objects in its list (which isn't the case if the containing
// block is a non-atomic inline).
if (!ObjectRequiresPrePaint(*box) &&
!ObjectRequiresTreeBuilderContext(*box))
continue;
DCHECK_EQ(box->Container(), &object);
Walk(*box, /* iterator */ nullptr);
}
}
void PrePaintTreeWalk::WalkChildren(const LayoutObject* object,
const NGFragmentChildIterator* iterator) {
DCHECK(iterator || object);
if (!iterator) {
// We're not doing LayoutNG fragment traversal of this object.
WalkLegacyChildren(*object);
return;
}
// If we are performing LayoutNG fragment traversal, but this object doesn't
// support that, we need to switch back to legacy LayoutObject traversal for
// its children. We're then also assuming that we're either not
// block-fragmenting, or that this is monolithic content. We may re-enter
// LayoutNG fragment traversal if we get to a descendant that supports that.
if (object && !object->CanTraversePhysicalFragments()) {
DCHECK(!object->FlowThreadContainingBlock() ||
(object->IsBox() &&
To<LayoutBox>(object)->GetNGPaginationBreakability() ==
LayoutBox::kForbidBreaks));
WalkLegacyChildren(*object);
return;
}
// Traverse child NG fragments.
NGFragmentChildIterator child_iterator(iterator->Descend());
WalkNGChildren(object, &child_iterator);
}
void PrePaintTreeWalk::Walk(const LayoutObject& object,
const NGFragmentChildIterator* iterator) {
const NGPhysicalBoxFragment* physical_fragment = nullptr;
bool is_last_fragment = true;
if (iterator) {
physical_fragment = (*iterator)->BoxFragment();
if (const auto* fragment_item = (*iterator)->FragmentItem())
is_last_fragment = fragment_item->IsLastForNode();
else if (physical_fragment)
is_last_fragment = !physical_fragment->BreakToken();
}
// We need to be careful not to have a reference to the parent context, since
// this reference will be to the context_storage_ memory which may be
// reallocated during this function call.
wtf_size_t parent_context_index = context_storage_.size() - 1;
auto parent_context = [this,
parent_context_index]() -> PrePaintTreeWalkContext& {
return context_storage_[parent_context_index];
};
bool needs_tree_builder_context_update =
ContextRequiresChildTreeBuilderContext(parent_context()) ||
ObjectRequiresTreeBuilderContext(object);
#if DCHECK_IS_ON()
CheckTreeBuilderContextState(object, parent_context());
#endif
// Early out from the tree walk if possible.
if (!needs_tree_builder_context_update && !ObjectRequiresPrePaint(object) &&
!ContextRequiresChildPrePaint(parent_context())) {
return;
}
// Note that because we're emplacing an object constructed from
// parent_context() (which is a reference to the vector itself), it's
// important to first ensure that there's sufficient capacity in the vector.
// Otherwise, it may reallocate causing parent_context() to point to invalid
// memory.
ResizeContextStorageIfNeeded();
context_storage_.emplace_back(parent_context(),
PaintInvalidatorContext::ParentContextAccessor(
this, parent_context_index),
needs_tree_builder_context_update);
auto context = [this]() -> PrePaintTreeWalkContext& {
return context_storage_.back();
};
if (object.StyleRef().HasTransform()) {
// Ignore clip changes from ancestor across transform boundaries.
context().clip_changed = false;
if (context().tree_builder_context)
context().tree_builder_context->clip_changed = false;
}
WalkInternal(object, iterator, context());
bool child_walk_blocked = object.ChildPrePaintBlockedByDisplayLock();
// If we need a subtree walk due to context flags, we need to store that
// information on the display lock, since subsequent walks might not set the
// same bits on the context.
if (child_walk_blocked &&
(ContextRequiresChildTreeBuilderContext(context()) ||
ContextRequiresChildPrePaint(context()))) {
// Note that |effective_allowed_touch_action_changed| and
// |blocking_wheel_event_handler_changed| are special in that they requires
// us to specifically recalculate this value on each subtree element. Other
// flags simply need a subtree walk. Some consideration needs to be given to
// |clip_changed| which ensures that we repaint every layer, but for the
// purposes of PrePaint, this flag is just forcing a subtree walk.
object.GetDisplayLockContext()->SetNeedsPrePaintSubtreeWalk(
context().effective_allowed_touch_action_changed,
context().blocking_wheel_event_handler_changed);
}
if (!child_walk_blocked) {
WalkChildren(&object, iterator);
if (const auto* layout_embedded_content =
DynamicTo<LayoutEmbeddedContent>(object)) {
if (auto* embedded_view =
layout_embedded_content->GetEmbeddedContentView()) {
if (context().tree_builder_context) {
auto& current = context().tree_builder_context->fragments[0].current;
current.paint_offset = PhysicalOffset(RoundedIntPoint(
current.paint_offset +
layout_embedded_content->ReplacedContentRect().offset -
PhysicalOffset(embedded_view->FrameRect().Location())));
// Subpixel accumulation doesn't propagate across embedded view.
current.directly_composited_container_paint_offset_subpixel_delta =
PhysicalOffset();
}
if (embedded_view->IsLocalFrameView()) {
Walk(*To<LocalFrameView>(embedded_view));
} else if (embedded_view->IsPluginView()) {
// If it is a webview plugin, walk into the content frame view.
if (auto* plugin_content_frame_view =
FindWebViewPluginContentFrameView(*layout_embedded_content))
Walk(*plugin_content_frame_view);
} else {
// We need to do nothing for RemoteFrameView. See crbug.com/579281.
}
}
}
}
if (is_last_fragment)
object.GetMutableForPainting().ClearPaintFlags();
context_storage_.pop_back();
}
void PrePaintTreeWalk::ResizeContextStorageIfNeeded() {
if (UNLIKELY(context_storage_.size() == context_storage_.capacity())) {
DCHECK_GT(context_storage_.size(), 0u);
context_storage_.ReserveCapacity(context_storage_.size() * 2);
}
}
} // namespace blink