Resource Load Statistics: Switch NSURLSession on top navigation to prevalent resource...
authorwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Aug 2019 22:34:29 +0000 (22:34 +0000)
committerwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Aug 2019 22:34:29 +0000 (22:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=200642
<rdar://problem/53962073>

Reviewed by Alex Christensen.

Source/WebCore:

Tests: http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html
       http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html

This patch splits m_registrableDomainsToBlockCookieFor in WebCore:NetworkStorageSession into:
- m_registrableDomainsToBlockAndDeleteCookiesFor
- m_registrableDomainsToBlockButKeepCookiesFor
... to support different network load policies based on this distinction.

* page/RuntimeEnabledFeatures.h:
(WebCore::RuntimeEnabledFeatures::setITPSessionSwitchingEnabled):
(WebCore::RuntimeEnabledFeatures::itpSessionSwitchingEnabled const):
* page/Settings.yaml:
* platform/network/NetworkStorageSession.cpp:
(WebCore::NetworkStorageSession::shouldBlockThirdPartyCookies const):
(WebCore::NetworkStorageSession::shouldBlockThirdPartyCookiesButKeepFirstPartyCookiesFor const):
(WebCore::NetworkStorageSession::setPrevalentDomainsToBlockAndDeleteCookiesFor):
(WebCore::NetworkStorageSession::setPrevalentDomainsToBlockButKeepCookiesFor):
(WebCore::NetworkStorageSession::removePrevalentDomains):
(WebCore::NetworkStorageSession::setPrevalentDomainsToBlockCookiesFor): Deleted.
* platform/network/NetworkStorageSession.h:

Source/WebKit:

Since prevalent resources with user interaction get to keep their cookies and website
data, we should use a different NSURLSessions for when they are first-party websites
and have access to that data. This patch achieves that.

The WebKit::NetworkDataTaskCocoa constructor now checks with the network storage session
if the first party for this load should be isolated. The category for which this is true
is checked in the new function
WebCore:NetworkStorageSession::shouldBlockThirdPartyCookiesButKeepFirstPartyCookiesFor()
which in turn is backed by a new split of m_registrableDomainsToBlockCookieFor into:
- m_registrableDomainsToBlockAndDeleteCookiesFor
- m_registrableDomainsToBlockButKeepCookiesFor
... in WebCore:NetworkStorageSession.

Non-isolated sessions are now picked up through the convenience function
WebKit::NetworkSessionCocoa::session() whereas isolated sessions are created lazily and
picked up through WebKit::NetworkSessionCocoa::isolatedSession().

The number of isolated NSURLSessions in memory is capped to 10. When the cap is hit,
the session that's been unused the longest is aged out.

The C API changes are test infrastructure.

* NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.cpp:
(WebKit::ResourceLoadStatisticsDatabaseStore::clear):
(WebKit::ResourceLoadStatisticsDatabaseStore::domainsToBlockAndDeleteCookiesFor const):
(WebKit::ResourceLoadStatisticsDatabaseStore::domainsToBlockButKeepCookiesFor const):
(WebKit::ResourceLoadStatisticsDatabaseStore::updateCookieBlocking):
(WebKit::ResourceLoadStatisticsDatabaseStore::domainsToBlock const): Deleted.
* NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.h:
* NetworkProcess/Classifier/ResourceLoadStatisticsMemoryStore.cpp:
(WebKit::ResourceLoadStatisticsMemoryStore::clear):
(WebKit::ResourceLoadStatisticsMemoryStore::updateCookieBlocking):
* NetworkProcess/Classifier/ResourceLoadStatisticsStore.cpp:
(WebKit::ResourceLoadStatisticsStore::updateCookieBlockingForDomains):
(WebKit::ResourceLoadStatisticsStore::debugLogDomainsInBatches):
* NetworkProcess/Classifier/ResourceLoadStatisticsStore.h:
* NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp:
(WebKit::WebResourceLoadStatisticsStore::callUpdatePrevalentDomainsToBlockCookiesForHandler):
(WebKit::WebResourceLoadStatisticsStore::scheduleCookieBlockingUpdateForDomains): Deleted.
(WebKit::WebResourceLoadStatisticsStore::scheduleClearBlockingStateForDomains): Deleted.
    Dead code.
* NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h:
(WebKit::RegistrableDomainsToBlockCookiesFor::isolatedCopy const):
* NetworkProcess/NetworkProcess.cpp:
(WebKit::NetworkProcess::updatePrevalentDomainsToBlockCookiesFor):
(WebKit::NetworkProcess::scheduleClearInMemoryAndPersistent):
(WebKit::NetworkProcess::hasIsolatedSession const):
* NetworkProcess/NetworkProcess.h:
* NetworkProcess/NetworkProcess.messages.in:
* NetworkProcess/NetworkSession.h:
(WebKit::NetworkSession::shouldIsolateSessionsForPrevalentTopFrames const):
(WebKit::NetworkSession::hasIsolatedSession const):
(WebKit::NetworkSession::clearIsolatedSessions):
* NetworkProcess/NetworkSessionCreationParameters.cpp:
(WebKit::NetworkSessionCreationParameters::encode const):
(WebKit::NetworkSessionCreationParameters::decode):
* NetworkProcess/NetworkSessionCreationParameters.h:
* NetworkProcess/cocoa/NetworkDataTaskCocoa.mm:
(WebKit::NetworkDataTaskCocoa::NetworkDataTaskCocoa):
* NetworkProcess/cocoa/NetworkSessionCocoa.h:
* NetworkProcess/cocoa/NetworkSessionCocoa.mm:
(WebKit::NetworkSessionCocoa::NetworkSessionCocoa):
(WebKit::NetworkSessionCocoa::session):
(WebKit::NetworkSessionCocoa::isolatedSession):
(WebKit::NetworkSessionCocoa::hasIsolatedSession const):
(WebKit::NetworkSessionCocoa::clearIsolatedSessions):
(WebKit::NetworkSessionCocoa::invalidateAndCancel):
(WebKit::NetworkSessionCocoa::clearCredentials):
* Shared/WebPreferences.yaml:
* UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
(WKWebsiteDataStoreStatisticsHasIsolatedSession):
* UIProcess/API/C/WKWebsiteDataStoreRef.h:
* UIProcess/Network/NetworkProcessProxy.cpp:
(WebKit::NetworkProcessProxy::hasIsolatedSession):
* UIProcess/Network/NetworkProcessProxy.h:
* UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm:
(WebKit::WebsiteDataStore::parameters):
* UIProcess/WebsiteData/WebsiteDataStore.cpp:
(WebKit::WebsiteDataStore::hasLocalStorageForTesting const):
(WebKit::WebsiteDataStore::hasIsolatedSessionForTesting const):
* UIProcess/WebsiteData/WebsiteDataStore.h:

Tools:

This patch adds test infrastructure to query whether an origin has an
isolated NSURLSession or not.

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

LayoutTests:

* http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction-expected.txt: Added.
* http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html: Added.
* http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction-expected.txt: Added.
* http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html: Added.

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

42 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html [new file with mode: 0644]
LayoutTests/http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/page/RuntimeEnabledFeatures.h
Source/WebCore/page/Settings.yaml
Source/WebCore/platform/network/NetworkStorageSession.cpp
Source/WebCore/platform/network/NetworkStorageSession.h
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.cpp
Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.h
Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsMemoryStore.cpp
Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsStore.cpp
Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsStore.h
Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp
Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h
Source/WebKit/NetworkProcess/NetworkProcess.cpp
Source/WebKit/NetworkProcess/NetworkProcess.h
Source/WebKit/NetworkProcess/NetworkProcess.messages.in
Source/WebKit/NetworkProcess/NetworkSession.h
Source/WebKit/NetworkProcess/NetworkSessionCreationParameters.cpp
Source/WebKit/NetworkProcess/NetworkSessionCreationParameters.h
Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm
Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h
Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm
Source/WebKit/Shared/WebPreferences.yaml
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/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm
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 fd8e5ce..49d3ce3 100644 (file)
@@ -1,3 +1,16 @@
+2019-08-13  John Wilander  <wilander@apple.com>
+
+        Resource Load Statistics: Switch NSURLSession on top navigation to prevalent resource with user interaction
+        https://bugs.webkit.org/show_bug.cgi?id=200642
+        <rdar://problem/53962073>
+
+        Reviewed by Alex Christensen.
+
+        * http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction-expected.txt: Added.
+        * http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html: Added.
+        * http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction-expected.txt: Added.
+        * http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html: Added.
+
 2019-08-13  Ryan Haddad  <ryanhaddad@apple.com>
 
         [WebAuthN] Enable LocalAuthenticator for macOS
diff --git a/LayoutTests/http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction-expected.txt b/LayoutTests/http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction-expected.txt
new file mode 100644 (file)
index 0000000..d05dc09
--- /dev/null
@@ -0,0 +1,12 @@
+Tests that the session is not switched upon top frame navigation to a prevalent resource without user interaction.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS Should have and has the session cookie.
+PASS Should have and has the persistent cookie.
+PASS Origin has no isolated session.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html b/LayoutTests/http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html
new file mode 100644 (file)
index 0000000..9b6fce3
--- /dev/null
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <script src="/js-test-resources/js-test.js"></script>
+    <script src="resources/util.js"></script>
+</head>
+<body onload="runTest()">
+<script>
+    description("Tests that the session is not switched upon top frame navigation to a prevalent resource without user interaction.");
+    jsTestIsAsync = true;
+
+    const prevalentOrigin = "http://127.0.0.1:8000";
+    const nonPrevalentOrigin = "http://localhost:8000";
+    const sessionCookieName = "sessionCookie";
+    const persistentCookieName = "persistentCookie";
+    const twoMinutesInSeconds = 120;
+
+    function setSessionCookie() {
+        document.cookie = sessionCookieName + "=1; path=/";
+    }
+
+    function setPersistentCookie() {
+        document.cookie = persistentCookieName + "=1; path=/; Max-Age=" + twoMinutesInSeconds + ";";
+    }
+
+    function checkCookies(shouldHaveSessionCookie, shouldHavePersistentCookie) {
+        let hasSessionCookie = (document.cookie + "").includes(sessionCookieName),
+            hasPersistentCookie = (document.cookie + "").includes(persistentCookieName);
+
+        if (shouldHaveSessionCookie && hasSessionCookie)
+            testPassed("Should have and has the session cookie.");
+        else if (shouldHaveSessionCookie && !hasSessionCookie) {
+            testFailed("Should have but doesn't have the session cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else if (!shouldHaveSessionCookie && hasSessionCookie) {
+            testFailed("Shouldn't have but has the session cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else
+            testPassed("Shouldn't have and doesn't have the session cookie.");
+
+
+        if (shouldHavePersistentCookie && hasPersistentCookie)
+            testPassed("Should have and has the persistent cookie.");
+        else if (shouldHavePersistentCookie && !hasPersistentCookie) {
+            testFailed("Should have but doesn't have the persistent cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else if (!shouldHavePersistentCookie && hasPersistentCookie) {
+            testFailed("Shouldn't have but has the persistent cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else
+            testPassed("Shouldn't have and doesn't have the persistent cookie.");
+    }
+
+    function runTest() {
+        switch (document.location.hash) {
+            case "#step1":
+                setSessionCookie();
+                setPersistentCookie();
+                checkCookies(true, true);
+                if (testRunner.hasStatisticsIsolatedSession(prevalentOrigin)) {
+                    testFailed("Origin has isolated session.");
+                    setEnableFeature(false, finishJSTest);
+                } else
+                    testPassed("Origin has no isolated session.");
+                document.location.href = nonPrevalentOrigin + "/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html#step2";
+                break;
+            case "#step2":
+                document.location.hash = "step3";
+                if (document.location.origin !== nonPrevalentOrigin)
+                    testFailed("Step 2 is not on " + nonPrevalentOrigin + ".");
+                testRunner.setStatisticsPrevalentResource(prevalentOrigin, true, function() {
+                    if (!testRunner.isStatisticsPrevalentResource(prevalentOrigin)) {
+                        testFailed(prevalentOrigin + " did not get set as prevalent resource.");
+                        setEnableFeature(false, finishJSTest);
+                    }
+                    testRunner.statisticsUpdateCookieBlocking(runTest);
+                });
+                break;
+            case "#step3":
+                document.location.href = prevalentOrigin + "/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html#step4";
+                break;
+            case "#step4":
+                checkCookies(true, true);
+                if (testRunner.hasStatisticsIsolatedSession(prevalentOrigin))
+                    testFailed("Origin has isolated session.");
+                else
+                    testPassed("Origin has no isolated session.");
+                setEnableFeature(false, finishJSTest);
+                break;
+            default:
+                testFailed("Unknown hash.");
+                setEnableFeature(false, finishJSTest);
+        }
+    }
+
+    if (document.location.hash === "") {
+        if (document.location.origin !== prevalentOrigin)
+            testFailed("Test is not starting out on " + prevalentOrigin + ".");
+        setEnableFeature(true, function () {
+            if (testRunner.isStatisticsPrevalentResource(prevalentOrigin))
+                testFailed(prevalentOrigin + " was classified as prevalent resource before the test starts.");
+            document.location.hash = "step1";
+        });
+    }
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction-expected.txt b/LayoutTests/http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction-expected.txt
new file mode 100644 (file)
index 0000000..343922f
--- /dev/null
@@ -0,0 +1,12 @@
+Tests that the session is switched upon top frame navigation to a prevalent resource with user interaction.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS Should have and has the session cookie.
+PASS Should have and has the persistent cookie.
+PASS Origin has isolated session.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html b/LayoutTests/http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html
new file mode 100644 (file)
index 0000000..6abf7e5
--- /dev/null
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <script src="/js-test-resources/js-test.js"></script>
+    <script src="resources/util.js"></script>
+</head>
+<body onload="runTest()">
+<script>
+    description("Tests that the session is switched upon top frame navigation to a prevalent resource with user interaction.");
+    jsTestIsAsync = true;
+
+    const prevalentOrigin = "http://127.0.0.1:8000";
+    const nonPrevalentOrigin = "http://localhost:8000";
+    const sessionCookieName = "sessionCookie";
+    const persistentCookieName = "persistentCookie";
+    const twoMinutesInSeconds = 120;
+
+    function setSessionCookie() {
+        document.cookie = sessionCookieName + "=1; path=/";
+    }
+
+    function setPersistentCookie() {
+        document.cookie = persistentCookieName + "=1; path=/; Max-Age=" + twoMinutesInSeconds + ";";
+    }
+
+    function checkCookies(shouldHaveSessionCookie, shouldHavePersistentCookie) {
+        let hasSessionCookie = (document.cookie + "").includes(sessionCookieName),
+            hasPersistentCookie = (document.cookie + "").includes(persistentCookieName);
+
+        if (shouldHaveSessionCookie && hasSessionCookie)
+            testPassed("Should have and has the session cookie.");
+        else if (shouldHaveSessionCookie && !hasSessionCookie) {
+            testFailed("Should have but doesn't have the session cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else if (!shouldHaveSessionCookie && hasSessionCookie) {
+            testFailed("Shouldn't have but has the session cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else
+            testPassed("Shouldn't have and doesn't have the session cookie.");
+
+
+        if (shouldHavePersistentCookie && hasPersistentCookie)
+            testPassed("Should have and has the persistent cookie.");
+        else if (shouldHavePersistentCookie && !hasPersistentCookie) {
+            testFailed("Should have but doesn't have the persistent cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else if (!shouldHavePersistentCookie && hasPersistentCookie) {
+            testFailed("Shouldn't have but has the persistent cookie.");
+            setEnableFeature(false, finishJSTest);
+        } else
+            testPassed("Shouldn't have and doesn't have the persistent cookie.");
+    }
+
+    function runTest() {
+        switch (document.location.hash) {
+            case "#step1":
+                testRunner.setStatisticsHasHadUserInteraction(prevalentOrigin, true, function() {
+                    setSessionCookie();
+                    setPersistentCookie();
+                    checkCookies(true, true);
+                    if (testRunner.hasStatisticsIsolatedSession(prevalentOrigin)) {
+                        testFailed("Origin has isolated session.");
+                        setEnableFeature(false, finishJSTest);
+                    } else
+                        testPassed("Origin has no isolated session.");
+                    document.location.href = nonPrevalentOrigin + "/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html#step2";
+                });
+                break;
+            case "#step2":
+                document.location.hash = "step3";
+                if (document.location.origin !== nonPrevalentOrigin)
+                    testFailed("Step 2 is not on " + nonPrevalentOrigin + ".");
+                testRunner.setStatisticsPrevalentResource(prevalentOrigin, true, function() {
+                    if (!testRunner.isStatisticsPrevalentResource(prevalentOrigin)) {
+                        testFailed(prevalentOrigin + " did not get set as prevalent resource.");
+                        setEnableFeature(false, finishJSTest);
+                    }
+                    testRunner.statisticsUpdateCookieBlocking(runTest);
+                });
+                break;
+            case "#step3":
+                document.location.href = prevalentOrigin + "/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html#step4";
+                break;
+            case "#step4":
+                checkCookies(true, true);
+                if (testRunner.hasStatisticsIsolatedSession(prevalentOrigin))
+                    testPassed("Origin has isolated session.");
+                else
+                    testFailed("Origin has no isolated session.");
+                setEnableFeature(false, finishJSTest);
+                break;
+            default:
+                testFailed("Unknown hash.");
+                setEnableFeature(false, finishJSTest);
+        }
+    }
+
+    if (document.location.hash === "") {
+        if (document.location.origin !== prevalentOrigin)
+            testFailed("Test is not starting out on " + prevalentOrigin + ".");
+        setEnableFeature(true, function () {
+            if (testRunner.isStatisticsPrevalentResource(prevalentOrigin))
+                testFailed(prevalentOrigin + " was classified as prevalent resource before the test starts.");
+            document.location.hash = "step1";
+        });
+    }
+</script>
+</body>
+</html>
index a1b899b..6b78eac 100644 (file)
@@ -1,3 +1,32 @@
+2019-08-13  John Wilander  <wilander@apple.com>
+
+        Resource Load Statistics: Switch NSURLSession on top navigation to prevalent resource with user interaction
+        https://bugs.webkit.org/show_bug.cgi?id=200642
+        <rdar://problem/53962073>
+
+        Reviewed by Alex Christensen.
+
+        Tests: http/tests/resourceLoadStatistics/do-not-switch-session-on-navigation-to-prevalent-without-interaction.html
+               http/tests/resourceLoadStatistics/switch-session-on-navigation-to-prevalent-with-interaction.html
+
+        This patch splits m_registrableDomainsToBlockCookieFor in WebCore:NetworkStorageSession into:
+        - m_registrableDomainsToBlockAndDeleteCookiesFor
+        - m_registrableDomainsToBlockButKeepCookiesFor
+        ... to support different network load policies based on this distinction.
+
+        * page/RuntimeEnabledFeatures.h:
+        (WebCore::RuntimeEnabledFeatures::setITPSessionSwitchingEnabled):
+        (WebCore::RuntimeEnabledFeatures::itpSessionSwitchingEnabled const):
+        * page/Settings.yaml:
+        * platform/network/NetworkStorageSession.cpp:
+        (WebCore::NetworkStorageSession::shouldBlockThirdPartyCookies const):
+        (WebCore::NetworkStorageSession::shouldBlockThirdPartyCookiesButKeepFirstPartyCookiesFor const):
+        (WebCore::NetworkStorageSession::setPrevalentDomainsToBlockAndDeleteCookiesFor):
+        (WebCore::NetworkStorageSession::setPrevalentDomainsToBlockButKeepCookiesFor):
+        (WebCore::NetworkStorageSession::removePrevalentDomains):
+        (WebCore::NetworkStorageSession::setPrevalentDomainsToBlockCookiesFor): Deleted.
+        * platform/network/NetworkStorageSession.h:
+
 2019-08-13  Zalan Bujtas  <zalan@apple.com>
 
         [ContentChangeObserver] setShouldObserveDOMTimerScheduling and setShouldObserveTransitions are always called in pairs.
index 5d288d3..74f9d3a 100644 (file)
@@ -147,6 +147,9 @@ public:
     void setIsITPDatabaseEnabled(bool isEnabled) { m_isITPDatabaseEnabled = isEnabled; }
     bool isITPDatabaseEnabled() const { return m_isITPDatabaseEnabled; }
 
+    void setIsITPSessionSwitchingEnabled(bool isEnabled) { m_isITPSessionSwitchingEnabled = isEnabled; }
+    bool isITPSessionSwitchingEnabled() const { return m_isITPSessionSwitchingEnabled; }
+
     void setRestrictedHTTPResponseAccess(bool isEnabled) { m_isRestrictedHTTPResponseAccess = isEnabled; }
     bool restrictedHTTPResponseAccess() const { return m_isRestrictedHTTPResponseAccess; }
 
@@ -410,6 +413,7 @@ private:
     bool m_accessibilityObjectModelEnabled { false };
     bool m_ariaReflectionEnabled { true };
     bool m_itpDebugMode { false };
+    bool m_isITPSessionSwitchingEnabled { true };
     bool m_isRestrictedHTTPResponseAccess { true };
     bool m_crossOriginResourcePolicyEnabled { true };
     bool m_isWebGLCompressedTextureASTCSupportEnabled { false };
index 45625e1..9e1e6d9 100644 (file)
@@ -861,6 +861,9 @@ webRTCEncryptionEnabled:
   initial: true
   inspectorOverride: true
 
+isITPSessionSwitchingEnabled:
+  initial: true
+
 # Deprecated
 
 iceCandidateFilteringEnabled:
index 7a27eb5..e71907d 100644 (file)
@@ -63,7 +63,20 @@ bool NetworkStorageSession::shouldBlockThirdPartyCookies(const RegistrableDomain
     if (registrableDomain.isEmpty())
         return false;
 
-    return m_registrableDomainsToBlockCookieFor.contains(registrableDomain);
+    ASSERT(!(m_registrableDomainsToBlockAndDeleteCookiesFor.contains(registrableDomain) && m_registrableDomainsToBlockButKeepCookiesFor.contains(registrableDomain)));
+
+    return m_registrableDomainsToBlockAndDeleteCookiesFor.contains(registrableDomain)
+        || m_registrableDomainsToBlockButKeepCookiesFor.contains(registrableDomain);
+}
+
+bool NetworkStorageSession::shouldBlockThirdPartyCookiesButKeepFirstPartyCookiesFor(const RegistrableDomain& registrableDomain) const
+{
+    if (registrableDomain.isEmpty())
+        return false;
+
+    ASSERT(!(m_registrableDomainsToBlockAndDeleteCookiesFor.contains(registrableDomain) && m_registrableDomainsToBlockButKeepCookiesFor.contains(registrableDomain)));
+
+    return m_registrableDomainsToBlockButKeepCookiesFor.contains(registrableDomain);
 }
 
 bool NetworkStorageSession::shouldBlockCookies(const ResourceRequest& request, Optional<uint64_t> frameID, Optional<PageIdentifier> pageID) const
@@ -103,16 +116,24 @@ void NetworkStorageSession::setAgeCapForClientSideCookies(Optional<Seconds> seco
     m_ageCapForClientSideCookiesShort = seconds ? Seconds { seconds->seconds() / 7. } : seconds;
 }
 
-void NetworkStorageSession::setPrevalentDomainsToBlockCookiesFor(const Vector<RegistrableDomain>& domains)
+void NetworkStorageSession::setPrevalentDomainsToBlockAndDeleteCookiesFor(const Vector<RegistrableDomain>& domains)
+{
+    m_registrableDomainsToBlockAndDeleteCookiesFor.clear();
+    m_registrableDomainsToBlockAndDeleteCookiesFor.add(domains.begin(), domains.end());
+}
+
+void NetworkStorageSession::setPrevalentDomainsToBlockButKeepCookiesFor(const Vector<RegistrableDomain>& domains)
 {
-    m_registrableDomainsToBlockCookieFor.clear();
-    m_registrableDomainsToBlockCookieFor.add(domains.begin(), domains.end());
+    m_registrableDomainsToBlockButKeepCookiesFor.clear();
+    m_registrableDomainsToBlockButKeepCookiesFor.add(domains.begin(), domains.end());
 }
 
 void NetworkStorageSession::removePrevalentDomains(const Vector<RegistrableDomain>& domains)
 {
-    for (auto& domain : domains)
-        m_registrableDomainsToBlockCookieFor.remove(domain);
+    for (auto& domain : domains) {
+        m_registrableDomainsToBlockAndDeleteCookiesFor.remove(domain);
+        m_registrableDomainsToBlockButKeepCookiesFor.remove(domain);
+    }
 }
 
 bool NetworkStorageSession::hasStorageAccess(const RegistrableDomain& resourceDomain, const RegistrableDomain& firstPartyDomain, Optional<uint64_t> frameID, PageIdentifier pageID) const
index e2e106a..c8ef8f7 100644 (file)
@@ -143,7 +143,9 @@ public:
     WEBCORE_EXPORT bool shouldBlockCookies(const ResourceRequest&, Optional<uint64_t> frameID, Optional<PageIdentifier>) const;
     WEBCORE_EXPORT bool shouldBlockCookies(const URL& firstPartyForCookies, const URL& resource, Optional<uint64_t> frameID, Optional<PageIdentifier>) const;
     WEBCORE_EXPORT bool shouldBlockThirdPartyCookies(const RegistrableDomain&) const;
-    WEBCORE_EXPORT void setPrevalentDomainsToBlockCookiesFor(const Vector<RegistrableDomain>&);
+    WEBCORE_EXPORT bool shouldBlockThirdPartyCookiesButKeepFirstPartyCookiesFor(const RegistrableDomain&) const;
+    WEBCORE_EXPORT void setPrevalentDomainsToBlockAndDeleteCookiesFor(const Vector<RegistrableDomain>&);
+    WEBCORE_EXPORT void setPrevalentDomainsToBlockButKeepCookiesFor(const Vector<RegistrableDomain>&);
     WEBCORE_EXPORT void setAgeCapForClientSideCookies(Optional<Seconds>);
     WEBCORE_EXPORT void removePrevalentDomains(const Vector<RegistrableDomain>& domains);
     WEBCORE_EXPORT bool hasStorageAccess(const RegistrableDomain& resourceDomain, const RegistrableDomain& firstPartyDomain, Optional<uint64_t> frameID, PageIdentifier) const;
@@ -181,7 +183,8 @@ private:
 
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
     Optional<Seconds> clientSideCookieCap(const RegistrableDomain& firstParty, Optional<PageIdentifier>) const;
-    HashSet<RegistrableDomain> m_registrableDomainsToBlockCookieFor;
+    HashSet<RegistrableDomain> m_registrableDomainsToBlockAndDeleteCookiesFor;
+    HashSet<RegistrableDomain> m_registrableDomainsToBlockButKeepCookiesFor;
     HashMap<PageIdentifier, HashMap<uint64_t, RegistrableDomain, DefaultHash<uint64_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<uint64_t>>> m_framesGrantedStorageAccess;
     HashMap<PageIdentifier, HashMap<RegistrableDomain, RegistrableDomain>> m_pagesGrantedStorageAccess;
     Optional<Seconds> m_cacheMaxAgeCapForPrevalentResources { };
index 58e7133..d01a2c7 100644 (file)
@@ -1,3 +1,93 @@
+2019-08-13  John Wilander  <wilander@apple.com>
+
+        Resource Load Statistics: Switch NSURLSession on top navigation to prevalent resource with user interaction
+        https://bugs.webkit.org/show_bug.cgi?id=200642
+        <rdar://problem/53962073>
+
+        Reviewed by Alex Christensen.
+
+        Since prevalent resources with user interaction get to keep their cookies and website
+        data, we should use a different NSURLSessions for when they are first-party websites
+        and have access to that data. This patch achieves that.
+
+        The WebKit::NetworkDataTaskCocoa constructor now checks with the network storage session
+        if the first party for this load should be isolated. The category for which this is true
+        is checked in the new function
+        WebCore:NetworkStorageSession::shouldBlockThirdPartyCookiesButKeepFirstPartyCookiesFor()
+        which in turn is backed by a new split of m_registrableDomainsToBlockCookieFor into:
+        - m_registrableDomainsToBlockAndDeleteCookiesFor
+        - m_registrableDomainsToBlockButKeepCookiesFor
+        ... in WebCore:NetworkStorageSession.
+
+        Non-isolated sessions are now picked up through the convenience function
+        WebKit::NetworkSessionCocoa::session() whereas isolated sessions are created lazily and
+        picked up through WebKit::NetworkSessionCocoa::isolatedSession().
+
+        The number of isolated NSURLSessions in memory is capped to 10. When the cap is hit,
+        the session that's been unused the longest is aged out.
+
+        The C API changes are test infrastructure.
+
+        * NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.cpp:
+        (WebKit::ResourceLoadStatisticsDatabaseStore::clear):
+        (WebKit::ResourceLoadStatisticsDatabaseStore::domainsToBlockAndDeleteCookiesFor const):
+        (WebKit::ResourceLoadStatisticsDatabaseStore::domainsToBlockButKeepCookiesFor const):
+        (WebKit::ResourceLoadStatisticsDatabaseStore::updateCookieBlocking):
+        (WebKit::ResourceLoadStatisticsDatabaseStore::domainsToBlock const): Deleted.
+        * NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.h:
+        * NetworkProcess/Classifier/ResourceLoadStatisticsMemoryStore.cpp:
+        (WebKit::ResourceLoadStatisticsMemoryStore::clear):
+        (WebKit::ResourceLoadStatisticsMemoryStore::updateCookieBlocking):
+        * NetworkProcess/Classifier/ResourceLoadStatisticsStore.cpp:
+        (WebKit::ResourceLoadStatisticsStore::updateCookieBlockingForDomains):
+        (WebKit::ResourceLoadStatisticsStore::debugLogDomainsInBatches):
+        * NetworkProcess/Classifier/ResourceLoadStatisticsStore.h:
+        * NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp:
+        (WebKit::WebResourceLoadStatisticsStore::callUpdatePrevalentDomainsToBlockCookiesForHandler):
+        (WebKit::WebResourceLoadStatisticsStore::scheduleCookieBlockingUpdateForDomains): Deleted.
+        (WebKit::WebResourceLoadStatisticsStore::scheduleClearBlockingStateForDomains): Deleted.
+            Dead code.
+        * NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h:
+        (WebKit::RegistrableDomainsToBlockCookiesFor::isolatedCopy const):
+        * NetworkProcess/NetworkProcess.cpp:
+        (WebKit::NetworkProcess::updatePrevalentDomainsToBlockCookiesFor):
+        (WebKit::NetworkProcess::scheduleClearInMemoryAndPersistent):
+        (WebKit::NetworkProcess::hasIsolatedSession const):
+        * NetworkProcess/NetworkProcess.h:
+        * NetworkProcess/NetworkProcess.messages.in:
+        * NetworkProcess/NetworkSession.h:
+        (WebKit::NetworkSession::shouldIsolateSessionsForPrevalentTopFrames const):
+        (WebKit::NetworkSession::hasIsolatedSession const):
+        (WebKit::NetworkSession::clearIsolatedSessions):
+        * NetworkProcess/NetworkSessionCreationParameters.cpp:
+        (WebKit::NetworkSessionCreationParameters::encode const):
+        (WebKit::NetworkSessionCreationParameters::decode):
+        * NetworkProcess/NetworkSessionCreationParameters.h:
+        * NetworkProcess/cocoa/NetworkDataTaskCocoa.mm:
+        (WebKit::NetworkDataTaskCocoa::NetworkDataTaskCocoa):
+        * NetworkProcess/cocoa/NetworkSessionCocoa.h:
+        * NetworkProcess/cocoa/NetworkSessionCocoa.mm:
+        (WebKit::NetworkSessionCocoa::NetworkSessionCocoa):
+        (WebKit::NetworkSessionCocoa::session):
+        (WebKit::NetworkSessionCocoa::isolatedSession):
+        (WebKit::NetworkSessionCocoa::hasIsolatedSession const):
+        (WebKit::NetworkSessionCocoa::clearIsolatedSessions):
+        (WebKit::NetworkSessionCocoa::invalidateAndCancel):
+        (WebKit::NetworkSessionCocoa::clearCredentials):
+        * Shared/WebPreferences.yaml:
+        * UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
+        (WKWebsiteDataStoreStatisticsHasIsolatedSession):
+        * UIProcess/API/C/WKWebsiteDataStoreRef.h:
+        * UIProcess/Network/NetworkProcessProxy.cpp:
+        (WebKit::NetworkProcessProxy::hasIsolatedSession):
+        * UIProcess/Network/NetworkProcessProxy.h:
+        * UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm:
+        (WebKit::WebsiteDataStore::parameters):
+        * UIProcess/WebsiteData/WebsiteDataStore.cpp:
+        (WebKit::WebsiteDataStore::hasLocalStorageForTesting const):
+        (WebKit::WebsiteDataStore::hasIsolatedSessionForTesting const):
+        * UIProcess/WebsiteData/WebsiteDataStore.h:
+
 2019-08-13  Chris Dumez  <cdumez@apple.com>
 
         Fix potential thread safety issue under WebResourceLoadStatisticsStore::hasHadUserInteraction()
index 04923ee..687d0b0 100644 (file)
@@ -1317,8 +1317,9 @@ void ResourceLoadStatisticsDatabaseStore::clear(CompletionHandler<void()>&& comp
 
     removeAllStorageAccess([callbackAggregator = callbackAggregator.copyRef()] { });
 
-    auto primaryDomainsToBlock = ensurePrevalentResourcesForDebugMode();
-    updateCookieBlockingForDomains(primaryDomainsToBlock, [callbackAggregator = callbackAggregator.copyRef()] { });
+    auto registrableDomainsToBlockAndDeleteCookiesFor = ensurePrevalentResourcesForDebugMode();
+    RegistrableDomainsToBlockCookiesFor domainsToBlock { registrableDomainsToBlockAndDeleteCookiesFor, { } };
+    updateCookieBlockingForDomains(domainsToBlock, [callbackAggregator = callbackAggregator.copyRef()] { });
 }
 
 ResourceLoadStatisticsDatabaseStore::CookieTreatmentResult ResourceLoadStatisticsDatabaseStore::cookieTreatmentForOrigin(const RegistrableDomain& domain) const
@@ -1353,12 +1354,27 @@ StorageAccessPromptWasShown ResourceLoadStatisticsDatabaseStore::hasUserGrantedS
     return !statement.getColumnInt(0) ? StorageAccessPromptWasShown::Yes : StorageAccessPromptWasShown::No;
 }
 
-Vector<RegistrableDomain> ResourceLoadStatisticsDatabaseStore::domainsToBlock() const
+Vector<RegistrableDomain> ResourceLoadStatisticsDatabaseStore::domainsToBlockAndDeleteCookiesFor() const
+{
+    ASSERT(!RunLoop::isMain());
+    
+    Vector<RegistrableDomain> results;
+    SQLiteStatement statement(m_database, "SELECT registrableDomain FROM ObservedDomains WHERE isPrevalent = 1 AND hadUserInteraction = 0"_s);
+    if (statement.prepare() != SQLITE_OK)
+        return results;
+    
+    while (statement.step() == SQLITE_ROW)
+        results.append(RegistrableDomain::uncheckedCreateFromRegistrableDomainString(statement.getColumnText(0)));
+    
+    return results;
+}
+
+Vector<RegistrableDomain> ResourceLoadStatisticsDatabaseStore::domainsToBlockButKeepCookiesFor() const
 {
     ASSERT(!RunLoop::isMain());
 
     Vector<RegistrableDomain> results;
-    SQLiteStatement statement(m_database, "SELECT registrableDomain FROM ObservedDomains WHERE isPrevalent = 1"_s);
+    SQLiteStatement statement(m_database, "SELECT registrableDomain FROM ObservedDomains WHERE isPrevalent = 1 AND hadUserInteraction = 1"_s);
     if (statement.prepare() != SQLITE_OK)
         return results;
     
@@ -1372,14 +1388,17 @@ void ResourceLoadStatisticsDatabaseStore::updateCookieBlocking(CompletionHandler
 {
     ASSERT(!RunLoop::isMain());
 
-    auto domainsToBlock = this->domainsToBlock();
+    auto domainsToBlockAndDeleteCookiesFor = this->domainsToBlockAndDeleteCookiesFor();
+    auto domainsToBlockButKeepCookiesFor = this->domainsToBlockButKeepCookiesFor();
 
-    if (domainsToBlock.isEmpty()) {
+    if (domainsToBlockAndDeleteCookiesFor.isEmpty() && domainsToBlockButKeepCookiesFor.isEmpty()) {
         completionHandler();
         return;
     }
 
-    if (debugLoggingEnabled() && !domainsToBlock.isEmpty())
+    RegistrableDomainsToBlockCookiesFor domainsToBlock { domainsToBlockAndDeleteCookiesFor, domainsToBlockButKeepCookiesFor };
+
+    if (debugLoggingEnabled() && !domainsToBlockAndDeleteCookiesFor.isEmpty() && !domainsToBlockButKeepCookiesFor.isEmpty())
         debugLogDomainsInBatches("block", domainsToBlock);
 
     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), store = makeRef(store()), domainsToBlock = crossThreadCopy(domainsToBlock), completionHandler = WTFMove(completionHandler)] () mutable {
index 11d7333..3c8d2b1 100644 (file)
@@ -117,7 +117,8 @@ private:
 #endif
     void updateLastSeen(const RegistrableDomain&, WallTime);
     void setUserInteraction(const RegistrableDomain&, bool hadUserInteraction, WallTime);
-    Vector<RegistrableDomain> domainsToBlock() const;
+    Vector<RegistrableDomain> domainsToBlockAndDeleteCookiesFor() const;
+    Vector<RegistrableDomain> domainsToBlockButKeepCookiesFor() const;
 
     struct PrevalentDomainData {
         unsigned domainID;
index 0770e8d..e239fcc 100644 (file)
@@ -712,8 +712,9 @@ void ResourceLoadStatisticsMemoryStore::clear(CompletionHandler<void()>&& comple
 
     removeAllStorageAccess([callbackAggregator = callbackAggregator.copyRef()] { });
 
-    auto primaryDomainsToBlock = ensurePrevalentResourcesForDebugMode();
-    updateCookieBlockingForDomains(primaryDomainsToBlock, [callbackAggregator = callbackAggregator.copyRef()] { });
+    auto registrableDomainsToBlockAndDeleteCookiesFor = ensurePrevalentResourcesForDebugMode();
+    RegistrableDomainsToBlockCookiesFor domainsToBlock { registrableDomainsToBlockAndDeleteCookiesFor, { } };
+    updateCookieBlockingForDomains(domainsToBlock, [callbackAggregator = callbackAggregator.copyRef()] { });
 }
 
 bool ResourceLoadStatisticsMemoryStore::wasAccessedAsFirstPartyDueToUserInteraction(const ResourceLoadStatistics& current, const ResourceLoadStatistics& updated) const
@@ -761,18 +762,25 @@ void ResourceLoadStatisticsMemoryStore::updateCookieBlocking(CompletionHandler<v
 {
     ASSERT(!RunLoop::isMain());
 
-    Vector<RegistrableDomain> domainsToBlock;
+    Vector<RegistrableDomain> domainsToBlockAndDeleteCookiesFor;
+    Vector<RegistrableDomain> domainsToBlockButKeepCookiesFor;
     for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
-        if (resourceStatistic.isPrevalentResource)
-            domainsToBlock.append(resourceStatistic.registrableDomain);
+        if (resourceStatistic.isPrevalentResource) {
+            if (hasHadUnexpiredRecentUserInteraction(resourceStatistic, OperatingDatesWindow::Long))
+                domainsToBlockButKeepCookiesFor.append(resourceStatistic.registrableDomain);
+            else
+                domainsToBlockAndDeleteCookiesFor.append(resourceStatistic.registrableDomain);
+        }
     }
 
-    if (domainsToBlock.isEmpty() && !debugModeEnabled()) {
+    if (domainsToBlockAndDeleteCookiesFor.isEmpty() && domainsToBlockButKeepCookiesFor.isEmpty() && !debugModeEnabled()) {
         completionHandler();
         return;
     }
 
-    if (debugLoggingEnabled() && !domainsToBlock.isEmpty())
+    RegistrableDomainsToBlockCookiesFor domainsToBlock { domainsToBlockAndDeleteCookiesFor, domainsToBlockButKeepCookiesFor };
+
+    if (debugLoggingEnabled() && !domainsToBlockAndDeleteCookiesFor.isEmpty() && !domainsToBlockButKeepCookiesFor.isEmpty())
         debugLogDomainsInBatches("block", domainsToBlock);
 
     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), store = makeRef(store()), domainsToBlock = crossThreadCopy(domainsToBlock), completionHandler = WTFMove(completionHandler)] () mutable {
index b93f07d..d33fd3e 100644 (file)
@@ -413,7 +413,7 @@ void ResourceLoadStatisticsStore::setDataRecordsBeingRemoved(bool value)
         m_lastTimeDataRecordsWereRemoved = MonotonicTime::now();
 }
 
-void ResourceLoadStatisticsStore::updateCookieBlockingForDomains(const Vector<RegistrableDomain>& domainsToBlock, CompletionHandler<void()>&& completionHandler)
+void ResourceLoadStatisticsStore::updateCookieBlockingForDomains(const RegistrableDomainsToBlockCookiesFor& domainsToBlock, CompletionHandler<void()>&& completionHandler)
 {
     ASSERT(!RunLoop::isMain());
     
@@ -571,8 +571,11 @@ void ResourceLoadStatisticsStore::didCreateNetworkProcess()
     updateClientSideCookiesAgeCap();
 }
 
-void ResourceLoadStatisticsStore::debugLogDomainsInBatches(const char* action, const Vector<RegistrableDomain>& domains)
+void ResourceLoadStatisticsStore::debugLogDomainsInBatches(const char* action, const RegistrableDomainsToBlockCookiesFor& domainsToBlock)
 {
+    Vector<RegistrableDomain> domains;
+    domains.appendVector(domainsToBlock.domainsToBlockAndDeleteCookiesFor);
+    domains.appendVector(domainsToBlock.domainsToBlockButKeepCookiesFor);
     static const auto maxNumberOfDomainsInOneLogStatement = 50;
     if (domains.isEmpty())
         return;
index adec14c..70acbdc 100644 (file)
@@ -98,7 +98,7 @@ public:
     virtual bool isEmpty() const = 0;
 
     virtual void updateCookieBlocking(CompletionHandler<void()>&&) = 0;
-    void updateCookieBlockingForDomains(const Vector<RegistrableDomain>& domainsToBlock, CompletionHandler<void()>&&);
+    void updateCookieBlockingForDomains(const RegistrableDomainsToBlockCookiesFor&, CompletionHandler<void()>&&);
     void clearBlockingStateForDomains(const Vector<RegistrableDomain>& domains, CompletionHandler<void()>&&);
 
     void includeTodayAsOperatingDateIfNecessary();
@@ -186,7 +186,7 @@ public:
 protected:
     static unsigned computeImportance(const WebCore::ResourceLoadStatistics&);
     static Vector<OperatingDate> mergeOperatingDates(const Vector<OperatingDate>& existingDates, Vector<OperatingDate>&& newDates);
-    static void debugLogDomainsInBatches(const char* action, const Vector<RegistrableDomain>& domains);
+    static void debugLogDomainsInBatches(const char* action, const RegistrableDomainsToBlockCookiesFor&);
 
     ResourceLoadStatisticsStore(WebResourceLoadStatisticsStore&, WorkQueue&, ShouldIncludeLocalhost);
     
index 2abd3bd..8c21157 100644 (file)
@@ -840,38 +840,6 @@ void WebResourceLoadStatisticsStore::scheduleCookieBlockingUpdate(CompletionHand
     });
 }
 
-void WebResourceLoadStatisticsStore::scheduleCookieBlockingUpdateForDomains(const Vector<RegistrableDomain>& domainsToBlock, CompletionHandler<void()>&& completionHandler)
-{
-    // Helper function used by testing system. Should only be called from the main thread.
-    ASSERT(RunLoop::isMain());
-    postTask([this, domainsToBlock = crossThreadCopy(domainsToBlock), completionHandler = WTFMove(completionHandler)]() mutable {
-        if (!m_statisticsStore) {
-            postTaskReply(WTFMove(completionHandler));
-            return;
-        }
-
-        m_statisticsStore->updateCookieBlockingForDomains(domainsToBlock, [completionHandler = WTFMove(completionHandler)]() mutable {
-            postTaskReply(WTFMove(completionHandler));
-        });
-    });
-}
-
-void WebResourceLoadStatisticsStore::scheduleClearBlockingStateForDomains(const Vector<RegistrableDomain>& domains, CompletionHandler<void()>&& completionHandler)
-{
-    // Helper function used by testing system. Should only be called from the main thread.
-    ASSERT(RunLoop::isMain());
-    postTask([this, domains = crossThreadCopy(domains), completionHandler = WTFMove(completionHandler)]() mutable {
-        if (!m_statisticsStore) {
-            postTaskReply(WTFMove(completionHandler));
-            return;
-        }
-
-        m_statisticsStore->clearBlockingStateForDomains(domains, [completionHandler = WTFMove(completionHandler)]() mutable {
-            postTaskReply(WTFMove(completionHandler));
-        });
-    });
-}
-
 void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(ShouldGrandfatherStatistics shouldGrandfather, CompletionHandler<void()>&& completionHandler)
 {
     ASSERT(RunLoop::isMain());
@@ -956,13 +924,15 @@ void WebResourceLoadStatisticsStore::setCacheMaxAgeCap(Seconds seconds, Completi
     completionHandler();
 }
 
-void WebResourceLoadStatisticsStore::callUpdatePrevalentDomainsToBlockCookiesForHandler(const Vector<RegistrableDomain>& domainsToBlock, CompletionHandler<void()>&& completionHandler)
+void WebResourceLoadStatisticsStore::callUpdatePrevalentDomainsToBlockCookiesForHandler(const RegistrableDomainsToBlockCookiesFor& domainsToBlock, CompletionHandler<void()>&& completionHandler)
 {
     ASSERT(RunLoop::isMain());
 
     if (m_networkSession) {
-        if (auto* storageSession = m_networkSession->networkStorageSession())
-            storageSession->setPrevalentDomainsToBlockCookiesFor(domainsToBlock);
+        if (auto* storageSession = m_networkSession->networkStorageSession()) {
+            storageSession->setPrevalentDomainsToBlockAndDeleteCookiesFor(domainsToBlock.domainsToBlockAndDeleteCookiesFor);
+            storageSession->setPrevalentDomainsToBlockButKeepCookiesFor(domainsToBlock.domainsToBlockButKeepCookiesFor);
+        }
     }
 
     completionHandler();
index 01242a9..023dc27 100644 (file)
@@ -63,11 +63,17 @@ class WebProcessProxy;
 enum class ShouldGrandfatherStatistics : bool;
 enum class ShouldIncludeLocalhost : bool { No, Yes };
 enum class EnableResourceLoadStatisticsDebugMode : bool { No, Yes };
+enum class EnableResourceLoadStatisticsNSURLSessionSwitching : bool { No, Yes };
 enum class WebsiteDataToRemove : uint8_t {
     All,
     AllButHttpOnlyCookies,
     AllButCookies
 };
+struct RegistrableDomainsToBlockCookiesFor {
+    Vector<WebCore::RegistrableDomain> domainsToBlockAndDeleteCookiesFor;
+    Vector<WebCore::RegistrableDomain> domainsToBlockButKeepCookiesFor;
+    RegistrableDomainsToBlockCookiesFor isolatedCopy() const { return { domainsToBlockAndDeleteCookiesFor.isolatedCopy(), domainsToBlockButKeepCookiesFor.isolatedCopy() }; }
+};
 
 class WebResourceLoadStatisticsStore final : public ThreadSafeRefCounted<WebResourceLoadStatisticsStore, WTF::DestructionThread::Main> {
 public:
@@ -168,7 +174,7 @@ public:
     void logTestingEvent(const String&);
     void callGrantStorageAccessHandler(const SubFrameDomain&, const TopFrameDomain&, Optional<FrameID>, WebCore::PageIdentifier, CompletionHandler<void(StorageAccessWasGranted)>&&);
     void removeAllStorageAccess(CompletionHandler<void()>&&);
-    void callUpdatePrevalentDomainsToBlockCookiesForHandler(const Vector<RegistrableDomain>&, CompletionHandler<void()>&&);
+    void callUpdatePrevalentDomainsToBlockCookiesForHandler(const RegistrableDomainsToBlockCookiesFor&, CompletionHandler<void()>&&);
     void callRemoveDomainsHandler(const Vector<RegistrableDomain>&);
     void callHasStorageAccessForFrameHandler(const SubFrameDomain&, const TopFrameDomain&, FrameID, WebCore::PageIdentifier, CompletionHandler<void(bool)>&&);
 
index a98f70f..481cec7 100644 (file)
@@ -636,7 +636,7 @@ void NetworkProcess::dumpResourceLoadStatistics(PAL::SessionID sessionID, Comple
 void NetworkProcess::updatePrevalentDomainsToBlockCookiesFor(PAL::SessionID sessionID, const Vector<RegistrableDomain>& domainsToBlock, CompletionHandler<void()>&& completionHandler)
 {
     if (auto* networkStorageSession = storageSession(sessionID))
-        networkStorageSession->setPrevalentDomainsToBlockCookiesFor(domainsToBlock);
+        networkStorageSession->setPrevalentDomainsToBlockAndDeleteCookiesFor(domainsToBlock);
     completionHandler();
 }
 
@@ -780,6 +780,7 @@ void NetworkProcess::scheduleCookieBlockingUpdate(PAL::SessionID sessionID, Comp
 void NetworkProcess::scheduleClearInMemoryAndPersistent(PAL::SessionID sessionID, Optional<WallTime> modifiedSince, ShouldGrandfatherStatistics shouldGrandfather, CompletionHandler<void()>&& completionHandler)
 {
     if (auto* networkSession = this->networkSession(sessionID)) {
+        networkSession->clearIsolatedSessions();
         if (auto* resourceLoadStatistics = networkSession->resourceLoadStatistics()) {
             if (modifiedSince)
                 resourceLoadStatistics->scheduleClearInMemoryAndPersistent(modifiedSince.value(), shouldGrandfather, WTFMove(completionHandler));
@@ -1235,6 +1236,14 @@ void NetworkProcess::resetCrossSiteLoadsWithLinkDecorationForTesting(PAL::Sessio
         ASSERT_NOT_REACHED();
     completionHandler();
 }
+
+void NetworkProcess::hasIsolatedSession(PAL::SessionID sessionID, const WebCore::RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler) const
+{
+    bool result = false;
+    if (auto* networkSession = this->networkSession(sessionID))
+        result = networkSession->hasIsolatedSession(domain);
+    completionHandler(result);
+}
 #endif // ENABLE(RESOURCE_LOAD_STATISTICS)
 
 bool NetworkProcess::sessionIsControlledByAutomation(PAL::SessionID sessionID) const
index 9486904..33dfffb 100644 (file)
@@ -259,6 +259,7 @@ public:
     void didCommitCrossSiteLoadWithDataTransfer(PAL::SessionID, const RegistrableDomain& fromDomain, const RegistrableDomain& toDomain, OptionSet<WebCore::CrossSiteNavigationDataTransfer::Flag>, WebCore::PageIdentifier);
     void setCrossSiteLoadWithLinkDecorationForTesting(PAL::SessionID, const RegistrableDomain& fromDomain, const RegistrableDomain& toDomain, CompletionHandler<void()>&&);
     void resetCrossSiteLoadsWithLinkDecorationForTesting(PAL::SessionID, CompletionHandler<void()>&&);
+    void hasIsolatedSession(PAL::SessionID, const WebCore::RegistrableDomain&, CompletionHandler<void(bool)>&&) const;
 #endif
 
     using CacheStorageRootPathCallback = CompletionHandler<void(String&&)>;
index 6c2827e..200683b 100644 (file)
@@ -137,6 +137,7 @@ messages -> NetworkProcess LegacyReceiver {
     SetCrossSiteLoadWithLinkDecorationForTesting(PAL::SessionID sessionID, WebCore::RegistrableDomain fromDomain, WebCore::RegistrableDomain toDomain) -> () Async
     ResetCrossSiteLoadsWithLinkDecorationForTesting(PAL::SessionID sessionID) -> () Async
     DeleteCookiesForTesting(PAL::SessionID sessionID, WebCore::RegistrableDomain domain, bool includeHttpOnlyCookies) -> () Async
+    HasIsolatedSession(PAL::SessionID sessionID, WebCore::RegistrableDomain domain) -> (bool hasIsolatedSession) Async
 #endif
 
     SetSessionIsControlledByAutomation(PAL::SessionID sessionID, bool controlled);
index c4e102e..506e7ff 100644 (file)
@@ -93,6 +93,9 @@ public:
     void notifyPageStatisticsTelemetryFinished(unsigned totalPrevalentResources, unsigned totalPrevalentResourcesWithUserInteraction, unsigned top3SubframeUnderTopFrameOrigins);
     bool enableResourceLoadStatisticsLogTestingEvent() const { return m_enableResourceLoadStatisticsLogTestingEvent; }
     void setResourceLoadStatisticsLogTestingEvent(bool log) { m_enableResourceLoadStatisticsLogTestingEvent = log; }
+    bool shouldIsolateSessionsForPrevalentTopFrames() const { return m_enableResourceLoadStatisticsNSURLSessionSwitching == EnableResourceLoadStatisticsNSURLSessionSwitching::Yes; }
+    virtual bool hasIsolatedSession(const WebCore::RegistrableDomain) const { return false; }
+    virtual void clearIsolatedSessions() { }
 #endif
     void storeAdClickAttribution(WebCore::AdClickAttribution&&);
     void handleAdClickAttributionConversion(WebCore::AdClickAttribution::Conversion&&, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest);
@@ -133,6 +136,7 @@ protected:
     ShouldIncludeLocalhost m_shouldIncludeLocalhostInResourceLoadStatistics { ShouldIncludeLocalhost::Yes };
     EnableResourceLoadStatisticsDebugMode m_enableResourceLoadStatisticsDebugMode { EnableResourceLoadStatisticsDebugMode::No };
     WebCore::RegistrableDomain m_resourceLoadStatisticsManualPrevalentResource;
+    EnableResourceLoadStatisticsNSURLSessionSwitching m_enableResourceLoadStatisticsNSURLSessionSwitching { EnableResourceLoadStatisticsNSURLSessionSwitching::No };
     bool m_enableResourceLoadStatisticsLogTestingEvent;
 #endif
     UniqueRef<AdClickAttributionManager> m_adClickAttribution;
index 3514cc0..403f91f 100644 (file)
@@ -50,7 +50,7 @@ NetworkSessionCreationParameters NetworkSessionCreationParameters::privateSessio
 #if USE(CURL)
         , { }, { }
 #endif
-        , { }, { }, false, false, { }, { }, { }, { }, { }, { }, { }, { }, { }
+        , { }, { }, false, false, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }
     };
 }
 
@@ -84,6 +84,7 @@ void NetworkSessionCreationParameters::encode(IPC::Encoder& encoder) const
     encoder << shouldIncludeLocalhostInResourceLoadStatistics;
     encoder << enableResourceLoadStatisticsDebugMode;
     encoder << resourceLoadStatisticsManualPrevalentResource;
+    encoder << enableResourceLoadStatisticsNSURLSessionSwitching;
 
     encoder << localStorageDirectory << localStorageDirectoryExtensionHandle;
     encoder << networkCacheDirectory << networkCacheDirectoryExtensionHandle;
@@ -208,6 +209,11 @@ Optional<NetworkSessionCreationParameters> NetworkSessionCreationParameters::dec
     if (!resourceLoadStatisticsManualPrevalentResource)
         return WTF::nullopt;
 
+    Optional<bool> enableResourceLoadStatisticsNSURLSessionSwitching;
+    decoder >> enableResourceLoadStatisticsNSURLSessionSwitching;
+    if (!enableResourceLoadStatisticsNSURLSessionSwitching)
+        return WTF::nullopt;
+    
     Optional<String> localStorageDirectory;
     decoder >> localStorageDirectory;
     if (!localStorageDirectory)
@@ -266,6 +272,7 @@ Optional<NetworkSessionCreationParameters> NetworkSessionCreationParameters::dec
         , WTFMove(*enableResourceLoadStatisticsLogTestingEvent)
         , WTFMove(*shouldIncludeLocalhostInResourceLoadStatistics)
         , WTFMove(*enableResourceLoadStatisticsDebugMode)
+        , WTFMove(*enableResourceLoadStatisticsNSURLSessionSwitching)
         , WTFMove(*deviceManagementRestrictionsEnabled)
         , WTFMove(*allLoadsBlockedByDeviceManagementRestrictionsForTesting)
         , WTFMove(*resourceLoadStatisticsManualPrevalentResource)
index 7a47414..1833e46 100644 (file)
@@ -87,6 +87,7 @@ struct NetworkSessionCreationParameters {
     bool enableResourceLoadStatisticsLogTestingEvent { false };
     bool shouldIncludeLocalhostInResourceLoadStatistics { true };
     bool enableResourceLoadStatisticsDebugMode { false };
+    bool enableResourceLoadStatisticsNSURLSessionSwitching { true };
     bool deviceManagementRestrictionsEnabled { false };
     bool allLoadsBlockedByDeviceManagementRestrictionsForTesting { false };
     WebCore::RegistrableDomain resourceLoadStatisticsManualPrevalentResource { };
index 5ad2134..42e7ace 100644 (file)
@@ -38,6 +38,7 @@
 #import <WebCore/AuthenticationChallenge.h>
 #import <WebCore/NetworkStorageSession.h>
 #import <WebCore/NotImplemented.h>
+#import <WebCore/RegistrableDomain.h>
 #import <WebCore/ResourceRequest.h>
 #import <pal/spi/cf/CFNetworkSPI.h>
 #import <wtf/BlockPtr.h>
@@ -205,9 +206,15 @@ NetworkDataTaskCocoa::NetworkDataTaskCocoa(NetworkSession& session, NetworkDataT
 #endif
 
     bool shouldBlockCookies = false;
+    bool needsIsolatedSession = false;
+    auto firstParty = WebCore::RegistrableDomain(request.firstPartyForCookies());
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
-    shouldBlockCookies = storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::EphemeralStatelessCookieless
-        || (session.networkStorageSession() && session.networkStorageSession()->shouldBlockCookies(request, frameID, pageID));
+    shouldBlockCookies = storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::EphemeralStatelessCookieless;
+    if (auto* networkStorageSession = session.networkStorageSession()) {
+        if (!shouldBlockCookies)
+            shouldBlockCookies = networkStorageSession->shouldBlockCookies(request, frameID, pageID);
+        needsIsolatedSession = session.shouldIsolateSessionsForPrevalentTopFrames() && networkStorageSession->shouldBlockThirdPartyCookiesButKeepFirstPartyCookiesFor(firstParty);
+    }
 #endif
     restrictRequestReferrerToOriginIfNeeded(request, shouldBlockCookies);
 
@@ -215,23 +222,22 @@ NetworkDataTaskCocoa::NetworkDataTaskCocoa(NetworkSession& session, NetworkDataT
     applySniffingPoliciesAndBindRequestToInferfaceIfNeeded(nsRequest, shouldContentSniff == WebCore::ContentSniffingPolicy::SniffContent && !url.isLocalFile(), shouldContentEncodingSniff == WebCore::ContentEncodingSniffingPolicy::Sniff);
 
     auto& cocoaSession = static_cast<NetworkSessionCocoa&>(*m_session);
+    if (needsIsolatedSession)
+        m_task = [cocoaSession.isolatedSession(storedCredentialsPolicy, firstParty) dataTaskWithRequest:nsRequest];
+    else
+        m_task = [cocoaSession.session(storedCredentialsPolicy) dataTaskWithRequest:nsRequest];
     switch (storedCredentialsPolicy) {
     case WebCore::StoredCredentialsPolicy::Use:
-        m_task = [cocoaSession.m_sessionWithCredentialStorage dataTaskWithRequest:nsRequest];
         ASSERT(!cocoaSession.m_dataTaskMapWithCredentials.contains([m_task taskIdentifier]));
         cocoaSession.m_dataTaskMapWithCredentials.add([m_task taskIdentifier], this);
         LOG(NetworkSession, "%llu Creating stateful NetworkDataTask with URL %s", [m_task taskIdentifier], nsRequest.URL.absoluteString.UTF8String);
         break;
     case WebCore::StoredCredentialsPolicy::DoNotUse:
-        m_task = [cocoaSession.m_statelessSession dataTaskWithRequest:nsRequest];
         ASSERT(!cocoaSession.m_dataTaskMapWithoutState.contains([m_task taskIdentifier]));
         cocoaSession.m_dataTaskMapWithoutState.add([m_task taskIdentifier], this);
         LOG(NetworkSession, "%llu Creating stateless NetworkDataTask with URL %s", [m_task taskIdentifier], nsRequest.URL.absoluteString.UTF8String);
         break;
     case WebCore::StoredCredentialsPolicy::EphemeralStatelessCookieless:
-        if (!cocoaSession.m_ephemeralStatelessCookielessSession)
-            cocoaSession.initializeEphemeralStatelessCookielessSession();
-        m_task = [cocoaSession.m_ephemeralStatelessCookielessSession dataTaskWithRequest:nsRequest];
         ASSERT(!cocoaSession.m_dataTaskMapEphemeralStatelessCookieless.contains([m_task taskIdentifier]));
         cocoaSession.m_dataTaskMapEphemeralStatelessCookieless.add([m_task taskIdentifier], this);
         LOG(NetworkSession, "%llu Creating ephemeral, stateless, cookieless NetworkDataTask with URL %s", [m_task taskIdentifier], nsRequest.URL.absoluteString.UTF8String);
index 474a441..70f819e 100644 (file)
@@ -38,7 +38,9 @@ OBJC_CLASS WKNetworkSessionWebSocketDelegate;
 #include "NetworkSession.h"
 #include "WebSocketTask.h"
 #include <WebCore/NetworkLoadMetrics.h>
+#include <WebCore/RegistrableDomain.h>
 #include <wtf/HashMap.h>
+#include <wtf/Seconds.h>
 
 namespace WebKit {
 
@@ -83,6 +85,11 @@ public:
 
     CFDictionaryRef proxyConfiguration() const { return m_proxyConfiguration.get(); }
 
+    NSURLSession* session(WebCore::StoredCredentialsPolicy);
+    NSURLSession* isolatedSession(WebCore::StoredCredentialsPolicy, const WebCore::RegistrableDomain);
+    bool hasIsolatedSession(const WebCore::RegistrableDomain) const override;
+    void clearIsolatedSessions() override;
+
 private:
     void invalidateAndCancel() override;
     void clearCredentials() override;
@@ -103,6 +110,16 @@ private:
     HashMap<NetworkDataTaskCocoa::TaskIdentifier, WebSocketTask*> m_webSocketDataTaskMap;
 #endif
 
+    struct IsolatedSession {
+        RetainPtr<NSURLSession> sessionWithCredentialStorage;
+        RetainPtr<WKNetworkSessionDelegate> sessionWithCredentialStorageDelegate;
+        RetainPtr<NSURLSession> statelessSession;
+        RetainPtr<WKNetworkSessionDelegate> statelessSessionDelegate;
+        WallTime lastUsed;
+    };
+
+    HashMap<WebCore::RegistrableDomain, IsolatedSession> m_isolatedSessions;
+
     RetainPtr<NSURLSession> m_sessionWithCredentialStorage;
     RetainPtr<WKNetworkSessionDelegate> m_sessionWithCredentialStorageDelegate;
     RetainPtr<NSURLSession> m_statelessSession;
index 1f8d4cb..a95e5c9 100644 (file)
@@ -67,6 +67,8 @@ using namespace WebKit;
 CFStringRef const WebKit2HTTPProxyDefaultsKey = static_cast<CFStringRef>(@"WebKit2HTTPProxy");
 CFStringRef const WebKit2HTTPSProxyDefaultsKey = static_cast<CFStringRef>(@"WebKit2HTTPSProxy");
 
+constexpr unsigned maxNumberOfIsolatedSessions { 10 };
+
 static NSURLSessionResponseDisposition toNSURLSessionResponseDisposition(WebCore::PolicyAction disposition)
 {
     switch (disposition) {
@@ -1034,6 +1036,7 @@ NetworkSessionCocoa::NetworkSessionCocoa(NetworkProcess& networkProcess, Network
     m_shouldIncludeLocalhostInResourceLoadStatistics = parameters.shouldIncludeLocalhostInResourceLoadStatistics ? ShouldIncludeLocalhost::Yes : ShouldIncludeLocalhost::No;
     m_enableResourceLoadStatisticsDebugMode = parameters.enableResourceLoadStatisticsDebugMode ? EnableResourceLoadStatisticsDebugMode::Yes : EnableResourceLoadStatisticsDebugMode::No;
     m_resourceLoadStatisticsManualPrevalentResource = parameters.resourceLoadStatisticsManualPrevalentResource;
+    m_enableResourceLoadStatisticsNSURLSessionSwitching = parameters.enableResourceLoadStatisticsNSURLSessionSwitching ? EnableResourceLoadStatisticsNSURLSessionSwitching::Yes : EnableResourceLoadStatisticsNSURLSessionSwitching::No;
     setResourceLoadStatisticsEnabled(parameters.enableResourceLoadStatistics);
 #endif
 
@@ -1068,6 +1071,75 @@ void NetworkSessionCocoa::initializeEphemeralStatelessCookielessSession()
     m_ephemeralStatelessCookielessSession = [NSURLSession sessionWithConfiguration:configuration delegate:static_cast<id>(m_ephemeralStatelessCookielessSessionDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
 }
 
+NSURLSession* NetworkSessionCocoa::session(WebCore::StoredCredentialsPolicy storedCredentialsPolicy)
+{
+    switch (storedCredentialsPolicy) {
+    case WebCore::StoredCredentialsPolicy::Use:
+        return m_sessionWithCredentialStorage.get();
+    case WebCore::StoredCredentialsPolicy::DoNotUse:
+        return m_statelessSession.get();
+    case WebCore::StoredCredentialsPolicy::EphemeralStatelessCookieless:
+        if (!m_ephemeralStatelessCookielessSession)
+            initializeEphemeralStatelessCookielessSession();
+        return m_ephemeralStatelessCookielessSession.get();
+    }
+}
+
+NSURLSession* NetworkSessionCocoa::isolatedSession(WebCore::StoredCredentialsPolicy storedCredentialsPolicy, const WebCore::RegistrableDomain firstPartyDomain)
+{
+    auto entry = m_isolatedSessions.ensure(firstPartyDomain, [this] {
+        IsolatedSession newEntry { };
+        newEntry.sessionWithCredentialStorageDelegate = adoptNS([[WKNetworkSessionDelegate alloc] initWithNetworkSession:*this withCredentials:true]);
+        newEntry.sessionWithCredentialStorage = [NSURLSession sessionWithConfiguration:m_sessionWithCredentialStorage.get().configuration delegate:static_cast<id>(newEntry.sessionWithCredentialStorageDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
+
+        newEntry.statelessSessionDelegate = adoptNS([[WKNetworkSessionDelegate alloc] initWithNetworkSession:*this withCredentials:false]);
+        newEntry.statelessSession = [NSURLSession sessionWithConfiguration:m_statelessSession.get().configuration delegate:static_cast<id>(newEntry.statelessSessionDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
+
+        return newEntry;
+    }).iterator->value;
+
+    entry.lastUsed = WallTime::now();
+
+    if (m_isolatedSessions.size() > maxNumberOfIsolatedSessions) {
+        WebCore::RegistrableDomain keyToRemove;
+        auto oldestTimestamp = WallTime::now();
+        for (auto& key : m_isolatedSessions.keys()) {
+            auto timestamp = m_isolatedSessions.get(key).lastUsed;
+            if (timestamp < oldestTimestamp) {
+                oldestTimestamp = timestamp;
+                keyToRemove = key;
+            }
+        }
+        LOG(NetworkSession, "About to remove isolated NSURLSession.");
+        m_isolatedSessions.remove(keyToRemove);
+    }
+
+    RELEASE_ASSERT(m_isolatedSessions.size() <= maxNumberOfIsolatedSessions);
+
+    switch (storedCredentialsPolicy) {
+    case WebCore::StoredCredentialsPolicy::Use:
+        LOG(NetworkSession, "Using isolated NSURLSession with credential storage.");
+        return entry.sessionWithCredentialStorage.get();
+    case WebCore::StoredCredentialsPolicy::DoNotUse:
+        LOG(NetworkSession, "Using isolated NSURLSession without credential storage.");
+        return entry.statelessSession.get();
+    case WebCore::StoredCredentialsPolicy::EphemeralStatelessCookieless:
+        if (!m_ephemeralStatelessCookielessSession)
+            initializeEphemeralStatelessCookielessSession();
+        return m_ephemeralStatelessCookielessSession.get();
+    }
+}
+
+bool NetworkSessionCocoa::hasIsolatedSession(const WebCore::RegistrableDomain domain) const
+{
+    return m_isolatedSessions.contains(domain);
+}
+
+void NetworkSessionCocoa::clearIsolatedSessions()
+{
+    m_isolatedSessions.clear();
+}
+
 void NetworkSessionCocoa::invalidateAndCancel()
 {
     NetworkSession::invalidateAndCancel();
@@ -1078,6 +1150,12 @@ void NetworkSessionCocoa::invalidateAndCancel()
     [m_sessionWithCredentialStorageDelegate sessionInvalidated];
     [m_statelessSessionDelegate sessionInvalidated];
     [m_ephemeralStatelessCookielessSessionDelegate sessionInvalidated];
+
+    for (auto& session : m_isolatedSessions.values()) {
+        [session.sessionWithCredentialStorage invalidateAndCancel];
+        [session.sessionWithCredentialStorageDelegate sessionInvalidated];
+    }
+    m_isolatedSessions.clear();
 }
 
 void NetworkSessionCocoa::clearCredentials()
@@ -1089,6 +1167,8 @@ void NetworkSessionCocoa::clearCredentials()
     // FIXME: Use resetWithCompletionHandler instead.
     m_sessionWithCredentialStorage = [NSURLSession sessionWithConfiguration:m_sessionWithCredentialStorage.get().configuration delegate:static_cast<id>(m_sessionWithCredentialStorageDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
     m_statelessSession = [NSURLSession sessionWithConfiguration:m_statelessSession.get().configuration delegate:static_cast<id>(m_statelessSessionDelegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
+    for (auto& entry : m_isolatedSessions.values())
+        entry.session = [NSURLSession sessionWithConfiguration:entry.session.get().configuration delegate:static_cast<id>(entry.delegate.get()) delegateQueue:[NSOperationQueue mainQueue]];
 #endif
 }
 
index 2bb9af1..0357134 100644 (file)
@@ -1723,6 +1723,14 @@ LazyImageLoadingEnabled:
   webcoreBinding: RuntimeEnabledFeatures
   category: experimental
 
+IsITPSessionSwitchingEnabled:
+  type: bool
+  defaultValue: true
+  humanReadableName: "ITP Session Switching"
+  humanReadableDescription: "Enable session switching for domains classified by ITP"
+  webcoreBinding: RuntimeEnabledFeatures
+  category: internal
+
 # Deprecated
 
 ICECandidateFilteringEnabled:
index 3c9660e..96b2337 100644 (file)
@@ -472,6 +472,17 @@ void WKWebsiteDataStoreSetStatisticsCacheMaxAgeCap(WKWebsiteDataStoreRef dataSto
 #endif
 }
 
+void WKWebsiteDataStoreStatisticsHasIsolatedSession(WKWebsiteDataStoreRef dataStoreRef, WKStringRef host, void* context, WKWebsiteDataStoreStatisticsHasIsolatedSessionFunction callback)
+{
+#if ENABLE(RESOURCE_LOAD_STATISTICS)
+    WebKit::toImpl(dataStoreRef)->websiteDataStore().hasIsolatedSessionForTesting(URL(URL(), WebKit::toImpl(host)->string()), [context, callback](bool hasIsolatedSession) {
+        callback(hasIsolatedSession, context);
+    });
+#else
+    callback(false, context);
+#endif
+}
+
 void WKWebsiteDataStoreStatisticsResetToConsistentState(WKWebsiteDataStoreRef dataStoreRef, void* context, WKWebsiteDataStoreStatisticsResetToConsistentStateFunction completionHandler)
 {
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
index c76eb2d..cde14ca 100644 (file)
@@ -103,6 +103,8 @@ typedef void (*WKWebsiteDataStoreStatisticsHasLocalStorageFunction)(bool hasLoca
 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 (*WKWebsiteDataStoreStatisticsHasIsolatedSessionFunction)(bool hasIsolatedSession, void* functionContext);
+WK_EXPORT void WKWebsiteDataStoreStatisticsHasIsolatedSession(WKWebsiteDataStoreRef dataStoreRef, WKStringRef host, void* context, WKWebsiteDataStoreStatisticsHasIsolatedSessionFunction callback);
 typedef void (*WKWebsiteDataStoreStatisticsResetToConsistentStateFunction)(void* functionContext);
 WK_EXPORT void WKWebsiteDataStoreStatisticsResetToConsistentState(WKWebsiteDataStoreRef dataStoreRef, void* context, WKWebsiteDataStoreStatisticsResetToConsistentStateFunction completionHandler);
 
index 0d3d03c..94faf6b 100644 (file)
@@ -974,6 +974,16 @@ void NetworkProcessProxy::deleteWebsiteDataInUIProcessForRegistrableDomains(PAL:
     });
 }
 
+void NetworkProcessProxy::hasIsolatedSession(PAL::SessionID sessionID, const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler)
+{
+    if (!canSendMessage()) {
+        completionHandler(false);
+        return;
+    }
+    
+    sendWithAsyncReply(Messages::NetworkProcess::HasIsolatedSession(sessionID, domain), WTFMove(completionHandler));
+}
+
 #endif // ENABLE(RESOURCE_LOAD_STATISTICS)
 
 void NetworkProcessProxy::sendProcessWillSuspendImminently()
index 35b8213..433545c 100644 (file)
@@ -150,6 +150,7 @@ public:
     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>&&)>&&);
+    void hasIsolatedSession(PAL::SessionID, const RegistrableDomain&, CompletionHandler<void(bool)>&&);
 #endif
 
     void processReadyToSuspend();
index a6836d9..ba9fcce 100644 (file)
@@ -32,6 +32,7 @@
 #import "WebsiteDataStoreParameters.h"
 #import <WebCore/RegistrableDomain.h>
 #import <WebCore/RuntimeApplicationChecks.h>
+#import <WebCore/RuntimeEnabledFeatures.h>
 #import <WebCore/SearchPopupMenuCocoa.h>
 #import <pal/spi/cf/CFNetworkSPI.h>
 #import <wtf/FileSystem.h>
@@ -66,6 +67,7 @@ WebsiteDataStoreParameters WebsiteDataStore::parameters()
     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
     bool shouldLogCookieInformation = false;
     bool enableResourceLoadStatisticsDebugMode = false;
+    bool enableResourceLoadStatisticsNSURLSessionSwitching = WebCore::RuntimeEnabledFeatures::sharedFeatures().isITPSessionSwitchingEnabled();
     WebCore::RegistrableDomain resourceLoadStatisticsManualPrevalentResource { };
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
     enableResourceLoadStatisticsDebugMode = [defaults boolForKey:@"ITPDebugMode"];
@@ -137,6 +139,7 @@ WebsiteDataStoreParameters WebsiteDataStore::parameters()
         false,
         shouldIncludeLocalhostInResourceLoadStatistics,
         enableResourceLoadStatisticsDebugMode,
+        enableResourceLoadStatisticsNSURLSessionSwitching,
         m_configuration->deviceManagementRestrictionsEnabled(),
         m_configuration->allLoadsBlockedByDeviceManagementRestrictionsForTesting(),
         WTFMove(resourceLoadStatisticsManualPrevalentResource),
index 94c4d59..02587b1 100644 (file)
@@ -1684,6 +1684,18 @@ void WebsiteDataStore::hasLocalStorageForTesting(const URL& url, CompletionHandl
     }
     ASSERT(!completionHandler);
 }
+
+void WebsiteDataStore::hasIsolatedSessionForTesting(const URL& url, CompletionHandler<void(bool)>&& completionHandler) const
+{
+    for (auto& processPool : processPools()) {
+        if (auto* networkProcess = processPool->networkProcess()) {
+            networkProcess->hasIsolatedSession(m_sessionID, WebCore::RegistrableDomain { url }, WTFMove(completionHandler));
+            ASSERT(processPools().size() == 1);
+            break;
+        }
+    }
+    ASSERT(!completionHandler);
+}
 #endif // ENABLE(RESOURCE_LOAD_STATISTICS)
 
 void WebsiteDataStore::setCacheMaxAgeCapForPrevalentResources(Seconds seconds, CompletionHandler<void()>&& completionHandler)
index f2fa416..66f356a 100644 (file)
@@ -179,6 +179,7 @@ public:
     void resetCrossSiteLoadsWithLinkDecorationForTesting(CompletionHandler<void()>&&);
     void deleteCookiesForTesting(const URL&, bool includeHttpOnlyCookies, CompletionHandler<void()>&&);
     void hasLocalStorageForTesting(const URL&, CompletionHandler<void(bool)>&&) const;
+    void hasIsolatedSessionForTesting(const URL&, CompletionHandler<void(bool)>&&) const;
 #endif
     void setCacheMaxAgeCapForPrevalentResources(Seconds, CompletionHandler<void()>&&);
     void resetCacheMaxAgeCapForPrevalentResources(CompletionHandler<void()>&&);
index f28a2cc..88837a3 100644 (file)
@@ -1,3 +1,24 @@
+2019-08-13  John Wilander  <wilander@apple.com>
+
+        Resource Load Statistics: Switch NSURLSession on top navigation to prevalent resource with user interaction
+        https://bugs.webkit.org/show_bug.cgi?id=200642
+        <rdar://problem/53962073>
+
+        Reviewed by Alex Christensen.
+
+        This patch adds test infrastructure to query whether an origin has an
+        isolated NSURLSession or not.
+
+        * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
+        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
+        (WTR::TestRunner::hasStatisticsIsolatedSession):
+        * WebKitTestRunner/InjectedBundle/TestRunner.h:
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::hasStatisticsIsolatedSession):
+        * WebKitTestRunner/TestController.h:
+        * WebKitTestRunner/TestInvocation.cpp:
+        (WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):
+
 2019-08-13  Zhifei Fang  <zhifei_fang@apple.com>
 
         Update my status in contributors.json to committer.
index 0aeb93e..aceed40 100644 (file)
@@ -333,6 +333,7 @@ interface TestRunner {
     boolean isStatisticsHasLocalStorage(DOMString hostName);
     void setStatisticsCacheMaxAgeCap(double seconds);
     void statisticsResetToConsistentState(object completionHandler);
+    boolean hasStatisticsIsolatedSession(DOMString hostName);
 
     // Injected bundle form client.
     void installTextDidChangeInTextFieldCallback(object callback);
index 59f25c1..3729e05 100644 (file)
@@ -2066,6 +2066,16 @@ void TestRunner::setStatisticsCacheMaxAgeCap(double seconds)
     WKBundlePostSynchronousMessage(InjectedBundle::singleton().bundle(), messageName.get(), messageBody.get(), nullptr);
 }
 
+bool TestRunner::hasStatisticsIsolatedSession(JSStringRef hostName)
+{
+    auto messageName = adoptWK(WKStringCreateWithUTF8CString("HasStatisticsIsolatedSession"));
+    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::statisticsCallClearThroughWebsiteDataRemovalCallback()
 {
     callTestRunnerCallback(StatisticsDidClearThroughWebsiteDataRemovalCallbackID);
index 7e8588e..cafe776 100644 (file)
@@ -431,6 +431,7 @@ public:
     void statisticsCallClearThroughWebsiteDataRemovalCallback();
     bool isStatisticsHasLocalStorage(JSStringRef hostName);
     void setStatisticsCacheMaxAgeCap(double seconds);
+    bool hasStatisticsIsolatedSession(JSStringRef hostName);
     void statisticsResetToConsistentState(JSValueRef completionHandler);
     void statisticsCallDidResetToConsistentStateCallback();
 
index 1654629..8fed592 100644 (file)
@@ -3511,6 +3511,15 @@ void TestController::setStatisticsCacheMaxAgeCap(double seconds)
     runUntil(context.done, noTimeout);
 }
 
+bool TestController::hasStatisticsIsolatedSession(WKStringRef host)
+{
+    auto* dataStore = WKContextGetWebsiteDataStore(platformContext());
+    ResourceStatisticsCallbackContext context(*this);
+    WKWebsiteDataStoreStatisticsHasIsolatedSession(dataStore, host, &context, resourceStatisticsBooleanResultCallback);
+    runUntil(context.done, noTimeout);
+    return context.result;
+}
+
 void TestController::statisticsResetToConsistentState()
 {
     auto* dataStore = WKContextGetWebsiteDataStore(platformContext());
index c8fe4e5..8e11197 100644 (file)
@@ -244,6 +244,7 @@ public:
     void statisticsDeleteCookiesForHost(WKStringRef host, bool includeHttpOnlyCookies);
     bool isStatisticsHasLocalStorage(WKStringRef hostName);
     void setStatisticsCacheMaxAgeCap(double seconds);
+    bool hasStatisticsIsolatedSession(WKStringRef hostName);
     void statisticsResetToConsistentState();
 
     void getAllStorageAccessEntries();
index 1073ab1..4d59e83 100644 (file)
@@ -1462,6 +1462,15 @@ WKRetainPtr<WKTypeRef> TestInvocation::didReceiveSynchronousMessageFromInjectedB
         return nullptr;
     }
 
+    if (WKStringIsEqualToUTF8CString(messageName, "HasStatisticsIsolatedSession")) {
+        ASSERT(WKGetTypeID(messageBody) == WKStringGetTypeID());
+        
+        WKStringRef hostName = static_cast<WKStringRef>(messageBody);
+        bool hasIsolatedSession = TestController::singleton().hasStatisticsIsolatedSession(hostName);
+        auto result = adoptWK(WKBooleanCreate(hasIsolatedSession));
+        return result;
+    }
+    
     if (WKStringIsEqualToUTF8CString(messageName, "RemoveAllSessionCredentials")) {
         TestController::singleton().removeAllSessionCredentials();
         return nullptr;