Skip to content

Conversation

@cathiechen
Copy link
Contributor

@cathiechen cathiechen commented Apr 24, 2025

7bbc1c0

Canvas with relative width does not repaint correctly on parent resize
https://bugs.webkit.org/show_bug.cgi?id=267986

Reviewed by Alan Baradlay.

Elements with relative width do not trigger a full repaint when their size changes because they are not selfNeedsLayout.
For elements like <canvas> and <img>, a size change implies a scale change, so they should trigger a full repaint.
This issue is only revealed if the replaced element is on a tiled layer and its ancestor with selfNeedsLayout on other layer.
If they are on the same layer, the ancestor would trigger full repaint for itself, the repaint rect will cover the replace element.
If the replaced element is on an untiled layer, this layer triggers a full repaint on layer size change.
This patch triggers full repaint for elements like canvas and image in after layout.

* LayoutTests/compositing/tiling/huge-layer-canvas-resize-expected.txt: Added.
* LayoutTests/compositing/tiling/huge-layer-canvas-resize.html: Added.
* LayoutTests/compositing/tiling/huge-layer-img-resize-expected.txt: Added.
* LayoutTests/compositing/tiling/huge-layer-img-resize.html: Added.
* LayoutTests/platform/glib/TestExpectations:
* LayoutTests/platform/ios/compositing/tiling/huge-layer-canvas-resize-expected.txt: Added.
* LayoutTests/platform/ios/compositing/tiling/huge-layer-img-resize-expected.txt: Added.
* LayoutTests/platform/win/TestExpectations:
* Source/WebCore/rendering/RenderReplaced.cpp:
(WebCore::issueFullRepaintOnSizeChangeIfNeeded):
(WebCore::RenderReplaced::layout):

Canonical link: https://commits.webkit.org/294401@main

fae6baa

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows
✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 win
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 ✅ 🧪 win-tests
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ✅ 🧪 api-mac ❌ 🧪 api-wpe
✅ 🧪 ios-wk2-wpt ✅ 🧪 mac-wk1 ✅ 🛠 wpe-cairo
✅ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🛠 gtk
✅ 🛠 vision ✅ 🧪 mac-AS-debug-wk2 ✅ 🧪 gtk-wk2
✅ 🛠 vision-sim ✅ 🧪 mac-wk2-stress ✅ 🧪 api-gtk
✅ 🛠 🧪 merge ✅ 🧪 vision-wk2 ✅ 🧪 mac-intel-wk2 ✅ 🛠 playstation
✅ 🛠 tv ✅ 🛠 mac-safer-cpp
✅ 🛠 tv-sim
✅ 🛠 watch
✅ 🛠 watch-sim

@cathiechen cathiechen self-assigned this Apr 24, 2025
@cathiechen cathiechen added the Canvas Bugs related to the canvas element. label Apr 24, 2025
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 24, 2025
@cathiechen cathiechen removed the merging-blocked Applied to prevent a change from being merged label Apr 24, 2025
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 24, 2025
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why not just pass in shouldAlwaysIssueFullRepaint to LayoutRepainter in RenderReplaced::layout() when needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, shouldAlwaysIssueFullRepaint would work for replaced element without layer. For element hasLayer, repaintAfterLayoutIfNeeded is triggered in recursiveUpdateLayerPositions, we need to set NeedsFullRepaint to its RenderLayer.
I don't have a strong opinion. But if you prefer shouldAlwaysIssueFullRepaint + NeedsFullRepaint approach, I will adjust the patch later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I just realized this requires post layout check. ok, I mean I am fine either way but isn't this just a repaint() call which I think could be achieved with a more focused change, something along the lines of

static void issueFullRepaintOnSizeChangeIfNeeded(RenderReplaced& renderer)
{
    auto shouldRepaintOnSizeChange = [&] {
        if (is<RenderHTMLCanvas>(renderer))
            return true;
        if (auto* renderImage = dynamicDowncast<RenderImage>(renderer); renderImage && !is<RenderMedia>(*renderImage) && !renderImage->isShowingMissingOrImageError())
            return true;
        return false;
    };
    if (shouldRepaintOnSizeChange())
        renderer.repaint();
}

called by RenderReplaced::layout when replacedContentRect() != oldContentRect

or alternatively RenderImage/RenderHTMLCanvas::layout could just call repaint() after coming backing from base class layout when content box size changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, it looks clearer.

@cathiechen cathiechen removed the merging-blocked Applied to prevent a change from being merged label Apr 25, 2025
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 26, 2025
@cathiechen cathiechen removed the merging-blocked Applied to prevent a change from being merged label May 1, 2025
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 1, 2025
@cathiechen cathiechen removed the merging-blocked Applied to prevent a change from being merged label May 1, 2025
@cathiechen cathiechen added the merge-queue Applied to send a pull request to merge-queue label May 1, 2025
https://bugs.webkit.org/show_bug.cgi?id=267986

Reviewed by Alan Baradlay.

Elements with relative width do not trigger a full repaint when their size changes because they are not selfNeedsLayout.
For elements like <canvas> and <img>, a size change implies a scale change, so they should trigger a full repaint.
This issue is only revealed if the replaced element is on a tiled layer and its ancestor with selfNeedsLayout on other layer.
If they are on the same layer, the ancestor would trigger full repaint for itself, the repaint rect will cover the replace element.
If the replaced element is on an untiled layer, this layer triggers a full repaint on layer size change.
This patch triggers full repaint for elements like canvas and image in after layout.

* LayoutTests/compositing/tiling/huge-layer-canvas-resize-expected.txt: Added.
* LayoutTests/compositing/tiling/huge-layer-canvas-resize.html: Added.
* LayoutTests/compositing/tiling/huge-layer-img-resize-expected.txt: Added.
* LayoutTests/compositing/tiling/huge-layer-img-resize.html: Added.
* LayoutTests/platform/glib/TestExpectations:
* LayoutTests/platform/ios/compositing/tiling/huge-layer-canvas-resize-expected.txt: Added.
* LayoutTests/platform/ios/compositing/tiling/huge-layer-img-resize-expected.txt: Added.
* LayoutTests/platform/win/TestExpectations:
* Source/WebCore/rendering/RenderReplaced.cpp:
(WebCore::issueFullRepaintOnSizeChangeIfNeeded):
(WebCore::RenderReplaced::layout):

Canonical link: https://commits.webkit.org/294401@main
@webkit-commit-queue
Copy link
Collaborator

Committed 294401@main (7bbc1c0): https://commits.webkit.org/294401@main

Reviewed commits have been landed. Closing PR #44463 and removing active labels.

@webkit-commit-queue webkit-commit-queue merged commit 7bbc1c0 into WebKit:main May 1, 2025
@webkit-commit-queue webkit-commit-queue removed the merge-queue Applied to send a pull request to merge-queue label May 1, 2025
}
if (window.testRunner)
testRunner.notifyDone();
}, 1000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly are you waiting 1000ms for? This makes the behavior of the test un-deterministic. Besides one second is too long time to wait. Can not it be replaced by

requestAnimationFrame(() => {
    ...
});

Copy link
Contributor Author

@cathiechen cathiechen May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure the repaint rects before css change has flashed, so I added 1000 ms. requestAnimationFrame seems not long enough, especially in stress mode.

Copy link
Contributor Author

@cathiechen cathiechen May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rego suggested me to use two requestAnimationFrame, it works! I have created a PR to address the code review comments. Please take a look, thanks:) #45040

img.src = canvas.toDataURL();
}

async function onImageLoad()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this function marked as async? It does not have await statement.

if (replacedContentRect() != oldContentRect)
if (replacedContentRect() != oldContentRect) {
setPreferredLogicalWidthsDirty(true);
issueFullRepaintOnSizeChangeIfNeeded(*this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a long name for a function. Could it be just?

  if (shouldRepaintOnSizeChange(*this))
        repaint();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Canvas Bugs related to the canvas element.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants