Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions LayoutTests/fast/images/async-image-background-change.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
image.onload = (() => {
if (window.internals && window.testRunner && forceAsyncImageDrawing) {
// Force async image decoding for this image.
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

image.addEventListener("webkitImageFrameReady", function() {
internals.setLargeImageAsyncDecodingEnabledForTesting(image, false);
internals.setAsyncDecodingEnabledForTesting(image, false);
setTimeout(function() {
// Force redraw to get the red image drawn.
testRunner.display();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
image.onload = function() {
// Force async image decoding for this image.
if (window.internals)
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

// Change the background of the elements.
var elements = document.getElementsByClassName("image-background");
Expand Down
2 changes: 1 addition & 1 deletion LayoutTests/fast/images/async-image-background-image.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
image.onload = function() {
// Force async image decoding for this image.
if (window.internals)
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

// Change the background of the element.
var element = document.getElementsByClassName("image-background")[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
image.onload = function() {
// Force async image decoding for this image.
if (window.internals)
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

var iframeDocument = document.querySelector('iframe').contentWindow.document;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
image.onload = function() {
// Force async image decoding for this image.
if (window.internals)
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

if (window.internals && window.testRunner) {
setElementImageBackground(document.querySelector(".small-box"), image).then(() => {
Expand Down
4 changes: 2 additions & 2 deletions LayoutTests/fast/images/async-image-src-change.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
image.onload = (() => {
if (window.internals && window.testRunner && forceAsyncImageDrawing) {
// Force async image decoding for this image.
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

// Force layout and display so the image gets drawn.
document.body.offsetHeight;
if (window.testRunner)
testRunner.display();

image.addEventListener("webkitImageFrameReady", function() {
internals.setLargeImageAsyncDecodingEnabledForTesting(image, false);
internals.setAsyncDecodingEnabledForTesting(image, false);
setTimeout(function() {
// Force redraw to get the red image drawn.
testRunner.display();
Expand Down
2 changes: 1 addition & 1 deletion LayoutTests/fast/images/decode-render-static-image.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
image.onload = (() => {
if (window.internals && window.testRunner) {
// Force async image decoding for this image.
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

// Force layout and display so the image gets drawn.
document.body.offsetHeight;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
return new Promise((resolve) => {
image.onload = (() => {
if (window.internals && window.testRunner) {
// Force async image decoding for this image.
internals.setAsyncDecodingEnabledForTesting(image, true);

// Force layout and display so the image gets drawn.
document.body.offsetHeight;
testRunner.display();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
return new Promise((resolve) => {
image.onload = (() => {
if (window.internals && window.testRunner) {
// Force async image decoding for this image.
internals.setAsyncDecodingEnabledForTesting(image, true);

// Force layout and display so the image gets drawn.
document.body.offsetHeight;
testRunner.display();
Expand Down
2 changes: 1 addition & 1 deletion LayoutTests/fast/images/sprite-sheet-image-draw.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
image.onload = function() {
// Force async image decoding for this image.
if (window.internals)
internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
internals.setAsyncDecodingEnabledForTesting(image, true);

// Change the background of the element.
var element = document.getElementsByClassName("image-background")[0];
Expand Down
10 changes: 10 additions & 0 deletions Source/WebCore/dom/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,16 @@ HTMLSlotElement* Node::assignedSlotForBindings() const
return nullptr;
}

bool Node::hasEverPaintedImages() const
{
return hasRareData() && rareData()->hasEverPaintedImages();
}

void Node::setHasEverPaintedImages(bool hasEverPaintedImages)
{
ensureRareData().setHasEverPaintedImages(hasEverPaintedImages);
}

ContainerNode* Node::parentInComposedTree() const
{
ASSERT(isMainThreadOrGCThread());
Expand Down
3 changes: 3 additions & 0 deletions Source/WebCore/dom/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ class Node : public EventTarget {
HTMLSlotElement* assignedSlot() const;
HTMLSlotElement* assignedSlotForBindings() const;

bool hasEverPaintedImages() const;
void setHasEverPaintedImages(bool);

bool isUndefinedCustomElement() const { return customElementState() == CustomElementState::Undefined || customElementState() == CustomElementState::Failed; }
bool isCustomElementUpgradeCandidate() const { return customElementState() == CustomElementState::Undefined; }
bool isDefinedCustomElement() const { return customElementState() == CustomElementState::Custom; }
Expand Down
4 changes: 4 additions & 0 deletions Source/WebCore/dom/NodeRareData.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@ class NodeRareData {
return *m_mutationObserverData;
}

bool hasEverPaintedImages() const { return m_hasEverPaintedImages; }
void setHasEverPaintedImages(bool hasEverPaintedImages) { m_hasEverPaintedImages = hasEverPaintedImages; }

#if DUMP_NODE_STATISTICS
OptionSet<UseType> useTypes() const
{
Expand All @@ -314,6 +317,7 @@ class NodeRareData {

private:
bool m_isElementRareData;
bool m_hasEverPaintedImages { false }; // Keep last for better bit packing with ElementRareData.

std::unique_ptr<NodeListsNodeData> m_nodeLists;
std::unique_ptr<NodeMutationObserverData> m_mutationObserverData;
Expand Down
6 changes: 3 additions & 3 deletions Source/WebCore/platform/graphics/BitmapImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ class BitmapImage final : public Image {
bool canUseAsyncDecodingForLargeImages() const;
bool shouldUseAsyncDecodingForAnimatedImages() const;
void setClearDecoderAfterAsyncFrameRequestForTesting(bool value) { m_clearDecoderAfterAsyncFrameRequestForTesting = value; }
void setLargeImageAsyncDecodingEnabledForTesting(bool enabled) { m_largeImageAsyncDecodingEnabledForTesting = enabled; }
bool isLargeImageAsyncDecodingEnabledForTesting() const { return m_largeImageAsyncDecodingEnabledForTesting; }
void setAsyncDecodingEnabledForTesting(bool enabled) { m_asyncDecodingEnabledForTesting = enabled; }
bool isAsyncDecodingEnabledForTesting() const { return m_asyncDecodingEnabledForTesting; }
void stopAsyncDecodingQueue() { m_source->stopAsyncDecodingQueue(); }

DestinationColorSpace colorSpace() final;
Expand Down Expand Up @@ -249,7 +249,7 @@ class BitmapImage final : public Image {
bool m_showDebugBackground { false };

bool m_clearDecoderAfterAsyncFrameRequestForTesting { false };
bool m_largeImageAsyncDecodingEnabledForTesting { false };
bool m_asyncDecodingEnabledForTesting { false };

#if ASSERT_ENABLED || !LOG_DISABLED
size_t m_lateFrameCount { 0 };
Expand Down
63 changes: 46 additions & 17 deletions Source/WebCore/rendering/RenderBoxModelObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,32 +306,61 @@ DecodingMode RenderBoxModelObject::decodingModeForImageDraw(const Image& image,
return DecodingMode::Synchronous;
}

// Large image case.
// Some document types force synchronous decoding.
#if PLATFORM(IOS_FAMILY)
if (IOSApplication::isIBooksStorytime())
return DecodingMode::Synchronous;
#endif
if (is<HTMLImageElement>(element())) {
auto decodingMode = downcast<HTMLImageElement>(*element()).decodingMode();
if (decodingMode != DecodingMode::Auto)
return decodingMode;
}
if (bitmapImage.isLargeImageAsyncDecodingEnabledForTesting())
return DecodingMode::Asynchronous;
if (document().isImageDocument())
return DecodingMode::Synchronous;
if (paintInfo.paintBehavior.contains(PaintBehavior::Snapshotting))
return DecodingMode::Synchronous;
if (!settings().largeImageAsyncDecodingEnabled())

// A PaintBehavior may force synchronous decoding.
if (paintInfo.paintBehavior.contains(PaintBehavior::Snapshotting))
return DecodingMode::Synchronous;
if (!bitmapImage.canUseAsyncDecodingForLargeImages())
return DecodingMode::Synchronous;
if (paintInfo.paintBehavior.contains(PaintBehavior::TileFirstPaint))
return DecodingMode::Asynchronous;
// FIXME: isVisibleInViewport() is not cheap. Find a way to make this condition faster.
if (!isVisibleInViewport())

auto defaultDecodingMode = [&]() -> DecodingMode {
// if (paintInfo.paintBehavior.contains(PaintBehavior::ForceSynchronousImageDecode))
// return DecodingMode::Synchronous;
Comment on lines +323 to +324
Copy link
Copy Markdown

@pgorszkowski-igalia pgorszkowski-igalia Mar 11, 2026

Choose a reason for hiding this comment

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

@BunioFH : These lines can be removed from the change

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@BunioFH : ping


// First tile paint.
if (paintInfo.paintBehavior.contains(PaintBehavior::TileFirstPaint)) {
// And the images has not been painted in this element yet.
if (element() && !element()->hasEverPaintedImages())
return DecodingMode::Asynchronous;
}

// FIXME: Calling isVisibleInViewport() is not cheap. Find a way to make this faster.
return isVisibleInViewport() ? DecodingMode::Synchronous : DecodingMode::Asynchronous;
};

if (is<HTMLImageElement>(element())) {
// <img decoding="sync"> forces synchronous decoding.
if (downcast<HTMLImageElement>(*element()).decodingMode() == DecodingMode::Synchronous)
return DecodingMode::Synchronous;

// <img decoding="async"> forces asynchronous decoding but make sure this
// will not cause flickering.
if (downcast<HTMLImageElement>(*element()).decodingMode() == DecodingMode::Asynchronous) {
// isAsyncDecodingEnabledForTesting() forces async image decoding regardless whether it is in the viewport or not.
if (bitmapImage.isAsyncDecodingEnabledForTesting())
return DecodingMode::Asynchronous;

// Choose a decodingMode such that the image does not flicker.
return defaultDecodingMode();
}
}

// isAsyncDecodingEnabledForTesting() forces async image decoding regardless of the size.
if (bitmapImage.isAsyncDecodingEnabledForTesting())
return DecodingMode::Asynchronous;
return DecodingMode::Synchronous;

// Large image case.
if (!(bitmapImage.canUseAsyncDecodingForLargeImages() && settings().largeImageAsyncDecodingEnabled()))
return DecodingMode::Synchronous;

// Choose a decodingMode such that the image does not flicker.
return defaultDecodingMode();
}

LayoutSize RenderBoxModelObject::relativePositionOffset() const
Expand Down
3 changes: 3 additions & 0 deletions Source/WebCore/rendering/RenderImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,9 @@ ImageDrawResult RenderImage::paintIntoRect(PaintInfo& paintInfo, const FloatRect
theme().paintSystemPreviewBadge(*img, paintInfo, rect);
#endif

if (element() && !paintInfo.context().paintingDisabled())
element()->setHasEverPaintedImages(true);

return drawResult;
}

Expand Down
4 changes: 2 additions & 2 deletions Source/WebCore/rendering/RenderObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -934,8 +934,8 @@ class RenderObject : public CachedImageClient {

private:
unsigned m_positionedState : 2; // PositionedState
unsigned m_selectionState : 3; // SelectionState
unsigned m_fragmentedFlowState : 2; // FragmentedFlowState
unsigned m_selectionState : 3; // HighlightState
unsigned m_fragmentedFlowState : 1; // FragmentedFlowState
unsigned m_boxDecorationState : 2; // BoxDecorationState

public:
Expand Down
4 changes: 2 additions & 2 deletions Source/WebCore/testing/Internals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1087,10 +1087,10 @@ unsigned Internals::remoteImagesCountForTesting() const
return document->page()->chrome().client().remoteImagesCountForTesting();
}

void Internals::setLargeImageAsyncDecodingEnabledForTesting(HTMLImageElement& element, bool enabled)
void Internals::setAsyncDecodingEnabledForTesting(HTMLImageElement& element, bool enabled)
{
if (auto* bitmapImage = bitmapImageFromImageElement(element))
bitmapImage->setLargeImageAsyncDecodingEnabledForTesting(enabled);
bitmapImage->setAsyncDecodingEnabledForTesting(enabled);
}

void Internals::setForceUpdateImageDataEnabledForTesting(HTMLImageElement& element, bool enabled)
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/testing/Internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class Internals final : public RefCounted<Internals>, private ContextDestruction
unsigned imageDecodeCount(HTMLImageElement&);
unsigned pdfDocumentCachingCount(HTMLImageElement&);
unsigned remoteImagesCountForTesting() const;
void setLargeImageAsyncDecodingEnabledForTesting(HTMLImageElement&, bool enabled);
void setAsyncDecodingEnabledForTesting(HTMLImageElement&, bool enabled);
void setForceUpdateImageDataEnabledForTesting(HTMLImageElement&, bool enabled);

void setGridMaxTracksLimit(unsigned);
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/testing/Internals.idl
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ typedef (FetchRequest or FetchResponse) FetchObject;
unsigned long imageDecodeCount(HTMLImageElement element);
unsigned long pdfDocumentCachingCount(HTMLImageElement element);
unsigned long remoteImagesCountForTesting();
undefined setLargeImageAsyncDecodingEnabledForTesting(HTMLImageElement element, boolean enabled);
undefined setAsyncDecodingEnabledForTesting(HTMLImageElement element, boolean enabled);
undefined setForceUpdateImageDataEnabledForTesting(HTMLImageElement element, boolean enabled);

undefined setGridMaxTracksLimit(unsigned long maxTracksLimit);
Expand Down