Resource Load Statistics: IPC to the WebsiteDataStore in the UI process from NetworkP...
authorwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 29 Mar 2019 01:15:59 +0000 (01:15 +0000)
committerwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 29 Mar 2019 01:15:59 +0000 (01:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=196281
<rdar://problem/48938748>

Reviewed by Alex Christensen.

Source/WebKit:

The move of Resource Load Statistics to the network process requires that it
calls the UI process when clearing website data (previously the other way
around). This patch achieves that.

Specifically, NetworkProcess::deleteWebsiteDataForRegistrableDomains() now
filters its WebsiteDataTypes down to just the ones applicable for the UI
process and then calls DeleteWebsiteDataInUIProcessForRegistrableDomains over
IPC.

NetworkProcessProxy::deleteWebsiteDataInUIProcessForRegistrableDomains() on
the UI process side makes use of the re-introduced
WebsiteDataStore::fetchDataForRegistrableDomains() function to get the relevant
data records and call WebsiteDataStore::removeData(). The re-introduced
WebsiteDataStore::fetchDataForRegistrableDomains() was removed as dead code in
https://trac.webkit.org/changeset/242056/webkit, then under the name
WebsiteDataStore::fetchDataForTopPrivatelyControlledDomains(). The reason it
was dead code was the lack of IPC call that this patch adds.

* NetworkProcess/NetworkProcess.cpp:
(WebKit::NetworkProcess::deleteWebsiteDataForRegistrableDomains):
   Now calls DeleteWebsiteDataInUIProcessForRegistrableDomains over IPC if there
   are WebsiteDataTypes applicable to the UI process.
* NetworkProcess/NetworkProcess.h:
* Shared/WebsiteData/WebsiteData.cpp:
(WebKit::WebsiteData::ownerProcess):
(WebKit::WebsiteData::filter):
    Convenience functions to manage process ownership of website data types.
* Shared/WebsiteData/WebsiteData.h:
* UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
(WKWebsiteDataStoreStatisticsHasLocalStorage):
    Test infrastructure, called by the TestRunner.
* UIProcess/API/C/WKWebsiteDataStoreRef.h:
* UIProcess/Network/NetworkProcessProxy.cpp:
(WebKit::NetworkProcessProxy::deleteWebsiteDataInUIProcessForRegistrableDomains):
    New function to be called from the network process.
* UIProcess/Network/NetworkProcessProxy.h:
* UIProcess/Network/NetworkProcessProxy.messages.in:
* UIProcess/WebsiteData/WebsiteDataRecord.cpp:
(WebKit::WebsiteDataRecord::matches const):
    Now matches with WebCore::RegistrableDomain instead of a string.
(WebKit::WebsiteDataRecord::matchesTopPrivatelyControlledDomain const): Deleted.
    Replaced by WebsiteDataRecord::matches().
* UIProcess/WebsiteData/WebsiteDataRecord.h:
* UIProcess/WebsiteData/WebsiteDataStore.cpp:
(WebKit::WebsiteDataStore::fetchDataForRegistrableDomains):
    Re-introduced. It was removed as dead code in r242056.
(WebKit::computeNetworkProcessAccessTypeForDataRemoval):
(WebKit::WebsiteDataStore::hasLocalStorageForTesting const):
    Test infrastructure, called by the TestRunner.
* UIProcess/WebsiteData/WebsiteDataStore.h:

Tools:

This patch adds the function isStatisticsHasLocalStorage() to the
TestRunner. With it, the page can query the WebsiteDataStore in the
UI process to make sure that it sees LocalStorage.

* WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
* WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::TestRunner::isStatisticsHasLocalStorage):
* WebKitTestRunner/InjectedBundle/TestRunner.h:
* WebKitTestRunner/TestController.cpp:
(WTR::TestController::isStatisticsHasLocalStorage):
* WebKitTestRunner/TestController.h:
* WebKitTestRunner/TestInvocation.cpp:
(WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):

LayoutTests:

This test now covers LocalStorage too.

* http/tests/resourceLoadStatistics/website-data-removal-for-site-navigated-to-with-link-decoration-expected.txt:
* http/tests/resourceLoadStatistics/website-data-removal-for-site-navigated-to-with-link-decoration.html:

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

24 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/resourceLoadStatistics/website-data-removal-for-site-navigated-to-with-link-decoration-expected.txt
LayoutTests/http/tests/resourceLoadStatistics/website-data-removal-for-site-navigated-to-with-link-decoration.html
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/NetworkProcess.cpp
Source/WebKit/NetworkProcess/NetworkProcess.h
Source/WebKit/Shared/WebsiteData/WebsiteData.cpp
Source/WebKit/Shared/WebsiteData/WebsiteData.h
Source/WebKit/UIProcess/API/C/WKWebsiteDataStoreRef.cpp
Source/WebKit/UIProcess/API/C/WKWebsiteDataStoreRef.h
Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp
Source/WebKit/UIProcess/Network/NetworkProcessProxy.h
Source/WebKit/UIProcess/Network/NetworkProcessProxy.messages.in
Source/WebKit/UIProcess/WebsiteData/WebsiteDataRecord.cpp
Source/WebKit/UIProcess/WebsiteData/WebsiteDataRecord.h
Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp
Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h
Tools/ChangeLog
Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl
Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp
Tools/WebKitTestRunner/InjectedBundle/TestRunner.h
Tools/WebKitTestRunner/TestController.cpp
Tools/WebKitTestRunner/TestController.h
Tools/WebKitTestRunner/TestInvocation.cpp

index a91e253..6d715d8 100644 (file)
@@ -1,3 +1,16 @@
+2019-03-28  John Wilander  <wilander@apple.com>
+
+        Resource Load Statistics: IPC to the WebsiteDataStore in the UI process from NetworkProcess::deleteWebsiteDataForRegistrableDomains()
+        https://bugs.webkit.org/show_bug.cgi?id=196281
+        <rdar://problem/48938748>
+
+        Reviewed by Alex Christensen.
+
+        This test now covers LocalStorage too.
+
+        * http/tests/resourceLoadStatistics/website-data-removal-for-site-navigated-to-with-link-decoration-expected.txt:
+        * http/tests/resourceLoadStatistics/website-data-removal-for-site-navigated-to-with-link-decoration.html:
+
 2019-03-28  Shawn Roberts  <sroberts@apple.com>
 
         The following layout tests are flaky failures
index 2aa2bd9..d36dfc4 100644 (file)
@@ -3,13 +3,13 @@ Check that non-cookie website data gets removed after a navigation with link dec
 Before deletion: Client-side cookie exists.
 Before deletion: HttpOnly cookie exists.
 Before deletion: Regular server-side cookie exists.
-
+Before deletion: LocalStorage entry does exist.
 Before deletion: IDB entry does exist.
 
 After deletion: HttpOnly cookie exists.
 After deletion: Client-side cookie exists.
 After deletion: Regular server-side cookie exists.
-
+After deletion: LocalStorage entry does not exist.
 After deletion: IDB entry does not exist.
 
 
index c917a94..1d08a99 100644 (file)
         };
     }
 
+    const maxIntervals = 20;
+    let intervalCounter;
+    let checkLocalStorageCallback;
+    let checkLocalStorageIntervalID;
+    const localStorageName = "test";
+    const localStorageValue = "value";
+    function checkLocalStorageExists(isAfterDeletion, callback) {
+        intervalCounter = 0;
+        checkLocalStorageCallback = callback;
+        if (!isAfterDeletion) {
+            // Check until there is LocalStorage.
+            checkLocalStorageIntervalID = setInterval(function() {
+                if (++intervalCounter === maxIntervals) {
+                    clearInterval(checkLocalStorageIntervalID);
+                    checkLocalStorageCallback();
+                } else if (testRunner.isStatisticsHasLocalStorage(destinationOrigin)) {
+                    clearInterval(checkLocalStorageIntervalID);
+                    let value = localStorage.getItem(localStorageName);
+                    addOutput("Before deletion: LocalStorage entry " + (value === localStorageValue ? "does" : "does not") + " exist.");
+                    checkLocalStorageCallback();
+                }
+            }, 100);
+        } else {
+            // Check until there is no LocalStorage.
+            checkLocalStorageIntervalID = setInterval(function() {
+                if (++intervalCounter === maxIntervals) {
+                    clearInterval(checkLocalStorageIntervalID);
+                    checkLocalStorageCallback();
+                } else if (!testRunner.isStatisticsHasLocalStorage(destinationOrigin)) {
+                    clearInterval(checkLocalStorageIntervalID);
+                    let value = localStorage.getItem(localStorageName);
+                    addOutput("After deletion: LocalStorage entry " + (value === localStorageValue ? "does" : "does not") + " exist.");
+                    checkLocalStorageCallback();
+                }
+            }, 100);
+        }
+    }
+
     async function writeWebsiteDataAndContinue() {
         // Write cookies.
         await fetch("/cookies/resources/set-http-only-cookie.php?cookieName=" + httpOnlyCookieName, { credentials: "same-origin" });
         document.cookie = clientSideCookieName + "=1";
 
         checkCookies(false);
-        addLinebreakToOutput();
 
-        // Write IndexedDB.
-        createIDBDataStore(function () {
-            checkIDBDataStoreExists(false, processWebsiteDataAndContinue);
+        // Write LocalStorage
+        localStorage.setItem(localStorageName, localStorageValue);
+        checkLocalStorageExists(false, function() {
+
+            // Write IndexedDB.
+            createIDBDataStore(function () {
+                checkIDBDataStoreExists(false, function() {
+                    addLinebreakToOutput();
+                    processWebsiteDataAndContinue();
+                });
+            });
         });
     }
 
     function processWebsiteDataAndContinue() {
         testRunner.statisticsProcessStatisticsAndDataRecords();
+        checkWebsiteDataAndContinue();
+    }
 
-        addLinebreakToOutput();
+    function checkWebsiteDataAndContinue() {
         checkCookies(true);
-        addLinebreakToOutput();
-        checkIDBDataStoreExists(true, finishTest);
+        checkLocalStorageExists(true, function () {
+            checkIDBDataStoreExists(true, finishTest);
+        });
     }
 
     function finishTest() {
index 7fcb89e..fd6c7a1 100644 (file)
@@ -1,3 +1,62 @@
+2019-03-28  John Wilander  <wilander@apple.com>
+
+        Resource Load Statistics: IPC to the WebsiteDataStore in the UI process from NetworkProcess::deleteWebsiteDataForRegistrableDomains()
+        https://bugs.webkit.org/show_bug.cgi?id=196281
+        <rdar://problem/48938748>
+
+        Reviewed by Alex Christensen.
+
+        The move of Resource Load Statistics to the network process requires that it
+        calls the UI process when clearing website data (previously the other way
+        around). This patch achieves that.
+
+        Specifically, NetworkProcess::deleteWebsiteDataForRegistrableDomains() now
+        filters its WebsiteDataTypes down to just the ones applicable for the UI
+        process and then calls DeleteWebsiteDataInUIProcessForRegistrableDomains over
+        IPC.
+
+        NetworkProcessProxy::deleteWebsiteDataInUIProcessForRegistrableDomains() on
+        the UI process side makes use of the re-introduced
+        WebsiteDataStore::fetchDataForRegistrableDomains() function to get the relevant
+        data records and call WebsiteDataStore::removeData(). The re-introduced
+        WebsiteDataStore::fetchDataForRegistrableDomains() was removed as dead code in
+        https://trac.webkit.org/changeset/242056/webkit, then under the name
+        WebsiteDataStore::fetchDataForTopPrivatelyControlledDomains(). The reason it
+        was dead code was the lack of IPC call that this patch adds.
+
+        * NetworkProcess/NetworkProcess.cpp:
+        (WebKit::NetworkProcess::deleteWebsiteDataForRegistrableDomains):
+           Now calls DeleteWebsiteDataInUIProcessForRegistrableDomains over IPC if there
+           are WebsiteDataTypes applicable to the UI process.
+        * NetworkProcess/NetworkProcess.h:
+        * Shared/WebsiteData/WebsiteData.cpp:
+        (WebKit::WebsiteData::ownerProcess):
+        (WebKit::WebsiteData::filter):
+            Convenience functions to manage process ownership of website data types.
+        * Shared/WebsiteData/WebsiteData.h:
+        * UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
+        (WKWebsiteDataStoreStatisticsHasLocalStorage):
+            Test infrastructure, called by the TestRunner.
+        * UIProcess/API/C/WKWebsiteDataStoreRef.h:
+        * UIProcess/Network/NetworkProcessProxy.cpp:
+        (WebKit::NetworkProcessProxy::deleteWebsiteDataInUIProcessForRegistrableDomains):
+            New function to be called from the network process.
+        * UIProcess/Network/NetworkProcessProxy.h:
+        * UIProcess/Network/NetworkProcessProxy.messages.in:
+        * UIProcess/WebsiteData/WebsiteDataRecord.cpp:
+        (WebKit::WebsiteDataRecord::matches const):
+            Now matches with WebCore::RegistrableDomain instead of a string.
+        (WebKit::WebsiteDataRecord::matchesTopPrivatelyControlledDomain const): Deleted.
+            Replaced by WebsiteDataRecord::matches().
+        * UIProcess/WebsiteData/WebsiteDataRecord.h:
+        * UIProcess/WebsiteData/WebsiteDataStore.cpp:
+        (WebKit::WebsiteDataStore::fetchDataForRegistrableDomains):
+            Re-introduced. It was removed as dead code in r242056.
+        (WebKit::computeNetworkProcessAccessTypeForDataRemoval):
+        (WebKit::WebsiteDataStore::hasLocalStorageForTesting const):
+            Test infrastructure, called by the TestRunner.
+        * UIProcess/WebsiteData/WebsiteDataStore.h:
+
 2019-03-28  Jiewen Tan  <jiewen_tan@apple.com>
 
         API::Data::createWithoutCopying should do a null check before calling CFRelease
index 73f1b48..664fa57 100644 (file)
@@ -57,7 +57,6 @@
 #include "WebSWOriginStore.h"
 #include "WebSWServerConnection.h"
 #include "WebSWServerToContextConnection.h"
-#include "WebsiteData.h"
 #include "WebsiteDataFetchOption.h"
 #include "WebsiteDataStore.h"
 #include "WebsiteDataStoreParameters.h"
@@ -1525,23 +1524,13 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
         
         ~CallbackAggregator()
         {
-            RunLoop::main().dispatch([completionHandler = WTFMove(m_completionHandler), websiteData = WTFMove(m_websiteData)] () mutable {
-                HashSet<RegistrableDomain> domains;
-                for (const auto& hostnameWithCookies : websiteData.hostNamesWithCookies)
-                    domains.add(RegistrableDomain::uncheckedCreateFromHost(hostnameWithCookies));
-
-                for (const auto& hostnameWithHSTS : websiteData.hostNamesWithHSTSCache)
-                    domains.add(RegistrableDomain::uncheckedCreateFromHost(hostnameWithHSTS));
-
-                for (const auto& entry : websiteData.entries)
-                    domains.add(RegistrableDomain::uncheckedCreateFromHost(entry.origin.host));
-
+            RunLoop::main().dispatch([completionHandler = WTFMove(m_completionHandler), domains = WTFMove(m_domains)] () mutable {
                 completionHandler(domains);
             });
         }
         
         CompletionHandler<void(const HashSet<RegistrableDomain>&)> m_completionHandler;
-        WebsiteData m_websiteData;
+        HashSet<RegistrableDomain> m_domains;
     };
     
     auto callbackAggregator = adoptRef(*new CallbackAggregator([this, completionHandler = WTFMove(completionHandler), shouldNotifyPage] (const HashSet<RegistrableDomain>& domainsWithData) mutable {
@@ -1553,7 +1542,8 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
         });
     }));
 
-    auto& websiteDataStore = callbackAggregator->m_websiteData;
+    HashSet<String> hostNamesWithCookies;
+    HashSet<String> hostNamesWithHSTSCache;
 
     Vector<RegistrableDomain> domainsToDeleteCookiesFor;
     Vector<RegistrableDomain> domainsToDeleteAllButHttpOnlyCookiesFor;
@@ -1575,13 +1565,19 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
             }
         }
         if (auto* networkStorageSession = storageSession(sessionID)) {
-            networkStorageSession->getHostnamesWithCookies(websiteDataStore.hostNamesWithCookies);
+            networkStorageSession->getHostnamesWithCookies(hostNamesWithCookies);
 
-            hostnamesWithCookiesToDelete = filterForRegistrableDomains(domainsToDeleteCookiesFor, websiteDataStore.hostNamesWithCookies);
+            hostnamesWithCookiesToDelete = filterForRegistrableDomains(domainsToDeleteCookiesFor, hostNamesWithCookies);
             networkStorageSession->deleteCookiesForHostnames(hostnamesWithCookiesToDelete, WebCore::IncludeHttpOnlyCookies::Yes);
 
-            hostnamesWithCookiesToDelete = filterForRegistrableDomains(domainsToDeleteAllButHttpOnlyCookiesFor, websiteDataStore.hostNamesWithCookies);
+            for (const auto& host : hostnamesWithCookiesToDelete)
+                callbackAggregator->m_domains.add(RegistrableDomain::uncheckedCreateFromHost(host));
+
+            hostnamesWithCookiesToDelete = filterForRegistrableDomains(domainsToDeleteAllButHttpOnlyCookiesFor, hostNamesWithCookies);
             networkStorageSession->deleteCookiesForHostnames(hostnamesWithCookiesToDelete, WebCore::IncludeHttpOnlyCookies::No);
+
+            for (const auto& host : hostnamesWithCookiesToDelete)
+                callbackAggregator->m_domains.add(RegistrableDomain::uncheckedCreateFromHost(host));
         }
     } else {
         for (auto& domain : domains.keys())
@@ -1592,8 +1588,12 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
 #if PLATFORM(COCOA)
     if (websiteDataTypes.contains(WebsiteDataType::HSTSCache)) {
         if (auto* networkStorageSession = storageSession(sessionID)) {
-            getHostNamesWithHSTSCache(*networkStorageSession, websiteDataStore.hostNamesWithHSTSCache);
-            hostnamesWithHSTSToDelete = filterForRegistrableDomains(domainsToDeleteAllButCookiesFor, websiteDataStore.hostNamesWithHSTSCache);
+            getHostNamesWithHSTSCache(*networkStorageSession, hostNamesWithHSTSCache);
+            hostnamesWithHSTSToDelete = filterForRegistrableDomains(domainsToDeleteAllButCookiesFor, hostNamesWithHSTSCache);
+
+            for (const auto& host : hostnamesWithHSTSToDelete)
+                callbackAggregator->m_domains.add(RegistrableDomain::uncheckedCreateFromHost(host));
+
             deleteHSTSCacheForHostNames(*networkStorageSession, hostnamesWithHSTSToDelete);
         }
     }
@@ -1601,9 +1601,10 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
 
     /*
     // FIXME: No API to delete credentials by origin
+    HashSet<String> originsWithCredentials;
     if (websiteDataTypes.contains(WebsiteDataType::Credentials)) {
         if (storageSession(sessionID))
-            websiteDataStore.originsWithCredentials = storageSession(sessionID)->credentialStorage().originsWithCredentials();
+            originsWithCredentials = storageSession(sessionID)->credentialStorage().originsWithCredentials();
     }
     */
     
@@ -1612,8 +1613,9 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
             
             auto entriesToDelete = filterForRegistrableDomains(domainsToDeleteAllButCookiesFor, entries);
 
-            callbackAggregator->m_websiteData.entries.appendVector(entriesToDelete);
-            
+            for (const auto& entry : entriesToDelete)
+                callbackAggregator->m_domains.add(RegistrableDomain::uncheckedCreateFromHost(entry.origin.host));
+
             for (auto& entry : entriesToDelete)
                 CacheStorage::Engine::clearCachesForOrigin(*this, sessionID, SecurityOriginData { entry.origin }, [callbackAggregator = callbackAggregator.copyRef()] { });
         });
@@ -1627,11 +1629,12 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
             RunLoop::main().dispatch([this, sessionID, domainsToDeleteAllButCookiesFor = crossThreadCopy(domainsToDeleteAllButCookiesFor), callbackAggregator = callbackAggregator.copyRef(), securityOrigins = indexedDatabaseOrigins(path)] {
                 Vector<SecurityOriginData> entriesToDelete;
                 for (const auto& securityOrigin : securityOrigins) {
-                    if (!domainsToDeleteAllButCookiesFor.contains(RegistrableDomain::uncheckedCreateFromHost(securityOrigin.host)))
+                    auto domain = RegistrableDomain::uncheckedCreateFromHost(securityOrigin.host);
+                    if (!domainsToDeleteAllButCookiesFor.contains(domain))
                         continue;
 
                     entriesToDelete.append(securityOrigin);
-                    callbackAggregator->m_websiteData.entries.append({ securityOrigin, WebsiteDataType::IndexedDBDatabases, 0 });
+                    callbackAggregator->m_domains.add(domain);
                 }
 
                 idbServer(sessionID).closeAndDeleteDatabasesForOrigins(entriesToDelete, [callbackAggregator = callbackAggregator.copyRef()] { });
@@ -1647,7 +1650,7 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
             for (auto& securityOrigin : securityOrigins) {
                 if (!domainsToDeleteAllButCookiesFor.contains(RegistrableDomain::uncheckedCreateFromHost(securityOrigin.host)))
                     continue;
-                callbackAggregator->m_websiteData.entries.append({ securityOrigin, WebsiteDataType::ServiceWorkerRegistrations, 0 });
+                callbackAggregator->m_domains.add(RegistrableDomain::uncheckedCreateFromHost(securityOrigin.host));
                 swServerForSession(sessionID).clear(securityOrigin, [callbackAggregator = callbackAggregator.copyRef()] { });
             }
         });
@@ -1662,11 +1665,20 @@ void NetworkProcess::deleteWebsiteDataForRegistrableDomains(PAL::SessionID sessi
                 if (!domainsToDeleteAllButCookiesFor.contains(RegistrableDomain::uncheckedCreateFromHost(entry.origin.host)))
                     continue;
                 entriesToDelete.append(entry.origin);
-                callbackAggregator->m_websiteData.entries.append(entry);
+                callbackAggregator->m_domains.add(RegistrableDomain::uncheckedCreateFromHost(entry.origin.host));
             }
             clearDiskCacheEntries(cache(), entriesToDelete, [callbackAggregator = callbackAggregator.copyRef()] { });
         });
     }
+
+    auto dataTypesForUIProcess = WebsiteData::filter(websiteDataTypes, WebsiteDataProcessType::UI);
+    if (!dataTypesForUIProcess.isEmpty() && !domainsToDeleteAllButCookiesFor.isEmpty()) {
+        CompletionHandler<void(const HashSet<RegistrableDomain>&)> completionHandler = [callbackAggregator = callbackAggregator.copyRef()] (const HashSet<RegistrableDomain>& domains) {
+            for (auto& domain : domains)
+                callbackAggregator->m_domains.add(domain);
+        };
+        parentProcessConnection()->sendWithAsyncReply(Messages::NetworkProcessProxy::DeleteWebsiteDataInUIProcessForRegistrableDomains(sessionID, dataTypesForUIProcess, fetchOptions, domainsToDeleteAllButCookiesFor), WTFMove(completionHandler));
+    }
 }
 
 void NetworkProcess::deleteCookiesForTesting(PAL::SessionID sessionID, RegistrableDomain domain, bool includeHttpOnlyCookies, CompletionHandler<void()>&& completionHandler)
index 529f879..d85301c 100644 (file)
@@ -33,6 +33,7 @@
 #include "NetworkHTTPSUpgradeChecker.h"
 #include "SandboxExtension.h"
 #include "WebResourceLoadStatisticsStore.h"
+#include "WebsiteData.h"
 #include <WebCore/AdClickAttribution.h>
 #include <WebCore/ClientOrigin.h>
 #include <WebCore/DiagnosticLoggingClient.h>
index 0124519..9d5f4b8 100644 (file)
@@ -27,6 +27,7 @@
 #include "WebsiteData.h"
 
 #include "ArgumentCoders.h"
+#include "WebsiteDataType.h"
 #include <WebCore/SecurityOriginData.h>
 #include <wtf/text/StringHash.h>
 
@@ -86,4 +87,59 @@ bool WebsiteData::decode(IPC::Decoder& decoder, WebsiteData& result)
     return true;
 }
 
+WebsiteDataProcessType WebsiteData::ownerProcess(WebsiteDataType dataType)
+{
+    switch (dataType) {
+    case WebsiteDataType::Cookies:
+        return WebsiteDataProcessType::Network;
+    case WebsiteDataType::DiskCache:
+        return WebsiteDataProcessType::Network;
+    case WebsiteDataType::MemoryCache:
+        return WebsiteDataProcessType::Web;
+    case WebsiteDataType::OfflineWebApplicationCache:
+        return WebsiteDataProcessType::UI;
+    case WebsiteDataType::SessionStorage:
+        return WebsiteDataProcessType::UI;
+    case WebsiteDataType::LocalStorage:
+        return WebsiteDataProcessType::UI;
+    case WebsiteDataType::WebSQLDatabases:
+        return WebsiteDataProcessType::UI;
+    case WebsiteDataType::IndexedDBDatabases:
+        return WebsiteDataProcessType::Network;
+    case WebsiteDataType::MediaKeys:
+        return WebsiteDataProcessType::UI;
+    case WebsiteDataType::HSTSCache:
+        return WebsiteDataProcessType::Network;
+    case WebsiteDataType::SearchFieldRecentSearches:
+        return WebsiteDataProcessType::UI;
+#if ENABLE(NETSCAPE_PLUGIN_API)
+    case WebsiteDataType::PlugInData:
+        return WebsiteDataProcessType::UI;
+#endif
+    case WebsiteDataType::ResourceLoadStatistics:
+        return WebsiteDataProcessType::Network;
+    case WebsiteDataType::Credentials:
+        return WebsiteDataProcessType::Network;
+#if ENABLE(SERVICE_WORKER)
+    case WebsiteDataType::ServiceWorkerRegistrations:
+        return WebsiteDataProcessType::Network;
+#endif
+    case WebsiteDataType::DOMCache:
+        return WebsiteDataProcessType::Network;
+    case WebsiteDataType::DeviceIdHashSalt:
+        return WebsiteDataProcessType::UI;
+    }
+}
+
+OptionSet<WebsiteDataType> WebsiteData::filter(OptionSet<WebsiteDataType> unfilteredWebsiteDataTypes, WebsiteDataProcessType WebsiteDataProcessType)
+{
+    OptionSet<WebsiteDataType> filtered;
+    for (auto dataType : unfilteredWebsiteDataTypes) {
+        if (ownerProcess(dataType) == WebsiteDataProcessType)
+            filtered.add(dataType);
+    }
+    
+    return filtered;
+}
+
 }
index b608f4f..7697a0e 100644 (file)
@@ -28,6 +28,7 @@
 #include <WebCore/SecurityOriginData.h>
 #include <wtf/HashMap.h>
 #include <wtf/HashSet.h>
+#include <wtf/OptionSet.h>
 #include <wtf/Vector.h>
 
 namespace IPC {
@@ -39,6 +40,8 @@ namespace WebKit {
 
 enum class WebsiteDataType;
 
+enum class WebsiteDataProcessType { Network, UI, Web };
+
 struct WebsiteData {
     struct Entry {
         WebCore::SecurityOriginData origin;
@@ -62,6 +65,8 @@ struct WebsiteData {
 
     void encode(IPC::Encoder&) const;
     static bool decode(IPC::Decoder&, WebsiteData&);
+    static WebsiteDataProcessType ownerProcess(WebsiteDataType);
+    static OptionSet<WebsiteDataType> filter(OptionSet<WebsiteDataType>, WebsiteDataProcessType);
 };
 
 }
index 90285b3..b5d8e2a 100644 (file)
@@ -449,6 +449,17 @@ void WKWebsiteDataStoreStatisticsDeleteCookiesForTesting(WKWebsiteDataStoreRef d
 #endif
 }
 
+void WKWebsiteDataStoreStatisticsHasLocalStorage(WKWebsiteDataStoreRef dataStoreRef, WKStringRef host, void* context, WKWebsiteDataStoreStatisticsHasLocalStorageFunction callback)
+{
+#if ENABLE(RESOURCE_LOAD_STATISTICS)
+    WebKit::toImpl(dataStoreRef)->websiteDataStore().hasLocalStorageForTesting(URL(URL(), WebKit::toImpl(host)->string()), [context, callback](bool hasLocalStorage) {
+        callback(hasLocalStorage, context);
+    });
+#else
+    callback(false, context);
+#endif
+}
+
 void WKWebsiteDataStoreSetStatisticsCacheMaxAgeCap(WKWebsiteDataStoreRef dataStoreRef, double seconds, void* context, WKWebsiteDataStoreSetStatisticsCacheMaxAgeCapFunction callback)
 {
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
index e6a2a00..479cad4 100644 (file)
@@ -99,6 +99,8 @@ typedef void (*WKWebsiteDataStoreStatisticsClearThroughWebsiteDataRemovalFunctio
 WK_EXPORT void WKWebsiteDataStoreStatisticsClearThroughWebsiteDataRemoval(WKWebsiteDataStoreRef dataStoreRef, void* context, WKWebsiteDataStoreStatisticsClearThroughWebsiteDataRemovalFunction callback);
 typedef void (*WKWebsiteDataStoreStatisticsDeleteCookiesForTestingFunction)(void* functionContext);
 WK_EXPORT void WKWebsiteDataStoreStatisticsDeleteCookiesForTesting(WKWebsiteDataStoreRef dataStoreRef, WKStringRef host, bool includeHttpOnlyCookies, void* context, WKWebsiteDataStoreStatisticsDeleteCookiesForTestingFunction callback);
+typedef void (*WKWebsiteDataStoreStatisticsHasLocalStorageFunction)(bool hasLocalStorage, void* functionContext);
+WK_EXPORT void WKWebsiteDataStoreStatisticsHasLocalStorage(WKWebsiteDataStoreRef dataStoreRef, WKStringRef host, void* context, WKWebsiteDataStoreStatisticsHasLocalStorageFunction callback);
 typedef void (*WKWebsiteDataStoreSetStatisticsCacheMaxAgeCapFunction)(void* functionContext);
 WK_EXPORT void WKWebsiteDataStoreSetStatisticsCacheMaxAgeCap(WKWebsiteDataStoreRef dataStoreRef, double seconds, void* context, WKWebsiteDataStoreSetStatisticsCacheMaxAgeCapFunction);
 typedef void (*WKWebsiteDataStoreStatisticsResetToConsistentStateFunction)(void* functionContext);
index 0b91e39..c1631f9 100644 (file)
@@ -991,6 +991,22 @@ void NetworkProcessProxy::deleteCookiesForTesting(PAL::SessionID sessionID, cons
     
     sendWithAsyncReply(Messages::NetworkProcess::DeleteCookiesForTesting(sessionID, domain, includeHttpOnlyCookies), WTFMove(completionHandler));
 }
+
+void NetworkProcessProxy::deleteWebsiteDataInUIProcessForRegistrableDomains(PAL::SessionID sessionID, OptionSet<WebsiteDataType> dataTypes, OptionSet<WebsiteDataFetchOption> fetchOptions, Vector<RegistrableDomain> domains, CompletionHandler<void(HashSet<WebCore::RegistrableDomain>&&)>&& completionHandler)
+{
+    auto* websiteDataStore = websiteDataStoreFromSessionID(sessionID);
+    if (!websiteDataStore || dataTypes.isEmpty() || domains.isEmpty()) {
+        completionHandler({ });
+        return;
+    }
+
+    websiteDataStore->fetchDataForRegistrableDomains(dataTypes, fetchOptions, domains, [dataTypes, websiteDataStore = makeRef(*websiteDataStore), completionHandler = WTFMove(completionHandler)] (Vector<WebsiteDataRecord>&& matchingDataRecords, HashSet<WebCore::RegistrableDomain>&& domainsWithMatchingDataRecords) mutable {
+        websiteDataStore->removeData(dataTypes, WTFMove(matchingDataRecords), [domainsWithMatchingDataRecords = WTFMove(domainsWithMatchingDataRecords), completionHandler = WTFMove(completionHandler)] () mutable {
+            completionHandler(WTFMove(domainsWithMatchingDataRecords));
+        });
+    });
+}
+
 #endif // ENABLE(RESOURCE_LOAD_STATISTICS)
 
 void NetworkProcessProxy::sendProcessWillSuspendImminently()
index 372356b..5ef5111 100644 (file)
@@ -149,6 +149,7 @@ public:
     void setCrossSiteLoadWithLinkDecorationForTesting(PAL::SessionID, const NavigatedFromDomain&, const NavigatedToDomain&, CompletionHandler<void()>&&);
     void resetCrossSiteLoadsWithLinkDecorationForTesting(PAL::SessionID, CompletionHandler<void()>&&);
     void deleteCookiesForTesting(PAL::SessionID, const RegistrableDomain&, bool includeHttpOnlyCookies, CompletionHandler<void()>&&);
+    void deleteWebsiteDataInUIProcessForRegistrableDomains(PAL::SessionID, OptionSet<WebsiteDataType>, OptionSet<WebsiteDataFetchOption>, Vector<RegistrableDomain>, CompletionHandler<void(HashSet<WebCore::RegistrableDomain>&&)>&&);
 #endif
 
     void processReadyToSuspend();
index 563889b..16e5f30 100644 (file)
@@ -48,6 +48,7 @@ messages -> NetworkProcessProxy LegacyReceiver {
     NotifyWebsiteDataScanForRegistrableDomainsFinished()
     NotifyResourceLoadStatisticsTelemetryFinished(unsigned totalPrevalentResources, unsigned totalPrevalentResourcesWithUserInteraction, unsigned top3SubframeUnderTopFrameOrigins)
     RequestStorageAccessConfirm(uint64_t pageID, uint64_t frameID, WebCore::RegistrableDomain subFrameDomain, WebCore::RegistrableDomain topFrameDomain) -> (bool userDidGrantAccess) Async
+    DeleteWebsiteDataInUIProcessForRegistrableDomains(PAL::SessionID sessionID, OptionSet<WebKit::WebsiteDataType> dataTypes, OptionSet<WebKit::WebsiteDataFetchOption> fetchOptions, Vector<WebCore::RegistrableDomain> domains) -> (HashSet<WebCore::RegistrableDomain> domainsWithMatchingDataRecords) Async
 #endif
 #if ENABLE(CONTENT_EXTENSIONS)
     ContentExtensionRules(WebKit::UserContentControllerIdentifier identifier)
index 56d40c7..3fcbc8f 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <WebCore/LocalizedStrings.h>
 #include <WebCore/PublicSuffix.h>
+#include <WebCore/RegistrableDomain.h>
 #include <WebCore/SecurityOrigin.h>
 
 #if PLATFORM(COCOA)
@@ -113,20 +114,20 @@ static inline bool hostIsInDomain(StringView host, StringView domain)
     return !suffixOffset || host[suffixOffset - 1] == '.';
 }
 
-bool WebsiteDataRecord::matchesTopPrivatelyControlledDomain(const String& topPrivatelyControlledDomain) const
+bool WebsiteDataRecord::matches(const WebCore::RegistrableDomain& domain) const
 {
-    if (topPrivatelyControlledDomain.isEmpty())
+    if (domain.isEmpty())
         return false;
 
     if (types.contains(WebsiteDataType::Cookies)) {
         for (const auto& hostName : cookieHostNames) {
-            if (hostIsInDomain(hostName, topPrivatelyControlledDomain))
+            if (hostIsInDomain(hostName, domain.string()))
                 return true;
         }
     }
 
     for (const auto& dataRecordOriginData : origins) {
-        if (hostIsInDomain(dataRecordOriginData.host, topPrivatelyControlledDomain))
+        if (hostIsInDomain(dataRecordOriginData.host, domain.string()))
             return true;
     }
 
index ccf30a7..3dff401 100644 (file)
@@ -36,6 +36,7 @@
 #include <wtf/text/WTFString.h>
 
 namespace WebCore {
+class RegistrableDomain;
 class SecurityOrigin;
 }
 
@@ -72,7 +73,7 @@ struct WebsiteDataRecord {
     HashSet<String> originsWithCredentials;
     HashSet<String> HSTSCacheHostNames;
 
-    bool matchesTopPrivatelyControlledDomain(const String&) const;
+    bool matches(const WebCore::RegistrableDomain&) const;
     String topPrivatelyControlledDomain();
 };
 
index 978d67d..43116b9 100644 (file)
@@ -623,37 +623,44 @@ void WebsiteDataStore::fetchDataAndApply(OptionSet<WebsiteDataType> dataTypes, O
     callbackAggregator->callIfNeeded();
 }
 
+#if ENABLE(RESOURCE_LOAD_STATISTICS)
+void WebsiteDataStore::fetchDataForRegistrableDomains(OptionSet<WebsiteDataType> dataTypes, OptionSet<WebsiteDataFetchOption> fetchOptions, const Vector<WebCore::RegistrableDomain>& domains, CompletionHandler<void(Vector<WebsiteDataRecord>&&, HashSet<WebCore::RegistrableDomain>&&)>&& completionHandler)
+{
+    fetchDataAndApply(dataTypes, fetchOptions, m_queue.copyRef(), [domains = crossThreadCopy(domains), completionHandler = WTFMove(completionHandler)] (auto&& existingDataRecords) mutable {
+        ASSERT(!RunLoop::isMain());
+        
+        Vector<WebsiteDataRecord> matchingDataRecords;
+        HashSet<WebCore::RegistrableDomain> domainsWithMatchingDataRecords;
+        for (auto&& dataRecord : existingDataRecords) {
+            for (auto& domain : domains) {
+                if (dataRecord.matches(domain)) {
+                    matchingDataRecords.append(WTFMove(dataRecord));
+                    domainsWithMatchingDataRecords.add(domain.isolatedCopy());
+                    break;
+                }
+            }
+        }
+        RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), matchingDataRecords = WTFMove(matchingDataRecords), domainsWithMatchingDataRecords = WTFMove(domainsWithMatchingDataRecords)] () mutable {
+            completionHandler(WTFMove(matchingDataRecords), WTFMove(domainsWithMatchingDataRecords));
+        });
+    });
+}
+#endif
+
 static ProcessAccessType computeNetworkProcessAccessTypeForDataRemoval(OptionSet<WebsiteDataType> dataTypes, bool isNonPersistentStore)
 {
     ProcessAccessType processAccessType = ProcessAccessType::None;
 
-    if (dataTypes.contains(WebsiteDataType::Cookies)) {
-        if (isNonPersistentStore)
-            processAccessType = std::max(processAccessType, ProcessAccessType::OnlyIfLaunched);
-        else
-            processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
+    for (auto dataType : dataTypes) {
+        if (dataType == WebsiteDataType::Cookies) {
+            if (isNonPersistentStore)
+                processAccessType = std::max(processAccessType, ProcessAccessType::OnlyIfLaunched);
+            else
+                processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
+        } else if (WebsiteData::ownerProcess(dataType) == WebsiteDataProcessType::Network)
+            return ProcessAccessType::Launch;
     }
-
-    if (dataTypes.contains(WebsiteDataType::DiskCache) && !isNonPersistentStore)
-        processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
-
-    if (dataTypes.contains(WebsiteDataType::HSTSCache))
-        processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
-
-    if (dataTypes.contains(WebsiteDataType::Credentials))
-        processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
-
-    if (dataTypes.contains(WebsiteDataType::DOMCache))
-        processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
-
-    if (dataTypes.contains(WebsiteDataType::IndexedDBDatabases) && !isNonPersistentStore)
-        processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
     
-#if ENABLE(SERVICE_WORKER)
-    if (dataTypes.contains(WebsiteDataType::ServiceWorkerRegistrations) && !isNonPersistentStore)
-        processAccessType = std::max(processAccessType, ProcessAccessType::Launch);
-#endif
-
     return processAccessType;
 }
 
@@ -1795,6 +1802,25 @@ void WebsiteDataStore::deleteCookiesForTesting(const URL& url, bool includeHttpO
             networkProcess->deleteCookiesForTesting(m_sessionID, WebCore::RegistrableDomain { url }, includeHttpOnlyCookies, [callbackAggregator = callbackAggregator.copyRef()] { });
     }
 }
+
+void WebsiteDataStore::hasLocalStorageForTesting(const URL& url, CompletionHandler<void(bool)>&& completionHandler) const
+{
+    if (!m_storageManager) {
+        completionHandler(false);
+        return;
+    }
+
+    m_storageManager->getLocalStorageOrigins([url, completionHandler = WTFMove(completionHandler)](HashSet<WebCore::SecurityOriginData>&& origins) mutable {
+        for (auto& origin : origins) {
+            if (origin.host == url.host()) {
+                completionHandler(true);
+                return;
+            }
+        }
+
+        completionHandler(false);
+    });
+}
 #endif // ENABLE(RESOURCE_LOAD_STATISTICS)
 
 void WebsiteDataStore::setCacheMaxAgeCapForPrevalentResources(Seconds seconds, CompletionHandler<void()>&& completionHandler)
index 2539ff9..0c6f396 100644 (file)
@@ -61,6 +61,7 @@ class HTTPCookieStore;
 }
 
 namespace WebCore {
+class RegistrableDomain;
 class SecurityOrigin;
 }
 
@@ -125,6 +126,7 @@ public:
     void removeData(OptionSet<WebsiteDataType>, const Vector<WebsiteDataRecord>&, Function<void()>&& completionHandler);
 
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
+    void fetchDataForRegistrableDomains(OptionSet<WebsiteDataType>, OptionSet<WebsiteDataFetchOption>, const Vector<WebCore::RegistrableDomain>&, CompletionHandler<void(Vector<WebsiteDataRecord>&&, HashSet<WebCore::RegistrableDomain>&&)>&&);
     void clearPrevalentResource(const URL&, CompletionHandler<void()>&&);
     void clearUserInteraction(const URL&, CompletionHandler<void()>&&);
     void dumpResourceLoadStatistics(CompletionHandler<void(const String&)>&&);
@@ -171,6 +173,7 @@ public:
     void setCrossSiteLoadWithLinkDecorationForTesting(const URL& fromURL, const URL& toURL, CompletionHandler<void()>&&);
     void resetCrossSiteLoadsWithLinkDecorationForTesting(CompletionHandler<void()>&&);
     void deleteCookiesForTesting(const URL&, bool includeHttpOnlyCookies, CompletionHandler<void()>&&);
+    void hasLocalStorageForTesting(const URL&, CompletionHandler<void(bool)>&&) const;
 #endif
     void setCacheMaxAgeCapForPrevalentResources(Seconds, CompletionHandler<void()>&&);
     void resetCacheMaxAgeCapForPrevalentResources(CompletionHandler<void()>&&);
index 4896118..20b3bc8 100644 (file)
@@ -1,3 +1,25 @@
+2019-03-28  John Wilander  <wilander@apple.com>
+
+        Resource Load Statistics: IPC to the WebsiteDataStore in the UI process from NetworkProcess::deleteWebsiteDataForRegistrableDomains()
+        https://bugs.webkit.org/show_bug.cgi?id=196281
+        <rdar://problem/48938748>
+
+        Reviewed by Alex Christensen.
+
+        This patch adds the function isStatisticsHasLocalStorage() to the
+        TestRunner. With it, the page can query the WebsiteDataStore in the
+        UI process to make sure that it sees LocalStorage.
+
+        * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
+        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
+        (WTR::TestRunner::isStatisticsHasLocalStorage):
+        * WebKitTestRunner/InjectedBundle/TestRunner.h:
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::isStatisticsHasLocalStorage):
+        * WebKitTestRunner/TestController.h:
+        * WebKitTestRunner/TestInvocation.cpp:
+        (WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):
+
 2019-03-28  Jiewen Tan  <jiewen_tan@apple.com>
 
         API::Data::createWithoutCopying should do a null check before calling CFRelease
index 481ceeb..087da84 100644 (file)
@@ -321,6 +321,7 @@ interface TestRunner {
     void statisticsClearInMemoryAndPersistentStoreModifiedSinceHours(unsigned long hours, object callback);
     void statisticsClearThroughWebsiteDataRemoval(object callback);
     void statisticsDeleteCookiesForHost(DOMString hostName, boolean includeHttpOnlyCookies);
+    boolean isStatisticsHasLocalStorage(DOMString hostName);
     void setStatisticsCacheMaxAgeCap(double seconds);
     void statisticsResetToConsistentState(object completionHandler);
 
index 26f2575..d736f0d 100644 (file)
@@ -2070,6 +2070,16 @@ void TestRunner::statisticsDeleteCookiesForHost(JSStringRef hostName, bool inclu
     WKBundlePostSynchronousMessage(InjectedBundle::singleton().bundle(), messageName.get(), messageBody.get(), nullptr);
 }
 
+bool TestRunner::isStatisticsHasLocalStorage(JSStringRef hostName)
+{
+    auto messageName = adoptWK(WKStringCreateWithUTF8CString("IsStatisticsHasLocalStorage"));
+    auto messageBody = adoptWK(WKStringCreateWithJSString(hostName));
+    WKTypeRef returnData = nullptr;
+    WKBundlePagePostSynchronousMessageForTesting(InjectedBundle::singleton().page()->page(), messageName.get(), messageBody.get(), &returnData);
+    ASSERT(WKGetTypeID(returnData) == WKBooleanGetTypeID());
+    return WKBooleanGetValue(adoptWK(static_cast<WKBooleanRef>(returnData)).get());
+}
+
 void TestRunner::setStatisticsCacheMaxAgeCap(double seconds)
 {
     WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString("SetStatisticsCacheMaxAgeCap"));
index 3bf3d1a..7a355a8 100644 (file)
@@ -431,6 +431,7 @@ public:
     void statisticsClearThroughWebsiteDataRemoval(JSValueRef callback);
     void statisticsDeleteCookiesForHost(JSStringRef hostName, bool includeHttpOnlyCookies);
     void statisticsCallClearThroughWebsiteDataRemovalCallback();
+    bool isStatisticsHasLocalStorage(JSStringRef hostName);
     void setStatisticsCacheMaxAgeCap(double seconds);
     void statisticsResetToConsistentState(JSValueRef completionHandler);
     void statisticsCallDidResetToConsistentStateCallback();
index 0f30e73..ae375d5 100644 (file)
@@ -3420,6 +3420,15 @@ void TestController::statisticsDeleteCookiesForHost(WKStringRef host, bool inclu
     runUntil(context.done, noTimeout);
 }
 
+bool TestController::isStatisticsHasLocalStorage(WKStringRef host)
+{
+    auto* dataStore = WKContextGetWebsiteDataStore(platformContext());
+    ResourceStatisticsCallbackContext context(*this);
+    WKWebsiteDataStoreStatisticsHasLocalStorage(dataStore, host, &context, resourceStatisticsBooleanResultCallback);
+    runUntil(context.done, noTimeout);
+    return context.result;
+}
+
 void TestController::setStatisticsCacheMaxAgeCap(double seconds)
 {
     auto* dataStore = WKContextGetWebsiteDataStore(platformContext());
index 2e1f1d6..048d197 100644 (file)
@@ -242,6 +242,7 @@ public:
     void statisticsClearInMemoryAndPersistentStoreModifiedSinceHours(unsigned);
     void statisticsClearThroughWebsiteDataRemoval();
     void statisticsDeleteCookiesForHost(WKStringRef host, bool includeHttpOnlyCookies);
+    bool isStatisticsHasLocalStorage(WKStringRef hostName);
     void setStatisticsCacheMaxAgeCap(double seconds);
     void statisticsResetToConsistentState();
 
index 67a155e..4c7037b 100644 (file)
@@ -1427,6 +1427,15 @@ WKRetainPtr<WKTypeRef> TestInvocation::didReceiveSynchronousMessageFromInjectedB
         return nullptr;
     }
 
+    if (WKStringIsEqualToUTF8CString(messageName, "IsStatisticsHasLocalStorage")) {
+        ASSERT(WKGetTypeID(messageBody) == WKStringGetTypeID());
+        
+        WKStringRef hostName = static_cast<WKStringRef>(messageBody);
+        bool hasLocalStorage = TestController::singleton().isStatisticsHasLocalStorage(hostName);
+        auto result = adoptWK(WKBooleanCreate(hasLocalStorage));
+        return result;
+    }
+
     if (WKStringIsEqualToUTF8CString(messageName, "SetStatisticsCacheMaxAgeCap")) {
         ASSERT(WKGetTypeID(messageBody) == WKDoubleGetTypeID());
         WKDoubleRef seconds = static_cast<WKDoubleRef>(messageBody);