Skip to content

Commit

Permalink
Repaint should only be called on attached renderers
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=272292

Reviewed by Antti Koivisto.

1. In some cases (e.g. pseudo content) a subtree root gets detached first followed by the descendant renderers
2. Repaint functions start with traversing all the way to the RenderView just to check if dispatching repaint is safe -which in most cases comes back true (normal at-layout repaint, invalidation, standard tree teardown)

This patch fixes a correctness and a performance issue:

correctness: In case of #1, when calling willBeRemovedFromTree() (right before deleting the descendant renderer), the renderer is
already detached from the render tree. It is still attached to the root of the subtree (which is already detached), but has no access to the rest of the render tree (i.e. willBeRemovedFromTree is technically just willBeRemovedFromParent)

performance: ensuring correctness eliminates the need for checking if the renderer is still attached.

(Sadly ::imageChanged gets called indirectly through initializeStyle before the initial attach)

* Source/WebCore/rendering/RenderObject.cpp:
(WebCore::RenderObject::repaint const): Use isDescendantOf instead of isRooted() as I am planning on completely remove isRooted().
(WebCore::RenderObject::repaintRectangle const):
(WebCore::RenderObject::repaintSlowRepaintObject const):
* Source/WebCore/rendering/updating/RenderTreeBuilder.cpp:
(WebCore::RenderTreeBuilder::destroy):
(WebCore::RenderTreeBuilder::detachFromRenderElement):
* Source/WebCore/rendering/updating/RenderTreeBuilder.h:

Canonical link: https://commits.webkit.org/277186@main
  • Loading branch information
alanbaradlay committed Apr 8, 2024
1 parent 1f93f36 commit 6f39fe0
Show file tree
Hide file tree
Showing 12 changed files with 69 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
some text
(repaint rects
(rect 144 0 64 116)
(rect 144 0 64 116)
(rect 0 0 64 116)
(rect 144 0 64 116)
Expand Down
9 changes: 5 additions & 4 deletions Source/WebCore/rendering/RenderBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2030,12 +2030,10 @@ static StyleImage* findLayerUsedImage(WrappedImagePtr image, const FillLayer& la

void RenderBox::imageChanged(WrappedImagePtr image, const IntRect*)
{
if (!parent())
return;

if ((style().borderImage().image() && style().borderImage().image()->data() == image) ||
(style().maskBorder().image() && style().maskBorder().image()->data() == image)) {
repaint();
if (parent())
repaint();
return;
}

Expand All @@ -2048,6 +2046,9 @@ void RenderBox::imageChanged(WrappedImagePtr image, const IntRect*)
bool didFullRepaint = false;

auto repaintForBackgroundAndMask = [&](auto& style) {
if (!parent())
return;

if (!didFullRepaint)
didFullRepaint = repaintLayerRectsForImage(image, style.backgroundLayers(), true);
if (!didFullRepaint)
Expand Down
15 changes: 8 additions & 7 deletions Source/WebCore/rendering/RenderImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,14 +398,15 @@ void RenderImage::repaintOrMarkForLayout(ImageSizeChangeType imageSizeChange, co
updateInnerContentRect();
}

LayoutRect repaintRect = contentBoxRect();
if (rect) {
// The image changed rect is in source image coordinates (pre-zooming),
// so map from the bounds of the image to the contentsBox.
repaintRect.intersect(enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), imageResource().imageSize(1.0f)), repaintRect)));
if (parent()) {
auto repaintRect = contentBoxRect();
if (rect) {
// The image changed rect is in source image coordinates (pre-zooming),
// so map from the bounds of the image to the contentsBox.
repaintRect.intersect(enclosingIntRect(mapRect(*rect, FloatRect(FloatPoint(), imageResource().imageSize(1.0f)), repaintRect)));
}
repaintRectangle(repaintRect);
}

repaintRectangle(repaintRect);

// Tell any potential compositing layers that the image needs updating.
contentChanged(ImageChanged);
Expand Down
12 changes: 7 additions & 5 deletions Source/WebCore/rendering/RenderListMarker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,13 @@ void RenderListMarker::layout()

void RenderListMarker::imageChanged(WrappedImagePtr o, const IntRect* rect)
{
if (m_image && o == m_image->data()) {
if (width() != m_image->imageSize(this, style().usedZoom()).width() || height() != m_image->imageSize(this, style().usedZoom()).height() || m_image->errorOccurred())
setNeedsLayoutAndPrefWidthsRecalc();
else
repaint();
if (parent()) {
if (m_image && o == m_image->data()) {
if (width() != m_image->imageSize(this, style().usedZoom()).width() || height() != m_image->imageSize(this, style().usedZoom()).height() || m_image->errorOccurred())
setNeedsLayoutAndPrefWidthsRecalc();
else
repaint();
}
}
RenderBox::imageChanged(o, rect);
}
Expand Down
15 changes: 8 additions & 7 deletions Source/WebCore/rendering/RenderObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1039,21 +1039,24 @@ void RenderObject::issueRepaint(std::optional<LayoutRect> partialRepaintRect, Cl

void RenderObject::repaint(ForceRepaint forceRepaint) const
{
// Don't repaint if we're unrooted (note that view() still returns the view when unrooted)
if (!isRooted() || view().printing())
ASSERT(isDescendantOf(&view()) || is<RenderScrollbarPart>(this) || is<RenderReplica>(this));

if (view().printing())
return;
issueRepaint({ }, ClipRepaintToLayer::No, forceRepaint);
}

void RenderObject::repaintRectangle(const LayoutRect& repaintRect, bool shouldClipToLayer) const
{
ASSERT(isDescendantOf(&view()) || is<RenderScrollbarPart>(this));
return repaintRectangle(repaintRect, shouldClipToLayer ? ClipRepaintToLayer::Yes : ClipRepaintToLayer::No, ForceRepaint::No);
}

void RenderObject::repaintRectangle(const LayoutRect& repaintRect, ClipRepaintToLayer shouldClipToLayer, ForceRepaint forceRepaint, std::optional<LayoutBoxExtent> additionalRepaintOutsets) const
{
// Don't repaint if we're unrooted (note that view() still returns the view when unrooted)
if (!isRooted() || view().printing())
ASSERT(isDescendantOf(&view()) || is<RenderScrollbarPart>(this) || is<RenderReplica>(this));

if (view().printing())
return;
// FIXME: layoutDelta needs to be applied in parts before/after transforms and
// repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308
Expand All @@ -1064,9 +1067,7 @@ void RenderObject::repaintRectangle(const LayoutRect& repaintRect, ClipRepaintTo

void RenderObject::repaintSlowRepaintObject() const
{
// Don't repaint if we're unrooted (note that view() still returns the view when unrooted)
if (!isRooted())
return;
ASSERT(isDescendantOf(&view()) || is<RenderScrollbarPart>(this) || is<RenderReplica>(this));

CheckedRef view = this->view();
if (view->printing())
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/rendering/RenderTableCol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ auto RenderTableCol::rectsForRepaintingAfterLayout(const RenderLayerModelObject*
void RenderTableCol::imageChanged(WrappedImagePtr, const IntRect*)
{
// FIXME: Repaint only the rect the image paints in.
if (!parent())
return;
repaint();
}

Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/rendering/RenderTableRow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ void RenderTableRow::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
void RenderTableRow::imageChanged(WrappedImagePtr, const IntRect*)
{
// FIXME: Examine cells and repaint only the rect the image paints in.
if (!parent())
return;
repaint();
}

Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/rendering/RenderTableSection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,8 @@ void RenderTableSection::paintObject(PaintInfo& paintInfo, const LayoutPoint& pa
void RenderTableSection::imageChanged(WrappedImagePtr, const IntRect*)
{
// FIXME: Examine cells and repaint only the rect the image paints in.
if (!parent())
return;
repaint();
}

Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/rendering/svg/RenderSVGImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ void RenderSVGImage::notifyFinished(CachedResource& newImage, const NetworkLoadM

void RenderSVGImage::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
{
if (renderTreeBeingDestroyed())
if (renderTreeBeingDestroyed() || !parent())
return;

repaintClientsOfReferencedSVGResources();
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/rendering/svg/legacy/LegacyRenderSVGImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ bool LegacyRenderSVGImage::nodeAtFloatPoint(const HitTestRequest& request, HitTe

void LegacyRenderSVGImage::imageChanged(WrappedImagePtr, const IntRect*)
{
if (!parent())
return;
// The image resource defaults to nullImage until the resource arrives.
// This empty image may be cached by SVG resources which must be invalidated.
if (auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this))
Expand Down
42 changes: 29 additions & 13 deletions Source/WebCore/rendering/updating/RenderTreeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ void RenderTreeBuilder::destroy(RenderObject& renderer, CanCollapseAnonymousBloc
{
RELEASE_ASSERT(RenderTreeMutationDisallowedScope::isMutationAllowed());
ASSERT(renderer.parent());

auto notifyDescendantRenderersBeforeSubtreeTearDownIfApplicable = [&] {
if (renderer.renderTreeBeingDestroyed())
return;
auto* rendererToDelete = dynamicDowncast<RenderElement>(renderer);
if (!rendererToDelete || !rendererToDelete->firstChild())
return;

for (auto& descendant : descendantsOfType<RenderObject>(*rendererToDelete))
descendant.willBeRemovedFromTree();
};
notifyDescendantRenderersBeforeSubtreeTearDownIfApplicable();

auto toDestroy = detach(*renderer.parent(), renderer, WillBeDestroyed::Yes, canCollapseAnonymousBlock);

if (auto* textFragment = dynamicDowncast<RenderTextFragment>(renderer))
Expand All @@ -168,19 +181,21 @@ void RenderTreeBuilder::destroy(RenderObject& renderer, CanCollapseAnonymousBloc
if (auto* renderBox = dynamicDowncast<RenderBoxModelObject>(renderer))
continuationBuilder().cleanupOnDestroy(*renderBox);

// We need to detach the subtree first so that the descendants don't have
// access to previous/next sublings at detach().
// FIXME: webkit.org/b/182909.
auto* childToDestroy = dynamicDowncast<RenderElement>(toDestroy.get());
if (!childToDestroy)
return;
auto tearDownSubTreeIfApplicable = [&] {
auto* rendererToDelete = dynamicDowncast<RenderElement>(toDestroy.get());
if (!rendererToDelete)
return;

while (childToDestroy->firstChild()) {
auto& firstChild = *childToDestroy->firstChild();
if (auto* node = firstChild.node())
node->setRenderer(nullptr);
destroy(firstChild);
}
auto isSubtreeTeardown = SetForScope { m_isSubtreeTeardown, IsSubtreeTeardown::Yes };
while (rendererToDelete->firstChild()) {
auto& firstChild = *rendererToDelete->firstChild();
if (auto* node = firstChild.node())
node->setRenderer(nullptr);
destroy(firstChild);
}
};
// FIXME: webkit.org/b/182909.
tearDownSubTreeIfApplicable();
}

void RenderTreeBuilder::attach(RenderElement& parent, RenderPtr<RenderObject> child, RenderObject* beforeChild)
Expand Down Expand Up @@ -953,8 +968,9 @@ RenderPtr<RenderObject> RenderTreeBuilder::detachFromRenderElement(RenderElement
RELEASE_ASSERT_WITH_MESSAGE(!parent.view().frameView().layoutContext().layoutState(), "Layout must not mutate render tree");
ASSERT(parent.canHaveChildren() || parent.canHaveGeneratedChildren());
ASSERT(child.parent() == &parent);
ASSERT(m_isSubtreeTeardown == IsSubtreeTeardown::No || willBeDestroyed == WillBeDestroyed::Yes);

if (parent.renderTreeBeingDestroyed())
if (parent.renderTreeBeingDestroyed() || m_isSubtreeTeardown == IsSubtreeTeardown::Yes)
return parent.detachRendererInternal(child);

if (child.everHadLayout())
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/rendering/updating/RenderTreeBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class RenderTreeBuilder {
enum class CanCollapseAnonymousBlock : bool { No, Yes };
RenderPtr<RenderObject> detach(RenderElement&, RenderObject&, WillBeDestroyed, CanCollapseAnonymousBlock = CanCollapseAnonymousBlock::Yes) WARN_UNUSED_RETURN;

enum class IsSubtreeTeardown : bool { No, Yes };
void destroy(RenderObject& renderer, CanCollapseAnonymousBlock = CanCollapseAnonymousBlock::Yes);

// NormalizeAfterInsertion::Yes ensures that the destination subtree is consistent after the insertion (anonymous wrappers etc).
Expand Down Expand Up @@ -148,6 +149,7 @@ class RenderTreeBuilder {
std::unique_ptr<Continuation> m_continuationBuilder;
bool m_hasBrokenContinuation { false };
IsInternalMove m_internalMovesType { IsInternalMove::No };
IsSubtreeTeardown m_isSubtreeTeardown { IsSubtreeTeardown::No };
};

}

0 comments on commit 6f39fe0

Please sign in to comment.