Title: [221710] trunk
Revision
221710
Author
[email protected]
Date
2017-09-06 17:03:13 -0700 (Wed, 06 Sep 2017)

Log Message

NetworkProcess Cache and Caches should be cleared when the last related WebProcess Cache or CacheStorage is destroyed
https://bugs.webkit.org/show_bug.cgi?id=176249

Patch by Youenn Fablet <[email protected]> on 2017-09-06
Reviewed by Alex Christensen.

Source/WebCore:

Test: http/tests/cache-storage/cache-representation.https.html

Each Cache construction/destruction is notified to the CacheStorageConnection
so that the WebKit2 engine can handle memory management accordingly.

Adding an internal API to grab the representation of an engine at any time.

* Modules/cache/Cache.cpp:
(WebCore::Cache::Cache):
(WebCore::Cache::~Cache):
* Modules/cache/CacheStorageConnection.h:
(WebCore::CacheStorageConnection::reference):
(WebCore::CacheStorageConnection::dereference):
* Modules/cache/WorkerCacheStorageConnection.cpp:
(WebCore::WorkerCacheStorageConnection::reference):
(WebCore::WorkerCacheStorageConnection::dereference):
* Modules/cache/WorkerCacheStorageConnection.h:
* testing/Internals.cpp:
(WebCore::Internals::cacheStorageEngineRepresentation):
* testing/Internals.h:
* testing/Internals.idl:

Source/WebKit:

CacheStorageEngineConnection will store the number of Caches references for its related WebProcess.
For a new reference, CacheStorageEngineConnection will notify its engine to lock the related Cache.
When the last reference is gone, CacheStorageEngineConnection will ask the engine to release the lock.

CacheStorageEngine will keep locks as a counter for each Cache object.
When the counter goes to zero, the engine asks the Cache to dispose itself.
Disposal is done by its parent Caches as only Caches knows whether the Cache is persistent or has been removed.

The Caches will remove the Cache from memory.
For removed caches, all information will be lost.
For caches that are not removed, they will keep their identifiers but all in-memory record information will be discarded.
If there is a need to reopen the Cache, it will need to read its information from the filesystem.
If the Caches has no longer any active Cache object, it will ask the engine to release itself.

WebProcess crashing is handled correctly as the CacheStorageEngineConnection will release its locks at destruction
time of the connection to the web process.

Adding the possiblity to grab an engine representation for test purposes.

* NetworkProcess/cache/CacheStorageEngine.cpp:
(WebKit::CacheStorage::Engine::readCachesFromDisk):
(WebKit::CacheStorage::Engine::removeCaches):
(WebKit::CacheStorage::Engine::lock):
(WebKit::CacheStorage::Engine::unlock):
* NetworkProcess/cache/CacheStorageEngine.h:
* NetworkProcess/cache/CacheStorageEngineCache.cpp:
(WebKit::CacheStorage::Cache::Cache):
(WebKit::CacheStorage::Cache::dispose):
(WebKit::CacheStorage::Cache::clearMemoryRepresentation):
* NetworkProcess/cache/CacheStorageEngineCache.h:
(WebKit::CacheStorage::Cache::isActive const):
* NetworkProcess/cache/CacheStorageEngineCaches.cpp:
(WebKit::CacheStorage::Caches::Caches):
(WebKit::CacheStorage::Caches::open):
(WebKit::CacheStorage::Caches::dispose):
(WebKit::CacheStorage::Caches::readCachesFromDisk):
(WebKit::CacheStorage::Caches::clearMemoryRepresentation):
* NetworkProcess/cache/CacheStorageEngineCaches.h:
(WebKit::CacheStorage::Caches::create):
* NetworkProcess/cache/CacheStorageEngineConnection.cpp:
(WebKit::CacheStorageEngineConnection::~CacheStorageEngineConnection):
(WebKit::CacheStorageEngineConnection::reference):
(WebKit::CacheStorageEngineConnection::dereference):
* NetworkProcess/cache/CacheStorageEngineConnection.h:
* NetworkProcess/cache/CacheStorageEngineConnection.messages.in:
* WebProcess/Cache/WebCacheStorageConnection.cpp:
(WebKit::WebCacheStorageConnection::reference):
(WebKit::WebCacheStorageConnection::dereference):
* WebProcess/Cache/WebCacheStorageConnection.h:

LayoutTests:

* http/tests/cache-storage/cache-representation.https-expected.txt: Added.
* http/tests/cache-storage/cache-representation.https.html: Added.
* http/tests/cache-storage/resources/cache-persistency-iframe.html:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (221709 => 221710)


--- trunk/LayoutTests/ChangeLog	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/LayoutTests/ChangeLog	2017-09-07 00:03:13 UTC (rev 221710)
@@ -1,3 +1,14 @@
+2017-09-06  Youenn Fablet  <[email protected]>
+
+        NetworkProcess Cache and Caches should be cleared when the last related WebProcess Cache or CacheStorage is destroyed
+        https://bugs.webkit.org/show_bug.cgi?id=176249
+
+        Reviewed by Alex Christensen.
+
+        * http/tests/cache-storage/cache-representation.https-expected.txt: Added.
+        * http/tests/cache-storage/cache-representation.https.html: Added.
+        * http/tests/cache-storage/resources/cache-persistency-iframe.html:
+
 2017-09-06  Matt Lewis  <[email protected]>
 
         Marked media/video-main-content-allow-then-scroll.html as flaky on macOS and failing on iOS.

Modified: trunk/LayoutTests/http/tests/cache-storage/cache-origins.https.html (221709 => 221710)


--- trunk/LayoutTests/http/tests/cache-storage/cache-origins.https.html	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/LayoutTests/http/tests/cache-storage/cache-origins.https.html	2017-09-07 00:03:13 UTC (rev 221710)
@@ -4,7 +4,6 @@
 <title>Cache Storage: testing persistency of different origins</title>
 <script src=""
 <script src=""
-<script src=""
 </head>
 <body>
     <script>

Added: trunk/LayoutTests/http/tests/cache-storage/cache-representation.https-expected.txt (0 => 221710)


--- trunk/LayoutTests/http/tests/cache-storage/cache-representation.https-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/cache-storage/cache-representation.https-expected.txt	2017-09-07 00:03:13 UTC (rev 221710)
@@ -0,0 +1,12 @@
+
+
+PASS Cleaning existing caches 
+PASS Create a cache storage and look at the representation 
+PASS Look at the representation a second time after removing the iframe 
+PASS Remove a cache and look at the representation a second time 
+PASS Look at the representation a second time after reloading the iframe 
+PASS A cache is created 
+PASS A cache is persisting 
+PASS A cache is going in remove bucket 
+PASS A removed cache gets collected 
+

Added: trunk/LayoutTests/http/tests/cache-storage/cache-representation.https.html (0 => 221710)


--- trunk/LayoutTests/http/tests/cache-storage/cache-representation.https.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/cache-storage/cache-representation.https.html	2017-09-07 00:03:13 UTC (rev 221710)
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Cache Storage: testing persistency</title>
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+    <div id="check"></div>
+    <script>
+    if (window.testRunner)
+        testRunner.setPrivateBrowsingEnabled(true);
+
+    function checkCaches(hasPersistent, hasRemoved, name, value) {
+        test(() => {
+            var results = JSON.parse(value);
+            var caches = results[0].caches;
+            assert_equals(!!caches["persistent"].length, hasPersistent, "persistent");
+            assert_equals(!!caches["removed"].length, hasRemoved, "removed");
+        }, name);
+    }
+
+    promise_test(test => {
+        return self.caches.keys().then(keys => {
+            var pending = [];
+            for (key of keys)
+                pending.push(self.caches.delete(keys[0]));
+            return Promise.all(pending);
+        });
+    }, "Cleaning existing caches");
+
+    promise_test(test => {
+        if (!window.internals)
+            return Promise.reject("Test requires internals");
+
+        return new Promise((resolve, reject) => {
+            window.addEventListener("message", test.step_func((event) => {
+                return internals.cacheStorageEngineRepresentation().then(value => {
+                    checkCaches(true, false, "A cache is created", value);
+                    resolve();
+                });
+            }));
+            check.innerHTML = "<iframe src=''></iframe>";
+        })
+    }, "Create a cache storage and look at the representation");
+
+    promise_test(test => {
+        if (!window.internals)
+            return Promise.reject("Test requires internals");
+
+        return new Promise((resolve, reject) => {
+            window.addEventListener("message", test.step_func((event) => {
+                return internals.cacheStorageEngineRepresentation().then(value => {
+                    checkCaches(true, false, "A cache is persisting", value);
+                    resolve();
+                });
+            }));
+            check.innerHTML = "<iframe src=''></iframe>";
+        })
+    }, "Look at the representation a second time after removing the iframe");
+
+    promise_test(test => {
+        if (!window.internals)
+            return Promise.reject("Test requires internals");
+
+        return new Promise((resolve, reject) => {
+            window.addEventListener("message", test.step_func((event) => {
+                return internals.cacheStorageEngineRepresentation().then(value => {
+                    checkCaches(false, true, "A cache is going in remove bucket", value);
+                    resolve();
+                });
+            }));
+            check.innerHTML = "<iframe src=''></iframe>";
+        })
+    }, "Remove a cache and look at the representation a second time");
+
+    promise_test(test => {
+        if (!window.internals)
+            return Promise.reject("Test requires internals");
+
+        return new Promise((resolve, reject) => {
+            window.addEventListener("message", test.step_func((event) => {
+                gc();
+                setTimeout(() => {
+                    return internals.cacheStorageEngineRepresentation().then(value => {
+                        checkCaches(false, false, "A removed cache gets collected", value);
+                        resolve();
+                    });
+                }, 0);
+            }));
+            check.innerHTML = "<iframe src=''></iframe>";
+        });
+    }, "Look at the representation a second time after reloading the iframe");
+
+    </script>
+</body>
+</html>
+

Modified: trunk/LayoutTests/http/tests/cache-storage/resources/cache-persistency-iframe.html (221709 => 221710)


--- trunk/LayoutTests/http/tests/cache-storage/resources/cache-persistency-iframe.html	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/LayoutTests/http/tests/cache-storage/resources/cache-persistency-iframe.html	2017-09-07 00:03:13 UTC (rev 221710)
@@ -2,6 +2,7 @@
 <html>
 <body>
     <script>
+var cache;
 function doTest()
 {
     if (window.location.hash === "#check") {
@@ -11,6 +12,16 @@
         return;
     }
 
+    if (window.location.hash === "#remove") {
+        self.caches.open("testCacheName").then(c => {
+            cache = c
+            self.caches.delete("testCacheName").then(() => {
+                window.parent.postMessage("removed", "*");
+            });
+        });
+        return;
+    }
+
     var cacheName = "testCacheName";
     if (window.location.hash.indexOf("#name=") === 0)
         cacheName = window.location.hash.substring(6);

Modified: trunk/Source/WebCore/ChangeLog (221709 => 221710)


--- trunk/Source/WebCore/ChangeLog	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/ChangeLog	2017-09-07 00:03:13 UTC (rev 221710)
@@ -1,3 +1,32 @@
+2017-09-06  Youenn Fablet  <[email protected]>
+
+        NetworkProcess Cache and Caches should be cleared when the last related WebProcess Cache or CacheStorage is destroyed
+        https://bugs.webkit.org/show_bug.cgi?id=176249
+
+        Reviewed by Alex Christensen.
+
+        Test: http/tests/cache-storage/cache-representation.https.html
+
+        Each Cache construction/destruction is notified to the CacheStorageConnection
+        so that the WebKit2 engine can handle memory management accordingly.
+
+        Adding an internal API to grab the representation of an engine at any time.
+
+        * Modules/cache/Cache.cpp:
+        (WebCore::Cache::Cache):
+        (WebCore::Cache::~Cache):
+        * Modules/cache/CacheStorageConnection.h:
+        (WebCore::CacheStorageConnection::reference):
+        (WebCore::CacheStorageConnection::dereference):
+        * Modules/cache/WorkerCacheStorageConnection.cpp:
+        (WebCore::WorkerCacheStorageConnection::reference):
+        (WebCore::WorkerCacheStorageConnection::dereference):
+        * Modules/cache/WorkerCacheStorageConnection.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::cacheStorageEngineRepresentation):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2017-09-05  Matt Rajca  <[email protected]>
 
         Support new autoplay quirk for arbitrary user gestures.

Modified: trunk/Source/WebCore/Modules/cache/CacheStorageConnection.h (221709 => 221710)


--- trunk/Source/WebCore/Modules/cache/CacheStorageConnection.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/Modules/cache/CacheStorageConnection.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -45,8 +45,12 @@
     void batchDeleteOperation(uint64_t cacheIdentifier, const ResourceRequest&, CacheQueryOptions&&, DOMCacheEngine::RecordIdentifiersCallback&&);
     void batchPutOperation(uint64_t cacheIdentifier, Vector<DOMCacheEngine::Record>&&, DOMCacheEngine::RecordIdentifiersCallback&&);
 
+    virtual void reference(uint64_t /* cacheIdentifier */) { }
+    virtual void dereference(uint64_t /* cacheIdentifier */) { }
+
     // Used only for testing purposes.
     virtual void clearMemoryRepresentation(const String& /* origin */, DOMCacheEngine::CompletionCallback&& callback) { callback(DOMCacheEngine::Error::NotImplemented); }
+    virtual void engineRepresentation(WTF::Function<void(const String&)>&& callback) { callback(String { }); }
 
 protected:
     CacheStorageConnection() =  default;

Modified: trunk/Source/WebCore/Modules/cache/DOMCache.cpp (221709 => 221710)


--- trunk/Source/WebCore/Modules/cache/DOMCache.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/Modules/cache/DOMCache.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -47,10 +47,12 @@
     , m_connection(WTFMove(connection))
 {
     suspendIfNeeded();
+    m_connection->reference(m_identifier);
 }
 
 DOMCache::~DOMCache()
 {
+    m_connection->dereference(m_identifier);
 }
 
 void DOMCache::match(RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)

Modified: trunk/Source/WebCore/Modules/cache/WorkerCacheStorageConnection.cpp (221709 => 221710)


--- trunk/Source/WebCore/Modules/cache/WorkerCacheStorageConnection.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/Modules/cache/WorkerCacheStorageConnection.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -163,6 +163,26 @@
     });
 }
 
+void WorkerCacheStorageConnection::reference(uint64_t cacheIdentifier)
+{
+    m_proxy.postTaskToLoader([this, protectedThis = makeRef(*this), cacheIdentifier](ScriptExecutionContext&) {
+        ASSERT(isMainThread());
+        ASSERT(m_mainThreadConnection);
+
+        m_mainThreadConnection->reference(cacheIdentifier);
+    });
+}
+
+void WorkerCacheStorageConnection::dereference(uint64_t cacheIdentifier)
+{
+    m_proxy.postTaskToLoader([this, protectedThis = makeRef(*this), cacheIdentifier](ScriptExecutionContext&) {
+        ASSERT(isMainThread());
+        ASSERT(m_mainThreadConnection);
+
+        m_mainThreadConnection->dereference(cacheIdentifier);
+    });
+}
+
 static inline Vector<CrossThreadRecordData> recordsDataFromRecords(const Vector<Record>& records)
 {
     Vector<CrossThreadRecordData> recordsData;

Modified: trunk/Source/WebCore/Modules/cache/WorkerCacheStorageConnection.h (221709 => 221710)


--- trunk/Source/WebCore/Modules/cache/WorkerCacheStorageConnection.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/Modules/cache/WorkerCacheStorageConnection.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -47,6 +47,10 @@
     void doRetrieveCaches(uint64_t requestIdentifier, const String& origin, uint64_t updateCounter) final;
 
     void doRetrieveRecords(uint64_t requestIdentifier, uint64_t cacheIdentifier, const URL&) final;
+
+    void reference(uint64_t cacheIdentifier) final;
+    void dereference(uint64_t cacheIdentifier) final;
+
     void doBatchDeleteOperation(uint64_t requestIdentifier, uint64_t cacheIdentifier, const WebCore::ResourceRequest&, WebCore::CacheQueryOptions&&) final;
     void doBatchPutOperation(uint64_t requestIdentifier, uint64_t cacheIdentifier, Vector<DOMCacheEngine::Record>&&) final;
 

Modified: trunk/Source/WebCore/testing/Internals.cpp (221709 => 221710)


--- trunk/Source/WebCore/testing/Internals.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/testing/Internals.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -4146,4 +4146,21 @@
     m_cacheStorageConnection->clearMemoryRepresentation(document->securityOrigin().toString(), [](std::optional<DOMCacheEngine::Error>&&) { });
 }
 
+void Internals::cacheStorageEngineRepresentation(DOMPromiseDeferred<IDLDOMString>&& promise)
+{
+    auto* document = contextDocument();
+    if (!document)
+        return;
+
+    if (!m_cacheStorageConnection) {
+        if (auto* page = contextDocument()->page())
+            m_cacheStorageConnection = page->cacheStorageProvider().createCacheStorageConnection(page->sessionID());
+        if (!m_cacheStorageConnection)
+            return;
+    }
+    m_cacheStorageConnection->engineRepresentation([promise = WTFMove(promise)](const String& result) mutable {
+        promise.resolve(result);
+    });
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/testing/Internals.h (221709 => 221710)


--- trunk/Source/WebCore/testing/Internals.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/testing/Internals.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -600,6 +600,7 @@
     String audioSessionCategory() const;
 
     void clearCacheStorageMemoryRepresentation();
+    void cacheStorageEngineRepresentation(DOMPromiseDeferred<IDLDOMString>&&);
 
 private:
     explicit Internals(Document&);

Modified: trunk/Source/WebCore/testing/Internals.idl (221709 => 221710)


--- trunk/Source/WebCore/testing/Internals.idl	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebCore/testing/Internals.idl	2017-09-07 00:03:13 UTC (rev 221710)
@@ -545,6 +545,7 @@
     [Conditional=MEDIA_STREAM] void simulateMediaStreamTrackCaptureSourceFailure(MediaStreamTrack track);
 
     void clearCacheStorageMemoryRepresentation();
+    Promise<DOMString> cacheStorageEngineRepresentation();
 
     DOMString audioSessionCategory();
 };

Modified: trunk/Source/WebKit/ChangeLog (221709 => 221710)


--- trunk/Source/WebKit/ChangeLog	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/ChangeLog	2017-09-07 00:03:13 UTC (rev 221710)
@@ -1,3 +1,60 @@
+2017-09-06  Youenn Fablet  <[email protected]>
+
+        NetworkProcess Cache and Caches should be cleared when the last related WebProcess Cache or CacheStorage is destroyed
+        https://bugs.webkit.org/show_bug.cgi?id=176249
+
+        Reviewed by Alex Christensen.
+
+        CacheStorageEngineConnection will store the number of Caches references for its related WebProcess.
+        For a new reference, CacheStorageEngineConnection will notify its engine to lock the related Cache.
+        When the last reference is gone, CacheStorageEngineConnection will ask the engine to release the lock.
+
+        CacheStorageEngine will keep locks as a counter for each Cache object.
+        When the counter goes to zero, the engine asks the Cache to dispose itself.
+        Disposal is done by its parent Caches as only Caches knows whether the Cache is persistent or has been removed.
+
+        The Caches will remove the Cache from memory.
+        For removed caches, all information will be lost.
+        For caches that are not removed, they will keep their identifiers but all in-memory record information will be discarded.
+        If there is a need to reopen the Cache, it will need to read its information from the filesystem.
+        If the Caches has no longer any active Cache object, it will ask the engine to release itself.
+
+        WebProcess crashing is handled correctly as the CacheStorageEngineConnection will release its locks at destruction
+        time of the connection to the web process.
+
+        Adding the possiblity to grab an engine representation for test purposes.
+
+        * NetworkProcess/cache/CacheStorageEngine.cpp:
+        (WebKit::CacheStorage::Engine::readCachesFromDisk):
+        (WebKit::CacheStorage::Engine::removeCaches):
+        (WebKit::CacheStorage::Engine::lock):
+        (WebKit::CacheStorage::Engine::unlock):
+        * NetworkProcess/cache/CacheStorageEngine.h:
+        * NetworkProcess/cache/CacheStorageEngineCache.cpp:
+        (WebKit::CacheStorage::Cache::Cache):
+        (WebKit::CacheStorage::Cache::dispose):
+        (WebKit::CacheStorage::Cache::clearMemoryRepresentation):
+        * NetworkProcess/cache/CacheStorageEngineCache.h:
+        (WebKit::CacheStorage::Cache::isActive const):
+        * NetworkProcess/cache/CacheStorageEngineCaches.cpp:
+        (WebKit::CacheStorage::Caches::Caches):
+        (WebKit::CacheStorage::Caches::open):
+        (WebKit::CacheStorage::Caches::dispose):
+        (WebKit::CacheStorage::Caches::readCachesFromDisk):
+        (WebKit::CacheStorage::Caches::clearMemoryRepresentation):
+        * NetworkProcess/cache/CacheStorageEngineCaches.h:
+        (WebKit::CacheStorage::Caches::create):
+        * NetworkProcess/cache/CacheStorageEngineConnection.cpp:
+        (WebKit::CacheStorageEngineConnection::~CacheStorageEngineConnection):
+        (WebKit::CacheStorageEngineConnection::reference):
+        (WebKit::CacheStorageEngineConnection::dereference):
+        * NetworkProcess/cache/CacheStorageEngineConnection.h:
+        * NetworkProcess/cache/CacheStorageEngineConnection.messages.in:
+        * WebProcess/Cache/WebCacheStorageConnection.cpp:
+        (WebKit::WebCacheStorageConnection::reference):
+        (WebKit::WebCacheStorageConnection::dereference):
+        * WebProcess/Cache/WebCacheStorageConnection.h:
+
 2017-09-06  Alex Christensen  <[email protected]>
 
         Add WKUIDelegatePrivate equivalent of WKPageUIClient's pinnedStateDidChange

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngine.cpp (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngine.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngine.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -32,6 +32,7 @@
 #include <pal/SessionID.h>
 #include <wtf/MainThread.h>
 #include <wtf/NeverDestroyed.h>
+#include <wtf/text/StringBuilder.h>
 #include <wtf/text/StringHash.h>
 
 using namespace WebCore::DOMCacheEngine;
@@ -190,7 +191,7 @@
 {
     initialize([this, origin, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
         auto& caches = m_caches.ensure(origin, [&origin, this] {
-            return Caches::create(*this, origin);
+            return Caches::create(*this, String { origin });
         }).iterator->value;
 
         if (caches->isInitialized()) {
@@ -289,6 +290,12 @@
     });
 }
 
+void Engine::removeCaches(const String& origin)
+{
+    ASSERT(m_caches.contains(origin));
+    m_caches.remove(origin);
+}
+
 void Engine::clearMemoryRepresentation(const String& origin)
 {
     readCachesFromDisk(origin, [](CachesOrError&& result) {
@@ -298,6 +305,54 @@
     });
 }
 
+void Engine::lock(uint64_t cacheIdentifier)
+{
+    auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() {
+        return 0;
+    }).iterator->value;
+
+    ++counter;
+}
+
+void Engine::unlock(uint64_t cacheIdentifier)
+{
+    auto lockCount = m_cacheLocks.find(cacheIdentifier);
+    ASSERT(lockCount != m_cacheLocks.end());
+    if (lockCount == m_cacheLocks.end())
+        return;
+
+    ASSERT(lockCount->value);
+    if (--lockCount->value)
+        return;
+
+    readCache(cacheIdentifier, [this](CacheOrError&& result) mutable {
+        if (!result.hasValue())
+            return;
+
+        result.value().get().dispose();
+    });
+}
+
+String Engine::representation()
+{
+    bool isFirst = true;
+    StringBuilder builder;
+    builder.append("[");
+    for (auto& keyValue : m_caches) {
+        if (!isFirst)
+            builder.append(",");
+        isFirst = false;
+
+        builder.append("\n{ \"origin\" : \"");
+        builder.append(keyValue.key);
+        builder.append("\", \"caches\" : ");
+        keyValue.value->appendRepresentation(builder);
+        builder.append("}");
+    }
+    builder.append("\n]");
+    return builder.toString();
+}
+
 } // namespace CacheStorage
 
 } // namespace WebKit

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngine.h (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngine.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngine.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -43,6 +43,9 @@
 
 namespace CacheStorage {
 
+using CacheIdentifier = uint64_t;
+using LockCount = uint64_t;
+
 class Engine : public ThreadSafeRefCounted<Engine> {
 public:
     ~Engine();
@@ -61,6 +64,9 @@
     void putRecords(uint64_t cacheIdentifier, Vector<WebCore::DOMCacheEngine::Record>&&, WebCore::DOMCacheEngine::RecordIdentifiersCallback&&);
     void deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&&, WebCore::CacheQueryOptions&&, WebCore::DOMCacheEngine::RecordIdentifiersCallback&&);
 
+    void lock(uint64_t cacheIdentifier);
+    void unlock(uint64_t cacheIdentifier);
+
     void writeFile(const String& filename, NetworkCache::Data&&, WebCore::DOMCacheEngine::CompletionCallback&&);
     void readFile(const String& filename, WTF::Function<void(const NetworkCache::Data&, int error)>&&);
     void removeFile(const String& filename);
@@ -69,7 +75,10 @@
     const NetworkCache::Salt& salt() const { return m_salt.value(); }
     uint64_t nextCacheIdentifier() { return ++m_nextCacheIdentifier; }
 
+    void removeCaches(const String& origin);
+
     void clearMemoryRepresentation(const String& origin);
+    String representation();
 
 private:
     static Engine& defaultEngine();
@@ -92,6 +101,7 @@
     String m_rootPath;
     RefPtr<WorkQueue> m_ioQueue;
     std::optional<NetworkCache::Salt> m_salt;
+    HashMap<CacheIdentifier, LockCount> m_cacheLocks;
 };
 
 } // namespace CacheStorage

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCache.cpp (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCache.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCache.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "CacheStorageEngine.h"
 
+#include "CacheStorageEngineCaches.h"
 #include "NetworkCacheIOChannel.h"
 #include "NetworkCacheKey.h"
 #include "NetworkProcess.h"
@@ -73,6 +74,18 @@
 {
 }
 
+void Cache::dispose()
+{
+    m_caches.dispose(*this);
+}
+
+void Cache::clearMemoryRepresentation()
+{
+    m_records = { };
+    m_nextRecordIdentifier = 0;
+    m_state = State::Uninitialized;
+}
+ 
 void Cache::open(CompletionCallback&& callback)
 {
     if (m_state == State::Open) {

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCache.h (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCache.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCache.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -44,6 +44,7 @@
 
     uint64_t identifier() const { return m_identifier; }
     const String& name() const { return m_name; }
+    bool isActive() const { return m_state != State::Uninitialized; }
 
     Vector<WebCore::DOMCacheEngine::Record> retrieveRecords(const WebCore::URL&) const;
     WebCore::DOMCacheEngine::CacheInfo info() const { return { m_identifier, m_name }; }
@@ -51,6 +52,9 @@
     void put(Vector<WebCore::DOMCacheEngine::Record>&&, WebCore::DOMCacheEngine::RecordIdentifiersCallback&&);
     void remove(WebCore::ResourceRequest&&, WebCore::CacheQueryOptions&&, WebCore::DOMCacheEngine::RecordIdentifiersCallback&&);
 
+    void dispose();
+    void clearMemoryRepresentation();
+ 
 private:
     Vector<WebCore::DOMCacheEngine::Record>* recordsFromURL(const WebCore::URL&);
     const Vector<WebCore::DOMCacheEngine::Record>* recordsFromURL(const WebCore::URL&) const;

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCaches.cpp (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCaches.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCaches.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -50,9 +50,10 @@
     return WebCore::pathByAppendingComponent(cachesRootPath, ASCIILiteral("cacheslist"));
 }
 
-Caches::Caches(Engine& engine, const String& origin)
+Caches::Caches(Engine& engine, String&& origin)
     : m_engine(&engine)
-    , m_rootPath(cachesRootPath(engine, origin))
+    , m_origin(WTFMove(origin))
+    , m_rootPath(cachesRootPath(engine, m_origin))
 {
 }
 
@@ -163,6 +164,20 @@
     });
 }
 
+void Caches::dispose(Cache& cache)
+{
+    auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); });
+    if (position != notFound) {
+        m_removedCaches.remove(position);
+        return;
+    }
+    ASSERT(m_caches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); }) != notFound);
+    cache.clearMemoryRepresentation();
+
+    if (m_caches.findMatching([](const auto& item) { return item.isActive(); }) == notFound)
+        clearMemoryRepresentation();
+}
+
 static inline Data encodeCacheNames(const Vector<Cache>& caches)
 {
     WTF::Persistence::Encoder encoder;
@@ -260,6 +275,8 @@
     m_caches.clear();
     m_isInitialized = false;
     m_storage = nullptr;
+    if (m_engine)
+        m_engine->removeCaches(m_origin);
 }
 
 bool Caches::isDirty(uint64_t updateCounter) const
@@ -279,6 +296,34 @@
     return { WTFMove(cacheInfos), m_updateCounter };
 }
 
+void Caches::appendRepresentation(StringBuilder& builder) const
+{
+    builder.append("{ \"persistent\": [");
+
+    bool isFirst = true;
+    for (auto& cache : m_caches) {
+        if (!isFirst)
+            builder.append(", ");
+        isFirst = false;
+        builder.append("\"");
+        builder.append(cache.name());
+        builder.append("\"");
+    }
+
+    builder.append("], \"removed\": [");
+
+    isFirst = true;
+    for (auto& cache : m_removedCaches) {
+        if (!isFirst)
+            builder.append(", ");
+        isFirst = false;
+        builder.append("\"");
+        builder.append(cache.name());
+        builder.append("\"");
+    }
+    builder.append("]}\n");
+}
+
 } // namespace CacheStorage
 
 } // namespace WebKit

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCaches.h (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCaches.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineCaches.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -36,12 +36,13 @@
 
 class Caches : public RefCounted<Caches> {
 public:
-    static Ref<Caches> create(Engine& engine, const String& origin) { return adoptRef(*new Caches { engine, origin }); }
+    static Ref<Caches> create(Engine& engine, String&& origin) { return adoptRef(*new Caches { engine, WTFMove(origin) }); }
 
     void initialize(WebCore::DOMCacheEngine::CompletionCallback&&);
     void open(const String& name, WebCore::DOMCacheEngine::CacheIdentifierCallback&&);
     void remove(uint64_t identifier, WebCore::DOMCacheEngine::CacheIdentifierCallback&&);
     void clearMemoryRepresentation();
+    void dispose(Cache&);
 
     void detach();
 
@@ -49,9 +50,10 @@
     WebCore::DOMCacheEngine::CacheInfos cacheInfos(uint64_t updateCounter) const;
 
     Cache* find(uint64_t identifier);
+    void appendRepresentation(StringBuilder&) const;
 
 private:
-    Caches(Engine&, const String& rootPath);
+    Caches(Engine&, String&& origin);
 
     void readCachesFromDisk(WTF::Function<void(Expected<Vector<Cache>, WebCore::DOMCacheEngine::Error>&&)>&&);
     void writeCachesToDisk(WebCore::DOMCacheEngine::CompletionCallback&&);
@@ -67,6 +69,7 @@
     bool m_isInitialized { false };
     Engine* m_engine { nullptr };
     uint64_t m_updateCounter { 0 };
+    String m_origin;
     String m_rootPath;
     Vector<Cache> m_caches;
     Vector<Cache> m_removedCaches;

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.cpp (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -42,6 +42,17 @@
 {
 }
 
+CacheStorageEngineConnection::~CacheStorageEngineConnection()
+{
+    for (auto& keyValue : m_cachesLocks) {
+        auto& sessionID = keyValue.key;
+        for (auto& references : keyValue.value) {
+            ASSERT(references.value);
+            Engine::from(sessionID).unlock(references.key);
+        }
+    }
+}
+
 void CacheStorageEngineConnection::open(PAL::SessionID sessionID, uint64_t requestIdentifier, const String& origin, const String& cacheName)
 {
     Engine::from(sessionID).open(origin, cacheName, [protectedThis = makeRef(*this), this, sessionID, requestIdentifier](const CacheIdentifierOrError& result) {
@@ -63,11 +74,6 @@
     });
 }
 
-void CacheStorageEngineConnection::clearMemoryRepresentation(PAL::SessionID sessionID, const String& origin)
-{
-    Engine::from(sessionID).clearMemoryRepresentation(origin);
-}
-
 void CacheStorageEngineConnection::retrieveRecords(PAL::SessionID sessionID, uint64_t requestIdentifier, uint64_t cacheIdentifier, WebCore::URL&& url)
 {
     Engine::from(sessionID).retrieveRecords(cacheIdentifier, WTFMove(url), [protectedThis = makeRef(*this), this, sessionID, requestIdentifier](RecordsOrError&& result) {
@@ -89,4 +95,43 @@
     });
 }
 
+void CacheStorageEngineConnection::reference(PAL::SessionID sessionID, uint64_t cacheIdentifier)
+{
+    auto& references = m_cachesLocks.ensure(sessionID, []() {
+        return HashMap<CacheIdentifier, LockCount> { };
+    }).iterator->value;
+    auto& counter = references.ensure(cacheIdentifier, [this]() {
+        return 0;
+    }).iterator->value;
+    if (!counter++)
+        Engine::from(sessionID).lock(cacheIdentifier);
 }
+
+void CacheStorageEngineConnection::dereference(PAL::SessionID sessionID, uint64_t cacheIdentifier)
+{
+    ASSERT(m_cachesLocks.contains(sessionID));
+    auto& references = m_cachesLocks.ensure(sessionID, []() {
+        return HashMap<CacheIdentifier, LockCount> { };
+    }).iterator->value;
+
+    auto referenceResult = references.find(cacheIdentifier);
+    ASSERT(referenceResult != references.end());
+    if (referenceResult == references.end())
+        return;
+
+    ASSERT(referenceResult->value);
+    if (!--referenceResult->value)
+        Engine::from(sessionID).unlock(cacheIdentifier);
+}
+
+void CacheStorageEngineConnection::clearMemoryRepresentation(PAL::SessionID sessionID, const String& origin)
+{
+    Engine::from(sessionID).clearMemoryRepresentation(origin);
+}
+
+void CacheStorageEngineConnection::engineRepresentation(PAL::SessionID sessionID, uint64_t requestIdentifier)
+{
+    m_connection.connection().send(Messages::WebCacheStorageConnection::EngineRepresentationCompleted(requestIdentifier, Engine::from(sessionID).representation()), sessionID.sessionID());
+}
+
+}

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.h (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -44,7 +44,7 @@
 class CacheStorageEngineConnection : public RefCounted<CacheStorageEngineConnection> {
 public:
     static Ref<CacheStorageEngineConnection> create(NetworkConnectionToWebProcess& connection) { return adoptRef(*new CacheStorageEngineConnection(connection)); }
-
+    ~CacheStorageEngineConnection();
     void didReceiveMessage(IPC::Connection&, IPC::Decoder&);
 
 private:
@@ -54,13 +54,18 @@
     void remove(PAL::SessionID, uint64_t removeRequestIdentifier, uint64_t cacheIdentifier);
     void caches(PAL::SessionID, uint64_t retrieveCachesIdentifier, const String& origin, uint64_t updateCounter);
 
-    void clearMemoryRepresentation(PAL::SessionID, const String& origin);
-
     void retrieveRecords(PAL::SessionID, uint64_t requestIdentifier, uint64_t cacheIdentifier, WebCore::URL&&);
     void deleteMatchingRecords(PAL::SessionID, uint64_t requestIdentifier, uint64_t cacheIdentifier, WebCore::ResourceRequest&&, WebCore::CacheQueryOptions&&);
     void putRecords(PAL::SessionID, uint64_t requestIdentifier, uint64_t cacheIdentifier, Vector<WebCore::DOMCacheEngine::Record>&&);
 
+    void reference(PAL::SessionID, uint64_t cacheIdentifier);
+    void dereference(PAL::SessionID, uint64_t cacheIdentifier);
+
+    void clearMemoryRepresentation(PAL::SessionID, const String& origin);
+    void engineRepresentation(PAL::SessionID, uint64_t requestIdentifier);
+
     NetworkConnectionToWebProcess& m_connection;
+    HashMap<PAL::SessionID, HashMap<CacheStorage::CacheIdentifier, CacheStorage::LockCount>> m_cachesLocks;
 };
 
 }

Modified: trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.messages.in (221709 => 221710)


--- trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.messages.in	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.messages.in	2017-09-07 00:03:13 UTC (rev 221710)
@@ -21,11 +21,15 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 messages -> CacheStorageEngineConnection {
+    Reference(PAL::SessionID sessionID, uint64_t cacheIdentifier);
+    Dereference(PAL::SessionID sessionID, uint64_t cacheIdentifier);
+
     Open(PAL::SessionID sessionID, uint64_t requestIdentifier, String origin, String cacheName);
     Remove(PAL::SessionID sessionID, uint64_t requestIdentifier, uint64_t cacheIdentifier);
     Caches(PAL::SessionID sessionID, uint64_t requestIdentifier, String origin, uint64_t updateCounter);
 
     ClearMemoryRepresentation(PAL::SessionID sessionID, String origin);
+    EngineRepresentation(PAL::SessionID sessionID, uint64_t requestIdentifier);
 
     RetrieveRecords(PAL::SessionID sessionID, uint64_t requestIdentifier, uint64_t cacheIdentifier, WebCore::URL url);
     DeleteMatchingRecords(PAL::SessionID sessionID, uint64_t requestIdentifier, uint64_t cacheIdentifier, WebCore::ResourceRequest request, struct WebCore::CacheQueryOptions options);

Modified: trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.cpp (221709 => 221710)


--- trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.cpp	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.cpp	2017-09-07 00:03:13 UTC (rev 221710)
@@ -86,12 +86,16 @@
     connection().send(Messages::CacheStorageEngineConnection::PutRecords(m_sessionID, requestIdentifier, cacheIdentifier, records), 0);
 }
 
-void WebCacheStorageConnection::clearMemoryRepresentation(const String& origin, CompletionCallback&& callback)
+void WebCacheStorageConnection::reference(uint64_t cacheIdentifier)
 {
-    connection().send(Messages::CacheStorageEngineConnection::ClearMemoryRepresentation(m_sessionID, origin), 0);
-    callback(std::nullopt);
+    connection().send(Messages::CacheStorageEngineConnection::Reference(m_sessionID, cacheIdentifier), 0);
 }
 
+void WebCacheStorageConnection::dereference(uint64_t cacheIdentifier)
+{
+    connection().send(Messages::CacheStorageEngineConnection::Dereference(m_sessionID, cacheIdentifier), 0);
+}
+
 void WebCacheStorageConnection::openCompleted(uint64_t requestIdentifier, const CacheIdentifierOrError& result)
 {
     CacheStorageConnection::openCompleted(requestIdentifier, result);
@@ -122,4 +126,23 @@
     CacheStorageConnection::putRecordsCompleted(requestIdentifier, WTFMove(result));
 }
 
+void WebCacheStorageConnection::clearMemoryRepresentation(const String& origin, CompletionCallback&& callback)
+{
+    connection().send(Messages::CacheStorageEngineConnection::ClearMemoryRepresentation(m_sessionID, origin), 0);
+    callback(std::nullopt);
 }
+
+void WebCacheStorageConnection::engineRepresentation(WTF::Function<void(const String&)>&& callback)
+{
+    uint64_t requestIdentifier = ++m_engineRepresentationNextIdentifier;
+    m_engineRepresentationCallbacks.set(requestIdentifier, WTFMove(callback));
+    connection().send(Messages::CacheStorageEngineConnection::EngineRepresentation(m_sessionID, requestIdentifier), 0);
+}
+
+void WebCacheStorageConnection::engineRepresentationCompleted(uint64_t requestIdentifier, const String& result)
+{
+    if (auto callback = m_engineRepresentationCallbacks.take(requestIdentifier))
+        callback(result);
+}
+
+}

Modified: trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.h (221709 => 221710)


--- trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.h	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.h	2017-09-07 00:03:13 UTC (rev 221710)
@@ -60,7 +60,11 @@
     void doBatchDeleteOperation(uint64_t requestIdentifier, uint64_t cacheIdentifier, const WebCore::ResourceRequest&, WebCore::CacheQueryOptions&&) final;
     void doBatchPutOperation(uint64_t requestIdentifier, uint64_t cacheIdentifier, Vector<WebCore::DOMCacheEngine::Record>&&) final;
 
+    void reference(uint64_t cacheIdentifier) final;
+    void dereference(uint64_t cacheIdentifier) final;
+
     void clearMemoryRepresentation(const String& origin, WebCore::DOMCacheEngine::CompletionCallback&&) final;
+    void engineRepresentation(WTF::Function<void(const String&)>&&) final;
 
     void openCompleted(uint64_t requestIdentifier, const WebCore::DOMCacheEngine::CacheIdentifierOrError&);
     void removeCompleted(uint64_t requestIdentifier, const WebCore::DOMCacheEngine::CacheIdentifierOrError&);
@@ -70,8 +74,12 @@
     void deleteRecordsCompleted(uint64_t requestIdentifier, WebCore::DOMCacheEngine::RecordIdentifiersOrError&&);
     void putRecordsCompleted(uint64_t requestIdentifier, WebCore::DOMCacheEngine::RecordIdentifiersOrError&&);
 
+    void engineRepresentationCompleted(uint64_t requestIdentifier, const String& representation);
+
     WebCacheStorageProvider& m_provider;
     PAL::SessionID m_sessionID;
+    uint64_t m_engineRepresentationNextIdentifier { 0 };
+    HashMap<uint64_t, WTF::Function<void(const String&)>> m_engineRepresentationCallbacks;
 };
 
 }

Modified: trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.messages.in (221709 => 221710)


--- trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.messages.in	2017-09-07 00:02:27 UTC (rev 221709)
+++ trunk/Source/WebKit/WebProcess/Cache/WebCacheStorageConnection.messages.in	2017-09-07 00:03:13 UTC (rev 221710)
@@ -28,4 +28,6 @@
     UpdateRecords(uint64_t requestIdentifier, WebCore::DOMCacheEngine::RecordsOrError result);
     DeleteRecordsCompleted(uint64_t requestIdentifier, WebCore::DOMCacheEngine::RecordIdentifiersOrError result);
     PutRecordsCompleted(uint64_t requestIdentifier, WebCore::DOMCacheEngine::RecordIdentifiersOrError result);
+
+    EngineRepresentationCompleted(uint64_t requestIdentifier, String representation);
 }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to