| // 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 "core/paint/ObjectPaintInvalidator.h" |
| |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/layout/LayoutBlockFlow.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/api/LayoutPartItem.h" |
| #include "core/layout/compositing/CompositedLayerMapping.h" |
| #include "core/paint/PaintInvalidator.h" |
| #include "core/paint/PaintLayer.h" |
| #include "platform/HostWindow.h" |
| #include "platform/graphics/GraphicsLayer.h" |
| |
| namespace blink { |
| |
| static bool gDisablePaintInvalidationStateAsserts = false; |
| |
| typedef HashMap<const LayoutObject*, LayoutRect> SelectionPaintInvalidationMap; |
| static SelectionPaintInvalidationMap& selectionPaintInvalidationMap() { |
| DEFINE_STATIC_LOCAL(SelectionPaintInvalidationMap, map, ()); |
| return map; |
| } |
| |
| static void setPreviousSelectionPaintInvalidationRect( |
| const LayoutObject& object, |
| const LayoutRect& rect) { |
| if (rect.isEmpty()) |
| selectionPaintInvalidationMap().remove(&object); |
| else |
| selectionPaintInvalidationMap().set(&object, rect); |
| } |
| |
| void ObjectPaintInvalidator::objectWillBeDestroyed(const LayoutObject& object) { |
| selectionPaintInvalidationMap().remove(&object); |
| } |
| |
| // TODO(trchen): Use std::function<void, LayoutObject&> when available. |
| template <typename LayoutObjectTraversalFunctor> |
| void traverseNonCompositingDescendantsInPaintOrder( |
| const LayoutObject&, |
| const LayoutObjectTraversalFunctor&); |
| |
| template <typename LayoutObjectTraversalFunctor> |
| void traverseNonCompositingDescendantsBelongingToAncestorPaintInvalidationContainer( |
| const LayoutObject& object, |
| const LayoutObjectTraversalFunctor& functor) { |
| // |object| is a paint invalidation container but is not a stacking context, |
| // so the paint invalidation container of stacked descendants don't belong to |
| // |object| but belong to an ancestor. This function traverses all such |
| // descendants. |
| DCHECK(object.isPaintInvalidationContainer() && |
| !object.styleRef().isStackingContext()); |
| |
| 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. |
| 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()) { |
| // 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. |
| descendant = descendant->nextInPreOrderAfterChildren(&object); |
| } else { |
| // Case 4: The descendant is an invalidation container but not a stacking |
| // context. This is the same situation as |object|, thus keep searching. |
| descendant = descendant->nextInPreOrder(&object); |
| } |
| } |
| } |
| |
| template <typename LayoutObjectTraversalFunctor> |
| 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()) { |
| // 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. |
| descendant = descendant->nextInPreOrderAfterChildren(&object); |
| } else { |
| // If a paint invalidation container is not a stacking context, |
| // some of its descendants may belong to the parent container. |
| traverseNonCompositingDescendantsBelongingToAncestorPaintInvalidationContainer( |
| *descendant, functor); |
| descendant = descendant->nextInPreOrderAfterChildren(&object); |
| } |
| } |
| } |
| |
| 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( |
| m_object, [reason](const LayoutObject& object) { |
| if (object.hasLayer() && |
| toLayoutBoxModelObject(object).hasSelfPaintingLayer()) |
| toLayoutBoxModelObject(object).layer()->setNeedsRepaint(); |
| object.invalidateDisplayItemClients(reason); |
| }); |
| } |
| |
| DISABLE_CFI_PERF |
| void ObjectPaintInvalidator::invalidatePaintOfPreviousPaintInvalidationRect( |
| const LayoutBoxModelObject& paintInvalidationContainer, |
| 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(!m_object.paintingLayer() || m_object.paintingLayer()->needsRepaint()); |
| |
| // These disablers are valid because we want to use the current |
| // compositing/invalidation status. |
| DisablePaintInvalidationStateAsserts invalidationDisabler; |
| DisableCompositingQueryAsserts compositingDisabler; |
| |
| LayoutRect invalidationRect = m_object.previousPaintInvalidationRect(); |
| invalidatePaintUsingContainer(paintInvalidationContainer, invalidationRect, |
| reason); |
| m_object.invalidateDisplayItemClients(reason); |
| |
| // This method may be used to invalidate paint of an object changing paint |
| // invalidation container. Clear previous paint invalidation rect on the |
| // original paint invalidation container to avoid under-invalidation if the |
| // new paint invalidation rect on the new paint invalidation container happens |
| // to be the same as the old one. |
| m_object.getMutableForPainting().clearPreviousPaintInvalidationRects(); |
| } |
| |
| void ObjectPaintInvalidator:: |
| invalidatePaintIncludingNonCompositingDescendants() { |
| // Since we're only painting non-composited layers, we know that they all |
| // share the same paintInvalidationContainer. |
| const LayoutBoxModelObject& paintInvalidationContainer = |
| m_object.containerForPaintInvalidation(); |
| traverseNonCompositingDescendantsInPaintOrder( |
| m_object, [&paintInvalidationContainer](const LayoutObject& object) { |
| if (object.hasLayer()) |
| toLayoutBoxModelObject(object).layer()->setNeedsRepaint(); |
| ObjectPaintInvalidator(object) |
| .invalidatePaintOfPreviousPaintInvalidationRect( |
| paintInvalidationContainer, PaintInvalidationSubtree); |
| }); |
| } |
| |
| void ObjectPaintInvalidator:: |
| invalidatePaintIncludingNonSelfPaintingLayerDescendantsInternal( |
| const LayoutBoxModelObject& paintInvalidationContainer) { |
| invalidatePaintOfPreviousPaintInvalidationRect(paintInvalidationContainer, |
| PaintInvalidationSubtree); |
| for (LayoutObject* child = m_object.slowFirstChild(); child; |
| child = child->nextSibling()) { |
| if (!child->hasLayer() || |
| !toLayoutBoxModelObject(child)->layer()->isSelfPaintingLayer()) |
| ObjectPaintInvalidator(*child) |
| .invalidatePaintIncludingNonSelfPaintingLayerDescendantsInternal( |
| paintInvalidationContainer); |
| } |
| } |
| |
| void ObjectPaintInvalidator:: |
| invalidatePaintIncludingNonSelfPaintingLayerDescendants( |
| const LayoutBoxModelObject& paintInvalidationContainer) { |
| slowSetPaintingLayerNeedsRepaint(); |
| invalidatePaintIncludingNonSelfPaintingLayerDescendantsInternal( |
| paintInvalidationContainer); |
| } |
| |
| 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(!m_object.paintingLayer() || m_object.paintingLayer()->needsRepaint()); |
| |
| client.setDisplayItemsUncached(reason); |
| |
| if (FrameView* frameView = m_object.frameView()) |
| frameView->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& invalidationReason) { |
| std::unique_ptr<TracedValue> value = TracedValue::create(); |
| addJsonObjectForRect(value.get(), "rect", rect); |
| value->setString("invalidation_reason", invalidationReason); |
| return value; |
| } |
| |
| static void invalidatePaintRectangleOnWindow( |
| const LayoutBoxModelObject& paintInvalidationContainer, |
| const IntRect& dirtyRect) { |
| FrameView* frameView = paintInvalidationContainer.frameView(); |
| DCHECK(paintInvalidationContainer.isLayoutView() && |
| paintInvalidationContainer.layer()->compositingState() == |
| NotComposited); |
| if (!frameView || paintInvalidationContainer.document().printing()) |
| return; |
| |
| DCHECK(frameView->frame().ownerLayoutItem().isNull()); |
| |
| IntRect paintRect = dirtyRect; |
| paintRect.intersect(frameView->visibleContentRect()); |
| if (paintRect.isEmpty()) |
| return; |
| |
| if (HostWindow* window = frameView->getHostWindow()) |
| window->invalidateRect(frameView->contentsToRootFrame(paintRect)); |
| } |
| |
| void ObjectPaintInvalidator::setBackingNeedsPaintInvalidationInRect( |
| const LayoutBoxModelObject& paintInvalidationContainer, |
| const LayoutRect& rect, |
| PaintInvalidationReason reason) { |
| // https://bugs.webkit.org/show_bug.cgi?id=61159 describes an unreproducible |
| // crash here, so assert but check that the layer is composited. |
| DCHECK(paintInvalidationContainer.compositingState() != NotComposited); |
| |
| PaintLayer& layer = *paintInvalidationContainer.layer(); |
| if (layer.groupedMapping()) { |
| if (GraphicsLayer* squashingLayer = |
| layer.groupedMapping()->squashingLayer()) { |
| // Note: the subpixel accumulation of layer() does not need to be added |
| // here. It is already taken into account. |
| squashingLayer->setNeedsDisplayInRect(enclosingIntRect(rect), reason, |
| m_object); |
| } |
| } else if (m_object.compositedScrollsWithRespectTo( |
| paintInvalidationContainer)) { |
| layer.compositedLayerMapping()->setScrollingContentsNeedDisplayInRect( |
| rect, reason, m_object); |
| } else if (paintInvalidationContainer.usesCompositedScrolling()) { |
| if (layer.compositedLayerMapping() |
| ->backgroundPaintsOntoScrollingContentsLayer()) { |
| // TODO(flackr): Get a correct rect in the context of the scrolling |
| // contents layer to update rather than updating the entire rect. |
| const LayoutRect& scrollingContentsRect = |
| toLayoutBox(m_object).layoutOverflowRect(); |
| layer.compositedLayerMapping()->setScrollingContentsNeedDisplayInRect( |
| scrollingContentsRect, reason, m_object); |
| layer.setNeedsRepaint(); |
| invalidateDisplayItemClient( |
| *layer.compositedLayerMapping()->scrollingContentsLayer(), reason); |
| } |
| layer.compositedLayerMapping()->setNonScrollingContentsNeedDisplayInRect( |
| rect, reason, m_object); |
| } else { |
| // Otherwise invalidate everything. |
| layer.compositedLayerMapping()->setContentsNeedDisplayInRect(rect, reason, |
| m_object); |
| } |
| } |
| |
| void ObjectPaintInvalidator::invalidatePaintUsingContainer( |
| const LayoutBoxModelObject& paintInvalidationContainer, |
| const LayoutRect& dirtyRect, |
| PaintInvalidationReason invalidationReason) { |
| // TODO(wangxianzhu): Enable the following assert after paint invalidation for |
| // spv2 is ready. |
| // ASSERT(!RuntimeEnabledFeatures::slimmingPaintV2Enabled()); |
| |
| if (paintInvalidationContainer.frameView()->shouldThrottleRendering()) |
| return; |
| |
| DCHECK(gDisablePaintInvalidationStateAsserts || |
| m_object.document().lifecycle().state() == |
| (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() |
| ? DocumentLifecycle::InPrePaint |
| : DocumentLifecycle::InPaintInvalidation)); |
| |
| if (dirtyRect.isEmpty()) |
| return; |
| |
| RELEASE_ASSERT(m_object.isRooted()); |
| |
| // FIXME: Unify "devtools.timeline.invalidationTracking" and |
| // "blink.invalidation". crbug.com/413527. |
| TRACE_EVENT_INSTANT1( |
| TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"), |
| "PaintInvalidationTracking", TRACE_EVENT_SCOPE_THREAD, "data", |
| InspectorPaintInvalidationTrackingEvent::data( |
| &m_object, paintInvalidationContainer)); |
| TRACE_EVENT2( |
| TRACE_DISABLED_BY_DEFAULT("blink.invalidation"), |
| "LayoutObject::invalidatePaintUsingContainer()", "object", |
| m_object.debugName().ascii(), "info", |
| jsonObjectForPaintInvalidationInfo( |
| dirtyRect, paintInvalidationReasonToString(invalidationReason))); |
| |
| // This conditional handles situations where non-rooted (and hence |
| // non-composited) frames are painted, such as SVG images. |
| if (!paintInvalidationContainer.isPaintInvalidationContainer()) |
| invalidatePaintRectangleOnWindow(paintInvalidationContainer, |
| enclosingIntRect(dirtyRect)); |
| |
| if (paintInvalidationContainer.view()->usesCompositing() && |
| paintInvalidationContainer.isPaintInvalidationContainer()) |
| setBackingNeedsPaintInvalidationInRect(paintInvalidationContainer, |
| dirtyRect, invalidationReason); |
| } |
| |
| LayoutRect ObjectPaintInvalidator::invalidatePaintRectangle( |
| const LayoutRect& dirtyRect, |
| DisplayItemClient* displayItemClient) { |
| CHECK(m_object.isRooted()); |
| |
| if (dirtyRect.isEmpty()) |
| return LayoutRect(); |
| |
| if (m_object.view()->document().printing()) |
| return LayoutRect(); // Don't invalidate paints if we're printing. |
| |
| const LayoutBoxModelObject& paintInvalidationContainer = |
| m_object.containerForPaintInvalidation(); |
| LayoutRect dirtyRectOnBacking = dirtyRect; |
| PaintLayer::mapRectToPaintInvalidationBacking( |
| m_object, paintInvalidationContainer, dirtyRectOnBacking); |
| dirtyRectOnBacking.move(m_object.scrollAdjustmentForPaintInvalidation( |
| paintInvalidationContainer)); |
| invalidatePaintUsingContainer(paintInvalidationContainer, dirtyRectOnBacking, |
| PaintInvalidationRectangle); |
| |
| slowSetPaintingLayerNeedsRepaint(); |
| if (displayItemClient) |
| invalidateDisplayItemClient(*displayItemClient, PaintInvalidationRectangle); |
| else |
| m_object.invalidateDisplayItemClients(PaintInvalidationRectangle); |
| |
| return dirtyRectOnBacking; |
| } |
| |
| void ObjectPaintInvalidator::slowSetPaintingLayerNeedsRepaint() { |
| if (PaintLayer* paintingLayer = m_object.paintingLayer()) |
| paintingLayer->setNeedsRepaint(); |
| } |
| |
| bool ObjectPaintInvalidatorWithContext::incrementallyInvalidatePaint() { |
| const LayoutRect& oldBounds = m_context.oldBounds; |
| const LayoutRect& newBounds = m_context.newBounds; |
| |
| DCHECK(oldBounds.location() == newBounds.location()); |
| |
| LayoutUnit deltaRight = newBounds.maxX() - oldBounds.maxX(); |
| LayoutUnit deltaBottom = newBounds.maxY() - oldBounds.maxY(); |
| if (!deltaRight && !deltaBottom) |
| return false; |
| |
| if (deltaRight > 0) { |
| LayoutRect invalidationRect(oldBounds.maxX(), newBounds.y(), deltaRight, |
| newBounds.height()); |
| invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, |
| invalidationRect, |
| PaintInvalidationIncremental); |
| } else if (deltaRight < 0) { |
| LayoutRect invalidationRect(newBounds.maxX(), oldBounds.y(), -deltaRight, |
| oldBounds.height()); |
| invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, |
| invalidationRect, |
| PaintInvalidationIncremental); |
| } |
| |
| if (deltaBottom > 0) { |
| LayoutRect invalidationRect(newBounds.x(), oldBounds.maxY(), |
| newBounds.width(), deltaBottom); |
| invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, |
| invalidationRect, |
| PaintInvalidationIncremental); |
| } else if (deltaBottom < 0) { |
| LayoutRect invalidationRect(oldBounds.x(), newBounds.maxY(), |
| oldBounds.width(), -deltaBottom); |
| invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, |
| invalidationRect, |
| PaintInvalidationIncremental); |
| } |
| |
| return true; |
| } |
| |
| void ObjectPaintInvalidatorWithContext::fullyInvalidatePaint( |
| PaintInvalidationReason reason, |
| const LayoutRect& oldBounds, |
| const LayoutRect& newBounds) { |
| // The following logic avoids invalidating twice if one set of bounds contains |
| // the other. |
| if (!newBounds.contains(oldBounds)) { |
| LayoutRect invalidationRect = oldBounds; |
| invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, |
| invalidationRect, reason); |
| |
| if (invalidationRect.contains(newBounds)) |
| return; |
| } |
| |
| invalidatePaintUsingContainer(*m_context.paintInvalidationContainer, |
| newBounds, reason); |
| } |
| |
| PaintInvalidationReason |
| ObjectPaintInvalidatorWithContext::computePaintInvalidationReason() { |
| // This is before any early return to ensure the background obscuration status |
| // is saved. |
| bool backgroundObscurationChanged = false; |
| bool backgroundObscured = m_object.backgroundIsKnownToBeObscured(); |
| if (backgroundObscured != m_object.previousBackgroundObscured()) { |
| m_object.getMutableForPainting().setPreviousBackgroundObscured( |
| backgroundObscured); |
| backgroundObscurationChanged = true; |
| } |
| |
| if (m_context.forcedSubtreeInvalidationFlags & |
| PaintInvalidatorContext::ForcedSubtreeFullInvalidation) |
| return PaintInvalidationSubtree; |
| |
| if (m_object.shouldDoFullPaintInvalidation()) |
| return m_object.fullPaintInvalidationReason(); |
| |
| if (m_context.oldBounds.isEmpty() && m_context.newBounds.isEmpty()) |
| return PaintInvalidationNone; |
| |
| if (backgroundObscurationChanged) |
| return PaintInvalidationBackgroundObscurationChange; |
| |
| if (m_object.paintedOutputOfObjectHasNoEffectRegardlessOfSize()) |
| return PaintInvalidationNone; |
| |
| const ComputedStyle& style = m_object.styleRef(); |
| |
| // The outline may change shape because of position change of descendants. For |
| // simplicity, just force full paint invalidation if this object is marked for |
| // checking paint invalidation for any reason. |
| // TODO(wangxianzhu): Optimize this. |
| if (style.hasOutline()) |
| return PaintInvalidationOutline; |
| |
| bool locationChanged = m_context.newLocation != m_context.oldLocation; |
| |
| // If the bounds are the same then we know that none of the statements below |
| // can match, so we can early out. However, we can't return |
| // PaintInvalidationNone even if !locationChagned, but conservatively return |
| // PaintInvalidationIncremental because we are not sure whether paint |
| // invalidation is actually needed just based on information known to |
| // LayoutObject. For example, a LayoutBox may need paint invalidation if |
| // border box changes. |
| if (m_context.oldBounds == m_context.newBounds) |
| return locationChanged ? PaintInvalidationLocationChange |
| : PaintInvalidationIncremental; |
| |
| // 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 (m_context.oldBounds.isEmpty()) |
| return PaintInvalidationBecameVisible; |
| if (m_context.newBounds.isEmpty()) |
| return PaintInvalidationBecameInvisible; |
| |
| // 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 (m_context.newBounds.location() != m_context.oldBounds.location()) |
| return PaintInvalidationBoundsChange; |
| |
| if (locationChanged) |
| return PaintInvalidationLocationChange; |
| |
| return PaintInvalidationIncremental; |
| } |
| |
| void ObjectPaintInvalidatorWithContext::invalidateSelectionIfNeeded( |
| PaintInvalidationReason reason) { |
| // Update selection rect when we are doing full invalidation (in case that the |
| // object is moved, composite status changed, etc.) or |
| // shouldInvalidationSelection is set (in case that the selection itself |
| // changed). |
| bool fullInvalidation = isImmediateFullPaintInvalidationReason(reason); |
| if (!fullInvalidation && !m_object.shouldInvalidateSelection()) |
| return; |
| |
| LayoutRect oldSelectionRect = selectionPaintInvalidationMap().get(&m_object); |
| LayoutRect newSelectionRect = m_object.localSelectionRect(); |
| if (!newSelectionRect.isEmpty()) { |
| m_context.mapLocalRectToPaintInvalidationBacking(m_object, |
| newSelectionRect); |
| newSelectionRect.move(m_object.scrollAdjustmentForPaintInvalidation( |
| *m_context.paintInvalidationContainer)); |
| } |
| |
| setPreviousSelectionPaintInvalidationRect(m_object, newSelectionRect); |
| |
| if (!fullInvalidation) { |
| fullyInvalidatePaint(PaintInvalidationSelection, oldSelectionRect, |
| newSelectionRect); |
| m_context.paintingLayer->setNeedsRepaint(); |
| m_object.invalidateDisplayItemClients(PaintInvalidationSelection); |
| } |
| } |
| |
| PaintInvalidationReason |
| ObjectPaintInvalidatorWithContext::invalidatePaintIfNeededWithComputedReason( |
| PaintInvalidationReason 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. |
| invalidateSelectionIfNeeded(reason); |
| |
| if (reason == PaintInvalidationIncremental && !incrementallyInvalidatePaint()) |
| reason = PaintInvalidationNone; |
| |
| switch (reason) { |
| case PaintInvalidationNone: |
| // TODO(trchen): Currently we don't keep track of paint offset of layout |
| // objects. 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. This is a workaround to |
| // force display items to update paint offset. |
| if (m_context.forcedSubtreeInvalidationFlags & |
| PaintInvalidatorContext::ForcedSubtreeInvalidationChecking) { |
| reason = PaintInvalidationLocationChange; |
| break; |
| } |
| return PaintInvalidationNone; |
| case PaintInvalidationIncremental: |
| break; |
| case PaintInvalidationDelayedFull: |
| return PaintInvalidationDelayedFull; |
| default: |
| DCHECK(isImmediateFullPaintInvalidationReason(reason)); |
| fullyInvalidatePaint(reason, m_context.oldBounds, m_context.newBounds); |
| } |
| |
| m_context.paintingLayer->setNeedsRepaint(); |
| m_object.invalidateDisplayItemClients(reason); |
| return reason; |
| } |
| |
| DisablePaintInvalidationStateAsserts::DisablePaintInvalidationStateAsserts() |
| : m_disabler(&gDisablePaintInvalidationStateAsserts, true) {} |
| |
| } // namespace blink |