| // Copyright 2016 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/object_paint_invalidator.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/layout/layout_embedded_content.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/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/find_paint_offset_and_visual_rect_needing_update.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" |
| #include "third_party/blink/renderer/core/paint/paint_invalidator.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| |
| namespace blink { |
| |
| static bool g_disable_paint_invalidation_state_asserts = false; |
| |
| using LayoutObjectTraversalFunctor = std::function<void(const LayoutObject&)>; |
| |
| static void TraverseNonCompositingDescendantsInPaintOrder( |
| const LayoutObject&, |
| const LayoutObjectTraversalFunctor&); |
| |
| static void |
| TraverseNonCompositingDescendantsBelongingToAncestorPaintInvalidationContainer( |
| const LayoutObject& object, |
| const LayoutObjectTraversalFunctor& functor) { |
| // |object| is a paint invalidation container, but is not a stacking context |
| // or is a non-block, so the paint invalidation container of stacked |
| // descendants may not belong to |object| but belong to an ancestor. This |
| // function traverses all such descendants. See Case 1a and Case 2 below for |
| // details. |
| DCHECK(object.IsPaintInvalidationContainer() && |
| (!object.StyleRef().IsStackingContext() || !object.IsLayoutBlock())); |
| |
| LayoutObject* descendant = object.NextInPreOrder(&object); |
| while (descendant) { |
| if (!descendant->HasLayer() || !descendant->StyleRef().IsStacked()) { |
| // Case 1: The descendant is not stacked (or is stacked but has not been |
| // allocated a layer yet during style change), so either it's a paint |
| // invalidation container in the same situation as |object|, or its paint |
| // invalidation container is in such situation. Keep searching until a |
| // stacked layer is found. |
| if (!object.IsLayoutBlock() && descendant->IsFloating()) { |
| // Case 1a (rare): However, if the descendant is a floating object below |
| // a composited non-block object, the subtree may belong to an ancestor |
| // in paint order, thus recur into the subtree. Note that for |
| // performance, we don't check whether the floating object's container |
| // is above or under |object|, so we may traverse more than expected. |
| // Example: |
| // <span id="object" class="position: relative; will-change: transform"> |
| // <div id="descendant" class="float: left"></div>" |
| // </span> |
| TraverseNonCompositingDescendantsInPaintOrder(*descendant, functor); |
| descendant = descendant->NextInPreOrderAfterChildren(&object); |
| } else { |
| descendant = descendant->NextInPreOrder(&object); |
| } |
| } else if (!descendant->IsPaintInvalidationContainer()) { |
| // Case 2: The descendant is stacked and is not composited. |
| // The invalidation container of its subtree is our ancestor, |
| // thus recur into the subtree. |
| TraverseNonCompositingDescendantsInPaintOrder(*descendant, functor); |
| descendant = descendant->NextInPreOrderAfterChildren(&object); |
| } else if (descendant->StyleRef().IsStackingContext() && |
| descendant->IsLayoutBlock()) { |
| // Case 3: The descendant is an invalidation container and is a stacking |
| // context. No objects in the subtree can have invalidation container |
| // outside of it, thus skip the whole subtree. |
| // This excludes non-block because there might be floating objects under |
| // the descendant belonging to some ancestor in paint order (Case 1a). |
| descendant = descendant->NextInPreOrderAfterChildren(&object); |
| } else { |
| // Case 4: The descendant is an invalidation container but not a stacking |
| // context, or the descendant is a non-block stacking context. |
| // This is the same situation as |object|, thus keep searching. |
| descendant = descendant->NextInPreOrder(&object); |
| } |
| } |
| } |
| |
| static void TraverseNonCompositingDescendantsInPaintOrder( |
| const LayoutObject& object, |
| const LayoutObjectTraversalFunctor& functor) { |
| functor(object); |
| LayoutObject* descendant = object.NextInPreOrder(&object); |
| while (descendant) { |
| if (!descendant->IsPaintInvalidationContainer()) { |
| functor(*descendant); |
| descendant = descendant->NextInPreOrder(&object); |
| } else if (descendant->StyleRef().IsStackingContext() && |
| descendant->IsLayoutBlock()) { |
| // The descendant is an invalidation container and is a stacking context. |
| // No objects in the subtree can have invalidation container outside of |
| // it, thus skip the whole subtree. |
| // This excludes non-blocks because there might be floating objects under |
| // the descendant belonging to some ancestor in paint order (Case 1a). |
| descendant = descendant->NextInPreOrderAfterChildren(&object); |
| } else { |
| // If a paint invalidation container is not a stacking context, or the |
| // descendant is a non-block stacking context, some of its descendants may |
| // belong to the parent container. |
| TraverseNonCompositingDescendantsBelongingToAncestorPaintInvalidationContainer( |
| *descendant, functor); |
| descendant = descendant->NextInPreOrderAfterChildren(&object); |
| } |
| } |
| } |
| |
| static void SetPaintingLayerNeedsRepaintDuringTraverse( |
| const LayoutObject& object) { |
| if (object.HasLayer() && |
| ToLayoutBoxModelObject(object).HasSelfPaintingLayer()) { |
| ToLayoutBoxModelObject(object).Layer()->SetNeedsRepaint(); |
| } else if (object.IsFloating() && object.Parent() && |
| !object.Parent()->IsLayoutBlock()) { |
| object.PaintingLayer()->SetNeedsRepaint(); |
| } |
| } |
| |
| void ObjectPaintInvalidator:: |
| InvalidateDisplayItemClientsIncludingNonCompositingDescendants( |
| PaintInvalidationReason reason) { |
| // This is valid because we want to invalidate the client in the display item |
| // list of the current backing. |
| DisableCompositingQueryAsserts disabler; |
| |
| SlowSetPaintingLayerNeedsRepaint(); |
| TraverseNonCompositingDescendantsInPaintOrder( |
| object_, [reason](const LayoutObject& object) { |
| SetPaintingLayerNeedsRepaintDuringTraverse(object); |
| object.InvalidateDisplayItemClients(reason); |
| }); |
| } |
| |
| DISABLE_CFI_PERF |
| void ObjectPaintInvalidator::InvalidatePaintOfPreviousVisualRect( |
| const LayoutBoxModelObject& paint_invalidation_container, |
| PaintInvalidationReason reason) { |
| // It's caller's responsibility to ensure enclosingSelfPaintingLayer's |
| // needsRepaint is set. Don't set the flag here because getting |
| // enclosingSelfPaintLayer has cost and the caller can use various ways (e.g. |
| // PaintInvalidatinState::enclosingSelfPaintingLayer()) to reduce the cost. |
| DCHECK(!object_.PaintingLayer() || object_.PaintingLayer()->NeedsRepaint()); |
| |
| // For SPv175, raster invalidation will be issued after painting. |
| if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) { |
| // These disablers are valid because we want to use the current |
| // compositing/invalidation status. |
| DisablePaintInvalidationStateAsserts invalidation_disabler; |
| DisableCompositingQueryAsserts compositing_disabler; |
| |
| LayoutRect invalidation_rect = object_.FragmentsVisualRectBoundingBox(); |
| InvalidatePaintUsingContainer(paint_invalidation_container, |
| invalidation_rect, reason); |
| object_.InvalidateDisplayItemClients(reason); |
| } |
| |
| // This method may be used to invalidate paint of an object changing paint |
| // invalidation container. Clear previous visual rect on the original paint |
| // invalidation container to avoid under-invalidation if the visual rect on |
| // the new paint invalidation container happens to be the same as the old one. |
| object_.GetMutableForPainting().ClearPreviousVisualRects(); |
| } |
| |
| void ObjectPaintInvalidator:: |
| InvalidatePaintIncludingNonCompositingDescendants() { |
| DCHECK(!RuntimeEnabledFeatures::SlimmingPaintV2Enabled()); |
| // Since we're only painting non-composited layers, we know that they all |
| // share the same paintInvalidationContainer. |
| const LayoutBoxModelObject& paint_invalidation_container = |
| object_.ContainerForPaintInvalidation(); |
| SlowSetPaintingLayerNeedsRepaint(); |
| TraverseNonCompositingDescendantsInPaintOrder( |
| object_, [&paint_invalidation_container](const LayoutObject& object) { |
| SetPaintingLayerNeedsRepaintDuringTraverse(object); |
| ObjectPaintInvalidator(object).InvalidatePaintOfPreviousVisualRect( |
| paint_invalidation_container, PaintInvalidationReason::kSubtree); |
| }); |
| } |
| |
| void ObjectPaintInvalidator:: |
| InvalidatePaintIncludingNonSelfPaintingLayerDescendantsInternal( |
| const LayoutBoxModelObject& paint_invalidation_container) { |
| InvalidatePaintOfPreviousVisualRect(paint_invalidation_container, |
| PaintInvalidationReason::kSubtree); |
| for (LayoutObject* child = object_.SlowFirstChild(); child; |
| child = child->NextSibling()) { |
| if (!child->HasLayer() || |
| !ToLayoutBoxModelObject(child)->Layer()->IsSelfPaintingLayer()) |
| ObjectPaintInvalidator(*child) |
| .InvalidatePaintIncludingNonSelfPaintingLayerDescendantsInternal( |
| paint_invalidation_container); |
| } |
| } |
| |
| void ObjectPaintInvalidator:: |
| InvalidatePaintIncludingNonSelfPaintingLayerDescendants( |
| const LayoutBoxModelObject& paint_invalidation_container) { |
| SlowSetPaintingLayerNeedsRepaint(); |
| InvalidatePaintIncludingNonSelfPaintingLayerDescendantsInternal( |
| paint_invalidation_container); |
| } |
| |
| void ObjectPaintInvalidator::InvalidateDisplayItemClient( |
| const DisplayItemClient& client, |
| PaintInvalidationReason reason) { |
| // It's caller's responsibility to ensure enclosingSelfPaintingLayer's |
| // needsRepaint is set. Don't set the flag here because getting |
| // enclosingSelfPaintLayer has cost and the caller can use various ways (e.g. |
| // PaintInvalidatinState::enclosingSelfPaintingLayer()) to reduce the cost. |
| DCHECK(!object_.PaintingLayer() || object_.PaintingLayer()->NeedsRepaint()); |
| |
| if (&client == &object_) { |
| TRACE_EVENT_INSTANT1( |
| TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"), |
| "PaintInvalidationTracking", TRACE_EVENT_SCOPE_THREAD, "data", |
| InspectorPaintInvalidationTrackingEvent::Data(object_)); |
| } |
| |
| client.SetDisplayItemsUncached(reason); |
| |
| if (LocalFrameView* frame_view = object_.GetFrameView()) |
| frame_view->TrackObjectPaintInvalidation(client, reason); |
| } |
| |
| template <typename T> |
| void AddJsonObjectForRect(TracedValue* value, const char* name, const T& rect) { |
| value->BeginDictionary(name); |
| value->SetDouble("x", rect.X()); |
| value->SetDouble("y", rect.Y()); |
| value->SetDouble("width", rect.Width()); |
| value->SetDouble("height", rect.Height()); |
| value->EndDictionary(); |
| } |
| |
| static std::unique_ptr<TracedValue> JsonObjectForPaintInvalidationInfo( |
| const LayoutRect& rect, |
| const String& invalidation_reason) { |
| std::unique_ptr<TracedValue> value = TracedValue::Create(); |
| AddJsonObjectForRect(value.get(), "rect", rect); |
| value->SetString("invalidation_reason", invalidation_reason); |
| return value; |
| } |
| |
| static void InvalidatePaintRectangleOnWindow( |
| const LayoutBoxModelObject& paint_invalidation_container, |
| const IntRect& dirty_rect) { |
| LocalFrameView* frame_view = paint_invalidation_container.GetFrameView(); |
| DCHECK(paint_invalidation_container.IsLayoutView() && |
| paint_invalidation_container.Layer()->GetCompositingState() == |
| kNotComposited); |
| |
| if (!frame_view) |
| return; |
| |
| if (paint_invalidation_container.GetDocument().Printing() && |
| !RuntimeEnabledFeatures::PrintBrowserEnabled()) |
| return; |
| |
| DCHECK(!frame_view->GetFrame().OwnerLayoutObject()); |
| |
| IntRect paint_rect = dirty_rect; |
| paint_rect.Intersect(frame_view->VisibleContentRect()); |
| if (paint_rect.IsEmpty()) |
| return; |
| |
| if (ChromeClient* client = frame_view->GetChromeClient()) |
| client->InvalidateRect(frame_view->ConvertToRootFrame(paint_rect)); |
| } |
| |
| void ObjectPaintInvalidator::SetBackingNeedsPaintInvalidationInRect( |
| const LayoutBoxModelObject& paint_invalidation_container, |
| const LayoutRect& rect, |
| PaintInvalidationReason reason) { |
| DCHECK(!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()); |
| // https://bugs.webkit.org/show_bug.cgi?id=61159 describes an unreproducible |
| // crash here, so assert but check that the layer is composited. |
| DCHECK(paint_invalidation_container.GetCompositingState() != kNotComposited); |
| |
| PaintLayer& layer = *paint_invalidation_container.Layer(); |
| if (layer.GroupedMapping()) { |
| if (GraphicsLayer* squashing_layer = |
| layer.GroupedMapping()->SquashingLayer()) { |
| // Note: the subpixel accumulation of layer() does not need to be added |
| // here. It is already taken into account. |
| squashing_layer->SetNeedsDisplayInRect(EnclosingIntRect(rect), reason, |
| object_); |
| } |
| } else if (paint_invalidation_container.UsesCompositedScrolling()) { |
| // If object_ is not paint_invalidation_container, then it scrolls. |
| if (&object_ != paint_invalidation_container || |
| reason == |
| PaintInvalidationReason::kBackgroundOnScrollingContentsLayer || |
| reason == PaintInvalidationReason::kCaret) { |
| layer.GetCompositedLayerMapping()->SetScrollingContentsNeedDisplayInRect( |
| rect, reason, object_); |
| } else { |
| layer.GetCompositedLayerMapping() |
| ->SetNonScrollingContentsNeedDisplayInRect(rect, reason, object_); |
| } |
| } else { |
| // Otherwise invalidate everything. |
| layer.GetCompositedLayerMapping()->SetContentsNeedDisplayInRect( |
| rect, reason, object_); |
| } |
| } |
| |
| void ObjectPaintInvalidator::InvalidatePaintUsingContainer( |
| const LayoutBoxModelObject& paint_invalidation_container, |
| const LayoutRect& dirty_rect, |
| PaintInvalidationReason invalidation_reason) { |
| DCHECK(!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()); |
| |
| if (paint_invalidation_container.GetFrameView()->ShouldThrottleRendering()) |
| return; |
| |
| DCHECK(g_disable_paint_invalidation_state_asserts || |
| object_.GetDocument().Lifecycle().GetState() == |
| DocumentLifecycle::kInPrePaint); |
| |
| if (dirty_rect.IsEmpty()) |
| return; |
| |
| CHECK(object_.IsRooted()); |
| |
| TRACE_EVENT2( |
| TRACE_DISABLED_BY_DEFAULT("blink.invalidation"), |
| "LayoutObject::invalidatePaintUsingContainer()", "object", |
| object_.DebugName().Ascii(), "info", |
| JsonObjectForPaintInvalidationInfo( |
| dirty_rect, PaintInvalidationReasonToString(invalidation_reason))); |
| |
| // This conditional handles situations where non-rooted (and hence |
| // non-composited) frames are painted, such as SVG images. |
| if (!paint_invalidation_container.IsPaintInvalidationContainer()) { |
| InvalidatePaintRectangleOnWindow(paint_invalidation_container, |
| EnclosingIntRect(dirty_rect)); |
| } else { |
| SetBackingNeedsPaintInvalidationInRect(paint_invalidation_container, |
| dirty_rect, invalidation_reason); |
| } |
| } |
| |
| void ObjectPaintInvalidator::SlowSetPaintingLayerNeedsRepaint() { |
| if (PaintLayer* painting_layer = object_.PaintingLayer()) |
| painting_layer->SetNeedsRepaint(); |
| } |
| |
| void ObjectPaintInvalidatorWithContext::FullyInvalidatePaint( |
| PaintInvalidationReason reason, |
| const LayoutRect& old_visual_rect, |
| const LayoutRect& new_visual_rect) { |
| DCHECK(!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()); |
| // The following logic avoids invalidating twice if one set of bounds contains |
| // the other. |
| if (!new_visual_rect.Contains(old_visual_rect)) { |
| LayoutRect invalidation_rect = old_visual_rect; |
| InvalidatePaintRectangleWithContext(invalidation_rect, reason); |
| |
| if (invalidation_rect.Contains(new_visual_rect)) |
| return; |
| } |
| |
| InvalidatePaintRectangleWithContext(new_visual_rect, reason); |
| } |
| |
| bool ObjectPaintInvalidatorWithContext::ParentFullyInvalidatedOnSameBacking() { |
| if (!object_.Parent() || !context_.ParentContext()) |
| return false; |
| |
| if (!IsImmediateFullPaintInvalidationReason( |
| object_.Parent()->FullPaintInvalidationReason())) |
| return false; |
| |
| // Parent and child should have the same paint invalidation container. |
| if (context_.ParentContext()->paint_invalidation_container != |
| context_.paint_invalidation_container) |
| return false; |
| |
| // Both parent and child are contents of the paint invalidation container, |
| // so they are on the same backing. |
| if (object_.Parent() != context_.paint_invalidation_container) |
| return true; |
| |
| // If the paint invalidation container (i.e. parent) uses composited |
| // scrolling, parent and child might be on different backing (scrolling |
| // container vs scrolling contents). |
| return !context_.paint_invalidation_container->UsesCompositedScrolling(); |
| } |
| |
| void ObjectPaintInvalidatorWithContext::InvalidatePaintRectangleWithContext( |
| const LayoutRect& rect, |
| PaintInvalidationReason reason) { |
| if (rect.IsEmpty()) |
| return; |
| |
| base::Optional<ScopedSetNeedsDisplayInRectForTrackingOnly> scope; |
| // If the parent has fully invalidated and its visual rect covers this object |
| // on the same backing, skip the invalidation. |
| if (ParentFullyInvalidatedOnSameBacking() && |
| (context_.ParentContext()->old_visual_rect.Contains(rect) || |
| object_.Parent()->FirstFragment().VisualRect().Contains(rect))) { |
| if (!object_.GetFrameView()->IsTrackingPaintInvalidations()) |
| return; |
| // If we are tracking paint invalidations (e.g. when running a text-based- |
| // repaint layout test), still track the rectangle but the rectangle |
| // won't affect any other functionality including raster-under-invalidation |
| // checking. This is to reduce differences between layout test results of |
| // SPv1 and SPv2, to reduce rebaselines and chance of errors. |
| scope.emplace(); |
| } |
| |
| InvalidatePaintUsingContainer(*context_.paint_invalidation_container, rect, |
| reason); |
| } |
| |
| DISABLE_CFI_PERF |
| PaintInvalidationReason |
| ObjectPaintInvalidatorWithContext::ComputePaintInvalidationReason() { |
| // This is before any early return to ensure the background obscuration status |
| // is saved. |
| bool background_obscuration_changed = false; |
| bool background_obscured = object_.BackgroundIsKnownToBeObscured(); |
| if (background_obscured != object_.PreviousBackgroundObscured()) { |
| object_.GetMutableForPainting().SetPreviousBackgroundObscured( |
| background_obscured); |
| background_obscuration_changed = true; |
| } |
| |
| if (!object_.ShouldCheckForPaintInvalidation() && |
| (!context_.subtree_flags || |
| context_.subtree_flags == |
| PaintInvalidatorContext::kSubtreeVisualRectUpdate)) { |
| // No paint invalidation flag, or just kSubtreeVisualRectUpdate (which has |
| // been handled in PaintInvalidator). No paint invalidation is needed. |
| DCHECK(!background_obscuration_changed); |
| return PaintInvalidationReason::kNone; |
| } |
| |
| if (context_.subtree_flags & |
| PaintInvalidatorContext::kSubtreeFullInvalidation) |
| return PaintInvalidationReason::kSubtree; |
| |
| if (object_.ShouldDoFullPaintInvalidation()) |
| return object_.FullPaintInvalidationReason(); |
| |
| if (!(context_.subtree_flags & |
| PaintInvalidatorContext::kInvalidateEmptyVisualRect) && |
| context_.old_visual_rect.IsEmpty() && |
| context_.fragment_data->VisualRect().IsEmpty()) |
| return PaintInvalidationReason::kNone; |
| |
| if (background_obscuration_changed) |
| return PaintInvalidationReason::kBackground; |
| |
| if (object_.PaintedOutputOfObjectHasNoEffectRegardlessOfSize()) |
| return PaintInvalidationReason::kNone; |
| |
| // Force full paint invalidation if the outline may be affected by descendants |
| // and this object is marked for checking paint invalidation for any reason. |
| if (object_.OutlineMayBeAffectedByDescendants() || |
| object_.PreviousOutlineMayBeAffectedByDescendants()) { |
| object_.GetMutableForPainting() |
| .UpdatePreviousOutlineMayBeAffectedByDescendants(); |
| return PaintInvalidationReason::kOutline; |
| } |
| |
| // If the size is zero on one of our bounds then we know we're going to have |
| // to do a full invalidation of either old bounds or new bounds. |
| if (context_.old_visual_rect.IsEmpty()) |
| return PaintInvalidationReason::kAppeared; |
| if (context_.fragment_data->VisualRect().IsEmpty()) |
| return PaintInvalidationReason::kDisappeared; |
| |
| // If we shifted, we don't know the exact reason so we are conservative and |
| // trigger a full invalidation. Shifting could be caused by some layout |
| // property (left / top) or some in-flow layoutObject inserted / removed |
| // before us in the tree. |
| if (context_.fragment_data->VisualRect().Location() != |
| context_.old_visual_rect.Location()) |
| return PaintInvalidationReason::kGeometry; |
| |
| if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled() && |
| context_.fragment_data->LocationInBacking() != context_.old_location) |
| return PaintInvalidationReason::kGeometry; |
| |
| // Incremental invalidation is only applicable to LayoutBoxes. Return |
| // PaintInvalidationIncremental no matter if oldVisualRect and newVisualRect |
| // are equal because a LayoutBox may need paint invalidation if its border box |
| // changes. BoxPaintInvalidator may also override this reason with a full |
| // paint invalidation reason if needed. |
| if (object_.IsBox()) |
| return PaintInvalidationReason::kIncremental; |
| |
| if (context_.old_visual_rect != context_.fragment_data->VisualRect()) |
| return PaintInvalidationReason::kGeometry; |
| |
| return PaintInvalidationReason::kNone; |
| } |
| |
| DISABLE_CFI_PERF |
| void ObjectPaintInvalidatorWithContext::InvalidateSelection( |
| PaintInvalidationReason reason) { |
| // In LayoutNG, if NGPaintFragment paints the selection, we invalidate for |
| // selection change in PaintInvalidator. |
| if (RuntimeEnabledFeatures::LayoutNGEnabled() && object_.IsInline() && |
| // LayoutReplaced still paints selection tint by itself. |
| !object_.IsLayoutReplaced() && |
| NGPaintFragment::InlineFragmentsFor(&object_) |
| .IsInLayoutNGInlineFormattingContext()) |
| return; |
| |
| // Update selection rect when we are doing full invalidation with geometry |
| // change (in case that the object is moved, composite status changed, etc.) |
| // or shouldInvalidationSelection is set (in case that the selection itself |
| // changed). |
| bool full_invalidation = IsImmediateFullPaintInvalidationReason(reason); |
| if (!full_invalidation && !object_.ShouldInvalidateSelection()) |
| return; |
| |
| LayoutRect old_selection_rect = object_.SelectionVisualRect(); |
| LayoutRect new_selection_rect; |
| #if DCHECK_IS_ON() |
| FindVisualRectNeedingUpdateScope finder(object_, context_, old_selection_rect, |
| new_selection_rect); |
| #endif |
| if (context_.NeedsVisualRectUpdate(object_)) { |
| new_selection_rect = object_.LocalSelectionRect(); |
| context_.MapLocalRectToVisualRectInBacking(object_, new_selection_rect); |
| } else { |
| new_selection_rect = old_selection_rect; |
| } |
| |
| object_.GetMutableForPainting().SetSelectionVisualRect(new_selection_rect); |
| |
| if (full_invalidation) |
| return; |
| |
| if (RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) { |
| // PaintController will handle raster invalidation of the partial rect. |
| object_.GetMutableForPainting().SetPartialInvalidationVisualRect( |
| UnionRect(object_.PartialInvalidationVisualRect(), |
| UnionRect(new_selection_rect, old_selection_rect))); |
| } else { |
| FullyInvalidatePaint(PaintInvalidationReason::kSelection, |
| old_selection_rect, new_selection_rect); |
| } |
| context_.painting_layer->SetNeedsRepaint(); |
| object_.InvalidateDisplayItemClients(PaintInvalidationReason::kSelection); |
| } |
| |
| DISABLE_CFI_PERF |
| void ObjectPaintInvalidatorWithContext::InvalidatePartialRect( |
| PaintInvalidationReason reason) { |
| if (IsImmediateFullPaintInvalidationReason(reason)) |
| return; |
| |
| auto rect = object_.PartialInvalidationLocalRect(); |
| if (rect.IsEmpty()) |
| return; |
| |
| context_.MapLocalRectToVisualRectInBacking(object_, rect); |
| if (rect.IsEmpty()) |
| return; |
| |
| if (RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) { |
| // PaintController will handle raster invalidation of the partial rect. |
| object_.GetMutableForPainting().SetPartialInvalidationVisualRect( |
| UnionRect(object_.PartialInvalidationVisualRect(), rect)); |
| } else { |
| InvalidatePaintRectangleWithContext(rect, |
| PaintInvalidationReason::kRectangle); |
| } |
| |
| context_.painting_layer->SetNeedsRepaint(); |
| object_.InvalidateDisplayItemClients(PaintInvalidationReason::kRectangle); |
| } |
| |
| DISABLE_CFI_PERF |
| PaintInvalidationReason |
| ObjectPaintInvalidatorWithContext::InvalidatePaintWithComputedReason( |
| PaintInvalidationReason reason) { |
| DCHECK(!(context_.subtree_flags & |
| PaintInvalidatorContext::kSubtreeNoInvalidation)); |
| |
| // This is before InvalidateSelection before the latter will accumulate |
| // selection visual rects to the partial rect mapped in the former. |
| InvalidatePartialRect(reason); |
| |
| // We need to invalidate the selection before checking for whether we are |
| // doing a full invalidation. This is because we need to update the previous |
| // selection rect regardless. |
| InvalidateSelection(reason); |
| |
| switch (reason) { |
| case PaintInvalidationReason::kNone: |
| // There are corner cases that the display items need to be invalidated |
| // for paint offset mutation, but incurs no pixel difference (i.e. bounds |
| // stay the same) so no rect-based invalidation is issued. See |
| // crbug.com/508383 and crbug.com/515977. |
| if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled() && |
| (context_.subtree_flags & |
| PaintInvalidatorContext::kSubtreeInvalidationChecking) && |
| !object_.IsSVGChild()) { |
| // For SPv1, we conservatively assume the object changed paint offset |
| // except for non-root SVG whose paint offset is always zero. |
| reason = PaintInvalidationReason::kGeometry; |
| break; |
| } |
| |
| if (object_.IsSVG() && |
| (context_.subtree_flags & |
| PaintInvalidatorContext::kSubtreeSVGResourceChange)) { |
| reason = PaintInvalidationReason::kSVGResource; |
| break; |
| } |
| return PaintInvalidationReason::kNone; |
| case PaintInvalidationReason::kDelayedFull: |
| return PaintInvalidationReason::kDelayedFull; |
| default: |
| DCHECK(IsImmediateFullPaintInvalidationReason(reason)); |
| if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) { |
| // This allows descendants to know the computed reason if it's different |
| // from the original reason before paint invalidation. |
| object_.GetMutableForPainting() |
| .SetShouldDoFullPaintInvalidationWithoutGeometryChange(reason); |
| FullyInvalidatePaint(reason, context_.old_visual_rect, |
| context_.fragment_data->VisualRect()); |
| } |
| } |
| |
| context_.painting_layer->SetNeedsRepaint(); |
| object_.InvalidateDisplayItemClients(reason); |
| return reason; |
| } |
| |
| DisablePaintInvalidationStateAsserts::DisablePaintInvalidationStateAsserts() |
| : disabler_(&g_disable_paint_invalidation_state_asserts, true) {} |
| |
| } // namespace blink |