REGRESSION(r219045): A partially loaded image may not be repainted when its complete...
[WebKit-https.git] / Source / WebCore / platform / graphics / ImageFrameCache.cpp
index 846cebe..b564698 100644 (file)
@@ -28,6 +28,8 @@
 
 #include "Image.h"
 #include "ImageObserver.h"
+#include "Logging.h"
+#include "URL.h"
 #include <wtf/SystemTracing.h>
 
 #if USE(CG)
@@ -53,7 +55,7 @@ ImageFrameCache::ImageFrameCache(Image* image)
 ImageFrameCache::ImageFrameCache(NativeImagePtr&& nativeImage)
 {
     m_frameCount = 1;
-    m_isSizeAvailable = true;
+    m_encodedDataStatus = EncodedDataStatus::Complete;
     growFrames();
 
     setNativeImage(WTFMove(nativeImage));
@@ -97,7 +99,7 @@ void ImageFrameCache::destroyDecodedData(size_t frameCount, size_t excludeFrame)
     for (size_t index = 0; index < frameCount; ++index) {
         if (index == excludeFrame)
             continue;
-        decodedSize += m_frames[index++].clearImage();
+        decodedSize += m_frames[index].clearImage();
     }
 
     decodedSizeReset(decodedSize);
@@ -122,7 +124,7 @@ void ImageFrameCache::decodedSizeChanged(long long decodedSize)
     if (!decodedSize || !m_image || !m_image->imageObserver())
         return;
     
-    m_image->imageObserver()->decodedSizeChanged(m_image, decodedSize);
+    m_image->imageObserver()->decodedSizeChanged(*m_image, decodedSize);
 }
 
 void ImageFrameCache::decodedSizeIncreased(unsigned decodedSize)
@@ -174,8 +176,9 @@ void ImageFrameCache::didDecodeProperties(unsigned decodedPropertiesSize)
 void ImageFrameCache::growFrames()
 {
     ASSERT(isSizeAvailable());
-    ASSERT(m_frames.size() <= frameCount());
-    m_frames.grow(frameCount());
+    auto newSize = frameCount();
+    if (newSize > m_frames.size())
+        m_frames.grow(newSize);
 }
 
 void ImageFrameCache::setNativeImage(NativeImagePtr&& nativeImage)
@@ -187,18 +190,22 @@ void ImageFrameCache::setNativeImage(NativeImagePtr&& nativeImage)
 
     frame.m_nativeImage = WTFMove(nativeImage);
 
-    frame.m_decoding = ImageFrame::Decoding::Complete;
+    frame.m_decodingStatus = DecodingStatus::Complete;
     frame.m_size = nativeImageSize(frame.m_nativeImage);
     frame.m_hasAlpha = nativeImageHasAlpha(frame.m_nativeImage);
 }
 
-void ImageFrameCache::cacheFrameMetadataAtIndex(size_t index, SubsamplingLevel subsamplingLevel)
+void ImageFrameCache::cacheMetadataAtIndex(size_t index, SubsamplingLevel subsamplingLevel, DecodingStatus decodingStatus)
 {
     ASSERT(index < m_frames.size());
     ImageFrame& frame = m_frames[index];
 
     ASSERT(isDecoderAvailable());
-    frame.m_decoding = m_decoder->frameIsCompleteAtIndex(index) ? ImageFrame::Decoding::Complete : ImageFrame::Decoding::Partial;
+    if (decodingStatus == DecodingStatus::Invalid)
+        frame.m_decodingStatus = m_decoder->frameIsCompleteAtIndex(index) ? DecodingStatus::Complete : DecodingStatus::Partial;
+    else
+        frame.m_decodingStatus = decodingStatus;
+
     if (frame.hasMetadata())
         return;
     
@@ -217,7 +224,7 @@ void ImageFrameCache::cacheFrameMetadataAtIndex(size_t index, SubsamplingLevel s
         frame.m_duration = m_decoder->frameDurationAtIndex(index);
 }
 
-void ImageFrameCache::cacheFrameNativeImageAtIndex(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions)
+void ImageFrameCache::cacheNativeImageAtIndex(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions, DecodingStatus decodingStatus)
 {
     ASSERT(index < m_frames.size());
     ImageFrame& frame = m_frames[index];
@@ -233,26 +240,26 @@ void ImageFrameCache::cacheFrameNativeImageAtIndex(NativeImagePtr&& nativeImage,
     // Move the new image to the cache.
     frame.m_nativeImage = WTFMove(nativeImage);
     frame.m_decodingOptions = decodingOptions;
-    cacheFrameMetadataAtIndex(index, subsamplingLevel);
+    cacheMetadataAtIndex(index, subsamplingLevel, decodingStatus);
 
     // Update the observer with the new image frame bytes.
     decodedSizeIncreased(frame.frameBytes());
 }
 
-void ImageFrameCache::cacheAsyncFrameNativeImageAtIndex(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions)
+void ImageFrameCache::cacheNativeImageAtIndexAsync(NativeImagePtr&& nativeImage, size_t index, SubsamplingLevel subsamplingLevel, const DecodingOptions& decodingOptions, DecodingStatus decodingStatus)
 {
     if (!isDecoderAvailable())
         return;
 
     ASSERT(index < m_frames.size());
-    ASSERT(!frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(index, subsamplingLevel, decodingOptions));
 
     // Clean the old native image and set a new one
-    cacheFrameNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevel, decodingOptions);
+    cacheNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevel, decodingOptions, decodingStatus);
+    LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld has been cached]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
 
     // Notify the image with the readiness of the new frame NativeImage.
     if (m_image)
-        m_image->newFrameNativeImageAvailableAtIndex(index);
+        m_image->imageFrameAvailableAtIndex(index);
 }
 
 Ref<WorkQueue> ImageFrameCache::decodingQueue()
@@ -270,55 +277,48 @@ void ImageFrameCache::startAsyncDecodingQueue()
 
     m_frameRequestQueue.open();
 
-    Ref<ImageFrameCache> protectedThis = Ref<ImageFrameCache>(*this);
-    Ref<WorkQueue> protectedQueue = decodingQueue();
-    Ref<ImageDecoder> protectedDecoder = Ref<ImageDecoder>(*m_decoder);
-
     // We need to protect this, m_decodingQueue and m_decoder from being deleted while we are in the decoding loop.
-    decodingQueue()->dispatch([this, protectedThis = WTFMove(protectedThis), protectedQueue = WTFMove(protectedQueue), protectedDecoder = WTFMove(protectedDecoder)] {
+    decodingQueue()->dispatch([protectedThis = makeRef(*this), protectedQueue = decodingQueue(), protectedDecoder = makeRef(*m_decoder), sourceURL = sourceURL().string().isolatedCopy()] {
         ImageFrameRequest frameRequest;
 
-        while (m_frameRequestQueue.dequeue(frameRequest)) {
+        while (protectedThis->m_frameRequestQueue.dequeue(frameRequest)) {
             TraceScope tracingScope(AsyncImageDecodeStart, AsyncImageDecodeEnd);
 
             // Get the frame NativeImage on the decoding thread.
             NativeImagePtr nativeImage = protectedDecoder->createFrameImageAtIndex(frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions);
+            if (nativeImage)
+                LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld has been decoded]", __FUNCTION__, protectedThis.ptr(), sourceURL.utf8().data(), frameRequest.index);
+            else {
+                LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding for frame %ld has failed]", __FUNCTION__, protectedThis.ptr(), sourceURL.utf8().data(), frameRequest.index);
+                continue;
+            }
 
             // Update the cached frames on the main thread to avoid updating the MemoryCache from a different thread.
-            callOnMainThread([this, protectedQueue = protectedQueue.copyRef(), nativeImage, frameRequest] () mutable {
-                // The queue may be closed if after we got the frame NativeImage, stopAsyncDecodingQueue() was called
-                if (protectedQueue.ptr() == m_decodingQueue) {
-                    ASSERT(m_frameCommitQueue.first() == frameRequest);
-                    m_frameCommitQueue.removeFirst();
-                    cacheAsyncFrameNativeImageAtIndex(WTFMove(nativeImage), frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions);
-                }
+            callOnMainThread([protectedThis = protectedThis.copyRef(), protectedQueue = protectedQueue.copyRef(), protectedDecoder = protectedDecoder.copyRef(), sourceURL = sourceURL.isolatedCopy(), nativeImage = WTFMove(nativeImage), frameRequest] () mutable {
+                // The queue may have been closed if after we got the frame NativeImage, stopAsyncDecodingQueue() was called.
+                if (protectedQueue.ptr() == protectedThis->m_decodingQueue && protectedDecoder.ptr() == protectedThis->m_decoder) {
+                    ASSERT(protectedThis->m_frameCommitQueue.first() == frameRequest);
+                    protectedThis->m_frameCommitQueue.removeFirst();
+                    protectedThis->cacheNativeImageAtIndexAsync(WTFMove(nativeImage), frameRequest.index, frameRequest.subsamplingLevel, frameRequest.decodingOptions, frameRequest.decodingStatus);
+                } else
+                    LOG(Images, "ImageFrameCache::%s - %p - url: %s [frame %ld will not cached]", __FUNCTION__, protectedThis.ptr(), sourceURL.utf8().data(), frameRequest.index);
             });
         }
     });
 }
 
-bool ImageFrameCache::requestFrameAsyncDecodingAtIndex(size_t index, SubsamplingLevel subsamplingLevel, const std::optional<IntSize>& sizeForDrawing)
+void ImageFrameCache::requestFrameAsyncDecodingAtIndex(size_t index, SubsamplingLevel subsamplingLevel, const std::optional<IntSize>& sizeForDrawing)
 {
-    if (!isDecoderAvailable())
-        return false;
-
-    ASSERT(index < m_frames.size());
-
-    // We need to coalesce multiple requests for decoding the same ImageFrame while it
-    // is still being decoded. This may happen if the image rectangle is repainted
-    // multiple times while the ImageFrame has not finished decoding.
-    if (frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(index, sizeForDrawing))
-        return true;
-
-    if (frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(index, subsamplingLevel, sizeForDrawing))
-        return false;
-
+    ASSERT(isDecoderAvailable());
     if (!hasAsyncDecodingQueue())
         startAsyncDecodingQueue();
+    
+    ASSERT(index < m_frames.size());
+    DecodingStatus decodingStatus = m_decoder->frameIsCompleteAtIndex(index) ? DecodingStatus::Complete : DecodingStatus::Partial;
 
-    m_frameRequestQueue.enqueue({ index, subsamplingLevel, sizeForDrawing });
-    m_frameCommitQueue.append({ index, subsamplingLevel, sizeForDrawing });
-    return true;
+    LOG(Images, "ImageFrameCache::%s - %p - url: %s [enqueuing frame %ld for decoding]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
+    m_frameRequestQueue.enqueue({ index, subsamplingLevel, sizeForDrawing, decodingStatus });
+    m_frameCommitQueue.append({ index, subsamplingLevel, sizeForDrawing, decodingStatus });
 }
 
 bool ImageFrameCache::isAsyncDecodingQueueIdle() const
@@ -333,13 +333,16 @@ void ImageFrameCache::stopAsyncDecodingQueue()
     
     std::for_each(m_frameCommitQueue.begin(), m_frameCommitQueue.end(), [this](const ImageFrameRequest& frameRequest) {
         ImageFrame& frame = m_frames[frameRequest.index];
-        if (!frame.isEmpty())
+        if (!frame.isInvalid()) {
+            LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been cancelled for frame %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), frameRequest.index);
             frame.clear();
+        }
     });
 
     m_frameRequestQueue.close();
     m_frameCommitQueue.clear();
     m_decodingQueue = nullptr;
+    LOG(Images, "ImageFrameCache::%s - %p - url: %s [decoding has been stopped]", __FUNCTION__, this, sourceURL().string().utf8().data());
 }
 
 const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, ImageFrame::Caching caching, const std::optional<SubsamplingLevel>& subsamplingLevel)
@@ -356,7 +359,7 @@ const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, Image
         // Retrieve the metadata from ImageDecoder if the ImageFrame isn't complete.
         if (frame.isComplete())
             break;
-        cacheFrameMetadataAtIndex(index, subsamplingLevelValue);
+        cacheMetadataAtIndex(index, subsamplingLevelValue);
         break;
             
     case ImageFrame::Caching::MetadataAndImage:
@@ -366,7 +369,7 @@ const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, Image
         // We have to perform synchronous image decoding in this code. 
         NativeImagePtr nativeImage = m_decoder->createFrameImageAtIndex(index, subsamplingLevelValue);
         // Clean the old native image and set a new one.
-        cacheFrameNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevelValue, DecodingMode::Synchronous);
+        cacheNativeImageAtIndex(WTFMove(nativeImage), index, subsamplingLevelValue, DecodingMode::Synchronous);
         break;
     }
 
@@ -376,7 +379,15 @@ const ImageFrame& ImageFrameCache::frameAtIndexCacheIfNeeded(size_t index, Image
 void ImageFrameCache::clearMetadata()
 {
     m_frameCount = std::nullopt;
+    m_repetitionCount = std::nullopt;
     m_singlePixelSolidColor = std::nullopt;
+    m_encodedDataStatus = std::nullopt;
+    m_uti = std::nullopt;
+}
+
+URL ImageFrameCache::sourceURL() const
+{
+    return m_image ? m_image->sourceURL() : URL();
 }
 
 template<typename T, T (ImageDecoder::*functor)() const>
@@ -419,17 +430,9 @@ T ImageFrameCache::frameMetadataAtIndexCacheIfNeeded(size_t index, T (ImageFrame
     return cachedValue->value();
 }
 
-bool ImageFrameCache::isSizeAvailable()
+EncodedDataStatus ImageFrameCache::encodedDataStatus()
 {
-    if (m_isSizeAvailable)
-        return m_isSizeAvailable.value();
-    
-    if (!isDecoderAvailable() || !m_decoder->isSizeAvailable())
-        return false;
-    
-    m_isSizeAvailable = true;
-    didDecodeProperties(m_decoder->bytesDecodedToDetermineProperties());
-    return true;
+    return metadata<EncodedDataStatus, (&ImageDecoder::encodedDataStatus)>(EncodedDataStatus::Unknown, &m_encodedDataStatus);
 }
 
 size_t ImageFrameCache::frameCount()
@@ -441,6 +444,15 @@ RepetitionCount ImageFrameCache::repetitionCount()
 {
     return metadata<RepetitionCount, (&ImageDecoder::repetitionCount)>(RepetitionCountNone, &m_repetitionCount);
 }
+    
+String ImageFrameCache::uti()
+{
+#if USE(CG)
+    return metadata<String, (&ImageDecoder::uti)>(String(), &m_uti);
+#else
+    return String();
+#endif
+}
 
 String ImageFrameCache::filenameExtension()
 {
@@ -454,6 +466,12 @@ std::optional<IntPoint> ImageFrameCache::hotSpot()
 
 IntSize ImageFrameCache::size()
 {
+#if !USE(CG)
+    // It's possible that we have decoded the metadata, but not frame contents yet. In that case ImageDecoder claims to
+    // have the size available, but the frame cache is empty. Return the decoder size without caching in such case.
+    if (m_frames.isEmpty() && isDecoderAvailable())
+        return m_decoder->size();
+#endif
     return frameMetadataAtIndexCacheIfNeeded<IntSize>(0, (&ImageFrame::size), &m_size, ImageFrame::Caching::Metadata, SubsamplingLevel::Default);
 }
 
@@ -481,9 +499,9 @@ bool ImageFrameCache::frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(size_
     return it != m_frameCommitQueue.end();
 }
 
-bool ImageFrameCache::frameIsCompleteAtIndex(size_t index)
+DecodingStatus ImageFrameCache::frameDecodingStatusAtIndex(size_t index)
 {
-    return frameMetadataAtIndex<bool>(index, (&ImageFrame::isComplete));
+    return frameMetadataAtIndex<DecodingStatus>(index, (&ImageFrame::decodingStatus));
 }
 
 bool ImageFrameCache::frameHasAlphaAtIndex(size_t index)