When navigating, discard decoded image data that is only live due to page cache.
authorakling@apple.com <akling@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Jun 2016 17:23:49 +0000 (17:23 +0000)
committerakling@apple.com <akling@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Jun 2016 17:23:49 +0000 (17:23 +0000)
<https://webkit.org/b/158941>

Reviewed by Antti Koivisto.

A resource is "live" if it's currently in use by a web page, and "dead" if it's
only kept alive by the memory cache.

This patch adds a mechanism that looks at CachedImage resources to see if all the
clients that make them appear "live" are actually pages in the page cache.

If so, we let the "jettison expensive objects on top-level navigation" mechanism
discard the decoded data for such half-live images. This can reduce the peak
memory usage during navigations quite a bit.

* loader/FrameLoader.cpp:
(WebCore::FrameLoader::commitProvisionalLoad): Move the call to MemoryPressureHandler
before we add the outgoing page to the page cache. This allows the jettisoning code
to make decisions based on which pages were cached *before* the navigation.

* loader/cache/CachedImageClient.h:
(WebCore::CachedImageClient::inPageCache):
* loader/ImageLoader.h:
* loader/ImageLoader.cpp:
(WebCore::ImageLoader::inPageCache):
* rendering/RenderObject.h:
(WebCore::RenderObject::inPageCache): Added a CachedImageClient::inPageCache() virtual
to determine which clients are currently in page cache (answered by their Document.)

* loader/cache/CachedImage.h:
* loader/cache/CachedImage.cpp:
(WebCore::CachedImage::areAllClientsInPageCache): Walks all CachedImageClient clients
and returns true if all of them are inPageCache().

* platform/MemoryPressureHandler.cpp:
(WebCore::MemoryPressureHandler::jettisonExpensiveObjectsOnTopLevelNavigation):
Walk all the known CachedImages and nuke decoded data for those that have some but
are only considered live due to clients in the page cache.

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

Source/WebCore/ChangeLog
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/loader/ImageLoader.cpp
Source/WebCore/loader/ImageLoader.h
Source/WebCore/loader/cache/CachedImage.cpp
Source/WebCore/loader/cache/CachedImage.h
Source/WebCore/loader/cache/CachedImageClient.h
Source/WebCore/platform/MemoryPressureHandler.cpp
Source/WebCore/rendering/RenderObject.h

index c412cf8..e2a8e2b 100644 (file)
@@ -1,3 +1,44 @@
+2016-06-20  Andreas Kling  <akling@apple.com>
+
+        When navigating, discard decoded image data that is only live due to page cache.
+        <https://webkit.org/b/158941>
+
+        Reviewed by Antti Koivisto.
+
+        A resource is "live" if it's currently in use by a web page, and "dead" if it's
+        only kept alive by the memory cache.
+
+        This patch adds a mechanism that looks at CachedImage resources to see if all the
+        clients that make them appear "live" are actually pages in the page cache.
+
+        If so, we let the "jettison expensive objects on top-level navigation" mechanism
+        discard the decoded data for such half-live images. This can reduce the peak
+        memory usage during navigations quite a bit.
+
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::commitProvisionalLoad): Move the call to MemoryPressureHandler
+        before we add the outgoing page to the page cache. This allows the jettisoning code
+        to make decisions based on which pages were cached *before* the navigation.
+
+        * loader/cache/CachedImageClient.h:
+        (WebCore::CachedImageClient::inPageCache):
+        * loader/ImageLoader.h:
+        * loader/ImageLoader.cpp:
+        (WebCore::ImageLoader::inPageCache):
+        * rendering/RenderObject.h:
+        (WebCore::RenderObject::inPageCache): Added a CachedImageClient::inPageCache() virtual
+        to determine which clients are currently in page cache (answered by their Document.)
+
+        * loader/cache/CachedImage.h:
+        * loader/cache/CachedImage.cpp:
+        (WebCore::CachedImage::areAllClientsInPageCache): Walks all CachedImageClient clients
+        and returns true if all of them are inPageCache().
+
+        * platform/MemoryPressureHandler.cpp:
+        (WebCore::MemoryPressureHandler::jettisonExpensiveObjectsOnTopLevelNavigation):
+        Walk all the known CachedImages and nuke decoded data for those that have some but
+        are only considered live due to clients in the page cache.
+
 2016-06-20  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed, fix post-landing review comment from Darin on r202188.
index 0b0fbe8..532e021 100644 (file)
@@ -1783,11 +1783,11 @@ void FrameLoader::commitProvisionalLoad()
     willTransitionToCommitted();
 
     if (!m_frame.tree().parent() && history().currentItem()) {
+        MemoryPressureHandler::singleton().jettisonExpensiveObjectsOnTopLevelNavigation();
+
         // Check to see if we need to cache the page we are navigating away from into the back/forward cache.
         // We are doing this here because we know for sure that a new page is about to be loaded.
         PageCache::singleton().addIfCacheable(*history().currentItem(), m_frame.page());
-        
-        MemoryPressureHandler::singleton().jettisonExpensiveObjectsOnTopLevelNavigation();
     }
 
     if (m_loadType != FrameLoadType::Replace)
index 0f2b7d5..a929775 100644 (file)
@@ -472,4 +472,9 @@ inline void ImageLoader::clearFailedLoadURL()
     m_failedLoadURL = AtomicString();
 }
 
+bool ImageLoader::inPageCache() const
+{
+    return m_element.document().inPageCache();
+}
+
 }
index 56605b8..e1b45a3 100644 (file)
@@ -79,6 +79,8 @@ private:
     virtual void dispatchLoadEvent() = 0;
     virtual String sourceURI(const AtomicString&) const = 0;
 
+    bool inPageCache() const final;
+
     void updatedHasPendingEvent();
 
     void dispatchPendingBeforeLoadEvent();
index 4f54525..059f35e 100644 (file)
@@ -527,4 +527,16 @@ CachedResource::RevalidationDecision CachedImage::makeRevalidationDecision(Cache
     return CachedResource::makeRevalidationDecision(cachePolicy);
 }
 
+bool CachedImage::areAllClientsInPageCache() const
+{
+    for (auto& entry : m_clients) {
+        auto& client = *entry.key;
+        if (client.resourceClientType() != CachedImageClient::expectedType())
+            continue;
+        if (!static_cast<CachedImageClient&>(client).inPageCache())
+            return false;
+    }
+    return true;
+}
+
 } // namespace WebCore
index 34eaec0..9eccc6d 100644 (file)
@@ -75,6 +75,8 @@ public:
     void addDataBuffer(SharedBuffer&) override;
     void finishLoading(SharedBuffer*) override;
 
+    bool areAllClientsInPageCache() const;
+
     enum SizeType {
         UsedSize,
         IntrinsicSize
index 72235ac..5163c8e 100644 (file)
@@ -42,6 +42,8 @@ public:
 
     // Called when GIF animation progresses.
     virtual void newImageAnimationFrameAvailable(CachedImage& image) { imageChanged(&image); }
+
+    virtual bool inPageCache() const { return false; }
 };
 
 }
index ed0e88f..e42d902 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, 2014 Apple Inc. All Rights Reserved.
+ * Copyright (C) 2011-2016 Apple Inc. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -27,6 +27,7 @@
 #include "MemoryPressureHandler.h"
 
 #include "CSSValuePool.h"
+#include "CachedImage.h"
 #include "Chrome.h"
 #include "ChromeClient.h"
 #include "Document.h"
@@ -162,7 +163,6 @@ void MemoryPressureHandler::releaseCriticalMemory(Synchronous synchronous)
 
 void MemoryPressureHandler::jettisonExpensiveObjectsOnTopLevelNavigation()
 {
-#if PLATFORM(IOS)
     // Protect against doing excessive jettisoning during repeated navigations.
     const auto minimumTimeSinceNavigation = 2s;
 
@@ -174,6 +174,15 @@ void MemoryPressureHandler::jettisonExpensiveObjectsOnTopLevelNavigation()
     if (!shouldJettison)
         return;
 
+    MemoryCache::singleton().forEachResource([](CachedResource& resource) {
+        if (resource.isImage()
+            && resource.decodedSize()
+            && downcast<CachedImage>(resource).areAllClientsInPageCache()) {
+            resource.destroyDecodedData();
+        }
+    });
+
+#if PLATFORM(IOS)
     // Throw away linked JS code. Linked code is tied to a global object and is not reusable.
     // The immediate memory savings outweigh the cost of recompilation in case we go back again.
     GCController::singleton().deleteAllLinkedCode();
index e3a7d7f..8474cbb 100644 (file)
@@ -844,6 +844,8 @@ private:
     void setNeedsLayoutIsForbidden(bool flag) { m_setNeedsLayoutForbidden = flag; }
 #endif
 
+    bool inPageCache() const final { return document().inPageCache(); }
+
     void addAbsoluteRectForLayer(LayoutRect& result);
     void setLayerNeedsFullRepaint();
     void setLayerNeedsFullRepaintForPositionedMovementLayout();