Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

DestinationColorSpace colorSpace() final;
Expand Down Expand Up @@ -158,6 +158,7 @@ class BitmapImage final : public Image {

void imageFrameAvailableAtIndex(size_t);
void decode(Function<void()>&&);
WEBCORE_EXPORT DecodingOptions lastDecodingOptions() const;

private:
WEBCORE_EXPORT BitmapImage(Ref<NativeImage>&&);
Expand Down Expand Up @@ -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 };
Expand All @@ -258,6 +259,7 @@ class BitmapImage final : public Image {
#endif

unsigned m_decodeCountForTesting { 0 };
DecodingOptions m_lastDecodingOptions { DecodingMode::Auto };

#if USE(APPKIT)
mutable RetainPtr<NSImage> m_nsImage; // A cached NSImage of all the frames. Only built lazily if someone actually queries for one.
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/platform/graphics/DecodingOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/platform/graphics/ImageObserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) { };
Expand Down
65 changes: 48 additions & 17 deletions Source/WebCore/rendering/RenderBoxModelObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLImageElement>(element())) {
auto decodingMode = downcast<HTMLImageElement>(*element()).decodingMode();
if (decodingMode != DecodingMode::Auto)
return decodingMode;
}
if (bitmapImage.isLargeImageAsyncDecodingEnabledForTesting())
return DecodingMode::Asynchronous;
if (document().isImageDocument())
return DecodingMode::Synchronous;
if (paintInfo.paintBehavior.contains(PaintBehavior::Snapshotting))
return DecodingMode::Synchronous;
if (!settings().largeImageAsyncDecodingEnabled())

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

auto defaultDecodingMode = [&]() -> DecodingMode {
// if (paintInfo.paintBehavior.contains(PaintBehavior::ForceSynchronousImageDecode))
// return DecodingMode::Synchronous;

// 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<HTMLImageElement>(element())) {
// <img decoding="sync"> forces synchronous decoding.
if (downcast<HTMLImageElement>(*element()).decodingMode() == DecodingMode::Synchronous)
return DecodingMode::Synchronous;

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

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

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

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

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

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

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

return drawResult;
}

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

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

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

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

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

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

undefined setGridMaxTracksLimit(unsigned long maxTracksLimit);
Expand Down