| // 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 "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_embedded_content.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/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/compositing_layer_property_updater.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.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 { |
| |
| 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(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(); |
| } |
| } |
| |
| Walk(root_frame_view); |
| paint_invalidator_.ProcessPendingDelayedPaintInvalidations(); |
| context_storage_.pop_back(); |
| |
| #if DCHECK_IS_ON() |
| if (needs_tree_builder_context_update) { |
| if (VLOG_IS_ON(2) && root_frame_view.GetLayoutView()) { |
| LOG(ERROR) << "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) { |
| if (frame_view.ShouldThrottleRendering()) { |
| // Skip the throttled frame. Will update it when it becomes unthrottled. |
| return; |
| } |
| |
| // 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()); |
| |
| // 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_overflow_paint_layer does not cross frame boundaries. |
| context().ancestor_overflow_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) { |
| LOG(ERROR) << "PrePaintTreeWalk::Walk(frame_view=" << &frame_view |
| << ")\nLayout tree:"; |
| showLayoutTree(view); |
| } |
| #endif |
| |
| Walk(*view); |
| #if DCHECK_IS_ON() |
| view->AssertSubtreeClearedPaintInvalidationFlags(); |
| #endif |
| } |
| |
| frame_view.GetLayoutShiftTracker().NotifyPrePaintFinished(); |
| context_storage_.pop_back(); |
| } |
| |
| bool PrePaintTreeWalk::NeedsEffectiveAllowedTouchActionUpdate( |
| const LayoutObject& object, |
| PrePaintTreeWalk::PrePaintTreeWalkContext& context) const { |
| return context.effective_allowed_touch_action_changed || |
| object.EffectiveAllowedTouchActionChanged() || |
| object.DescendantEffectiveAllowedTouchActionChanged(); |
| } |
| |
| namespace { |
| bool HasBlockingTouchEventHandler(const LocalFrame& frame, |
| EventTarget& target) { |
| if (!target.HasEventListeners()) |
| return false; |
| const auto& registry = frame.GetEventHandlerRegistry(); |
| 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); |
| } |
| |
| bool HasBlockingTouchEventHandler(const LayoutObject& object) { |
| if (object.IsLayoutView()) { |
| auto* frame = object.GetFrame(); |
| if (HasBlockingTouchEventHandler(*frame, *frame->DomWindow())) |
| 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 HasBlockingTouchEventHandler(*object.GetFrame(), *node); |
| } |
| } // 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::InvalidatePaintForHitTesting( |
| const LayoutObject& object, |
| PrePaintTreeWalk::PrePaintTreeWalkContext& context) { |
| if (context.paint_invalidator_context.subtree_flags & |
| PaintInvalidatorContext::kSubtreeNoInvalidation) |
| return; |
| |
| if (!context.effective_allowed_touch_action_changed) |
| return; |
| |
| context.paint_invalidator_context.painting_layer->SetNeedsRepaint(); |
| ObjectPaintInvalidator(object).InvalidateDisplayItemClient( |
| object, PaintInvalidationReason::kHitTest); |
| } |
| |
| void PrePaintTreeWalk::UpdateAuxiliaryObjectProperties( |
| const LayoutObject& object, |
| PrePaintTreeWalk::PrePaintTreeWalkContext& context) { |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| if (!object.HasLayer()) |
| return; |
| |
| PaintLayer* paint_layer = ToLayoutBoxModelObject(object).Layer(); |
| paint_layer->UpdateAncestorOverflowLayer( |
| context.ancestor_overflow_paint_layer); |
| |
| if (object.StyleRef().HasStickyConstrainedPosition()) { |
| paint_layer->GetLayoutObject().UpdateStickyPositionConstraints(); |
| |
| // Sticky position constraints and ancestor overflow scroller affect the |
| // sticky layer position, so we need to update it again here. |
| // TODO(flackr): This should be refactored in the future to be clearer (i.e. |
| // update layer position and ancestor inputs updates in the same walk). |
| paint_layer->UpdateLayerPosition(); |
| } |
| if (paint_layer->IsRootLayer() || object.HasOverflowClip()) |
| context.ancestor_overflow_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()) || |
| ContextRequiresTreeBuilderContext(context, |
| *frame_view.GetLayoutView())); |
| } |
| |
| bool PrePaintTreeWalk::ObjectRequiresPrePaint(const LayoutObject& object) { |
| return object.ShouldCheckForPaintInvalidation() || |
| object.EffectiveAllowedTouchActionChanged() || |
| object.DescendantEffectiveAllowedTouchActionChanged(); |
| } |
| |
| bool PrePaintTreeWalk::ContextRequiresPrePaint( |
| const PrePaintTreeWalkContext& context) { |
| return context.paint_invalidator_context.NeedsSubtreeWalk() || |
| context.effective_allowed_touch_action_changed || context.clip_changed; |
| } |
| |
| bool PrePaintTreeWalk::ObjectRequiresTreeBuilderContext( |
| const LayoutObject& object) { |
| return object.NeedsPaintPropertyUpdate() || |
| (!object.PrePaintBlockedByDisplayLock( |
| DisplayLockLifecycleTarget::kChildren) && |
| (object.DescendantNeedsPaintPropertyUpdate() || |
| object.DescendantNeedsPaintOffsetAndVisualRectUpdate())); |
| } |
| |
| bool PrePaintTreeWalk::ContextRequiresTreeBuilderContext( |
| const PrePaintTreeWalkContext& context, |
| const LayoutObject& object) { |
| return (context.tree_builder_context && |
| context.tree_builder_context->force_subtree_update_reasons) || |
| context.paint_invalidator_context.NeedsVisualRectUpdate(object); |
| } |
| |
| void PrePaintTreeWalk::CheckTreeBuilderContextState( |
| const LayoutObject& object, |
| const PrePaintTreeWalkContext& parent_context) { |
| if (parent_context.tree_builder_context || |
| (!ObjectRequiresTreeBuilderContext(object) && |
| !ContextRequiresTreeBuilderContext(parent_context, object))) { |
| return; |
| } |
| |
| CHECK(!object.NeedsPaintPropertyUpdate()); |
| CHECK(!object.DescendantNeedsPaintPropertyUpdate()); |
| CHECK(!object.DescendantNeedsPaintOffsetAndVisualRectUpdate()); |
| if (parent_context.paint_invalidator_context.NeedsVisualRectUpdate(object)) { |
| // Note that if paint_invalidator_context's NeedsVisualRectUpdate(object) is |
| // true, we definitely want to CHECK. However, we would also like to know |
| // the value of object.NeedsPaintOffsetAndVisualRectUpdate(), hence one of |
| // the two CHECKs below will definitely trigger, and depending on which one |
| // does we will know the value. |
| CHECK(object.NeedsPaintOffsetAndVisualRectUpdate()); |
| CHECK(!object.NeedsPaintOffsetAndVisualRectUpdate()); |
| } |
| CHECK(false) << "Unknown reason."; |
| } |
| |
| void PrePaintTreeWalk::WalkInternal(const LayoutObject& object, |
| PrePaintTreeWalkContext& context) { |
| PaintInvalidatorContext& paint_invalidator_context = |
| context.paint_invalidator_context; |
| |
| // 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, *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. |
| UpdateEffectiveAllowedTouchAction(object, context); |
| |
| if (paint_invalidator_.InvalidatePaint( |
| object, base::OptionalOrNullptr(context.tree_builder_context), |
| paint_invalidator_context)) |
| needs_invalidate_chrome_client_ = true; |
| |
| InvalidatePaintForHitTesting(object, context); |
| |
| if (context.tree_builder_context) { |
| property_changed = |
| std::max(property_changed, property_tree_builder->UpdateForChildren()); |
| |
| // Save clip_changed flag in |context| so that all descendants will see it |
| // even if we don't create tree_builder_context. |
| if (context.tree_builder_context->clip_changed) |
| 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) { |
| const auto* paint_invalidation_layer = |
| paint_invalidator_context.paint_invalidation_container->Layer(); |
| if (!paint_invalidation_layer->NeedsRepaint()) { |
| auto* mapping = |
| paint_invalidation_layer->GetCompositedLayerMapping(); |
| if (!mapping) |
| mapping = paint_invalidation_layer->GroupedMapping(); |
| if (mapping) |
| mapping->SetNeedsCheckRasterInvalidation(); |
| } |
| } |
| } else if (!context.tree_builder_context |
| ->supports_composited_raster_invalidation) { |
| paint_invalidator_context.subtree_flags |= |
| PaintInvalidatorContext::kSubtreeFullInvalidation; |
| } |
| } |
| } |
| |
| // When this or ancestor clip changed, the layer needs repaint because it |
| // may paint more or less results according to the changed clip. |
| if (context.clip_changed && object.HasLayer()) |
| ToLayoutBoxModelObject(object).Layer()->SetNeedsRepaint(); |
| |
| CompositingLayerPropertyUpdater::Update(object); |
| } |
| |
| void PrePaintTreeWalk::Walk(const LayoutObject& object) { |
| // 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 = |
| ContextRequiresTreeBuilderContext(parent_context(), object) || |
| ObjectRequiresTreeBuilderContext(object); |
| |
| // The following is for debugging crbug.com/974639. |
| CheckTreeBuilderContextState(object, parent_context()); |
| |
| // Early out from the tree walk if possible. |
| if (!needs_tree_builder_context_update && !ObjectRequiresPrePaint(object) && |
| !ContextRequiresPrePaint(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(); |
| }; |
| |
| // Ignore clip changes from ancestor across transform boundaries. |
| if (object.StyleRef().HasTransform()) { |
| context().clip_changed = false; |
| if (context().tree_builder_context) |
| context().tree_builder_context->clip_changed = false; |
| } |
| |
| WalkInternal(object, context()); |
| object.NotifyDisplayLockDidPrePaint(DisplayLockLifecycleTarget::kSelf); |
| |
| bool child_walk_blocked = object.PrePaintBlockedByDisplayLock( |
| DisplayLockLifecycleTarget::kChildren); |
| // 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 && |
| (ContextRequiresTreeBuilderContext(context(), object) || |
| ContextRequiresPrePaint(context()))) { |
| // Note that effective allowed touch action changed is special in that |
| // it 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); |
| } |
| |
| if (!child_walk_blocked) { |
| for (const LayoutObject* child = object.SlowFirstChild(); child; |
| child = child->NextSibling()) { |
| if (child->IsLayoutMultiColumnSpannerPlaceholder()) { |
| child->GetMutableForPainting().ClearPaintFlags(); |
| continue; |
| } |
| Walk(*child); |
| } |
| |
| if (object.IsLayoutEmbeddedContent()) { |
| const LayoutEmbeddedContent& layout_embedded_content = |
| ToLayoutEmbeddedContent(object); |
| FrameView* frame_view = layout_embedded_content.ChildFrameView(); |
| if (auto* local_frame_view = DynamicTo<LocalFrameView>(frame_view)) { |
| if (context().tree_builder_context) { |
| auto& offset = |
| context().tree_builder_context->fragments[0].current.paint_offset; |
| offset += layout_embedded_content.ReplacedContentRect().offset; |
| offset -= PhysicalOffset(local_frame_view->FrameRect().Location()); |
| offset = PhysicalOffset(RoundedIntPoint(offset)); |
| } |
| Walk(*local_frame_view); |
| } |
| // TODO(pdr): Investigate RemoteFrameView (crbug.com/579281). |
| } |
| |
| object.NotifyDisplayLockDidPrePaint(DisplayLockLifecycleTarget::kChildren); |
| } |
| |
| 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 |