From f569f2b9a2d588e6f52f5a1fb7f2a8147ebd31f9 Mon Sep 17 00:00:00 2001 From: Said Abou-Hallawa Date: Tue, 20 Jun 2023 11:15:28 -0700 Subject: [PATCH 1/2] Charts on sixcolors.com flicker when zooming in https://bugs.webkit.org/show_bug.cgi?id=256620 rdar://108930635 Reviewed by Simon Fraser. Rearrange the logic of RenderBoxModelObject::decodingModeForImageDraw() such that we call isVisibleInViewport() at the end of this function. But there is only one exception to this. If the image has the attribute decoding="async" specified, then we have to make sure the image will not flicker. And to check that we have to call isVisibleInViewport(). Fix a subtle one-time-flickering we should not rely on the layer repaint count only because new layers can be created when pinch zoom the image. In addition to the repaint count, we can rely on a new flag called hasEverPaintedImages which can be stored in the NodeRareData. It is initialized to false and it is set to true when the image is drawn by its RenderObject. An image is allowed to be asynchronously decoded if layer repaint count is zero and the element's flag hasEverPaintedImages is false. The internal setting setLargeImageAsyncDecodingEnabledForTesting() will be renamed to setAsyncDecodingEnabledForTesting(). Its use will change to enable async image decoding for any image regardless of its size. * LayoutTests/fast/images/async-image-background-change.html: * LayoutTests/fast/images/async-image-background-image-repeated.html: * LayoutTests/fast/images/async-image-background-image.html: * LayoutTests/fast/images/async-image-body-background-image.html: * LayoutTests/fast/images/async-image-multiple-clients-repaint.html: * LayoutTests/fast/images/async-image-src-change.html: * LayoutTests/fast/images/decode-render-static-image.html: * LayoutTests/fast/images/decoding-attribute-async-small-image.html: * LayoutTests/fast/images/decoding-attribute-dynamic-async-small-image.html: * LayoutTests/fast/images/sprite-sheet-image-draw.html: * LayoutTests/http/tests/images/render-partial-image-load.html: * Source/WebCore/dom/Node.cpp: (WebCore::Node::hasEverPaintedImages const): (WebCore::Node::setHasEverPaintedImages): * Source/WebCore/dom/Node.h: * Source/WebCore/dom/NodeRareData.h: (WebCore::NodeRareData::hasEverPaintedImages const): (WebCore::NodeRareData::setHasEverPaintedImages): * Source/WebCore/platform/graphics/BitmapImage.h: * Source/WebCore/rendering/BackgroundPainter.cpp: (WebCore::BackgroundPainter::paintFillLayer): * Source/WebCore/rendering/RenderBoxModelObject.cpp: (WebCore::RenderBoxModelObject::decodingModeForImageDraw const): * Source/WebCore/rendering/RenderImage.cpp: (WebCore::RenderImage::paintIntoRect): * Source/WebCore/rendering/RenderObject.h: * Source/WebCore/testing/Internals.cpp: (WebCore::Internals::setAsyncDecodingEnabledForTesting): (WebCore::Internals::setLargeImageAsyncDecodingEnabledForTesting): Deleted. * Source/WebCore/testing/Internals.h: * Source/WebCore/testing/Internals.idl: Canonical link: https://commits.webkit.org/265328@main --- .../images/async-image-background-change.html | 4 +- ...async-image-background-image-repeated.html | 2 +- .../images/async-image-background-image.html | 2 +- .../async-image-body-background-image.html | 2 +- .../async-image-multiple-clients-repaint.html | 2 +- .../fast/images/async-image-src-change.html | 4 +- .../images/decode-render-static-image.html | 2 +- .../decoding-attribute-async-small-image.html | 3 + ...g-attribute-dynamic-async-small-image.html | 3 + .../fast/images/sprite-sheet-image-draw.html | 2 +- Source/WebCore/dom/Node.cpp | 10 +++ Source/WebCore/dom/Node.h | 3 + Source/WebCore/dom/NodeRareData.h | 4 ++ .../WebCore/platform/graphics/BitmapImage.h | 6 +- .../rendering/RenderBoxModelObject.cpp | 63 ++++++++++++++----- Source/WebCore/rendering/RenderImage.cpp | 3 + Source/WebCore/rendering/RenderObject.h | 4 +- Source/WebCore/testing/Internals.cpp | 4 +- Source/WebCore/testing/Internals.h | 2 +- Source/WebCore/testing/Internals.idl | 2 +- 20 files changed, 91 insertions(+), 36 deletions(-) diff --git a/LayoutTests/fast/images/async-image-background-change.html b/LayoutTests/fast/images/async-image-background-change.html index a6a4492ed6eea..de3e0cd7284f8 100644 --- a/LayoutTests/fast/images/async-image-background-change.html +++ b/LayoutTests/fast/images/async-image-background-change.html @@ -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(); diff --git a/LayoutTests/fast/images/async-image-background-image-repeated.html b/LayoutTests/fast/images/async-image-background-image-repeated.html index d3c7b3466bc7e..e0be4a1816042 100644 --- a/LayoutTests/fast/images/async-image-background-image-repeated.html +++ b/LayoutTests/fast/images/async-image-background-image-repeated.html @@ -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"); diff --git a/LayoutTests/fast/images/async-image-background-image.html b/LayoutTests/fast/images/async-image-background-image.html index db4877afec5fa..adce88e561226 100644 --- a/LayoutTests/fast/images/async-image-background-image.html +++ b/LayoutTests/fast/images/async-image-background-image.html @@ -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]; diff --git a/LayoutTests/fast/images/async-image-body-background-image.html b/LayoutTests/fast/images/async-image-body-background-image.html index aacdd14f4200d..6b2554ac5b920 100644 --- a/LayoutTests/fast/images/async-image-body-background-image.html +++ b/LayoutTests/fast/images/async-image-body-background-image.html @@ -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; diff --git a/LayoutTests/fast/images/async-image-multiple-clients-repaint.html b/LayoutTests/fast/images/async-image-multiple-clients-repaint.html index 816d5b0bc7b6b..b79538afb1dc9 100644 --- a/LayoutTests/fast/images/async-image-multiple-clients-repaint.html +++ b/LayoutTests/fast/images/async-image-multiple-clients-repaint.html @@ -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(() => { diff --git a/LayoutTests/fast/images/async-image-src-change.html b/LayoutTests/fast/images/async-image-src-change.html index ff0e0fd04da37..4084be755cfb5 100644 --- a/LayoutTests/fast/images/async-image-src-change.html +++ b/LayoutTests/fast/images/async-image-src-change.html @@ -7,7 +7,7 @@ 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; @@ -15,7 +15,7 @@ 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(); diff --git a/LayoutTests/fast/images/decode-render-static-image.html b/LayoutTests/fast/images/decode-render-static-image.html index 6c2b14850c18d..44972cba0cd2d 100644 --- a/LayoutTests/fast/images/decode-render-static-image.html +++ b/LayoutTests/fast/images/decode-render-static-image.html @@ -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; diff --git a/LayoutTests/fast/images/decoding-attribute-async-small-image.html b/LayoutTests/fast/images/decoding-attribute-async-small-image.html index f2c99dbac14ff..0f763a061da57 100644 --- a/LayoutTests/fast/images/decoding-attribute-async-small-image.html +++ b/LayoutTests/fast/images/decoding-attribute-async-small-image.html @@ -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(); diff --git a/LayoutTests/fast/images/decoding-attribute-dynamic-async-small-image.html b/LayoutTests/fast/images/decoding-attribute-dynamic-async-small-image.html index 61faa1c06684b..6128074e368d9 100644 --- a/LayoutTests/fast/images/decoding-attribute-dynamic-async-small-image.html +++ b/LayoutTests/fast/images/decoding-attribute-dynamic-async-small-image.html @@ -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(); diff --git a/LayoutTests/fast/images/sprite-sheet-image-draw.html b/LayoutTests/fast/images/sprite-sheet-image-draw.html index 7fc17106fe7c0..738b2ba661a90 100644 --- a/LayoutTests/fast/images/sprite-sheet-image-draw.html +++ b/LayoutTests/fast/images/sprite-sheet-image-draw.html @@ -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]; diff --git a/Source/WebCore/dom/Node.cpp b/Source/WebCore/dom/Node.cpp index d2795a35bfa7a..c65b7e1acf5b1 100644 --- a/Source/WebCore/dom/Node.cpp +++ b/Source/WebCore/dom/Node.cpp @@ -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()); diff --git a/Source/WebCore/dom/Node.h b/Source/WebCore/dom/Node.h index c3957c379513a..4cb85624cc332 100644 --- a/Source/WebCore/dom/Node.h +++ b/Source/WebCore/dom/Node.h @@ -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; } diff --git a/Source/WebCore/dom/NodeRareData.h b/Source/WebCore/dom/NodeRareData.h index f47d529da48b1..446856235463e 100644 --- a/Source/WebCore/dom/NodeRareData.h +++ b/Source/WebCore/dom/NodeRareData.h @@ -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 useTypes() const { @@ -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 m_nodeLists; std::unique_ptr m_mutationObserverData; diff --git a/Source/WebCore/platform/graphics/BitmapImage.h b/Source/WebCore/platform/graphics/BitmapImage.h index d3f4febd08046..afe54e4d118fc 100644 --- a/Source/WebCore/platform/graphics/BitmapImage.h +++ b/Source/WebCore/platform/graphics/BitmapImage.h @@ -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; @@ -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 }; diff --git a/Source/WebCore/rendering/RenderBoxModelObject.cpp b/Source/WebCore/rendering/RenderBoxModelObject.cpp index ec02705487e26..68360f74069e3 100644 --- a/Source/WebCore/rendering/RenderBoxModelObject.cpp +++ b/Source/WebCore/rendering/RenderBoxModelObject.cpp @@ -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(element())) { - auto decodingMode = downcast(*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; + + // 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(element())) { + // forces synchronous decoding. + if (downcast(*element()).decodingMode() == DecodingMode::Synchronous) + return DecodingMode::Synchronous; + + // forces asynchronous decoding but make sure this + // will not cause flickering. + if (downcast(*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 diff --git a/Source/WebCore/rendering/RenderImage.cpp b/Source/WebCore/rendering/RenderImage.cpp index ff7a9b35b78f1..25f360f694cb5 100644 --- a/Source/WebCore/rendering/RenderImage.cpp +++ b/Source/WebCore/rendering/RenderImage.cpp @@ -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; } diff --git a/Source/WebCore/rendering/RenderObject.h b/Source/WebCore/rendering/RenderObject.h index 889d063731c8f..877694da92ae4 100644 --- a/Source/WebCore/rendering/RenderObject.h +++ b/Source/WebCore/rendering/RenderObject.h @@ -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: diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp index cad9428421394..587fdebb3d2ce 100644 --- a/Source/WebCore/testing/Internals.cpp +++ b/Source/WebCore/testing/Internals.cpp @@ -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) diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h index e702cd4f2c34a..0b4ef8be9bc34 100644 --- a/Source/WebCore/testing/Internals.h +++ b/Source/WebCore/testing/Internals.h @@ -224,7 +224,7 @@ class Internals final : public RefCounted, 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); diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl index 82a8e99d7bc37..6e54b503b01f6 100644 --- a/Source/WebCore/testing/Internals.idl +++ b/Source/WebCore/testing/Internals.idl @@ -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); From df8600f13f1b6aa2bb71965e5ce683f58fc3b9ca Mon Sep 17 00:00:00 2001 From: Said Abou-Hallawa Date: Thu, 21 Mar 2024 18:33:23 -0700 Subject: [PATCH 2/2] Avoid flickering when showing a layer on a painted background for the first time by disabling async image decoding https://bugs.webkit.org/show_bug.cgi?id=270330 rdar://117533495 Reviewed by Simon Fraser; If an image is decoded asynchronously for a sizeForDrawing different from the current one, a flicker may happen. To avoid this flicker, decode the image synchronously if it has more than one RenderElement in the page and the last time it was decoded asynchronously. * LayoutTests/TestExpectations: * Source/WebCore/loader/cache/CachedImage.h: * Source/WebCore/platform/graphics/BitmapImage.cpp: (WebCore::BitmapImage::destroyDecodedData): (WebCore::BitmapImage::draw): (WebCore::BitmapImage::lastDecodingOptions const): (WebCore::BitmapImage::lastDecodingOptionsForTesting const): Deleted. * Source/WebCore/platform/graphics/BitmapImage.h: * Source/WebCore/platform/graphics/ImageObserver.h: (WebCore::ImageObserver::numberOfClients const): * Source/WebCore/rendering/RenderBoxModelObject.cpp: (WebCore::RenderBoxModelObject::decodingModeForImageDraw const): * Source/WebCore/testing/Internals.cpp: (WebCore::Internals::imageLastDecodingOptions): Canonical link: https://commits.webkit.org/276513@main --- Source/WebCore/loader/cache/CachedImage.h | 1 + Source/WebCore/platform/graphics/BitmapImage.cpp | 8 ++++++++ Source/WebCore/platform/graphics/BitmapImage.h | 2 ++ Source/WebCore/platform/graphics/DecodingOptions.h | 2 +- Source/WebCore/platform/graphics/ImageObserver.h | 1 + Source/WebCore/rendering/RenderBoxModelObject.cpp | 6 ++++-- 6 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Source/WebCore/loader/cache/CachedImage.h b/Source/WebCore/loader/cache/CachedImage.h index 6febf78ce0a24..35fea0442344a 100644 --- a/Source/WebCore/loader/cache/CachedImage.h +++ b/Source/WebCore/loader/cache/CachedImage.h @@ -147,6 +147,7 @@ class CachedImage final : public CachedResource { // ImageObserver API URL sourceUrl() const override { return !m_cachedImages.isEmpty() ? (*m_cachedImages.begin())->url() : URL(); } String mimeType() const override { return !m_cachedImages.isEmpty() ? (*m_cachedImages.begin())->mimeType() : emptyString(); } + unsigned numberOfClients() const override { return !m_cachedImages.isEmpty() ? (*m_cachedImages.begin())->numberOfClients() : 0; } long long expectedContentLength() const override { return !m_cachedImages.isEmpty() ? (*m_cachedImages.begin())->expectedContentLength() : 0; } void encodedDataStatusChanged(const Image&, EncodedDataStatus) final; diff --git a/Source/WebCore/platform/graphics/BitmapImage.cpp b/Source/WebCore/platform/graphics/BitmapImage.cpp index a87ac690b2eca..d0c6c6f65b705 100644 --- a/Source/WebCore/platform/graphics/BitmapImage.cpp +++ b/Source/WebCore/platform/graphics/BitmapImage.cpp @@ -86,6 +86,7 @@ void BitmapImage::destroyDecodedData(bool destroyAll) } else { m_source->destroyDecodedData(0, frameCount()); m_currentFrameDecodingStatus = DecodingStatus::Invalid; + m_lastDecodingOptions = { DecodingMode::Auto }; } // There's no need to throw away the decoder unless we're explicitly asked @@ -257,6 +258,7 @@ ImageDrawResult BitmapImage::draw(GraphicsContext& context, const FloatRect& des // it is currently being decoded. New data may have been received since the previous request was made. if ((!frameIsCompatible && !frameIsBeingDecoded) || m_currentFrameDecodingStatus == DecodingStatus::Invalid) { LOG(Images, "BitmapImage::%s - %p - url: %s [requesting large async decoding]", __FUNCTION__, this, sourceURL().string().utf8().data()); + m_lastDecodingOptions = { options.decodingMode() }; m_source->requestFrameAsyncDecodingAtIndex(m_currentFrame, m_currentSubsamplingLevel, sizeForDrawing); m_currentFrameDecodingStatus = DecodingStatus::Decoding; } @@ -300,6 +302,7 @@ ImageDrawResult BitmapImage::draw(GraphicsContext& context, const FloatRect& des } return ImageDrawResult::DidRequestDecoding; } else { + m_lastDecodingOptions = { options.decodingMode() }; image = frameImageAtIndexCacheIfNeeded(m_currentFrame, m_currentSubsamplingLevel); LOG(Images, "BitmapImage::%s - %p - url: %s [an image frame will be decoded synchronously]", __FUNCTION__, this, sourceURL().string().utf8().data()); } @@ -666,6 +669,11 @@ unsigned BitmapImage::decodeCountForTesting() const return m_decodeCountForTesting; } +DecodingOptions BitmapImage::lastDecodingOptions() const +{ + return m_lastDecodingOptions; +} + void BitmapImage::dump(TextStream& ts) const { Image::dump(ts); diff --git a/Source/WebCore/platform/graphics/BitmapImage.h b/Source/WebCore/platform/graphics/BitmapImage.h index afe54e4d118fc..fd41d9e2ffd4d 100644 --- a/Source/WebCore/platform/graphics/BitmapImage.h +++ b/Source/WebCore/platform/graphics/BitmapImage.h @@ -158,6 +158,7 @@ class BitmapImage final : public Image { void imageFrameAvailableAtIndex(size_t); void decode(Function&&); + WEBCORE_EXPORT DecodingOptions lastDecodingOptions() const; private: WEBCORE_EXPORT BitmapImage(Ref&&); @@ -258,6 +259,7 @@ class BitmapImage final : public Image { #endif unsigned m_decodeCountForTesting { 0 }; + DecodingOptions m_lastDecodingOptions { DecodingMode::Auto }; #if USE(APPKIT) mutable RetainPtr m_nsImage; // A cached NSImage of all the frames. Only built lazily if someone actually queries for one. diff --git a/Source/WebCore/platform/graphics/DecodingOptions.h b/Source/WebCore/platform/graphics/DecodingOptions.h index 7bbec11c94ac6..4d0af7676b4a7 100644 --- a/Source/WebCore/platform/graphics/DecodingOptions.h +++ b/Source/WebCore/platform/graphics/DecodingOptions.h @@ -40,7 +40,7 @@ enum class DecodingMode : uint8_t { class DecodingOptions { public: - explicit DecodingOptions(DecodingMode decodingMode = DecodingMode::Auto) + DecodingOptions(DecodingMode decodingMode = DecodingMode::Auto) : m_decodingModeOrSize(decodingMode) { } diff --git a/Source/WebCore/platform/graphics/ImageObserver.h b/Source/WebCore/platform/graphics/ImageObserver.h index 04270c83d337b..773ef34ee28c8 100644 --- a/Source/WebCore/platform/graphics/ImageObserver.h +++ b/Source/WebCore/platform/graphics/ImageObserver.h @@ -41,6 +41,7 @@ class ImageObserver { public: virtual URL sourceUrl() const = 0; virtual String mimeType() const = 0; + virtual unsigned numberOfClients() const { return 0; } virtual long long expectedContentLength() const = 0; virtual void encodedDataStatusChanged(const Image&, EncodedDataStatus) { }; diff --git a/Source/WebCore/rendering/RenderBoxModelObject.cpp b/Source/WebCore/rendering/RenderBoxModelObject.cpp index 68360f74069e3..14158363a5b4b 100644 --- a/Source/WebCore/rendering/RenderBoxModelObject.cpp +++ b/Source/WebCore/rendering/RenderBoxModelObject.cpp @@ -325,8 +325,10 @@ DecodingMode RenderBoxModelObject::decodingModeForImageDraw(const Image& image, // First tile paint. if (paintInfo.paintBehavior.contains(PaintBehavior::TileFirstPaint)) { - // And the images has not been painted in this element yet. - if (element() && !element()->hasEverPaintedImages()) + // No image has been painted in this element yet and it should not flicker with previous painting. + auto observer = bitmapImage.imageObserver(); + bool mayOverlapOtherClients = observer && observer->numberOfClients() > 1 && bitmapImage.lastDecodingOptions().isAsynchronous(); + if (element() && !element()->hasEverPaintedImages() && !mayOverlapOtherClients) return DecodingMode::Asynchronous; }