[WK2][Cocoa] Implement in-WebProcess cookie cache to avoid sync IPC for document...
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 18 Feb 2020 06:32:42 +0000 (06:32 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 18 Feb 2020 06:32:42 +0000 (06:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=207593
<rdar://problem/56027027>

Reviewed by Antti Koivisto.

Source/WebCore:

Implement in-WebProcess DOM cookie cache for serving `document.cookie` requests from JavaScript.

The first time document.cookie is called for a given host, the WebProcess will pull in all the
non-HTTPOnly cookies for that host from the NetworkProcess (still via sync IPC) and store them
in an in-memory cookie store. Later document.cookie calls for this host from this WebProcess
will then leverage the in-memory cookie store and avoid doing a sync IPC to the NetworkProcess
entirely.

To maintain the in-process cookie store up-to-date, the WebProcess subscribe for cookie-change
notifications from the NetworkProcess, only for the hosts it is interested in.

If the page's JavaScript sets a cookie by setting document.cookie, we will not invalidate the
cache for performance reasons. Instead, we set the cookie in our in-memory cookie before
sending the new cookie to the NetworkProcess.

For compatibility reasons, any sync IPC to a given host will currently invalidate the cookie
cache for this host. This is because this synchronous load may cause cookies to get set
synchronously and the page could access document.cookie right after the sync XHR. This behavior
is covered by the following existing test:
- http/tests/cookies/sync-xhr-set-cookie-invalidates-cache.html

Another limitation of the current implementation of the cookie cache is that it is currently
only leveraged for first party content. This is suboptimal and could be improved in a later
iteration. However, the default behavior in Safari is that third-party iframes do not have
cookie access unless they request it using the storage access API. We also currently have
a limit of 5 hosts with cached cookies per WebProcess.

Tests: http/tests/cookies/document-cookie-after-showModalDialog.html
       http/tests/cookies/document-cookie-during-iframe-parsing.html

* dom/Document.cpp:
(WebCore::Document::didLoadResourceSynchronously):
* dom/Document.h:
* dom/ScriptExecutionContext.cpp:
(WebCore::ScriptExecutionContext::didLoadResourceSynchronously):
* dom/ScriptExecutionContext.h:
* loader/CookieJar.h:
* loader/ThreadableLoader.cpp:
(WebCore::ThreadableLoader::loadResourceSynchronously):
* page/MemoryRelease.cpp:
(WebCore::releaseCriticalMemory):
* page/Settings.yaml:
* platform/network/NetworkStorageSession.h:
(WebCore::CookieChangeObserver::~CookieChangeObserver):
* platform/network/cf/NetworkStorageSessionCFNet.cpp:
(WebCore::NetworkStorageSession::NetworkStorageSession):
(WebCore::NetworkStorageSession::cookieStorage const):
* platform/network/cocoa/NetworkStorageSessionCocoa.mm:
(WebCore::NetworkStorageSession::~NetworkStorageSession):
(WebCore::NetworkStorageSession::setCookie):
(WebCore::NetworkStorageSession::setCookies):
(WebCore::NetworkStorageSession::deleteCookie):
(WebCore::nsCookiesToCookieVector):
(WebCore::NetworkStorageSession::nsCookieStorage const):
(WebCore::createPrivateStorageSession):
(WebCore::NetworkStorageSession::httpCookies const):
(WebCore::NetworkStorageSession::deleteHTTPCookie const):
(WebCore::NetworkStorageSession::setHTTPCookiesForURL const):
(WebCore::NetworkStorageSession::httpCookiesForURL const):
(WebCore::filterCookies):
(WebCore::NetworkStorageSession::cookiesForURL const):
(WebCore::NetworkStorageSession::cookiesForSession const):
(WebCore::NetworkStorageSession::cookiesForDOM const):
(WebCore::NetworkStorageSession::cookieRequestHeaderFieldValue const):
(WebCore::NetworkStorageSession::setCookiesFromDOM const):
(WebCore::NetworkStorageSession::getRawCookies const):
(WebCore::NetworkStorageSession::deleteCookiesForHostnames):
(WebCore::NetworkStorageSession::registerCookieChangeListenersIfNecessary):
(WebCore::NetworkStorageSession::unregisterCookieChangeListenersIfNecessary):
(WebCore::NetworkStorageSession::startListeningForCookieChangeNotifications):
(WebCore::NetworkStorageSession::stopListeningForCookieChangeNotifications):
(WebCore::NetworkStorageSession::domCookiesForHost):
(WebCore::NetworkStorageSession::supportsCookieChangeListenerAPI const):

Source/WebCore/PAL:

Add new CFNetwork SPI to CFNetworkSPI.h for open source builds and for using respondsToSelector.

* pal/spi/cf/CFNetworkSPI.h:

Source/WebKit:

See WebCore ChangeLog.

* NetworkProcess/NetworkConnectionToWebProcess.cpp:
(WebKit::NetworkConnectionToWebProcess::~NetworkConnectionToWebProcess):
(WebKit::NetworkConnectionToWebProcess::domCookiesForHost):
(WebKit::NetworkConnectionToWebProcess::unsubscribeFromCookieChangeNotifications):
(WebKit::NetworkConnectionToWebProcess::cookiesAdded):
(WebKit::NetworkConnectionToWebProcess::cookiesDeleted):
* NetworkProcess/NetworkConnectionToWebProcess.h:
* NetworkProcess/NetworkConnectionToWebProcess.messages.in:
* Scripts/webkit/messages.py:
* Shared/WebPreferences.yaml:
* Sources.txt:
* SourcesCocoa.txt:
* WebKit.xcodeproj/project.pbxproj:
* WebProcess/Network/NetworkProcessConnection.cpp:
(WebKit::NetworkProcessConnection::cookiesAdded):
(WebKit::NetworkProcessConnection::cookiesDeleted):
* WebProcess/Network/NetworkProcessConnection.h:
* WebProcess/Network/NetworkProcessConnection.messages.in:
* WebProcess/WebPage/Cocoa/WebCookieCacheCocoa.mm: Copied from Source/WebKit/WebProcess/WebPage/WebCookieJar.h.
(WebKit::WebCookieCache::inMemoryStorageSession):
* WebProcess/WebPage/WebCookieCache.cpp: Added.
(WebKit::WebCookieCache::isFunctional):
(WebKit::WebCookieCache::cookiesForDOM):
(WebKit::WebCookieCache::setCookiesFromDOM):
(WebKit::WebCookieCache::cookiesAdded):
(WebKit::WebCookieCache::cookiesDeleted):
(WebKit::WebCookieCache::clear):
(WebKit::WebCookieCache::clearForHost):
(WebKit::WebCookieCache::pruneCacheIfNecessary):
* WebProcess/WebPage/WebCookieCache.h: Copied from Source/WebKit/WebProcess/WebPage/WebCookieJar.h.
* WebProcess/WebPage/WebCookieJar.cpp:
(WebKit::WebCookieJar::isEligibleForCache const):
(WebKit::WebCookieJar::cookies const):
(WebKit::WebCookieJar::setCookies):
(WebKit::WebCookieJar::cookiesAdded):
(WebKit::WebCookieJar::cookiesDeleted):
(WebKit::WebCookieJar::clearCache):
(WebKit::WebCookieJar::clearCacheForHost):
* WebProcess/WebPage/WebCookieJar.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::m_overriddenMediaType):
* WebProcess/WebProcess.cpp:
(WebKit::WebProcess::WebProcess):
* WebProcess/WebProcess.h:
(WebKit::WebProcess::cookieJar):

Source/WTF:

Add build time flags for new feature.

* wtf/PlatformEnable.h:
* wtf/PlatformHave.h:

LayoutTests:

Add layout test coverage.

* http/tests/cookies/document-cookie-after-showModalDialog-expected.txt: Added.
* http/tests/cookies/document-cookie-after-showModalDialog.html: Added.
* http/tests/cookies/document-cookie-during-iframe-parsing-expected.txt: Added.
* http/tests/cookies/document-cookie-during-iframe-parsing.html: Added.
* http/tests/cookies/resources/close-modal-dialog.html: Added.
* http/tests/cookies/resources/document-cookie-during-iframe-parsing-iframe.html: Added.
* http/tests/cookies/resources/set-cookie-and-serve.php: Added.

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

48 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/cookies/document-cookie-after-showModalDialog-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/cookies/document-cookie-after-showModalDialog.html [new file with mode: 0644]
LayoutTests/http/tests/cookies/document-cookie-during-iframe-parsing-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/cookies/document-cookie-during-iframe-parsing.html [new file with mode: 0644]
LayoutTests/http/tests/cookies/resources/close-modal-dialog.html [new file with mode: 0644]
LayoutTests/http/tests/cookies/resources/document-cookie-during-iframe-parsing-iframe.html [new file with mode: 0644]
LayoutTests/http/tests/cookies/resources/set-cookie-and-serve.php [new file with mode: 0755]
Source/WTF/ChangeLog
Source/WTF/wtf/HashSet.h
Source/WTF/wtf/PlatformHave.h
Source/WebCore/ChangeLog
Source/WebCore/PAL/ChangeLog
Source/WebCore/PAL/pal/spi/cf/CFNetworkSPI.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/ScriptExecutionContext.cpp
Source/WebCore/dom/ScriptExecutionContext.h
Source/WebCore/loader/CookieJar.h
Source/WebCore/loader/ThreadableLoader.cpp
Source/WebCore/page/MemoryRelease.cpp
Source/WebCore/page/Settings.yaml
Source/WebCore/platform/network/NetworkStorageSession.cpp
Source/WebCore/platform/network/NetworkStorageSession.h
Source/WebCore/platform/network/cf/NetworkStorageSessionCFNet.cpp
Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in
Source/WebKit/Scripts/webkit/messages.py
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/Sources.txt
Source/WebKit/SourcesCocoa.txt
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/Network/NetworkProcessConnection.cpp
Source/WebKit/WebProcess/Network/NetworkProcessConnection.h
Source/WebKit/WebProcess/Network/NetworkProcessConnection.messages.in
Source/WebKit/WebProcess/WebPage/Cocoa/WebCookieCacheCocoa.mm [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/WebCookieCache.cpp [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/WebCookieCache.h [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp
Source/WebKit/WebProcess/WebPage/WebCookieJar.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebProcess.cpp
Source/WebKit/WebProcess/WebProcess.h
Tools/TestWebKitAPI/Tests/WebKitCocoa/CookieAcceptPolicy.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/CookiePrivateBrowsing.mm

index b08839a..9170552 100644 (file)
@@ -1,3 +1,21 @@
+2020-02-17  Chris Dumez  <cdumez@apple.com>
+
+        [WK2][Cocoa] Implement in-WebProcess cookie cache to avoid sync IPC for document.cookie in most cases
+        https://bugs.webkit.org/show_bug.cgi?id=207593
+        <rdar://problem/56027027>
+
+        Reviewed by Antti Koivisto.
+
+        Add layout test coverage.
+
+        * http/tests/cookies/document-cookie-after-showModalDialog-expected.txt: Added.
+        * http/tests/cookies/document-cookie-after-showModalDialog.html: Added.
+        * http/tests/cookies/document-cookie-during-iframe-parsing-expected.txt: Added.
+        * http/tests/cookies/document-cookie-during-iframe-parsing.html: Added.
+        * http/tests/cookies/resources/close-modal-dialog.html: Added.
+        * http/tests/cookies/resources/document-cookie-during-iframe-parsing-iframe.html: Added.
+        * http/tests/cookies/resources/set-cookie-and-serve.php: Added.
+
 2020-02-17  Ryan Haddad  <ryanhaddad@apple.com>
 
         Changed results due to ANGLE use
diff --git a/LayoutTests/http/tests/cookies/document-cookie-after-showModalDialog-expected.txt b/LayoutTests/http/tests/cookies/document-cookie-after-showModalDialog-expected.txt
new file mode 100644 (file)
index 0000000..f4af924
--- /dev/null
@@ -0,0 +1,11 @@
+Tests that document.cookie returns the right value after a showModalDialog() call
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS normalizeCookie(document.cookie) is "testKey=testValue"
+PASS normalizeCookie(document.cookie) is "foo=bar; testKey=testValue"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/cookies/document-cookie-after-showModalDialog.html b/LayoutTests/http/tests/cookies/document-cookie-after-showModalDialog.html
new file mode 100644 (file)
index 0000000..9ab847b
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src='resources/cookies-test-pre.js'></script>
+<script src="/js-test-resources/ui-helper.js"></script>
+</head>
+<body>
+<input id="testButton" type="button" value="Click me"></input>
+<script>
+description('Tests that document.cookie returns the right value after a showModalDialog() call');
+jsTestIsAsync = true;
+
+if (window.testRunner)
+    testRunner.setCanOpenWindows();
+
+testButton.onclick = () => {
+    document.cookie = "testKey=testValue";
+    shouldBeEqualToString('normalizeCookie(document.cookie)', 'testKey=testValue');
+
+    showModalDialog("resources/set-cookie-and-serve.php?cookie-name=foo&cookie-value=bar&destination=close-modal-dialog.html");
+
+    // This is so the cookie gets removed at the end of the test.
+    registerCookieForCleanup('foo=bar; path=/');
+    shouldBeEqualToString('normalizeCookie(document.cookie)', 'foo=bar; testKey=testValue');
+
+    finishJSTest();
+}
+
+onload = () => {
+    internals.withUserGesture(() => {
+        testButton.click();
+    });
+};
+</script>
+<script src='resources/cookies-test-post.js'></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/cookies/document-cookie-during-iframe-parsing-expected.txt b/LayoutTests/http/tests/cookies/document-cookie-during-iframe-parsing-expected.txt
new file mode 100644 (file)
index 0000000..3cf5ada
--- /dev/null
@@ -0,0 +1,12 @@
+Tests that document.cookie returns the right value after a showModalDialog() call
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS normalizeCookie(document.cookie) is "testKey=testValue"
+PASS normalizeCookie(cookiesInFrame) is "foo=bar; testKey=testValue"
+PASS normalizeCookie(document.cookie) is "foo=bar; testKey=testValue"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/cookies/document-cookie-during-iframe-parsing.html b/LayoutTests/http/tests/cookies/document-cookie-during-iframe-parsing.html
new file mode 100644 (file)
index 0000000..ff29efb
--- /dev/null
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src='resources/cookies-test-pre.js'></script>
+<script src="/js-test-resources/ui-helper.js"></script>
+</head>
+<body>
+<script>
+description('Tests that document.cookie returns the right value after a showModalDialog() call');
+jsTestIsAsync = true;
+
+function validateCookiesDuringChildFrameParsing(_cookiesInFrame)
+{
+    cookiesInFrame = _cookiesInFrame;
+    shouldBeEqualToString('normalizeCookie(cookiesInFrame)', 'foo=bar; testKey=testValue');
+    shouldBeEqualToString('normalizeCookie(document.cookie)', 'foo=bar; testKey=testValue');
+    finishJSTest();    
+}
+
+onload = () => {
+    document.cookie = "testKey=testValue";
+    shouldBeEqualToString('normalizeCookie(document.cookie)', 'testKey=testValue');
+
+    // This is so the cookie gets removed at the end of the test.
+    registerCookieForCleanup('foo=bar; path=/');
+
+    let iframe = document.createElement("iframe");
+    iframe.src = "resources/set-cookie-and-serve.php?cookie-name=foo&cookie-value=bar&destination=document-cookie-during-iframe-parsing-iframe.html";
+    document.body.appendChild(iframe);
+};
+</script>
+<script src='resources/cookies-test-post.js'></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/cookies/resources/close-modal-dialog.html b/LayoutTests/http/tests/cookies/resources/close-modal-dialog.html
new file mode 100644 (file)
index 0000000..ef568df
--- /dev/null
@@ -0,0 +1,7 @@
+<script>
+onload = () => {
+    close();
+    if (window.testRunner)
+       testRunner.abortModal();
+};
+</script>
diff --git a/LayoutTests/http/tests/cookies/resources/document-cookie-during-iframe-parsing-iframe.html b/LayoutTests/http/tests/cookies/resources/document-cookie-during-iframe-parsing-iframe.html
new file mode 100644 (file)
index 0000000..b2d7ab7
--- /dev/null
@@ -0,0 +1,3 @@
+<script>
+top.validateCookiesDuringChildFrameParsing(document.cookie);
+</script>
diff --git a/LayoutTests/http/tests/cookies/resources/set-cookie-and-serve.php b/LayoutTests/http/tests/cookies/resources/set-cookie-and-serve.php
new file mode 100755 (executable)
index 0000000..59f43f2
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+$cookieName = $_GET['cookie-name'];
+$cookieValue = $_GET['cookie-value'];
+$destination = $_GET['destination'];
+header("Content-Type: text/html");
+header("Cache-Control: no-store");
+header("Set-Cookie: ${cookieName}=${cookieValue}; path=/");
+$fp = fopen($destination, 'rb');
+header("Content-Length: " . filesize($destination));
+
+fpassthru($fp);
+exit;
+?>
index 8299b9f..45d464c 100644 (file)
@@ -1,3 +1,16 @@
+2020-02-17  Chris Dumez  <cdumez@apple.com>
+
+        [WK2][Cocoa] Implement in-WebProcess cookie cache to avoid sync IPC for document.cookie in most cases
+        https://bugs.webkit.org/show_bug.cgi?id=207593
+        <rdar://problem/56027027>
+
+        Reviewed by Antti Koivisto.
+
+        Add build time flags for new feature.
+
+        * wtf/PlatformEnable.h:
+        * wtf/PlatformHave.h:
+
 2020-02-17  Tim Horton  <timothy_horton@apple.com>
 
         Add and adopt HAVE(LOOKUP_GESTURE_RECOGNIZER)
index e2687e9..ee2c291 100644 (file)
@@ -112,6 +112,8 @@ public:
     // them are new to the set. Returns false if the set is unchanged.
     template<typename IteratorType>
     bool add(IteratorType begin, IteratorType end);
+    template<typename IteratorType>
+    bool remove(IteratorType begin, IteratorType end);
 
     bool remove(const ValueType&);
     bool remove(iterator);
@@ -271,6 +273,16 @@ inline bool HashSet<T, U, V>::add(IteratorType begin, IteratorType end)
 }
 
 template<typename T, typename U, typename V>
+template<typename IteratorType>
+inline bool HashSet<T, U, V>::remove(IteratorType begin, IteratorType end)
+{
+    bool changed = false;
+    for (IteratorType iter = begin; iter != end; ++iter)
+        changed |= remove(*iter);
+    return changed;
+}
+
+template<typename T, typename U, typename V>
 inline bool HashSet<T, U, V>::remove(iterator it)
 {
     if (it.m_impl == m_impl.end())
index 27b4793..2c1871b 100644 (file)
 #define HAVE_DEVICE_IDENTITY 1
 #endif
 
+#if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101600) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 140000)
+#define HAVE_COOKIE_CHANGE_LISTENER_API 1
+#endif
+
 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) || PLATFORM(IOS_FAMILY)
 #define HAVE_DATA_PROTECTION_KEYCHAIN 1
 #endif
index 959b7bb..a60f0c0 100644 (file)
@@ -1,5 +1,87 @@
 2020-02-17  Chris Dumez  <cdumez@apple.com>
 
+        [WK2][Cocoa] Implement in-WebProcess cookie cache to avoid sync IPC for document.cookie in most cases
+        https://bugs.webkit.org/show_bug.cgi?id=207593
+        <rdar://problem/56027027>
+
+        Reviewed by Antti Koivisto.
+
+        Implement in-WebProcess DOM cookie cache for serving `document.cookie` requests from JavaScript.
+
+        The first time document.cookie is called for a given host, the WebProcess will pull in all the
+        non-HTTPOnly cookies for that host from the NetworkProcess (still via sync IPC) and store them
+        in an in-memory cookie store. Later document.cookie calls for this host from this WebProcess
+        will then leverage the in-memory cookie store and avoid doing a sync IPC to the NetworkProcess
+        entirely.
+
+        To maintain the in-process cookie store up-to-date, the WebProcess subscribe for cookie-change
+        notifications from the NetworkProcess, only for the hosts it is interested in.
+
+        If the page's JavaScript sets a cookie by setting document.cookie, we will not invalidate the
+        cache for performance reasons. Instead, we set the cookie in our in-memory cookie before
+        sending the new cookie to the NetworkProcess.
+
+        For compatibility reasons, any sync IPC to a given host will currently invalidate the cookie
+        cache for this host. This is because this synchronous load may cause cookies to get set
+        synchronously and the page could access document.cookie right after the sync XHR. This behavior
+        is covered by the following existing test:
+        - http/tests/cookies/sync-xhr-set-cookie-invalidates-cache.html
+
+        Another limitation of the current implementation of the cookie cache is that it is currently
+        only leveraged for first party content. This is suboptimal and could be improved in a later
+        iteration. However, the default behavior in Safari is that third-party iframes do not have
+        cookie access unless they request it using the storage access API. We also currently have
+        a limit of 5 hosts with cached cookies per WebProcess.
+
+        Tests: http/tests/cookies/document-cookie-after-showModalDialog.html
+               http/tests/cookies/document-cookie-during-iframe-parsing.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::didLoadResourceSynchronously):
+        * dom/Document.h:
+        * dom/ScriptExecutionContext.cpp:
+        (WebCore::ScriptExecutionContext::didLoadResourceSynchronously):
+        * dom/ScriptExecutionContext.h:
+        * loader/CookieJar.h:
+        * loader/ThreadableLoader.cpp:
+        (WebCore::ThreadableLoader::loadResourceSynchronously):
+        * page/MemoryRelease.cpp:
+        (WebCore::releaseCriticalMemory):
+        * page/Settings.yaml:
+        * platform/network/NetworkStorageSession.h:
+        (WebCore::CookieChangeObserver::~CookieChangeObserver):
+        * platform/network/cf/NetworkStorageSessionCFNet.cpp:
+        (WebCore::NetworkStorageSession::NetworkStorageSession):
+        (WebCore::NetworkStorageSession::cookieStorage const):
+        * platform/network/cocoa/NetworkStorageSessionCocoa.mm:
+        (WebCore::NetworkStorageSession::~NetworkStorageSession):
+        (WebCore::NetworkStorageSession::setCookie):
+        (WebCore::NetworkStorageSession::setCookies):
+        (WebCore::NetworkStorageSession::deleteCookie):
+        (WebCore::nsCookiesToCookieVector):
+        (WebCore::NetworkStorageSession::nsCookieStorage const):
+        (WebCore::createPrivateStorageSession):
+        (WebCore::NetworkStorageSession::httpCookies const):
+        (WebCore::NetworkStorageSession::deleteHTTPCookie const):
+        (WebCore::NetworkStorageSession::setHTTPCookiesForURL const):
+        (WebCore::NetworkStorageSession::httpCookiesForURL const):
+        (WebCore::filterCookies):
+        (WebCore::NetworkStorageSession::cookiesForURL const):
+        (WebCore::NetworkStorageSession::cookiesForSession const):
+        (WebCore::NetworkStorageSession::cookiesForDOM const):
+        (WebCore::NetworkStorageSession::cookieRequestHeaderFieldValue const):
+        (WebCore::NetworkStorageSession::setCookiesFromDOM const):
+        (WebCore::NetworkStorageSession::getRawCookies const):
+        (WebCore::NetworkStorageSession::deleteCookiesForHostnames):
+        (WebCore::NetworkStorageSession::registerCookieChangeListenersIfNecessary):
+        (WebCore::NetworkStorageSession::unregisterCookieChangeListenersIfNecessary):
+        (WebCore::NetworkStorageSession::startListeningForCookieChangeNotifications):
+        (WebCore::NetworkStorageSession::stopListeningForCookieChangeNotifications):
+        (WebCore::NetworkStorageSession::domCookiesForHost):
+        (WebCore::NetworkStorageSession::supportsCookieChangeListenerAPI const):
+
+2020-02-17  Chris Dumez  <cdumez@apple.com>
+
         Defer execution of async scripts until until the document is loaded
         https://bugs.webkit.org/show_bug.cgi?id=207698
         <rdar://problem/57625747>
index 913f319..59c7f76 100644 (file)
@@ -1,3 +1,15 @@
+2020-02-17  Chris Dumez  <cdumez@apple.com>
+
+        [WK2][Cocoa] Implement in-WebProcess cookie cache to avoid sync IPC for document.cookie in most cases
+        https://bugs.webkit.org/show_bug.cgi?id=207593
+        <rdar://problem/56027027>
+
+        Reviewed by Antti Koivisto.
+
+        Add new CFNetwork SPI to CFNetworkSPI.h for open source builds and for using respondsToSelector.
+
+        * pal/spi/cf/CFNetworkSPI.h:
+
 2020-02-17  Peng Liu  <peng.liu6@apple.com>
 
         Fix check-webkit-style errors related to AVFoundationSPI.h
index 6658e72..a920760 100644 (file)
@@ -391,6 +391,10 @@ WTF_EXTERN_C_END
 
 @interface NSHTTPCookieStorage ()
 + (void)_setSharedHTTPCookieStorage:(NSHTTPCookieStorage *)storage;
+- (void)_setSubscribedDomainsForCookieChanges:(NSSet<NSString*>* __nullable)domainList;
+- (void)_setCookiesAddedHandler:(void(^__nullable)(NSArray<NSHTTPCookie*>* addedCookies, NSURL* __nullable urlForAddedCookies))cookiesAddedHandler onQueue:(dispatch_queue_t __nullable)queue;
+- (void)_setCookiesDeletedHandler:(void(^__nullable)(NSArray<NSHTTPCookie*>* __nullable deletedCookies, bool deletedAllCookies))cookiesDeletedHandler onQueue:(dispatch_queue_t __nullable)queue;
+- (NSArray* __nullable)_getCookiesForDomain:(NSString*)domain;
 @end
 
 @interface NSURLResponse ()
index bb40393..4c9ec30 100644 (file)
@@ -7203,11 +7203,14 @@ void Document::invalidateDOMCookieCache()
     m_cachedDOMCookies = String();
 }
 
-void Document::didLoadResourceSynchronously()
+void Document::didLoadResourceSynchronously(const URL& url)
 {
     // Synchronous resources loading can set cookies so we invalidate the cookies cache
     // in this case, to be safe.
     invalidateDOMCookieCache();
+
+    if (auto* page = this->page())
+        page->cookieJar().clearCacheForHost(url.host().toString());
 }
 
 void Document::ensurePlugInsInjectedScript(DOMWrapperWorld& world)
index 1bcf3cf..bc205d0 100644 (file)
@@ -1641,7 +1641,7 @@ private:
     void setCachedDOMCookies(const String&);
     bool isDOMCookieCacheValid() const { return m_cookieCacheExpiryTimer.isActive(); }
     void invalidateDOMCookieCache();
-    void didLoadResourceSynchronously() final;
+    void didLoadResourceSynchronously(const URL&) final;
 
     bool canNavigateInternal(Frame& targetFrame);
     bool isNavigationBlockedByThirdPartyIFrameRedirectBlocking(Frame& targetFrame, const URL& destinationURL);
index 5882e1a..32af744 100644 (file)
@@ -218,7 +218,7 @@ void ScriptExecutionContext::destroyedMessagePort(MessagePort& messagePort)
     m_messagePorts.remove(&messagePort);
 }
 
-void ScriptExecutionContext::didLoadResourceSynchronously()
+void ScriptExecutionContext::didLoadResourceSynchronously(const URL&)
 {
 }
 
index 00831a2..bc04df4 100644 (file)
@@ -152,7 +152,7 @@ public:
     void createdMessagePort(MessagePort&);
     void destroyedMessagePort(MessagePort&);
 
-    virtual void didLoadResourceSynchronously();
+    virtual void didLoadResourceSynchronously(const URL&);
 
     void ref() { refScriptExecutionContext(); }
     void deref() { derefScriptExecutionContext(); }
index 84dba40..300293a 100644 (file)
@@ -60,6 +60,10 @@ public:
     virtual bool getRawCookies(const Document&, const URL&, Vector<Cookie>&) const;
     virtual void deleteCookie(const Document&, const URL&, const String& cookieName);
 
+    // Cookie Cache.
+    virtual void clearCache() { }
+    virtual void clearCacheForHost(const String&) { }
+
     virtual ~CookieJar();
 protected:
     static SameSiteInfo sameSiteInfo(const Document&);
index 2b7bf42..c7e0f31 100644 (file)
@@ -110,11 +110,12 @@ RefPtr<ThreadableLoader> ThreadableLoader::create(ScriptExecutionContext& contex
 
 void ThreadableLoader::loadResourceSynchronously(ScriptExecutionContext& context, ResourceRequest&& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options)
 {
+    auto resourceURL = request.url();
     if (is<WorkerGlobalScope>(context))
         WorkerThreadableLoader::loadResourceSynchronously(downcast<WorkerGlobalScope>(context), WTFMove(request), client, options);
     else
         DocumentThreadableLoader::loadResourceSynchronously(downcast<Document>(context), WTFMove(request), client, options);
-    context.didLoadResourceSynchronously();
+    context.didLoadResourceSynchronously(resourceURL);
 }
 
 void ThreadableLoader::logError(ScriptExecutionContext& context, const ResourceError& error, const String& initiator)
index 316d216..411f872 100644 (file)
@@ -33,6 +33,7 @@
 #include "Chrome.h"
 #include "ChromeClient.h"
 #include "CommonVM.h"
+#include "CookieJar.h"
 #include "Document.h"
 #include "FontCache.h"
 #include "Frame.h"
@@ -99,6 +100,10 @@ static void releaseCriticalMemory(Synchronous synchronous, MaintainBackForwardCa
 
     CSSValuePool::singleton().drain();
 
+    Page::forEachPage([](auto& page) {
+        page.cookieJar().clearCache();
+    });
+
     for (auto& document : copyToVectorOf<RefPtr<Document>>(Document::allDocuments())) {
         document->styleScope().releaseMemory();
         document->fontSelector().emptyCaches();
index eb79b83..c2113d1 100644 (file)
@@ -361,6 +361,9 @@ requestIdleCallbackEnabled:
 HTTPSUpgradeEnabled:
   initial: false
 
+inProcessCookieCacheEnabled:
+  initial: false
+
 thirdPartyIframeRedirectBlockingEnabled:
   initial: true
 
index 2994275..a4eede1 100644 (file)
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "NetworkStorageSession.h"
 
+#include "Cookie.h"
 #include "HTTPCookieAcceptPolicy.h"
 #include "RuntimeApplicationChecks.h"
 #include <wtf/NeverDestroyed.h>
@@ -56,6 +57,14 @@ void NetworkStorageSession::permitProcessToUseCookieAPI(bool value)
         removeProcessPrivilege(ProcessPrivilege::CanAccessRawCookies);
 }
 
+#if !PLATFORM(COCOA)
+Vector<Cookie> NetworkStorageSession::domCookiesForHost(const String&)
+{
+    ASSERT_NOT_IMPLEMENTED_YET();
+    return { };
+}
+#endif // !PLATFORM(COCOA)
+
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
 
 bool NetworkStorageSession::shouldBlockThirdPartyCookies(const RegistrableDomain& registrableDomain) const
index 6506f10..f519269 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012-2018 Apple Inc. All rights reserved.
+ * Copyright (C) 2012-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -60,6 +60,9 @@ typedef struct _SoupCookieJar SoupCookieJar;
 
 #if PLATFORM(COCOA)
 #include "CookieStorageObserver.h"
+OBJC_CLASS NSArray;
+OBJC_CLASS NSHTTPCookie;
+OBJC_CLASS NSMutableSet;
 #endif
 
 namespace WebCore {
@@ -79,6 +82,15 @@ enum class ThirdPartyCookieBlockingMode : uint8_t { All, AllOnSitesWithoutUserIn
 enum class FirstPartyWebsiteDataRemovalMode : uint8_t { AllButCookies, None, AllButCookiesLiveOnTestingTimeout, AllButCookiesReproTestingTimeout };
 enum class ShouldAskITP : bool { No, Yes };
 
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+class CookieChangeObserver {
+public:
+    virtual ~CookieChangeObserver() { }
+    virtual void cookiesAdded(const String& host, const Vector<WebCore::Cookie>&) = 0;
+    virtual void cookiesDeleted() = 0;
+};
+#endif
+
 class NetworkStorageSession {
     WTF_MAKE_NONCOPYABLE(NetworkStorageSession); WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -92,9 +104,14 @@ public:
     WEBCORE_EXPORT NSHTTPCookieStorage *nsCookieStorage() const;
 #endif
 
+#if PLATFORM(COCOA)
+    WEBCORE_EXPORT ~NetworkStorageSession();
+#endif
+
 #if PLATFORM(COCOA) || USE(CFURLCONNECTION)
     WEBCORE_EXPORT static RetainPtr<CFURLStorageSessionRef> createCFStorageSessionForIdentifier(CFStringRef identifier);
-    WEBCORE_EXPORT NetworkStorageSession(PAL::SessionID, RetainPtr<CFURLStorageSessionRef>&&, RetainPtr<CFHTTPCookieStorageRef>&&);
+    enum class IsInMemoryCookieStore : bool { No, Yes };
+    WEBCORE_EXPORT NetworkStorageSession(PAL::SessionID, RetainPtr<CFURLStorageSessionRef>&&, RetainPtr<CFHTTPCookieStorageRef>&&, IsInMemoryCookieStore = IsInMemoryCookieStore::No);
     WEBCORE_EXPORT explicit NetworkStorageSession(PAL::SessionID);
 
     // May be null, in which case a Foundation default should be used.
@@ -146,6 +163,14 @@ public:
     WEBCORE_EXPORT std::pair<String, bool> cookieRequestHeaderFieldValue(const URL& firstParty, const SameSiteInfo&, const URL&, Optional<FrameIdentifier>, Optional<PageIdentifier>, IncludeSecureCookies, ShouldAskITP) const;
     WEBCORE_EXPORT std::pair<String, bool> cookieRequestHeaderFieldValue(const CookieRequestHeaderFieldProxy&) const;
 
+    WEBCORE_EXPORT Vector<Cookie> domCookiesForHost(const String& host);
+
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    WEBCORE_EXPORT void startListeningForCookieChangeNotifications(CookieChangeObserver&, const String& host);
+    WEBCORE_EXPORT void stopListeningForCookieChangeNotifications(CookieChangeObserver&, const HashSet<String>& hosts);
+    WEBCORE_EXPORT bool supportsCookieChangeListenerAPI() const;
+#endif
+
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
     void setResourceLoadStatisticsEnabled(bool enabled) { m_isResourceLoadStatisticsEnabled = enabled; }
     WEBCORE_EXPORT bool shouldBlockCookies(const ResourceRequest&, Optional<FrameIdentifier>, Optional<PageIdentifier>) const;
@@ -173,11 +198,27 @@ public:
 #endif
 
 private:
+#if PLATFORM(COCOA)
+    enum IncludeHTTPOnlyOrNot { DoNotIncludeHTTPOnly, IncludeHTTPOnly };
+    std::pair<String, bool> cookiesForSession(const URL& firstParty, const SameSiteInfo&, const URL&, Optional<FrameIdentifier>, Optional<PageIdentifier>, IncludeHTTPOnlyOrNot, IncludeSecureCookies, ShouldAskITP = ShouldAskITP::Yes) const;
+    NSArray *httpCookies(CFHTTPCookieStorageRef) const;
+    NSArray *httpCookiesForURL(CFHTTPCookieStorageRef, NSURL *firstParty, const Optional<SameSiteInfo>&, NSURL *) const;
+    NSArray *cookiesForURL(const URL& firstParty, const SameSiteInfo&, const URL&, Optional<FrameIdentifier>, Optional<PageIdentifier>, ShouldAskITP) const;
+    void setHTTPCookiesForURL(CFHTTPCookieStorageRef, NSArray *cookies, NSURL *, NSURL *mainDocumentURL, const SameSiteInfo&) const;
+    void deleteHTTPCookie(CFHTTPCookieStorageRef, NSHTTPCookie *) const;
+#endif
+
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    void registerCookieChangeListenersIfNecessary();
+    void unregisterCookieChangeListenersIfNecessary();
+#endif
+
     PAL::SessionID m_sessionID;
 
 #if PLATFORM(COCOA) || USE(CFURLCONNECTION)
     RetainPtr<CFURLStorageSessionRef> m_platformSession;
     RetainPtr<CFHTTPCookieStorageRef> m_platformCookieStorage;
+    bool m_isInMemoryCookieStore { false };
 #elif USE(SOUP)
     static void cookiesDidChange(NetworkStorageSession*);
 
@@ -190,6 +231,12 @@ private:
     RefPtr<NetworkingContext> m_context;
 #endif
 
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    bool m_didRegisterCookieListeners { false };
+    RetainPtr<NSMutableSet> m_subscribedDomainsForCookieChanges;
+    HashMap<String, HashSet<CookieChangeObserver*>> m_cookieChangeObservers;
+#endif
+
     CredentialStorage m_credentialStorage;
 
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
index b01a7e6..e0d84f7 100644 (file)
@@ -73,11 +73,12 @@ RetainPtr<CFURLStorageSessionRef> NetworkStorageSession::createCFStorageSessionF
     return storageSession;
 }
 
-NetworkStorageSession::NetworkStorageSession(PAL::SessionID sessionID, RetainPtr<CFURLStorageSessionRef>&& platformSession, RetainPtr<CFHTTPCookieStorageRef>&& platformCookieStorage)
+NetworkStorageSession::NetworkStorageSession(PAL::SessionID sessionID, RetainPtr<CFURLStorageSessionRef>&& platformSession, RetainPtr<CFHTTPCookieStorageRef>&& platformCookieStorage, IsInMemoryCookieStore isInMemoryCookieStore)
     : m_sessionID(sessionID)
     , m_platformSession(WTFMove(platformSession))
+    , m_isInMemoryCookieStore(isInMemoryCookieStore == IsInMemoryCookieStore::Yes)
 {
-    ASSERT(processMayUseCookieAPI() || !platformCookieStorage);
+    ASSERT(processMayUseCookieAPI() || !platformCookieStorage || m_isInMemoryCookieStore);
     m_platformCookieStorage = platformCookieStorage ? WTFMove(platformCookieStorage) : cookieStorage();
 }
 
@@ -88,10 +89,10 @@ NetworkStorageSession::NetworkStorageSession(PAL::SessionID sessionID)
 
 RetainPtr<CFHTTPCookieStorageRef> NetworkStorageSession::cookieStorage() const
 {
-    if (!processMayUseCookieAPI())
+    if (!processMayUseCookieAPI() && !m_isInMemoryCookieStore)
         return nullptr;
 
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
 
     if (m_platformCookieStorage)
         return m_platformCookieStorage;
index 9e9a91b..68aa323 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015-2018 Apple Inc.  All rights reserved.
+ * Copyright (C) 2015-2020 Apple Inc.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 namespace WebCore {
 
+NetworkStorageSession::~NetworkStorageSession()
+{
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    unregisterCookieChangeListenersIfNecessary();
+#endif
+}
+
 void NetworkStorageSession::setCookie(const Cookie& cookie)
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
 
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
     [nsCookieStorage() setCookie:(NSHTTPCookie *)cookie];
@@ -55,7 +62,7 @@ void NetworkStorageSession::setCookie(const Cookie& cookie)
 
 void NetworkStorageSession::setCookies(const Vector<Cookie>& cookies, const URL& url, const URL& mainDocumentURL)
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
 
     RetainPtr<NSMutableArray> nsCookies = adoptNS([[NSMutableArray alloc] initWithCapacity:cookies.size()]);
     for (const auto& cookie : cookies)
@@ -68,19 +75,18 @@ void NetworkStorageSession::setCookies(const Vector<Cookie>& cookies, const URL&
 
 void NetworkStorageSession::deleteCookie(const Cookie& cookie)
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
     [nsCookieStorage() deleteCookie:(NSHTTPCookie *)cookie];
 }
 
-static Vector<Cookie> nsCookiesToCookieVector(NSArray<NSHTTPCookie *> *nsCookies)
+static Vector<Cookie> nsCookiesToCookieVector(NSArray<NSHTTPCookie *> *nsCookies, const Function<bool(NSHTTPCookie *)>& filter = { })
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
-
     Vector<Cookie> cookies;
     cookies.reserveInitialCapacity(nsCookies.count);
-    for (NSHTTPCookie *nsCookie in nsCookies)
-        cookies.uncheckedAppend(nsCookie);
-
+    for (NSHTTPCookie *nsCookie in nsCookies) {
+        if (!filter || filter(nsCookie))
+            cookies.uncheckedAppend(nsCookie);
+    }
     return cookies;
 }
 
@@ -118,7 +124,7 @@ void NetworkStorageSession::flushCookieStore()
 
 NSHTTPCookieStorage *NetworkStorageSession::nsCookieStorage() const
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
     auto cfCookieStorage = cookieStorage();
     if (!cfCookieStorage || [NSHTTPCookieStorage sharedHTTPCookieStorage]._cookieStorage == cfCookieStorage)
         return [NSHTTPCookieStorage sharedHTTPCookieStorage];
@@ -158,11 +164,6 @@ CFURLStorageSessionRef createPrivateStorageSession(CFStringRef identifier)
     CFURLCacheSetDiskCapacity(cache.get(), 0); // Setting disk cache size should not be necessary once <rdar://problem/12656814> is fixed.
     CFURLCacheSetMemoryCapacity(cache.get(), [[NSURLCache sharedURLCache] memoryCapacity]);
 
-    if (!NetworkStorageSession::processMayUseCookieAPI())
-        return storageSession.leakRef();
-
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
-
     auto cookieStorage = adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, storageSession.get()));
     if (!cookieStorage)
         return nullptr;
@@ -173,20 +174,23 @@ CFURLStorageSessionRef createPrivateStorageSession(CFStringRef identifier)
     return storageSession.leakRef();
 }
 
-static NSArray *httpCookies(CFHTTPCookieStorageRef cookieStorage)
+NSArray *NetworkStorageSession::httpCookies(CFHTTPCookieStorageRef cookieStorage) const
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
-    if (!cookieStorage)
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
+    if (!cookieStorage) {
+        RELEASE_ASSERT(!m_isInMemoryCookieStore);
         return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
+    }
     
     auto cookies = adoptCF(CFHTTPCookieStorageCopyCookies(cookieStorage));
     return [NSHTTPCookie _cf2nsCookies:cookies.get()];
 }
 
-static void deleteHTTPCookie(CFHTTPCookieStorageRef cookieStorage, NSHTTPCookie *cookie)
+void NetworkStorageSession::deleteHTTPCookie(CFHTTPCookieStorageRef cookieStorage, NSHTTPCookie *cookie) const
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
     if (!cookieStorage) {
+        RELEASE_ASSERT(!m_isInMemoryCookieStore);
         [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
         return;
     }
@@ -226,9 +230,9 @@ static NSArray *cookiesForURL(NSHTTPCookieStorage *storage, NSURL *url, NSURL *m
     return cookiesPtr->autorelease();
 }
 
-static void setHTTPCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSArray *cookies, NSURL *url, NSURL *mainDocumentURL, const SameSiteInfo& sameSiteInfo)
+void NetworkStorageSession::setHTTPCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSArray *cookies, NSURL *url, NSURL *mainDocumentURL, const SameSiteInfo& sameSiteInfo) const
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
     if (!cookieStorage) {
 #if !(PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101400) && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
         if ([NSHTTPCookieStorage instancesRespondToSelector:@selector(_setCookies:forURL:mainDocumentURL:policyProperties:)])
@@ -255,21 +259,22 @@ static void setHTTPCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSArray *
 #endif
 }
 
-static NSArray *httpCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSURL *firstParty, const Optional<SameSiteInfo>& sameSiteInfo, NSURL *url)
+NSArray *NetworkStorageSession::httpCookiesForURL(CFHTTPCookieStorageRef cookieStorage, NSURL *firstParty, const Optional<SameSiteInfo>& sameSiteInfo, NSURL *url) const
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
-    if (!cookieStorage)
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
+    if (!cookieStorage) {
+        RELEASE_ASSERT(!m_isInMemoryCookieStore);
         cookieStorage = _CFHTTPCookieStorageGetDefault(kCFAllocatorDefault);
+    }
 
     // FIXME: Stop creating a new NSHTTPCookieStorage object each time we want to query the cookie jar.
     // NetworkStorageSession could instead keep a NSHTTPCookieStorage object for us.
     RetainPtr<NSHTTPCookieStorage> nsCookieStorage = adoptNS([[NSHTTPCookieStorage alloc] _initWithCFHTTPCookieStorage:cookieStorage]);
-    return cookiesForURL(nsCookieStorage.get(), url, firstParty, sameSiteInfo);
+    return WebCore::cookiesForURL(nsCookieStorage.get(), url, firstParty, sameSiteInfo);
 }
 
 static RetainPtr<NSArray> filterCookies(NSArray *unfilteredCookies, Optional<Seconds> cappedLifetime)
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
     NSUInteger count = [unfilteredCookies count];
     RetainPtr<NSMutableArray> filteredCookies = adoptNS([[NSMutableArray alloc] initWithCapacity:count]);
 
@@ -302,27 +307,26 @@ static RetainPtr<NSArray> filterCookies(NSArray *unfilteredCookies, Optional<Sec
     return filteredCookies;
 }
 
-static NSArray *cookiesForURL(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP)
+NSArray *NetworkStorageSession::cookiesForURL(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP) const
 {
 #if ENABLE(RESOURCE_LOAD_STATISTICS)
-    if (shouldAskITP == ShouldAskITP::Yes && session.shouldBlockCookies(firstParty, url, frameID, pageID))
+    if (shouldAskITP == ShouldAskITP::Yes && shouldBlockCookies(firstParty, url, frameID, pageID))
         return nil;
 #else
     UNUSED_PARAM(frameID);
     UNUSED_PARAM(pageID);
     UNUSED_PARAM(shouldAskITP);
 #endif
-    return httpCookiesForURL(session.cookieStorage().get(), firstParty, sameSiteInfo, url);
+    return httpCookiesForURL(cookieStorage().get(), firstParty, sameSiteInfo, url);
 }
 
-enum IncludeHTTPOnlyOrNot { DoNotIncludeHTTPOnly, IncludeHTTPOnly };
-static std::pair<String, bool> cookiesForSession(const NetworkStorageSession& session, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, IncludeHTTPOnlyOrNot includeHTTPOnly, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP = ShouldAskITP::Yes)
+std::pair<String, bool> NetworkStorageSession::cookiesForSession(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, IncludeHTTPOnlyOrNot includeHTTPOnly, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP) const
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
 
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
 
-    NSArray *cookies = cookiesForURL(session, firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP);
+    NSArray *cookies = cookiesForURL(firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP);
     if (![cookies count])
         return { String(), false }; // Return a null string, not an empty one that StringBuilder would create below.
 
@@ -374,22 +378,22 @@ static void deleteAllHTTPCookies(CFHTTPCookieStorageRef cookieStorage)
 
 std::pair<String, bool> NetworkStorageSession::cookiesForDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP) const
 {
-    return cookiesForSession(*this, firstParty, sameSiteInfo, url, frameID, pageID, DoNotIncludeHTTPOnly, includeSecureCookies, shouldAskITP);
+    return cookiesForSession(firstParty, sameSiteInfo, url, frameID, pageID, DoNotIncludeHTTPOnly, includeSecureCookies, shouldAskITP);
 }
 
 std::pair<String, bool> NetworkStorageSession::cookieRequestHeaderFieldValue(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, IncludeSecureCookies includeSecureCookies, ShouldAskITP shouldAskITP) const
 {
-    return cookiesForSession(*this, firstParty, sameSiteInfo, url, frameID, pageID, IncludeHTTPOnly, includeSecureCookies, shouldAskITP);
+    return cookiesForSession(firstParty, sameSiteInfo, url, frameID, pageID, IncludeHTTPOnly, includeSecureCookies, shouldAskITP);
 }
 
 std::pair<String, bool> NetworkStorageSession::cookieRequestHeaderFieldValue(const CookieRequestHeaderFieldProxy& headerFieldProxy) const
 {
-    return cookiesForSession(*this, headerFieldProxy.firstParty, headerFieldProxy.sameSiteInfo, headerFieldProxy.url, headerFieldProxy.frameID, headerFieldProxy.pageID, IncludeHTTPOnly, headerFieldProxy.includeSecureCookies);
+    return cookiesForSession(headerFieldProxy.firstParty, headerFieldProxy.sameSiteInfo, headerFieldProxy.url, headerFieldProxy.frameID, headerFieldProxy.pageID, IncludeHTTPOnly, headerFieldProxy.includeSecureCookies);
 }
 
 void NetworkStorageSession::setCookiesFromDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, Optional<FrameIdentifier> frameID, Optional<PageIdentifier> pageID, ShouldAskITP shouldAskITP, const String& cookieStr) const
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
 
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
 
@@ -457,7 +461,7 @@ bool NetworkStorageSession::getRawCookies(const URL& firstParty, const SameSiteI
     rawCookies.clear();
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
 
-    NSArray *cookies = cookiesForURL(*this, firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP);
+    NSArray *cookies = cookiesForURL(firstParty, sameSiteInfo, url, frameID, pageID, shouldAskITP);
     NSUInteger count = [cookies count];
     rawCookies.reserveCapacity(count);
     for (NSUInteger i = 0; i < count; ++i) {
@@ -518,7 +522,7 @@ void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& host
 
 void NetworkStorageSession::deleteCookiesForHostnames(const Vector<String>& hostnames, IncludeHttpOnlyCookies includeHttpOnlyCookies)
 {
-    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies));
+    ASSERT(hasProcessPrivilege(ProcessPrivilege::CanAccessRawCookies) || m_isInMemoryCookieStore);
 
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
 
@@ -566,4 +570,98 @@ void NetworkStorageSession::deleteAllCookiesModifiedSince(WallTime timePoint)
     [storage _saveCookies];
 }
 
+Vector<Cookie> NetworkStorageSession::domCookiesForHost(const String& host)
+{
+    NSArray *nsCookies = [nsCookieStorage() _getCookiesForDomain:(NSString *)host];
+    return nsCookiesToCookieVector(nsCookies, [](NSHTTPCookie *cookie) { return !cookie.HTTPOnly; });
+}
+
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+
+void NetworkStorageSession::registerCookieChangeListenersIfNecessary()
+{
+    if (m_didRegisterCookieListeners)
+        return;
+
+    m_didRegisterCookieListeners = true;
+    [nsCookieStorage() _setCookiesAddedHandler:^(NSArray<NSHTTPCookie *> * nsCookies, NSURL *urlForAddedCookies) {
+        Vector<Cookie> cookies = nsCookiesToCookieVector(nsCookies);
+        auto host = URL(urlForAddedCookies).host().toString();
+        RELEASE_ASSERT(!host.isNull());
+        auto it = m_cookieChangeObservers.find(host);
+        if (it == m_cookieChangeObservers.end())
+            return;
+        for (auto* observer : it->value)
+            observer->cookiesAdded(host, cookies);
+    } onQueue:dispatch_get_main_queue()];
+    [nsCookieStorage() _setCookiesDeletedHandler:^(NSArray<NSHTTPCookie *> *, bool /*deletedAllCookies*/) {
+        for (auto& observers : m_cookieChangeObservers.values()) {
+            for (auto* observer : observers)
+                observer->cookiesDeleted();
+        }
+    } onQueue:dispatch_get_main_queue()];
+}
+
+void NetworkStorageSession::unregisterCookieChangeListenersIfNecessary()
+{
+    if (!m_didRegisterCookieListeners)
+        return;
+
+    [nsCookieStorage() _setCookiesAddedHandler:nil onQueue:nil];
+    [nsCookieStorage() _setCookiesDeletedHandler:nil onQueue:nil];
+    [nsCookieStorage() _setSubscribedDomainsForCookieChanges:nil];
+    m_didRegisterCookieListeners = false;
+}
+
+void NetworkStorageSession::startListeningForCookieChangeNotifications(CookieChangeObserver& observer, const String& host)
+{
+    registerCookieChangeListenersIfNecessary();
+
+    auto& observers = m_cookieChangeObservers.ensure(host, [] {
+        return HashSet<CookieChangeObserver*> { };
+    }).iterator->value;
+    ASSERT(!observers.contains(&observer));
+    observers.add(&observer);
+
+    if (!m_subscribedDomainsForCookieChanges)
+        m_subscribedDomainsForCookieChanges = adoptNS([[NSMutableSet alloc] init]);
+    else if ([m_subscribedDomainsForCookieChanges containsObject:(NSString *)host])
+        return;
+
+    [m_subscribedDomainsForCookieChanges addObject:(NSString *)host];
+    [nsCookieStorage() _setSubscribedDomainsForCookieChanges:m_subscribedDomainsForCookieChanges.get()];
+}
+
+void NetworkStorageSession::stopListeningForCookieChangeNotifications(CookieChangeObserver& observer, const HashSet<String>& hosts)
+{
+    bool subscribedURLsChanged = false;
+    for (auto& host : hosts) {
+        auto it = m_cookieChangeObservers.find(host);
+        ASSERT(it != m_cookieChangeObservers.end());
+        if (it == m_cookieChangeObservers.end())
+            continue;
+
+        auto& observers = it->value;
+        ASSERT(observers.contains(&observer));
+        observers.remove(&observer);
+        if (observers.isEmpty()) {
+            m_cookieChangeObservers.remove(it);
+            ASSERT([m_subscribedDomainsForCookieChanges containsObject:(NSString *)host]);
+            [m_subscribedDomainsForCookieChanges removeObject:(NSString *)host];
+            subscribedURLsChanged = true;
+        }
+    }
+    if (subscribedURLsChanged)
+        [nsCookieStorage() _setSubscribedDomainsForCookieChanges:m_subscribedDomainsForCookieChanges.get()];
+}
+
+// FIXME: This can eventually go away, this is merely to ensure a smooth transition to the new API.
+bool NetworkStorageSession::supportsCookieChangeListenerAPI() const
+{
+    static const bool supportsAPI = [nsCookieStorage() respondsToSelector:@selector(_setSubscribedDomainsForCookieChanges:)];
+    return supportsAPI;
+}
+
+#endif // HAVE(COOKIE_CHANGE_LISTENER_API)
+
 } // namespace WebCore
index 7875082..78e62d4 100644 (file)
@@ -1,5 +1,61 @@
 2020-02-17  Chris Dumez  <cdumez@apple.com>
 
+        [WK2][Cocoa] Implement in-WebProcess cookie cache to avoid sync IPC for document.cookie in most cases
+        https://bugs.webkit.org/show_bug.cgi?id=207593
+        <rdar://problem/56027027>
+
+        Reviewed by Antti Koivisto.
+
+        See WebCore ChangeLog.
+
+        * NetworkProcess/NetworkConnectionToWebProcess.cpp:
+        (WebKit::NetworkConnectionToWebProcess::~NetworkConnectionToWebProcess):
+        (WebKit::NetworkConnectionToWebProcess::domCookiesForHost):
+        (WebKit::NetworkConnectionToWebProcess::unsubscribeFromCookieChangeNotifications):
+        (WebKit::NetworkConnectionToWebProcess::cookiesAdded):
+        (WebKit::NetworkConnectionToWebProcess::cookiesDeleted):
+        * NetworkProcess/NetworkConnectionToWebProcess.h:
+        * NetworkProcess/NetworkConnectionToWebProcess.messages.in:
+        * Scripts/webkit/messages.py:
+        * Shared/WebPreferences.yaml:
+        * Sources.txt:
+        * SourcesCocoa.txt:
+        * WebKit.xcodeproj/project.pbxproj:
+        * WebProcess/Network/NetworkProcessConnection.cpp:
+        (WebKit::NetworkProcessConnection::cookiesAdded):
+        (WebKit::NetworkProcessConnection::cookiesDeleted):
+        * WebProcess/Network/NetworkProcessConnection.h:
+        * WebProcess/Network/NetworkProcessConnection.messages.in:
+        * WebProcess/WebPage/Cocoa/WebCookieCacheCocoa.mm: Copied from Source/WebKit/WebProcess/WebPage/WebCookieJar.h.
+        (WebKit::WebCookieCache::inMemoryStorageSession):
+        * WebProcess/WebPage/WebCookieCache.cpp: Added.
+        (WebKit::WebCookieCache::isFunctional):
+        (WebKit::WebCookieCache::cookiesForDOM):
+        (WebKit::WebCookieCache::setCookiesFromDOM):
+        (WebKit::WebCookieCache::cookiesAdded):
+        (WebKit::WebCookieCache::cookiesDeleted):
+        (WebKit::WebCookieCache::clear):
+        (WebKit::WebCookieCache::clearForHost):
+        (WebKit::WebCookieCache::pruneCacheIfNecessary):
+        * WebProcess/WebPage/WebCookieCache.h: Copied from Source/WebKit/WebProcess/WebPage/WebCookieJar.h.
+        * WebProcess/WebPage/WebCookieJar.cpp:
+        (WebKit::WebCookieJar::isEligibleForCache const):
+        (WebKit::WebCookieJar::cookies const):
+        (WebKit::WebCookieJar::setCookies):
+        (WebKit::WebCookieJar::cookiesAdded):
+        (WebKit::WebCookieJar::cookiesDeleted):
+        (WebKit::WebCookieJar::clearCache):
+        (WebKit::WebCookieJar::clearCacheForHost):
+        * WebProcess/WebPage/WebCookieJar.h:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::m_overriddenMediaType):
+        * WebProcess/WebProcess.cpp:
+        (WebKit::WebProcess::WebProcess):
+        * WebProcess/WebProcess.h:
+        (WebKit::WebProcess::cookieJar):
+
+2020-02-17  Chris Dumez  <cdumez@apple.com>
+
         Defer execution of async scripts until until the document is loaded
         https://bugs.webkit.org/show_bug.cgi?id=207698
         <rdar://problem/57625747>
index 75e0b4a..72e1d26 100644 (file)
@@ -124,6 +124,11 @@ NetworkConnectionToWebProcess::~NetworkConnectionToWebProcess()
     for (auto& port : m_processEntangledPorts)
         networkProcess().messagePortChannelRegistry().didCloseMessagePort(port);
 
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    if (auto* networkStorageSession = storageSession())
+        networkStorageSession->stopListeningForCookieChangeNotifications(*this, m_hostsWithCookieListeners);
+#endif
+
 #if USE(LIBWEBRTC)
     if (m_rtcProvider)
         m_rtcProvider->close();
@@ -633,6 +638,48 @@ void NetworkConnectionToWebProcess::deleteCookie(const URL& url, const String& c
     networkStorageSession->deleteCookie(url, cookieName);
 }
 
+void NetworkConnectionToWebProcess::domCookiesForHost(const String& host, bool subscribeToCookieChangeNotifications, CompletionHandler<void(const Vector<WebCore::Cookie>&)>&& completionHandler)
+{
+    auto* networkStorageSession = storageSession();
+    if (!networkStorageSession)
+        return completionHandler({ });
+
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    if (subscribeToCookieChangeNotifications) {
+        ASSERT(!m_hostsWithCookieListeners.contains(host));
+        m_hostsWithCookieListeners.add(host);
+        networkStorageSession->startListeningForCookieChangeNotifications(*this, host);
+    }
+#else
+    UNUSED_PARAM(subscribeToCookieChangeNotifications);
+#endif
+
+    completionHandler(networkStorageSession->domCookiesForHost(host));
+}
+
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+
+void NetworkConnectionToWebProcess::unsubscribeFromCookieChangeNotifications(const HashSet<String>& hosts)
+{
+    bool removed = m_hostsWithCookieListeners.remove(hosts.begin(), hosts.end());
+    ASSERT_UNUSED(removed, removed);
+
+    if (auto* networkStorageSession = storageSession())
+        networkStorageSession->stopListeningForCookieChangeNotifications(*this, hosts);
+}
+
+void NetworkConnectionToWebProcess::cookiesAdded(const String& host, const Vector<WebCore::Cookie>& cookies)
+{
+    connection().send(Messages::NetworkProcessConnection::CookiesAdded(host, cookies), 0);
+}
+
+void NetworkConnectionToWebProcess::cookiesDeleted()
+{
+    connection().send(Messages::NetworkProcessConnection::CookiesDeleted(), 0);
+}
+
+#endif
+
 void NetworkConnectionToWebProcess::registerFileBlobURL(const URL& url, const String& path, SandboxExtension::Handle&& extensionHandle, const String& contentType)
 {
     auto* session = networkSession();
index eb6f55c..7edc5c6 100644 (file)
@@ -42,6 +42,7 @@
 #include <WebCore/MessagePortChannelProvider.h>
 #include <WebCore/MessagePortIdentifier.h>
 #include <WebCore/NetworkLoadInformation.h>
+#include <WebCore/NetworkStorageSession.h>
 #include <WebCore/PageIdentifier.h>
 #include <WebCore/ProcessIdentifier.h>
 #include <WebCore/RegistrableDomain.h>
@@ -88,6 +89,9 @@ class NetworkConnectionToWebProcess
 #if ENABLE(APPLE_PAY_REMOTE_UI)
     , public WebPaymentCoordinatorProxy::Client
 #endif
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    , public WebCore::CookieChangeObserver
+#endif
     , IPC::Connection::Client {
 public:
     using RegistrableDomain = WebCore::RegistrableDomain;
@@ -267,6 +271,16 @@ private:
 
     uint64_t nextMessageBatchIdentifier(Function<void()>&&);
 
+    void domCookiesForHost(const String& host, bool subscribeToCookieChangeNotifications, CompletionHandler<void(const Vector<WebCore::Cookie>&)>&&);
+
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    void unsubscribeFromCookieChangeNotifications(const HashSet<String>& hosts);
+
+    // WebCore::CookieChangeObserver.
+    void cookiesAdded(const String& host, const Vector<WebCore::Cookie>&) final;
+    void cookiesDeleted() final;
+#endif
+
     struct ResourceNetworkActivityTracker {
         ResourceNetworkActivityTracker() = default;
         ResourceNetworkActivityTracker(const ResourceNetworkActivityTracker&) = default;
@@ -330,6 +344,9 @@ private:
 #if ENABLE(WEB_RTC)
     NetworkMDNSRegister m_mdnsRegister;
 #endif
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    HashSet<String> m_hostsWithCookieListeners;
+#endif
 
     bool m_captureExtraNetworkLoadMetricsEnabled { false };
 
index ec9b0b8..825546c 100644 (file)
@@ -39,6 +39,10 @@ messages -> NetworkConnectionToWebProcess LegacyReceiver {
     CookieRequestHeaderFieldValue(URL firstParty, struct WebCore::SameSiteInfo sameSiteInfo, URL url, Optional<WebCore::FrameIdentifier> frameID, Optional<WebCore::PageIdentifier> pageID, enum:bool WebCore::IncludeSecureCookies includeSecureCookies, enum:bool WebCore::ShouldAskITP shouldAskITP) -> (String cookieString, bool didAccessSecureCookies) Synchronous
     GetRawCookies(URL firstParty, struct WebCore::SameSiteInfo sameSiteInfo, URL url, Optional<WebCore::FrameIdentifier> frameID, Optional<WebCore::PageIdentifier> pageID, enum:bool WebCore::ShouldAskITP shouldAskITP) -> (Vector<WebCore::Cookie> cookies) Synchronous
     DeleteCookie(URL url, String cookieName)
+    DomCookiesForHost(String host, bool subscribeToCookieChangeNotifications) -> (Vector<WebCore::Cookie> cookies) Synchronous
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    UnsubscribeFromCookieChangeNotifications(HashSet<String> hosts)
+#endif
 
     RegisterFileBlobURL(URL url, String path, WebKit::SandboxExtension::Handle extensionHandle, String contentType)
     RegisterBlobURL(URL url, Vector<WebCore::BlobPart> blobParts, String contentType)
index 7f1cf9c..bd16cfa 100644 (file)
@@ -625,6 +625,7 @@ def headers_for_type(type):
         'WebKit::WebWheelEvent': ['"WebEvent.h"'],
         'WebCore::MediaEngineSupportParameters': ['<WebCore/MediaPlayer.h>'],
         'WebCore::ISOWebVTTCue': ['<WebCore/ISOVTTCue.h>'],
+        'struct WebCore::Cookie': ['<WebCore/Cookie.h>'],
         'struct WebCore::ElementContext': ['<WebCore/ElementContext.h>'],
         'struct WebKit::WebUserScriptData': ['"WebUserContentControllerDataTypes.h"'],
         'struct WebKit::WebUserStyleSheetData': ['"WebUserContentControllerDataTypes.h"'],
index f2752a9..e330607 100644 (file)
@@ -55,6 +55,13 @@ HTTPSUpgradeEnabled:
    humanReadableDescription: "Automatic HTTPS upgrade for known supported sites"
    category: experimental
 
+InProcessCookieCacheEnabled:
+   type: bool
+   defaultValue: true
+   humanReadableName: "In-Process Cookie Cache"
+   humanReadableDescription: "In-Process DOM Cookie Cache"
+   category: experimental
+
 ThirdPartyIframeRedirectBlockingEnabled:
    type: bool
    defaultValue: true
index 74894bd..a5d0be1 100644 (file)
@@ -624,6 +624,7 @@ WebProcess/WebPage/PageBanner.cpp
 WebProcess/WebPage/VisitedLinkTableController.cpp
 WebProcess/WebPage/WebBackForwardListProxy.cpp
 WebProcess/WebPage/WebContextMenu.cpp
+WebProcess/WebPage/WebCookieCache.cpp
 WebProcess/WebPage/WebCookieJar.cpp
 WebProcess/WebPage/WebDocumentLoader.cpp
 WebProcess/WebPage/WebFrame.cpp
index e1fc6f7..d24a804 100644 (file)
@@ -613,6 +613,7 @@ WebProcess/WebPage/ViewUpdateDispatcher.cpp
 WebProcess/WebPage/WKAccessibilityWebPageObjectIOS.mm
 
 WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm
+WebProcess/WebPage/Cocoa/WebCookieCacheCocoa.mm
 WebProcess/WebPage/Cocoa/WebPageCocoa.mm
 WebProcess/WebPage/Cocoa/WebRemoteObjectRegistry.cpp
 
index 6a453fe..201e6c7 100644 (file)
                466BC03C1FA266DA002FA9C1 /* WebSWContextManagerConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 466BC0391FA266C9002FA9C1 /* WebSWContextManagerConnection.h */; };
                4671FF1F23217EFF001B64C7 /* WebResourceLoadObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 4671FF1D23217EFF001B64C7 /* WebResourceLoadObserver.h */; };
                467E43E82243FF7D00B13924 /* WebProcessDataStoreParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 467E43E72243FF6D00B13924 /* WebProcessDataStoreParameters.h */; };
+               46809A7C23D9225E00C297D0 /* WebCookieCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 46809A7A23D9225300C297D0 /* WebCookieCache.h */; };
                46A2B6091E5676A600C3DEDA /* BackgroundProcessResponsivenessTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 46A2B6071E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.h */; };
                46B0524722668D8500265B97 /* WebDeviceOrientationAndMotionAccessController.h in Headers */ = {isa = PBXBuildFile; fileRef = 46B0524422668D2300265B97 /* WebDeviceOrientationAndMotionAccessController.h */; };
                46BEB6D722FB9BD700269867 /* StorageArea.h in Headers */ = {isa = PBXBuildFile; fileRef = 46BEB6D522FB9BD600269867 /* StorageArea.h */; };
                4671FF1D23217EFF001B64C7 /* WebResourceLoadObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebResourceLoadObserver.h; sourceTree = "<group>"; };
                4671FF1E23217EFF001B64C7 /* WebResourceLoadObserver.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WebResourceLoadObserver.cpp; sourceTree = "<group>"; };
                467E43E72243FF6D00B13924 /* WebProcessDataStoreParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebProcessDataStoreParameters.h; sourceTree = "<group>"; };
+               46809A7A23D9225300C297D0 /* WebCookieCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebCookieCache.h; sourceTree = "<group>"; };
+               46809A7B23D9225300C297D0 /* WebCookieCache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebCookieCache.cpp; sourceTree = "<group>"; };
                4683569A21E81CC7006E27A3 /* ProvisionalPageProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProvisionalPageProxy.h; sourceTree = "<group>"; };
                4683569B21E81CC7006E27A3 /* ProvisionalPageProxy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ProvisionalPageProxy.cpp; sourceTree = "<group>"; };
                46A2B6061E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BackgroundProcessResponsivenessTimer.cpp; sourceTree = "<group>"; };
                                BC72B9F911E6476B001EB4EA /* WebBackForwardListProxy.h */,
                                51871B59127CB89D00F76232 /* WebContextMenu.cpp */,
                                51871B5A127CB89D00F76232 /* WebContextMenu.h */,
+                               46809A7B23D9225300C297D0 /* WebCookieCache.cpp */,
+                               46809A7A23D9225300C297D0 /* WebCookieCache.h */,
                                5C7FB46E21E97C0B009E3241 /* WebCookieJar.cpp */,
                                5C7FB46F21E97C0C009E3241 /* WebCookieJar.h */,
                                1A5B1C5218987EDF004FCF9B /* WebDocumentLoader.cpp */,
                                51ACBB82127A8BAD00D203B9 /* WebContextMenuProxy.h in Headers */,
                                51ACBBA0127A8F2C00D203B9 /* WebContextMenuProxyMac.h in Headers */,
                                BCF4DE25168FA44800C94AFC /* WebContextSupplement.h in Headers */,
+                               46809A7C23D9225E00C297D0 /* WebCookieCache.h in Headers */,
                                5C7FB47021E97DC5009E3241 /* WebCookieJar.h in Headers */,
                                330934501315B94D0097A7BC /* WebCookieManager.h in Headers */,
                                330934481315B9220097A7BC /* WebCookieManagerMessages.h in Headers */,
index 28bc12c..2bb479e 100644 (file)
@@ -32,6 +32,7 @@
 #include "StorageAreaMap.h"
 #include "StorageAreaMapMessages.h"
 #include "WebCacheStorageProvider.h"
+#include "WebCookieJar.h"
 #include "WebCoreArgumentCoders.h"
 #include "WebIDBConnectionToServer.h"
 #include "WebIDBConnectionToServerMessages.h"
@@ -234,6 +235,18 @@ void NetworkProcessConnection::cookieAcceptPolicyChanged(HTTPCookieAcceptPolicy
     m_cookieAcceptPolicy = newPolicy;
 }
 
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+void NetworkProcessConnection::cookiesAdded(const String& host, const Vector<WebCore::Cookie>& cookies)
+{
+    WebProcess::singleton().cookieJar().cookiesAdded(host, cookies);
+}
+
+void NetworkProcessConnection::cookiesDeleted()
+{
+    WebProcess::singleton().cookieJar().cookiesDeleted();
+}
+#endif
+
 #if ENABLE(SHAREABLE_RESOURCE)
 void NetworkProcessConnection::didCacheResource(const ResourceRequest& request, const ShareableResource::Handle& handle)
 {
index 51fdf35..76d2665 100644 (file)
@@ -41,6 +41,7 @@ namespace WebCore {
 class ResourceError;
 class ResourceRequest;
 class ResourceResponse;
+struct Cookie;
 struct MessagePortIdentifier;
 struct MessageWithMessagePorts;
 enum class HTTPCookieAcceptPolicy : uint8_t;
@@ -83,6 +84,11 @@ public:
 
     bool cookiesEnabled() const;
 
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    void cookiesAdded(const String& host, const Vector<WebCore::Cookie>&);
+    void cookiesDeleted();
+#endif
+
 private:
     NetworkProcessConnection(IPC::Connection::Identifier, WebCore::HTTPCookieAcceptPolicy);
 
index 2aa6886..6993fa7 100644 (file)
@@ -31,6 +31,11 @@ messages -> NetworkProcessConnection LegacyReceiver {
     SetOnLineState(bool isOnLine);
     CookieAcceptPolicyChanged(enum:uint8_t WebCore::HTTPCookieAcceptPolicy policy);
 
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    CookiesAdded(String host, Vector<struct WebCore::Cookie> cookies);
+    CookiesDeleted();
+#endif
+
     CheckProcessLocalPortForActivity(struct WebCore::MessagePortIdentifier port) -> (WebCore::MessagePortChannelProvider::HasActivity hasActivity) Async
     MessagesAvailableForPort(struct WebCore::MessagePortIdentifier port)
 }
diff --git a/Source/WebKit/WebProcess/WebPage/Cocoa/WebCookieCacheCocoa.mm b/Source/WebKit/WebProcess/WebPage/Cocoa/WebCookieCacheCocoa.mm
new file mode 100644 (file)
index 0000000..86bc66d
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "WebCookieCache.h"
+
+#import "WebProcess.h"
+#import <WebCore/NetworkStorageSession.h>
+
+namespace WebKit {
+
+using namespace WebCore;
+
+NetworkStorageSession& WebCookieCache::inMemoryStorageSession()
+{
+    if (!m_inMemoryStorageSession) {
+        String sessionName = makeString("WebKitInProcessStorage-", getCurrentProcessID());
+        auto storageSession = adoptCF(WebCore::createPrivateStorageSession(sessionName.createCFString().get()));
+        auto cookieStorage = adoptCF(_CFURLStorageSessionCopyCookieStorage(kCFAllocatorDefault, storageSession.get()));
+        m_inMemoryStorageSession = makeUnique<NetworkStorageSession>(WebProcess::singleton().sessionID(), WTFMove(storageSession), WTFMove(cookieStorage), NetworkStorageSession::IsInMemoryCookieStore::Yes);
+    }
+    return *m_inMemoryStorageSession;
+}
+
+} // namespace WebKit
diff --git a/Source/WebKit/WebProcess/WebPage/WebCookieCache.cpp b/Source/WebKit/WebProcess/WebPage/WebCookieCache.cpp
new file mode 100644 (file)
index 0000000..f3ed20c
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "WebCookieCache.h"
+
+#include "NetworkConnectionToWebProcessMessages.h"
+#include "NetworkProcessConnection.h"
+#include "WebProcess.h"
+
+namespace WebKit {
+
+using namespace WebCore;
+
+bool WebCookieCache::isSupported()
+{
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    // FIXME: This can eventually be removed, this is merely to ensure a smooth transition to the new API.
+    return inMemoryStorageSession().supportsCookieChangeListenerAPI();
+#else
+    return false;
+#endif
+}
+
+String WebCookieCache::cookiesForDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, FrameIdentifier frameID, PageIdentifier pageID, IncludeSecureCookies includeSecureCookies)
+{
+    String host = url.host().toString();
+    if (!m_hostsWithInMemoryStorage.contains(host)) {
+        Vector<Cookie> cookies;
+        bool subscribeToCookieChangeNotifications = true;
+        if (!WebProcess::singleton().ensureNetworkProcessConnection().connection().sendSync(Messages::NetworkConnectionToWebProcess::DomCookiesForHost(url.host().toString(), subscribeToCookieChangeNotifications), Messages::NetworkConnectionToWebProcess::DomCookiesForHost::Reply(cookies), 0))
+            return { };
+        pruneCacheIfNecessary();
+        m_hostsWithInMemoryStorage.add(host);
+        for (auto& cookie : cookies)
+            inMemoryStorageSession().setCookie(cookie);
+    }
+    return inMemoryStorageSession().cookiesForDOM(firstParty, sameSiteInfo, url, frameID, pageID, includeSecureCookies, ShouldAskITP::No).first;
+}
+
+void WebCookieCache::setCookiesFromDOM(const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, FrameIdentifier frameID, PageIdentifier pageID, const String& cookieString)
+{
+    String host = url.host().toString();
+    if (m_hostsWithInMemoryStorage.contains(host))
+        inMemoryStorageSession().setCookiesFromDOM(firstParty, sameSiteInfo, url, frameID, pageID, ShouldAskITP::No, cookieString);
+}
+
+void WebCookieCache::cookiesAdded(const String& host, const Vector<Cookie>& cookies)
+{
+    if (!m_hostsWithInMemoryStorage.contains(host))
+        return;
+
+    for (auto& cookie : cookies)
+        inMemoryStorageSession().setCookie(cookie);
+}
+
+void WebCookieCache::cookiesDeleted()
+{
+    clear();
+}
+
+void WebCookieCache::clear()
+{
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    if (!m_hostsWithInMemoryStorage.isEmpty())
+        WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::UnsubscribeFromCookieChangeNotifications(m_hostsWithInMemoryStorage), 0);
+#endif
+    m_hostsWithInMemoryStorage.clear();
+    m_inMemoryStorageSession = nullptr;
+}
+
+void WebCookieCache::clearForHost(const String& host)
+{
+    auto it = m_hostsWithInMemoryStorage.find(host);
+    if (it == m_hostsWithInMemoryStorage.end())
+        return;
+
+    m_hostsWithInMemoryStorage.remove(it);
+    inMemoryStorageSession().deleteCookiesForHostnames(Vector<String> { host });
+#if HAVE(COOKIE_CHANGE_LISTENER_API)
+    WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::UnsubscribeFromCookieChangeNotifications(HashSet<String> { host }), 0);
+#endif
+}
+
+void WebCookieCache::pruneCacheIfNecessary()
+{
+    // We may want to raise this limit if we start using the cache for third-party iframes.
+    static const unsigned maxCachedHosts = 5;
+
+    while (m_hostsWithInMemoryStorage.size() >= maxCachedHosts)
+        clearForHost(*m_hostsWithInMemoryStorage.random());
+}
+
+#if !PLATFORM(COCOA)
+NetworkStorageSession& WebCookieCache::inMemoryStorageSession()
+{
+    ASSERT_NOT_IMPLEMENTED_YET();
+    return *m_inMemoryStorageSession;
+}
+#endif
+
+} // namespace WebKit
diff --git a/Source/WebKit/WebProcess/WebPage/WebCookieCache.h b/Source/WebKit/WebProcess/WebPage/WebCookieCache.h
new file mode 100644 (file)
index 0000000..6d0c416
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <WebCore/CookieJar.h>
+#include <WebCore/SameSiteInfo.h>
+#include <wtf/HashSet.h>
+
+namespace WebCore {
+class NetworkStorageSession;
+}
+
+namespace WebKit {
+
+class WebCookieCache {
+public:
+    WebCookieCache() = default;
+
+    bool isSupported();
+
+    String cookiesForDOM(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, WebCore::FrameIdentifier, WebCore::PageIdentifier, WebCore::IncludeSecureCookies);
+    void setCookiesFromDOM(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, WebCore::FrameIdentifier, WebCore::PageIdentifier, const String& cookieString);
+
+    void cookiesAdded(const String& host, const Vector<WebCore::Cookie>&);
+    void cookiesDeleted();
+
+    void clear();
+    void clearForHost(const String&);
+
+private:
+    WebCore::NetworkStorageSession& inMemoryStorageSession();
+    void pruneCacheIfNecessary();
+
+    HashSet<String> m_hostsWithInMemoryStorage;
+    std::unique_ptr<WebCore::NetworkStorageSession> m_inMemoryStorageSession;
+};
+
+} // namespace WebKit
index 3258305..5ab3245 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 Apple Inc. All rights reserved.
+ * Copyright (C) 2019-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -37,6 +37,7 @@
 #include <WebCore/Frame.h>
 #include <WebCore/FrameLoader.h>
 #include <WebCore/FrameLoaderClient.h>
+#include <WebCore/Settings.h>
 #include <WebCore/StorageSessionProvider.h>
 
 namespace WebKit {
@@ -88,6 +89,23 @@ static bool shouldBlockCookies(WebFrame* frame, const URL& firstPartyForCookies,
 }
 #endif
 
+bool WebCookieJar::isEligibleForCache(WebFrame& frame, const URL& firstPartyForCookies, const URL& resourceURL) const
+{
+    auto* page = frame.page() ? frame.page()->corePage() : nullptr;
+    if (!page || !page->settings().inProcessCookieCacheEnabled())
+        return false;
+
+    if (!m_cache.isSupported())
+        return false;
+
+    // For now, we only cache cookies for first-party content. Third-party cookie caching is a bit more complicated due to partitioning and storage access.
+    RegistrableDomain resourceDomain { resourceURL };
+    if (resourceDomain.isEmpty())
+        return false;
+
+    return frame.isMainFrame() || RegistrableDomain { firstPartyForCookies } == resourceDomain;
+}
+
 String WebCookieJar::cookies(WebCore::Document& document, const URL& url) const
 {
     auto* webFrame = document.frame() ? WebFrame::fromCoreFrame(*document.frame()) : nullptr;
@@ -100,12 +118,17 @@ String WebCookieJar::cookies(WebCore::Document& document, const URL& url) const
         return { };
 #endif
 
+    auto sameSiteInfo = CookieJar::sameSiteInfo(document);
+    auto includeSecureCookies = CookieJar::shouldIncludeSecureCookies(document, url);
     auto frameID = webFrame->frameID();
     auto pageID = webFrame->page()->identifier();
 
+    if (isEligibleForCache(*webFrame, document.firstPartyForCookies(), url))
+        return m_cache.cookiesForDOM(document.firstPartyForCookies(), sameSiteInfo, url, frameID, pageID, includeSecureCookies);
+
     String cookieString;
     bool secureCookiesAccessed = false;
-    if (!WebProcess::singleton().ensureNetworkProcessConnection().connection().sendSync(Messages::NetworkConnectionToWebProcess::CookiesForDOM(document.firstPartyForCookies(), sameSiteInfo(document), url, frameID, pageID, shouldIncludeSecureCookies(document, url), shouldAskITPInNetworkProcess), Messages::NetworkConnectionToWebProcess::CookiesForDOM::Reply(cookieString, secureCookiesAccessed), 0))
+    if (!WebProcess::singleton().ensureNetworkProcessConnection().connection().sendSync(Messages::NetworkConnectionToWebProcess::CookiesForDOM(document.firstPartyForCookies(), sameSiteInfo, url, frameID, pageID, includeSecureCookies, shouldAskITPInNetworkProcess), Messages::NetworkConnectionToWebProcess::CookiesForDOM::Reply(cookieString, secureCookiesAccessed), 0))
         return { };
 
     return cookieString;
@@ -123,10 +146,34 @@ void WebCookieJar::setCookies(WebCore::Document& document, const URL& url, const
         return;
 #endif
 
+    auto sameSiteInfo = CookieJar::sameSiteInfo(document);
     auto frameID = webFrame->frameID();
     auto pageID = webFrame->page()->identifier();
 
-    WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::SetCookiesFromDOM(document.firstPartyForCookies(), sameSiteInfo(document), url, frameID, pageID, shouldAskITPInNetworkProcess, cookieString), 0);
+    if (isEligibleForCache(*webFrame, document.firstPartyForCookies(), url))
+        m_cache.setCookiesFromDOM(document.firstPartyForCookies(), sameSiteInfo, url, frameID, pageID, cookieString);
+
+    WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::SetCookiesFromDOM(document.firstPartyForCookies(), sameSiteInfo, url, frameID, pageID, shouldAskITPInNetworkProcess, cookieString), 0);
+}
+
+void WebCookieJar::cookiesAdded(const String& host, const Vector<WebCore::Cookie>& cookies)
+{
+    m_cache.cookiesAdded(host, cookies);
+}
+
+void WebCookieJar::cookiesDeleted()
+{
+    m_cache.cookiesDeleted();
+}
+
+void WebCookieJar::clearCache()
+{
+    m_cache.clear();
+}
+
+void WebCookieJar::clearCacheForHost(const String& host)
+{
+    m_cache.clearForHost(host);
 }
 
 bool WebCookieJar::cookiesEnabled(const Document& document) const
index 52948a9..d2d9446 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 Apple Inc. All rights reserved.
+ * Copyright (C) 2019-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 #pragma once
 
+#include "WebCookieCache.h"
 #include <WebCore/CookieJar.h>
 
+namespace WebCore {
+struct Cookie;
+}
+
 namespace WebKit {
 
+class WebFrame;
+
 class WebCookieJar final : public WebCore::CookieJar {
 public:
     static Ref<WebCookieJar> create() { return adoptRef(*new WebCookieJar); }
@@ -39,8 +46,18 @@ public:
     std::pair<String, WebCore::SecureCookiesAccessed> cookieRequestHeaderFieldValue(const URL& firstParty, const WebCore::SameSiteInfo&, const URL&, Optional<WebCore::FrameIdentifier>, Optional<WebCore::PageIdentifier>, WebCore::IncludeSecureCookies) const final;
     bool getRawCookies(const WebCore::Document&, const URL&, Vector<WebCore::Cookie>&) const final;
     void deleteCookie(const WebCore::Document&, const URL&, const String& cookieName) final;
+
+    void cookiesAdded(const String& host, const Vector<WebCore::Cookie>&);
+    void cookiesDeleted();
+
 private:
     WebCookieJar();
+
+    void clearCache() final;
+    void clearCacheForHost(const String&) final;
+    bool isEligibleForCache(WebFrame&, const URL& firstPartyForCookies, const URL& resourceURL) const;
+
+    mutable WebCookieCache m_cache;
 };
 
 } // namespace WebKit
index 8683c10..8abf645 100644 (file)
@@ -466,7 +466,7 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters)
         makeUniqueRef<WebKit::LibWebRTCProvider>(),
         WebProcess::singleton().cacheStorageProvider(),
         WebBackForwardListProxy::create(*this),
-        WebCookieJar::create(),
+        WebProcess::singleton().cookieJar(),
         makeUniqueRef<WebProgressTrackerClient>(*this),
         makeUniqueRef<MediaRecorderProvider>()
     );
index 681376c..47c9c08 100644 (file)
@@ -48,6 +48,7 @@
 #include "WebAutomationSessionProxy.h"
 #include "WebCacheStorageProvider.h"
 #include "WebConnectionToUIProcess.h"
+#include "WebCookieJar.h"
 #include "WebCoreArgumentCoders.h"
 #include "WebFrame.h"
 #include "WebFrameNetworkingContext.h"
@@ -205,6 +206,7 @@ WebProcess::WebProcess()
     , m_webInspectorInterruptDispatcher(WebInspectorInterruptDispatcher::create())
     , m_webLoaderStrategy(*new WebLoaderStrategy)
     , m_cacheStorageProvider(WebCacheStorageProvider::create())
+    , m_cookieJar(WebCookieJar::create())
     , m_dnsPrefetchHystereris([this](PAL::HysteresisState state) { if (state == PAL::HysteresisState::Stopped) m_dnsPrefetchedHosts.clear(); })
 #if ENABLE(NETSCAPE_PLUGIN_API)
     , m_pluginProcessConnectionManager(PluginProcessConnectionManager::create())
index 61e062e..68e3c3c 100644 (file)
@@ -120,6 +120,7 @@ class UserData;
 class WaylandCompositorDisplay;
 class WebAutomationSessionProxy;
 class WebCacheStorageProvider;
+class WebCookieJar;
 class WebCompiledContentRuleListData;
 class WebConnectionToUIProcess;
 class WebFrame;
@@ -293,6 +294,7 @@ public:
     WebAutomationSessionProxy* automationSessionProxy() { return m_automationSessionProxy.get(); }
 
     WebCacheStorageProvider& cacheStorageProvider() { return m_cacheStorageProvider.get(); }
+    WebCookieJar& cookieJar() { return m_cookieJar.get(); }
     WebSocketChannelManager& webSocketChannelManager() { return m_webSocketChannelManager; }
 
 #if PLATFORM(IOS_FAMILY)
@@ -558,6 +560,7 @@ private:
 #endif
 
     Ref<WebCacheStorageProvider> m_cacheStorageProvider;
+    Ref<WebCookieJar> m_cookieJar;
     WebSocketChannelManager m_webSocketChannelManager;
 
     std::unique_ptr<LibWebRTCNetwork> m_libWebRTCNetwork;
index 7aa2b5a..890d723 100644 (file)
 #import <pal/spi/cf/CFNetworkSPI.h>
 #import <wtf/RetainPtr.h>
 
+// FIXME: This test is causing flakiness in API tests. It sets the cookie accept policy to 'Never'
+// and following tests often are unable to set cookies.
+#if !PLATFORM(IOS_FAMILY)
+
 static bool receivedScriptMessage = false;
 static RetainPtr<WKScriptMessage> lastScriptMessage;
 
@@ -72,3 +76,5 @@ TEST(WebKit, CookieAcceptPolicy)
     [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:originalCookieAcceptPolicy];
     [[NSHTTPCookieStorage sharedHTTPCookieStorage] _saveCookies];
 }
+
+#endif // !PLATFORM(IOS_FAMILY)
index f500f3f..1889a9f 100644 (file)
 #include "config.h"
 
 #import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestWKWebView.h"
 #import <WebKit/WKProcessPool.h>
 #import <WebKit/WKProcessPoolPrivate.h>
 #import <WebKit/WKWebView.h>
 #import <WebKit/WKWebViewConfiguration.h>
 #import <wtf/RetainPtr.h>
+#import <wtf/text/WTFString.h>
 
 static bool receivedAlert;
 
@@ -68,3 +71,60 @@ TEST(WebKit, CookiePrivateBrowsing)
     [view2 loadHTMLString:alertOldCookie baseURL:[NSURL URLWithString:@"http://example.com/"]];
     TestWebKitAPI::Util::run(&receivedAlert);
 }
+
+TEST(WebKit, CookieCacheSyncAcrossProcess)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [configuration setWebsiteDataStore:[WKWebsiteDataStore nonPersistentDataStore]];
+    auto view1 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    auto view2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [view1 synchronouslyLoadHTMLString:@"foo" baseURL:[NSURL URLWithString:@"http://example.com/"]];
+    [view2 synchronouslyLoadHTMLString:@"bar" baseURL:[NSURL URLWithString:@"http://example.com/"]];
+
+    // Cache DOM cookies in first WebView.
+    __block bool doneEvaluatingJavaScript = false;
+    [view1 evaluateJavaScript:@"document.cookie;" completionHandler:^(id _Nullable cookie, NSError * _Nullable error) {
+        EXPECT_NULL(error);
+        EXPECT_TRUE([cookie isKindOfClass:[NSString class]]);
+        EXPECT_WK_STREQ("", (NSString *)cookie);
+        doneEvaluatingJavaScript = true;
+    }];
+    TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
+
+    // Cache DOM cookies in second WebView.
+    doneEvaluatingJavaScript = false;
+    [view2 evaluateJavaScript:@"document.cookie;" completionHandler:^(id _Nullable cookie, NSError * _Nullable error) {
+        EXPECT_NULL(error);
+        EXPECT_TRUE([cookie isKindOfClass:[NSString class]]);
+        EXPECT_WK_STREQ("", (NSString *)cookie);
+        doneEvaluatingJavaScript = true;
+    }];
+    TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
+
+    // Setting cookie in first Webview / process.
+    doneEvaluatingJavaScript = false;
+    [view1 evaluateJavaScript:@"document.cookie = 'foo=bar'; document.cookie;" completionHandler:^(id _Nullable cookie, NSError * _Nullable error) {
+        EXPECT_NULL(error);
+        EXPECT_TRUE([cookie isKindOfClass:[NSString class]]);
+        EXPECT_WK_STREQ("foo=bar", (NSString *)cookie);
+        doneEvaluatingJavaScript = true;
+    }];
+    TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
+
+    // Making sure new cookie gets sync'd to second WebView process.
+    int timeout = 0;
+    __block String cookieString;
+    do {
+        TestWebKitAPI::Util::sleep(0.1);
+        doneEvaluatingJavaScript = false;
+        [view2 evaluateJavaScript:@"document.cookie;" completionHandler:^(id _Nullable cookie, NSError * _Nullable error) {
+            EXPECT_NULL(error);
+            EXPECT_TRUE([cookie isKindOfClass:[NSString class]]);
+            cookieString = (NSString *)cookie;
+            doneEvaluatingJavaScript = true;
+        }];
+        TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
+        ++timeout;
+    } while (cookieString != "" && timeout < 50);
+    EXPECT_WK_STREQ("foo=bar", cookieString);
+}