Implement HTMLImageElement.decoode() method
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 9 Sep 2017 03:09:09 +0000 (03:09 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 9 Sep 2017 03:09:09 +0000 (03:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176016

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2017-09-08
Reviewed by Simon Fraser.

Source/WebCore:

The specs is:
https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode.

-- img.decode() waits till loading the image finishes. Otherwise it starts
decoding the image immediately.
-- If the image frame is already decoded, the promise will be resolved
before return.
-- If an error happens in loading the image or decoding the image frame,
the promise will be rejected with 'EncodingError' exception.
-- Animated image resolves the promise when the next frame is decoded and
the animation is advanced it. If the image is not displayed, decode() will
request the decoding the first frame and start animating the image.

Tests: fast/images/decode-animated-image.html
       fast/images/decode-render-animated-image.html
       fast/images/decode-render-static-image.html
       fast/images/decode-static-image-reject.html
       fast/images/decode-static-image-resolve.html

* html/HTMLImageElement.cpp:
(WebCore::HTMLImageElement::decode):
* html/HTMLImageElement.h:
* html/HTMLImageElement.idl:
* loader/ImageLoader.cpp:
(WebCore::ImageLoader::notifyFinished):
(WebCore::ImageLoader::decode):
(WebCore::ImageLoader::decodeError):
* loader/ImageLoader.h:
(WebCore::ImageLoader::hasPendingDecodePromise const):
* platform/graphics/BitmapImage.cpp:
(WebCore::BitmapImage::internalStartAnimation):
(WebCore::BitmapImage::internalAdvanceAnimation):
(WebCore::BitmapImage::decode):
(WebCore::BitmapImage::imageFrameAvailableAtIndex):
* platform/graphics/BitmapImage.h:
* platform/graphics/Image.h:
(WebCore::Image::decode):

LayoutTests:

* fast/images/decode-animated-image-expected.html: Added.
* fast/images/decode-animated-image.html: Added.
* fast/images/decode-render-animated-image-expected.html: Added.
* fast/images/decode-render-animated-image.html: Added.
* fast/images/decode-render-static-image-expected.html: Added.
* fast/images/decode-render-static-image.html: Added.
* fast/images/decode-static-image-reject-expected.txt: Added.
* fast/images/decode-static-image-reject.html: Added.
* fast/images/decode-static-image-resolve-expected.html: Added.
* fast/images/decode-static-image-resolve.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@221805 268f45cc-cd09-0410-ab3c-d52691b4dbfc

20 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/images/decode-animated-image-expected.html [new file with mode: 0644]
LayoutTests/fast/images/decode-animated-image.html [new file with mode: 0644]
LayoutTests/fast/images/decode-render-animated-image-expected.html [new file with mode: 0644]
LayoutTests/fast/images/decode-render-animated-image.html [new file with mode: 0644]
LayoutTests/fast/images/decode-render-static-image-expected.html [new file with mode: 0644]
LayoutTests/fast/images/decode-render-static-image.html [new file with mode: 0644]
LayoutTests/fast/images/decode-static-image-reject-expected.txt [new file with mode: 0644]
LayoutTests/fast/images/decode-static-image-reject.html [new file with mode: 0644]
LayoutTests/fast/images/decode-static-image-resolve-expected.html [new file with mode: 0644]
LayoutTests/fast/images/decode-static-image-resolve.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLImageElement.cpp
Source/WebCore/html/HTMLImageElement.h
Source/WebCore/html/HTMLImageElement.idl
Source/WebCore/loader/ImageLoader.cpp
Source/WebCore/loader/ImageLoader.h
Source/WebCore/platform/graphics/BitmapImage.cpp
Source/WebCore/platform/graphics/BitmapImage.h
Source/WebCore/platform/graphics/Image.h

index a505e70..b2f7db8 100644 (file)
@@ -1,3 +1,21 @@
+2017-09-08  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Implement HTMLImageElement.decoode() method
+        https://bugs.webkit.org/show_bug.cgi?id=176016
+
+        Reviewed by Simon Fraser.
+
+        * fast/images/decode-animated-image-expected.html: Added.
+        * fast/images/decode-animated-image.html: Added.
+        * fast/images/decode-render-animated-image-expected.html: Added.
+        * fast/images/decode-render-animated-image.html: Added.
+        * fast/images/decode-render-static-image-expected.html: Added.
+        * fast/images/decode-render-static-image.html: Added.
+        * fast/images/decode-static-image-reject-expected.txt: Added.
+        * fast/images/decode-static-image-reject.html: Added.
+        * fast/images/decode-static-image-resolve-expected.html: Added.
+        * fast/images/decode-static-image-resolve.html: Added.
+
 2017-09-08  Joseph Pecoraro  <pecoraro@apple.com>
 
         Fetch's Response.statusText is unexpectedly the full http status line for HTTP/2 responses
diff --git a/LayoutTests/fast/images/decode-animated-image-expected.html b/LayoutTests/fast/images/decode-animated-image-expected.html
new file mode 100644 (file)
index 0000000..4b70c25
--- /dev/null
@@ -0,0 +1,11 @@
+<style>
+    div {
+        width: 100px;
+        height: 100px;
+        background-color: green;
+    }
+</style>
+<body>
+       <p>This tests calling decode() multiple times for an animated image.</p>
+    <div></div>
+</body>
\ No newline at end of file
diff --git a/LayoutTests/fast/images/decode-animated-image.html b/LayoutTests/fast/images/decode-animated-image.html
new file mode 100644 (file)
index 0000000..015320a
--- /dev/null
@@ -0,0 +1,27 @@
+<style>
+    canvas {
+        width: 100px;
+        height: 100px;
+    }
+</style>
+<body>
+    <p>This tests calling decode() multiple times for an animated image.</p>
+    <canvas></canvas>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+        var image = new Image;
+        image.src = "resources/animated-red-green-blue-repeat-2.gif";
+        // First decode() will decode the red frame.
+        image.decode().then(() => {
+            // Second decode() will decode the green frame.
+            image.decode().then(() => {
+                let canvas = document.querySelector("canvas");
+                let context = canvas.getContext("2d");
+                context.drawImage(image, 0, 0, canvas.width, canvas.height);
+                if (window.testRunner)
+                    testRunner.notifyDone();
+            });
+        });
+    </script>
+</body>
diff --git a/LayoutTests/fast/images/decode-render-animated-image-expected.html b/LayoutTests/fast/images/decode-render-animated-image-expected.html
new file mode 100644 (file)
index 0000000..b5050b7
--- /dev/null
@@ -0,0 +1,11 @@
+<style>
+    div {
+        width: 100px;
+        height: 100px;
+        background-color: blue;
+    }
+</style>
+<body>
+       <p>This tests calling decode() for a animated image which is also an element in the DOM tree.</p>
+    <div></div>
+</body>
diff --git a/LayoutTests/fast/images/decode-render-animated-image.html b/LayoutTests/fast/images/decode-render-animated-image.html
new file mode 100644 (file)
index 0000000..f6c6925
--- /dev/null
@@ -0,0 +1,55 @@
+ <style>
+     div {
+        width: 100px;
+        height: 100px;
+    }
+    canvas {
+        width: 100px;
+        height: 100px;
+        background-color: black;
+    }
+    img {
+        max-width: 100%;
+        max-height: 100%;
+    }
+</style>
+<body>
+    <p>This tests calling decode() for a animated image which is also an element in the DOM tree.</p>
+    <canvas></canvas>
+    <div></div>
+    <script>
+        if (window.internals && window.testRunner) {
+            internals.clearMemoryCache();
+            internals.settings.setWebkitImageReadyEventEnabled(true);
+            testRunner.waitUntilDone();
+        }
+
+        var image = new Image;
+        var parent = document.querySelector("div");
+        parent.appendChild(image);
+
+        image.onload = (() => {
+            if (window.internals && window.testRunner) {
+                // Force layout and display so the image gets drawn. The image will draw its red frame.
+                document.body.offsetHeight;
+                testRunner.display();
+
+                // Wait till decoding the next (green) frame finishes and the animation advances to it.
+                image.addEventListener("webkitImageFrameReady", function() {
+                    setTimeout(function() {
+                        // Request decoding the next (blue) frame. Wait till decoding finishes and the
+                        // animation advances to it.
+                        image.decode().then(() => {
+                            var canvas = document.querySelector("canvas");
+                            var context = canvas.getContext("2d");
+                            context.drawImage(image, 0, 0, canvas.width, canvas.height);
+                            parent.remove();
+                            testRunner.notifyDone();
+                        });
+                    }, 0);
+                });
+            }
+        });
+        image.src = "resources/animated-red-green-blue-repeat-2.gif";
+    </script>
+</body>
diff --git a/LayoutTests/fast/images/decode-render-static-image-expected.html b/LayoutTests/fast/images/decode-render-static-image-expected.html
new file mode 100644 (file)
index 0000000..3b5ce66
--- /dev/null
@@ -0,0 +1,11 @@
+<style>
+    div {
+        width: 200px;
+        height: 200px;
+        background-color: green;
+    }
+</style>
+<body>
+       <p>This tests calling decode() for a static image which is also an element in the DOM tree.</p>
+    <div></div>
+</body>
diff --git a/LayoutTests/fast/images/decode-render-static-image.html b/LayoutTests/fast/images/decode-render-static-image.html
new file mode 100644 (file)
index 0000000..6c2b148
--- /dev/null
@@ -0,0 +1,56 @@
+<style>
+    div {
+        width: 100px;
+        height: 100px;
+    }
+    img {
+        max-width: 100%;
+        max-height: 100%;
+    }
+</style>
+<body>
+    <p>This tests calling decode() for a static image which is also an element in the DOM tree.</p>
+    <div></div>
+    <script>
+        if (window.internals && window.testRunner) {
+            internals.clearMemoryCache();
+            internals.settings.setWebkitImageReadyEventEnabled(true);
+            internals.settings.setLargeImageAsyncDecodingEnabled(true);
+            testRunner.waitUntilDone();
+        }
+
+        var image = new Image;
+        var parent = document.querySelector("div");
+        parent.appendChild(image);
+
+        image.onload = (() => {
+            if (window.internals && window.testRunner) {
+                // Force async image decoding for this image.
+                internals.setLargeImageAsyncDecodingEnabledForTesting(image, true);
+
+                // Force layout and display so the image gets drawn.
+                document.body.offsetHeight;
+                testRunner.display();
+
+                // testRunner.display() requests an async image decoding. Wait till it finishes.
+                image.addEventListener("webkitImageFrameReady", function() {
+                    // Execute this code in the next loop run.
+                    setTimeout(function() {
+                        testRunner.display();
+                        // The image frame was decoded for the renderer which is (100x100). This
+                        // decode() will request a new decoding with the native size which is (400x400).
+                        image.decode().then(() => {
+                            parent.style.width = "200px";
+                            parent.style.height = "200px";
+                            // No extra decoding is required to display the image after changing its size.
+                            // The image frame was decoded with the native size which is the maximum size
+                            // that an image can be decoded with.
+                            testRunner.notifyDone();
+                        });
+                    }, 0);
+                });
+            }
+        });
+        image.src = "resources/green-400x400.png";
+    </script>
+</body>
diff --git a/LayoutTests/fast/images/decode-static-image-reject-expected.txt b/LayoutTests/fast/images/decode-static-image-reject-expected.txt
new file mode 100644 (file)
index 0000000..cfb21d3
--- /dev/null
@@ -0,0 +1,12 @@
+Test rejecting the decode() promise when loading the image fails.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Failed to decode image with no source. Result is: EncodingError: Missing source URL.
+Failed to decode image with non-existent source. Result is: EncodingError: Loading error.
+Failed to decode image with unsupported image format. Result is: EncodingError: Loading error.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/images/decode-static-image-reject.html b/LayoutTests/fast/images/decode-static-image-reject.html
new file mode 100644 (file)
index 0000000..81baab1
--- /dev/null
@@ -0,0 +1,28 @@
+<head>
+    <script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+    <div></div>
+    <script>
+        description("Test rejecting the decode() promise when loading the image fails.");
+        jsTestIsAsync = true;
+
+        var image = new Image;
+        image.decode()
+        .catch(reason => {
+            debug("Failed to decode image with no source. Result is: " + reason);
+            image.src = "wrongname.png";
+            return image.decode();
+        })
+        .catch(reason => {
+            debug("Failed to decode image with non-existent source. Result is: " + reason);
+            image.src = "100x100-red.psd";
+            return image.decode();
+        })
+        .catch(reason => {
+            debug("Failed to decode image with unsupported image format. Result is: " + reason);
+            finishJSTest();
+        });
+    </script>
+    <script src="../../resources/js-test-post.js"></script>
+</body>
diff --git a/LayoutTests/fast/images/decode-static-image-resolve-expected.html b/LayoutTests/fast/images/decode-static-image-resolve-expected.html
new file mode 100644 (file)
index 0000000..d7c6465
--- /dev/null
@@ -0,0 +1,11 @@
+<style>
+    div {
+        width: 100px;
+        height: 100px;
+        background-color: green;
+    }
+</style>
+<body>
+    <p>This tests resolving the decode() promise when loading and decoding a static image succeeds.</p>
+    <div></div>
+</body>
\ No newline at end of file
diff --git a/LayoutTests/fast/images/decode-static-image-resolve.html b/LayoutTests/fast/images/decode-static-image-resolve.html
new file mode 100644 (file)
index 0000000..f7f8a38
--- /dev/null
@@ -0,0 +1,24 @@
+<style>
+    canvas {
+        width: 100px;
+        height: 100px;
+        background-color: red;
+    }
+</style>
+<body>
+    <p>This tests resolving the decode() promise when loading and decoding a static image succeeds.</p>
+    <canvas></canvas>
+    <script>
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+        var image = new Image;
+        image.src = "resources/green-400x400.png";
+        image.decode().then(() => {
+            let canvas = document.querySelector("canvas");
+            let context = canvas.getContext("2d");
+            context.drawImage(image, 0, 0, canvas.width, canvas.height);
+            if (window.testRunner)
+                testRunner.notifyDone();
+        });
+    </script>
+</body>
index 399da20..6e7f266 100644 (file)
@@ -1,3 +1,48 @@
+2017-09-08  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
+        Implement HTMLImageElement.decoode() method
+        https://bugs.webkit.org/show_bug.cgi?id=176016
+
+        Reviewed by Simon Fraser.
+
+        The specs is:
+        https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode.
+
+        -- img.decode() waits till loading the image finishes. Otherwise it starts
+        decoding the image immediately.
+        -- If the image frame is already decoded, the promise will be resolved
+        before return.
+        -- If an error happens in loading the image or decoding the image frame,
+        the promise will be rejected with 'EncodingError' exception.
+        -- Animated image resolves the promise when the next frame is decoded and 
+        the animation is advanced it. If the image is not displayed, decode() will
+        request the decoding the first frame and start animating the image.
+
+        Tests: fast/images/decode-animated-image.html
+               fast/images/decode-render-animated-image.html
+               fast/images/decode-render-static-image.html
+               fast/images/decode-static-image-reject.html
+               fast/images/decode-static-image-resolve.html
+
+        * html/HTMLImageElement.cpp:
+        (WebCore::HTMLImageElement::decode):
+        * html/HTMLImageElement.h:
+        * html/HTMLImageElement.idl:
+        * loader/ImageLoader.cpp:
+        (WebCore::ImageLoader::notifyFinished):
+        (WebCore::ImageLoader::decode):
+        (WebCore::ImageLoader::decodeError):
+        * loader/ImageLoader.h:
+        (WebCore::ImageLoader::hasPendingDecodePromise const):
+        * platform/graphics/BitmapImage.cpp:
+        (WebCore::BitmapImage::internalStartAnimation):
+        (WebCore::BitmapImage::internalAdvanceAnimation):
+        (WebCore::BitmapImage::decode):
+        (WebCore::BitmapImage::imageFrameAvailableAtIndex):
+        * platform/graphics/BitmapImage.h:
+        * platform/graphics/Image.h:
+        (WebCore::Image::decode):
+
 2017-09-08  Joseph Pecoraro  <pecoraro@apple.com>
 
         Fetch's Response.statusText is unexpectedly the full http status line for HTTP/2 responses
index 38ff3c9..8c1f56f 100644 (file)
@@ -538,6 +538,11 @@ bool HTMLImageElement::complete() const
     return m_imageLoader.imageComplete();
 }
 
+void HTMLImageElement::decode(Ref<DeferredPromise>&& promise)
+{
+    return m_imageLoader.decode(WTFMove(promise));
+}
+
 void HTMLImageElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
 {
     HTMLElement::addSubresourceAttributeURLs(urls);
index bfbde90..ce965e1 100644 (file)
@@ -79,6 +79,8 @@ public:
 
     WEBCORE_EXPORT bool complete() const;
 
+    WEBCORE_EXPORT void decode(Ref<DeferredPromise>&&);
+
 #if PLATFORM(IOS)
     bool willRespondToMouseClickEvents() override;
 #endif
index 7a36027..148b91d 100644 (file)
@@ -49,5 +49,7 @@
     readonly attribute long naturalWidth;
     readonly attribute long x;
     readonly attribute long y;
+
+    Promise<void> decode();
 };
 
index 165c919..522be1e 100644 (file)
@@ -291,6 +291,9 @@ void ImageLoader::notifyFinished(CachedResource& resource)
         static NeverDestroyed<String> consoleMessage(MAKE_STATIC_STRING_IMPL("Cross-origin image load denied by Cross-Origin Resource Sharing policy."));
         element().document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, consoleMessage);
 
+        if (hasPendingDecodePromises())
+            decodeError("Access control error.");
+        
         ASSERT(!m_hasPendingLoadEvent);
 
         // Only consider updating the protection ref-count of the Element immediately before returning
@@ -300,6 +303,8 @@ void ImageLoader::notifyFinished(CachedResource& resource)
     }
 
     if (m_image->wasCanceled()) {
+        if (hasPendingDecodePromises())
+            decodeError("Loading was canceled.");
         m_hasPendingLoadEvent = false;
         // Only consider updating the protection ref-count of the Element immediately before returning
         // from this function as doing so might result in the destruction of this ImageLoader.
@@ -307,6 +312,8 @@ void ImageLoader::notifyFinished(CachedResource& resource)
         return;
     }
 
+    if (hasPendingDecodePromises())
+        decode();
     loadEventSender().dispatchEventSoon(*this);
 }
 
@@ -369,6 +376,59 @@ void ImageLoader::updatedHasPendingEvent()
     }   
 }
 
+void ImageLoader::decode(Ref<DeferredPromise>&& promise)
+{
+    m_decodingPromises.append(WTFMove(promise));
+    
+    if (!element().document().domWindow()) {
+        decodeError("Inactive document.");
+        return;
+    }
+    
+    AtomicString attr = element().imageSourceURL();
+    if (attr.isNull() || stripLeadingAndTrailingHTMLSpaces(attr).isEmpty()) {
+        decodeError("Missing source URL.");
+        return;
+    }
+    
+    if (m_imageComplete)
+        decode();
+}
+
+void ImageLoader::decodeError(String&& message)
+{
+    ASSERT(hasPendingDecodePromises());
+    for (auto& promise : m_decodingPromises)
+        promise->reject(Exception { EncodingError, WTFMove(message) });
+    m_decodingPromises.clear();
+}
+
+void ImageLoader::decode()
+{
+    ASSERT(hasPendingDecodePromises());
+    
+    if (!element().document().domWindow()) {
+        decodeError("Inactive document.");
+        return;
+    }
+
+    Image* image = m_image->image();
+    if (!image || m_image->errorOccurred()) {
+        decodeError("Loading error.");
+        return;
+    }
+
+    if (!image->isBitmapImage()) {
+        decodeError("Invalid image type.");
+        return;
+    }
+    
+    image->decode([promises = WTFMove(m_decodingPromises)]() mutable {
+        for (auto& promise : promises)
+            promise->resolve();
+    });
+}
+
 void ImageLoader::timerFired()
 {
     m_protectedElement = nullptr;
index 7717667..d039902 100644 (file)
@@ -24,7 +24,9 @@
 
 #include "CachedImageClient.h"
 #include "CachedResourceHandle.h"
+#include "JSDOMPromiseDeferred.h"
 #include "Timer.h"
+#include <wtf/Vector.h>
 #include <wtf/text/AtomicString.h>
 
 namespace WebCore {
@@ -58,6 +60,8 @@ public:
 
     CachedImage* image() const { return m_image.get(); }
     void clearImage(); // Cancels pending beforeload and load events, and doesn't dispatch new ones.
+    
+    void decode(Ref<DeferredPromise>&&);
 
     void setLoadManually(bool loadManually) { m_loadManually = loadManually; }
 
@@ -90,6 +94,10 @@ private:
     void clearImageWithoutConsideringPendingLoadEvent();
     void clearFailedLoadURL();
 
+    bool hasPendingDecodePromises() const { return !m_decodingPromises.isEmpty(); }
+    void decodeError(String&&);
+    void decode();
+    
     void timerFired();
 
     Element& m_element;
@@ -97,6 +105,7 @@ private:
     Timer m_derefElementTimer;
     RefPtr<Element> m_protectedElement;
     AtomicString m_failedLoadURL;
+    Vector<RefPtr<DeferredPromise>, 1> m_decodingPromises;
     bool m_hasPendingBeforeLoadEvent : 1;
     bool m_hasPendingLoadEvent : 1;
     bool m_hasPendingErrorEvent : 1;
index 5c08e4b..b55eeff 100644 (file)
@@ -464,7 +464,9 @@ void BitmapImage::internalAdvanceAnimation()
 
     DecodingStatus decodingStatus = frameDecodingStatusAtIndex(m_currentFrame);
     setCurrentFrameDecodingStatusIfNecessary(decodingStatus);
-    
+
+    callDecodingCallbacks();
+
     if (imageObserver())
         imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::Yes, nullptr, decodingStatus);
 
@@ -497,6 +499,47 @@ void BitmapImage::resetAnimation()
     destroyDecodedDataIfNecessary(true);
 }
 
+void BitmapImage::decode(WTF::Function<void()>&& callback)
+{
+    m_decodingCallbacks.append(WTFMove(callback));
+
+    if (canAnimate())  {
+        if (m_desiredFrameStartTime) {
+            internalStartAnimation();
+            return;
+        }
+
+        // The animated image has not been displayed. In this case, either the first frame has not been decoded yet or the animation has not started yet.
+        bool frameIsCompatible = frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>());
+        bool frameIsBeingDecoded = frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, std::optional<IntSize>());
+
+        if (frameIsCompatible)
+            internalStartAnimation();
+        else if (!frameIsBeingDecoded) {
+            m_source.requestFrameAsyncDecodingAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>());
+            m_currentFrameDecodingStatus = DecodingStatus::Decoding;
+        }
+        return;
+    }
+
+    bool frameIsCompatible = frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>());
+    bool frameIsBeingDecoded = frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, std::optional<IntSize>());
+    
+    if (frameIsCompatible)
+        callDecodingCallbacks();
+    else if (!frameIsBeingDecoded) {
+        m_source.requestFrameAsyncDecodingAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>());
+        m_currentFrameDecodingStatus = DecodingStatus::Decoding;
+    }
+}
+
+void BitmapImage::callDecodingCallbacks()
+{
+    for (auto& decodingCallback : m_decodingCallbacks)
+        decodingCallback();
+    m_decodingCallbacks.clear();
+}
+
 void BitmapImage::imageFrameAvailableAtIndex(size_t index)
 {
     LOG(Images, "BitmapImage::%s - %p - url: %s [requested frame %ld is now available]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
@@ -524,10 +567,14 @@ void BitmapImage::imageFrameAvailableAtIndex(size_t index)
 
     DecodingStatus decodingStatus = frameDecodingStatusAtIndex(m_currentFrame);
     setCurrentFrameDecodingStatusIfNecessary(decodingStatus);
-    
+
     if (m_currentFrameDecodingStatus == DecodingStatus::Complete)
         ++m_decodeCountForTesting;
 
+    // Call m_decodingCallbacks only if the image frame was decoded with the native size.
+    if (frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, std::optional<IntSize>()))
+        callDecodingCallbacks();
+
     if (imageObserver())
         imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::No, nullptr, decodingStatus);
 }
index 5a6de09..95f85e2 100644 (file)
@@ -178,8 +178,7 @@ protected:
     // automatically pause once all observers no longer want to render the image anywhere.
     void stopAnimation() override;
     void resetAnimation() override;
-    void imageFrameAvailableAtIndex(size_t) override;
-
+    
     // Handle platform-specific data
     void invalidatePlatformData();
 
@@ -197,6 +196,9 @@ private:
     bool canDestroyDecodedData();
     void setCurrentFrameDecodingStatusIfNecessary(DecodingStatus);
     bool isBitmapImage() const override { return true; }
+    void decode(WTF::Function<void()>&&) override;
+    void callDecodingCallbacks();
+    void imageFrameAvailableAtIndex(size_t) override;
     void dump(WTF::TextStream&) const override;
 
     // Animated images over a certain size are considered large enough that we'll only hang on to one frame at a time.
@@ -211,6 +213,7 @@ private:
     RepetitionCount m_repetitionsComplete { RepetitionCountNone }; // How many repetitions we've finished.
     MonotonicTime m_desiredFrameStartTime; // The system time at which we hope to see the next call to startAnimation().
 
+    Vector<Function<void()>, 1> m_decodingCallbacks;
     Seconds m_frameDecodingDurationForTesting;
     MonotonicTime m_desiredFrameDecodeTimeForTesting;
 
index 5029684..e67ee9a 100644 (file)
@@ -133,10 +133,12 @@ public:
     void startAnimationAsynchronously();
     virtual void stopAnimation() {}
     virtual void resetAnimation() {}
-    virtual void imageFrameAvailableAtIndex(size_t) { }
     virtual bool isAnimating() const { return false; }
     bool animationPending() const { return m_animationStartTimer.isActive(); }
-    
+
+    virtual void decode(WTF::Function<void()>&&) { }
+    virtual void imageFrameAvailableAtIndex(size_t) { }
+
     // Typically the CachedImage that owns us.
     ImageObserver* imageObserver() const { return m_imageObserver; }
     void setImageObserver(ImageObserver* observer) { m_imageObserver = observer; }