<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[218826] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/218826">218826</a></dd>
<dt>Author</dt> <dd>antti@apple.com</dd>
<dt>Date</dt> <dd>2017-06-26 20:28:00 -0700 (Mon, 26 Jun 2017)</dd>
</dl>

<h3>Log Message</h3>
<pre>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.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsplatformmacwk1TestExpectations">trunk/LayoutTests/platform/mac-wk1/TestExpectations</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCorepageFrameViewcpp">trunk/Source/WebCore/page/FrameView.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsBitmapImagecpp">trunk/Source/WebCore/platform/graphics/BitmapImage.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsBitmapImageh">trunk/Source/WebCore/platform/graphics/BitmapImage.h</a></li>
<li><a href="#trunkSourceWebCoretestingInternalscpp">trunk/Source/WebCore/testing/Internals.cpp</a></li>
<li><a href="#trunkSourceWebCoretestingInternalsh">trunk/Source/WebCore/testing/Internals.h</a></li>
<li><a href="#trunkSourceWebCoretestingInternalsidl">trunk/Source/WebCore/testing/Internals.idl</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsfastimageslowmemorydecodeexpectedtxt">trunk/LayoutTests/fast/images/low-memory-decode-expected.txt</a></li>
<li><a href="#trunkLayoutTestsfastimageslowmemorydecodehtml">trunk/LayoutTests/fast/images/low-memory-decode.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog      2017-06-27 00:26:27 UTC (rev 218825)
+++ trunk/LayoutTests/ChangeLog 2017-06-27 03:28:00 UTC (rev 218826)
</span><span class="lines">@@ -1,3 +1,14 @@
</span><ins>+2017-06-26  Antti Koivisto  <antti@apple.com>
+
+        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.
+
</ins><span class="cx"> 2017-06-26  Matt Lewis  <jlewis3@apple.com>
</span><span class="cx"> 
</span><span class="cx">         Marked media/media-source/media-source-paint-to-canvas.html as flaky.
</span></span></pre></div>
<a id="trunkLayoutTestsfastimageslowmemorydecodeexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/images/low-memory-decode-expected.txt (0 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,2 @@
</span><ins>+Image decode count: 1
+
</ins></span></pre></div>
<a id="trunkLayoutTestsfastimageslowmemorydecodehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/fast/images/low-memory-decode.html (0 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,24 @@
</span><ins>+<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="resources/flowchart.jpg" width="1000" height="1000" onload="test()">
</ins></span></pre></div>
<a id="trunkLayoutTestsplatformmacwk1TestExpectations"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -376,3 +376,5 @@
</span><span class="cx"> 
</span><span class="cx"> webkit.org/b/173432 [ Debug ] imported/w3c/web-platform-tests/fetch/nosniff/importscripts.html [ Pass Crash ]
</span><span class="cx"> 
</span><ins>+# requires wk2 speculative tiling
+fast/images/low-memory-decode.html [ Skip ]
</ins></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,39 @@
</span><ins>+2017-06-26  Antti Koivisto  <antti@apple.com>
+
+        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:
+
</ins><span class="cx"> 2017-06-26  Chris Dumez  <cdumez@apple.com>
</span><span class="cx"> 
</span><span class="cx">         ImageFrameCache::startAsyncDecodingQueue() unsafely passes Strings across threads
</span></span></pre></div>
<a id="trunkSourceWebCorepageFrameViewcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/page/FrameView.cpp (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -4371,14 +4371,6 @@
</span><span class="cx"> 
</span><span class="cx">     paintingState.isTopLevelPainter = !sCurrentPaintTimeStamp;
</span><span class="cx"> 
</span><del>-    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();
-    }
-
</del><span class="cx">     if (paintingState.isTopLevelPainter)
</span><span class="cx">         sCurrentPaintTimeStamp = monotonicallyIncreasingTime();
</span><span class="cx"> 
</span><span class="lines">@@ -4413,11 +4405,6 @@
</span><span class="cx">     m_paintBehavior = paintingState.paintBehavior;
</span><span class="cx">     m_lastPaintTime = monotonicallyIncreasingTime();
</span><span class="cx"> 
</span><del>-    // 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);
-
</del><span class="cx">     // Regions may have changed as a result of the visibility/z-index of element changing.
</span><span class="cx"> #if ENABLE(DASHBOARD_SUPPORT)
</span><span class="cx">     if (frame().document()->annotatedRegionsDirty())
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsBitmapImagecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/BitmapImage.cpp (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -230,6 +230,9 @@
</span><span class="cx">         image = frameImageAtIndexCacheIfNeeded(m_currentFrame, m_currentSubsamplingLevel, &context);
</span><span class="cx">         if (!image) // If it's too early we won't have an image yet.
</span><span class="cx">             return;
</span><ins>+
+        if (m_currentFrameDecodingStatus != ImageFrame::DecodingStatus::Complete)
+            ++m_decodeCountForTesting;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     ASSERT(image);
</span><span class="lines">@@ -494,10 +497,19 @@
</span><span class="cx">         m_source.stopAsyncDecodingQueue();
</span><span class="cx">     if (m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Decoding)
</span><span class="cx">         m_currentFrameDecodingStatus = frameDecodingStatusAtIndex(m_currentFrame);
</span><ins>+
+    if (m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Complete)
+        ++m_decodeCountForTesting;
+
</ins><span class="cx">     if (imageObserver())
</span><span class="cx">         imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::No);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+unsigned BitmapImage::decodeCountForTesting() const
+{
+    return m_decodeCountForTesting;
+}
+
</ins><span class="cx"> void BitmapImage::dump(TextStream& ts) const
</span><span class="cx"> {
</span><span class="cx">     Image::dump(ts);
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsBitmapImageh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/BitmapImage.h (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -107,6 +107,8 @@
</span><span class="cx">     bool shouldUseAsyncDecodingForAnimatedImages();
</span><span class="cx">     void setClearDecoderAfterAsyncFrameRequestForTesting(bool value) { m_clearDecoderAfterAsyncFrameRequestForTesting = value; }
</span><span class="cx"> 
</span><ins>+    WEBCORE_EXPORT unsigned decodeCountForTesting() const;
+
</ins><span class="cx">     // Accessors for native image formats.
</span><span class="cx"> #if USE(APPKIT)
</span><span class="cx">     NSImage *nsImage() override;
</span><span class="lines">@@ -229,6 +231,8 @@
</span><span class="cx">     size_t m_cachedFrameCount { 0 };
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+    unsigned m_decodeCountForTesting { 0 };
+
</ins><span class="cx"> #if USE(APPKIT)
</span><span class="cx">     mutable RetainPtr<NSImage> m_nsImage; // A cached NSImage of all the frames. Only built lazily if someone actually queries for one.
</span><span class="cx"> #endif
</span></span></pre></div>
<a id="trunkSourceWebCoretestingInternalscpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/testing/Internals.cpp (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -826,6 +826,19 @@
</span><span class="cx">     downcast<BitmapImage>(*image).setClearDecoderAfterAsyncFrameRequestForTesting(value);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+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();
+}
+
</ins><span class="cx"> void Internals::setGridMaxTracksLimit(unsigned maxTrackLimit)
</span><span class="cx"> {
</span><span class="cx">     GridPosition::setMaxPositionForTesting(maxTrackLimit);
</span></span></pre></div>
<a id="trunkSourceWebCoretestingInternalsh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/testing/Internals.h (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -125,6 +125,7 @@
</span><span class="cx">     void resetImageAnimation(HTMLImageElement&);
</span><span class="cx">     bool isImageAnimating(HTMLImageElement&);
</span><span class="cx">     void setClearDecoderAfterAsyncFrameRequestForTesting(HTMLImageElement&, bool);
</span><ins>+    unsigned imageDecodeCount(HTMLImageElement&);
</ins><span class="cx"> 
</span><span class="cx">     void setGridMaxTracksLimit(unsigned);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoretestingInternalsidl"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/testing/Internals.idl (218825 => 218826)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -252,6 +252,7 @@
</span><span class="cx">     void resetImageAnimation(HTMLImageElement element);
</span><span class="cx">     boolean isImageAnimating(HTMLImageElement element);
</span><span class="cx">     void setClearDecoderAfterAsyncFrameRequestForTesting(HTMLImageElement element, boolean value);
</span><ins>+    unsigned long imageDecodeCount(HTMLImageElement element);
</ins><span class="cx"> 
</span><span class="cx">     void setGridMaxTracksLimit(unsigned long maxTracksLimit);
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>