| // 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 |