- Revision
- 218826
- Author
- [email protected]
- Date
- 2017-06-26 20:28:00 -0700 (Mon, 26 Jun 2017)
Log Message
REGRESSION (AsyncImageDecoding): A tab with the WWDC keynote paused is killed for using excessive power (Image thrashing)
https://bugs.webkit.org/show_bug.cgi?id=173804
<rdar://problem/32623745>
Reviewed by Simon Fraser.
Source/WebCore:
When under memory pressure MemoryCache::singleton().pruneLiveResources(true) is called inFrameView::didPaintContents()
after top level paint. We end up decoding and pruning bitmaps repeatedly for each tile, which is not great.
Situation gets worse with async decoding. Painting now doesn’t actually decode the image, it just starts the decoding.
When it completes we trigger another paint to get the bits to the tiles. The paint for the first tile then calls
pruneLiveResources and loses the bitmap and the second tile triggers another round of async decoding. We have code
that prevents pruning of visible images but non-visible images in tiling area can hit this bug easily.
Test: fast/images/low-memory-decode.html
* page/FrameView.cpp:
(WebCore::FrameView::willPaintContents):
(WebCore::FrameView::didPaintContents):
Eliminate synchronous pruning during painting. This is an obsolete mechanism from early iOS times.
* platform/graphics/BitmapImage.cpp:
(WebCore::BitmapImage::imageFrameAvailableAtIndex):
(WebCore::BitmapImage::decodeCountForTesting):
Testing support.
* platform/graphics/BitmapImage.h:
* testing/Internals.cpp:
(WebCore::Internals::imageDecodeCount):
* testing/Internals.h:
* testing/Internals.idl:
LayoutTests:
* fast/images/low-memory-decode-expected.txt: Added.
* fast/images/low-memory-decode.html: Added.
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (218825 => 218826)
--- trunk/LayoutTests/ChangeLog 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/LayoutTests/ChangeLog 2017-06-27 03:28:00 UTC (rev 218826)
@@ -1,3 +1,14 @@
+2017-06-26 Antti Koivisto <[email protected]>
+
+ REGRESSION (AsyncImageDecoding): A tab with the WWDC keynote paused is killed for using excessive power (Image thrashing)
+ https://bugs.webkit.org/show_bug.cgi?id=173804
+ <rdar://problem/32623745>
+
+ Reviewed by Simon Fraser.
+
+ * fast/images/low-memory-decode-expected.txt: Added.
+ * fast/images/low-memory-decode.html: Added.
+
2017-06-26 Matt Lewis <[email protected]>
Marked media/media-source/media-source-paint-to-canvas.html as flaky.
Added: trunk/LayoutTests/fast/images/low-memory-decode-expected.txt (0 => 218826)
--- trunk/LayoutTests/fast/images/low-memory-decode-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/images/low-memory-decode-expected.txt 2017-06-27 03:28:00 UTC (rev 218826)
@@ -0,0 +1,2 @@
+Image decode count: 1
+
Added: trunk/LayoutTests/fast/images/low-memory-decode.html (0 => 218826)
--- trunk/LayoutTests/fast/images/low-memory-decode.html (rev 0)
+++ trunk/LayoutTests/fast/images/low-memory-decode.html 2017-06-27 03:28:00 UTC (rev 218826)
@@ -0,0 +1,24 @@
+<script>
+if (window.testRunner) {
+ testRunner.waitUntilDone();
+ testRunner.dumpAsText();
+ internals.settings.setLargeImageAsyncDecodingEnabled(true);
+ internals.beginSimulatedMemoryPressure();
+}
+function tryFinish() {
+ const decodeCount = internals.imageDecodeCount(image);
+ if (decodeCount < 1)
+ return;
+ log.innerHTML = `Image decode count: ${internals.imageDecodeCount(image)}`;
+ internals.endSimulatedMemoryPressure();
+ testRunner.notifyDone()
+}
+
+function test() {
+ if (!window.testRunner)
+ return;
+ setInterval(tryFinish, 100);
+}
+</script>
+<div id=log style="height:1000px"></div>
+<img id=image src="" width="1000" height="1000" _onload_="test()">
Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (218825 => 218826)
--- trunk/LayoutTests/platform/mac-wk1/TestExpectations 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations 2017-06-27 03:28:00 UTC (rev 218826)
@@ -376,3 +376,5 @@
webkit.org/b/173432 [ Debug ] imported/w3c/web-platform-tests/fetch/nosniff/importscripts.html [ Pass Crash ]
+# requires wk2 speculative tiling
+fast/images/low-memory-decode.html [ Skip ]
Modified: trunk/Source/WebCore/ChangeLog (218825 => 218826)
--- trunk/Source/WebCore/ChangeLog 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/Source/WebCore/ChangeLog 2017-06-27 03:28:00 UTC (rev 218826)
@@ -1,3 +1,39 @@
+2017-06-26 Antti Koivisto <[email protected]>
+
+ REGRESSION (AsyncImageDecoding): A tab with the WWDC keynote paused is killed for using excessive power (Image thrashing)
+ https://bugs.webkit.org/show_bug.cgi?id=173804
+ <rdar://problem/32623745>
+
+ Reviewed by Simon Fraser.
+
+ When under memory pressure MemoryCache::singleton().pruneLiveResources(true) is called inFrameView::didPaintContents()
+ after top level paint. We end up decoding and pruning bitmaps repeatedly for each tile, which is not great.
+
+ Situation gets worse with async decoding. Painting now doesn’t actually decode the image, it just starts the decoding.
+ When it completes we trigger another paint to get the bits to the tiles. The paint for the first tile then calls
+ pruneLiveResources and loses the bitmap and the second tile triggers another round of async decoding. We have code
+ that prevents pruning of visible images but non-visible images in tiling area can hit this bug easily.
+
+ Test: fast/images/low-memory-decode.html
+
+ * page/FrameView.cpp:
+ (WebCore::FrameView::willPaintContents):
+ (WebCore::FrameView::didPaintContents):
+
+ Eliminate synchronous pruning during painting. This is an obsolete mechanism from early iOS times.
+
+ * platform/graphics/BitmapImage.cpp:
+ (WebCore::BitmapImage::imageFrameAvailableAtIndex):
+ (WebCore::BitmapImage::decodeCountForTesting):
+
+ Testing support.
+
+ * platform/graphics/BitmapImage.h:
+ * testing/Internals.cpp:
+ (WebCore::Internals::imageDecodeCount):
+ * testing/Internals.h:
+ * testing/Internals.idl:
+
2017-06-26 Chris Dumez <[email protected]>
ImageFrameCache::startAsyncDecodingQueue() unsafely passes Strings across threads
Modified: trunk/Source/WebCore/page/FrameView.cpp (218825 => 218826)
--- trunk/Source/WebCore/page/FrameView.cpp 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/Source/WebCore/page/FrameView.cpp 2017-06-27 03:28:00 UTC (rev 218826)
@@ -4371,14 +4371,6 @@
paintingState.isTopLevelPainter = !sCurrentPaintTimeStamp;
- if (paintingState.isTopLevelPainter && MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
- LOG(MemoryPressure, "Under memory pressure: %s", WTF_PRETTY_FUNCTION);
-
- // To avoid unnecessary image decoding, we don't prune recently-decoded live resources here since
- // we might need some live bitmaps on painting.
- MemoryCache::singleton().prune();
- }
-
if (paintingState.isTopLevelPainter)
sCurrentPaintTimeStamp = monotonicallyIncreasingTime();
@@ -4413,11 +4405,6 @@
m_paintBehavior = paintingState.paintBehavior;
m_lastPaintTime = monotonicallyIncreasingTime();
- // Painting can lead to decoding of large amounts of bitmaps
- // If we are low on memory, wipe them out after the paint.
- if (paintingState.isTopLevelPainter && MemoryPressureHandler::singleton().isUnderMemoryPressure())
- MemoryCache::singleton().pruneLiveResources(true);
-
// Regions may have changed as a result of the visibility/z-index of element changing.
#if ENABLE(DASHBOARD_SUPPORT)
if (frame().document()->annotatedRegionsDirty())
Modified: trunk/Source/WebCore/platform/graphics/BitmapImage.cpp (218825 => 218826)
--- trunk/Source/WebCore/platform/graphics/BitmapImage.cpp 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/Source/WebCore/platform/graphics/BitmapImage.cpp 2017-06-27 03:28:00 UTC (rev 218826)
@@ -230,6 +230,9 @@
image = frameImageAtIndexCacheIfNeeded(m_currentFrame, m_currentSubsamplingLevel, &context);
if (!image) // If it's too early we won't have an image yet.
return;
+
+ if (m_currentFrameDecodingStatus != ImageFrame::DecodingStatus::Complete)
+ ++m_decodeCountForTesting;
}
ASSERT(image);
@@ -494,10 +497,19 @@
m_source.stopAsyncDecodingQueue();
if (m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Decoding)
m_currentFrameDecodingStatus = frameDecodingStatusAtIndex(m_currentFrame);
+
+ if (m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Complete)
+ ++m_decodeCountForTesting;
+
if (imageObserver())
imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::No);
}
+unsigned BitmapImage::decodeCountForTesting() const
+{
+ return m_decodeCountForTesting;
+}
+
void BitmapImage::dump(TextStream& ts) const
{
Image::dump(ts);
Modified: trunk/Source/WebCore/platform/graphics/BitmapImage.h (218825 => 218826)
--- trunk/Source/WebCore/platform/graphics/BitmapImage.h 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/Source/WebCore/platform/graphics/BitmapImage.h 2017-06-27 03:28:00 UTC (rev 218826)
@@ -107,6 +107,8 @@
bool shouldUseAsyncDecodingForAnimatedImages();
void setClearDecoderAfterAsyncFrameRequestForTesting(bool value) { m_clearDecoderAfterAsyncFrameRequestForTesting = value; }
+ WEBCORE_EXPORT unsigned decodeCountForTesting() const;
+
// Accessors for native image formats.
#if USE(APPKIT)
NSImage *nsImage() override;
@@ -229,6 +231,8 @@
size_t m_cachedFrameCount { 0 };
#endif
+ unsigned m_decodeCountForTesting { 0 };
+
#if USE(APPKIT)
mutable RetainPtr<NSImage> m_nsImage; // A cached NSImage of all the frames. Only built lazily if someone actually queries for one.
#endif
Modified: trunk/Source/WebCore/testing/Internals.cpp (218825 => 218826)
--- trunk/Source/WebCore/testing/Internals.cpp 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/Source/WebCore/testing/Internals.cpp 2017-06-27 03:28:00 UTC (rev 218826)
@@ -826,6 +826,19 @@
downcast<BitmapImage>(*image).setClearDecoderAfterAsyncFrameRequestForTesting(value);
}
+unsigned Internals::imageDecodeCount(HTMLImageElement& element)
+{
+ auto* cachedImage = element.cachedImage();
+ if (!cachedImage)
+ return 0;
+
+ auto* image = cachedImage->image();
+ if (!is<BitmapImage>(image))
+ return 0;
+
+ return downcast<BitmapImage>(*image).decodeCountForTesting();
+}
+
void Internals::setGridMaxTracksLimit(unsigned maxTrackLimit)
{
GridPosition::setMaxPositionForTesting(maxTrackLimit);
Modified: trunk/Source/WebCore/testing/Internals.h (218825 => 218826)
--- trunk/Source/WebCore/testing/Internals.h 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/Source/WebCore/testing/Internals.h 2017-06-27 03:28:00 UTC (rev 218826)
@@ -125,6 +125,7 @@
void resetImageAnimation(HTMLImageElement&);
bool isImageAnimating(HTMLImageElement&);
void setClearDecoderAfterAsyncFrameRequestForTesting(HTMLImageElement&, bool);
+ unsigned imageDecodeCount(HTMLImageElement&);
void setGridMaxTracksLimit(unsigned);
Modified: trunk/Source/WebCore/testing/Internals.idl (218825 => 218826)
--- trunk/Source/WebCore/testing/Internals.idl 2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/Source/WebCore/testing/Internals.idl 2017-06-27 03:28:00 UTC (rev 218826)
@@ -252,6 +252,7 @@
void resetImageAnimation(HTMLImageElement element);
boolean isImageAnimating(HTMLImageElement element);
void setClearDecoderAfterAsyncFrameRequestForTesting(HTMLImageElement element, boolean value);
+ unsigned long imageDecodeCount(HTMLImageElement element);
void setGridMaxTracksLimit(unsigned long maxTracksLimit);