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/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 d3f4febd08046..fd41d9e2ffd4d 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; @@ -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&&); @@ -249,7 +250,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 }; @@ -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 ec02705487e26..14158363a5b4b 100644 --- a/Source/WebCore/rendering/RenderBoxModelObject.cpp +++ b/Source/WebCore/rendering/RenderBoxModelObject.cpp @@ -306,32 +306,63 @@ 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)) { + // 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; + } + + // 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);