Reuse existing WebPageProxy quota handler for NetworkProcessProxy quota requests
https://bugs.webkit.org/show_bug.cgi?id=197463
<rdar://problem/47403621>

Reviewed by Alex Christensen.

Source/WebKit:

Add a getter to know whether websitedatastore client implements the quota delegate.
If not, find the most visible page that is the same origin as the quota request
and reuse the existing exceededDatabasQuota delegate.
This approach allows to call the delegate even if the quota request comes from a service worker.
If no such page is found, the quota will not be increased.

Refactoring to make sure we are calling the delegate once a previous call to that delegate is completed.
Covered by API test.

* UIProcess/API/Cocoa/WKWebsiteDataStore.mm:
* UIProcess/Network/NetworkProcessProxy.cpp:
(WebKit::NetworkProcessProxy::requestStorageSpace):
* UIProcess/WebPageProxy.cpp:
(WebKit::StorageRequests::add):
(WebKit::StorageRequests::processNext):
(WebKit::StorageRequests::areBeingProcessed const):
(WebKit::StorageRequests::setAreBeingProcessed):
(WebKit::StorageRequests::StorageRequests):
(WebKit::StorageRequests::~StorageRequests):
(WebKit::StorageRequests::singleton):
(WebKit::WebPageProxy::forMostVisibleWebPageIfAny):
(WebKit::WebPageProxy::didChangeMainDocument):
(WebKit::WebPageProxy::exceededDatabaseQuota):
(WebKit::WebPageProxy::requestStorageSpace):
(WebKit::WebPageProxy::makeStorageSpaceRequest):
* UIProcess/WebPageProxy.h:
* UIProcess/WebProcessProxy.cpp:
(WebKit::WebProcessProxy::forWebPages):
* UIProcess/WebProcessProxy.h:
* UIProcess/WebsiteData/WebsiteDataStoreClient.h:
(WebKit::WebsiteDataStoreClient::implementsRequestStorageSpaceHandler const):

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm: Added.
(-[QuotaDelegate init]):
(-[QuotaDelegate _webView:decideDatabaseQuotaForSecurityOrigin:currentQuota:currentOriginUsage:currentDatabaseUsage:expectedUsage:decisionHandler:]):
(-[QuotaDelegate quotaDelegateCalled]):
(-[QuotaDelegate grantQuota]):
(-[StorageSchemes webView:startURLSchemeTask:]):
(-[StorageSchemes webView:stopURLSchemeTask:]):
(-[QuotaMessageHandler userContentController:didReceiveScriptMessage:]):
(doTest):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@245328 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index b2ee925..aec8e69 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,5 +1,45 @@
 2019-05-15  Youenn Fablet  <youenn@apple.com>
 
+        Reuse existing WebPageProxy quota handler for NetworkProcessProxy quota requests
+        https://bugs.webkit.org/show_bug.cgi?id=197463
+        <rdar://problem/47403621>
+
+        Reviewed by Alex Christensen.
+
+        Add a getter to know whether websitedatastore client implements the quota delegate.
+        If not, find the most visible page that is the same origin as the quota request
+        and reuse the existing exceededDatabasQuota delegate.
+        This approach allows to call the delegate even if the quota request comes from a service worker.
+        If no such page is found, the quota will not be increased.
+
+        Refactoring to make sure we are calling the delegate once a previous call to that delegate is completed.
+        Covered by API test.
+
+        * UIProcess/API/Cocoa/WKWebsiteDataStore.mm:
+        * UIProcess/Network/NetworkProcessProxy.cpp:
+        (WebKit::NetworkProcessProxy::requestStorageSpace):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::StorageRequests::add):
+        (WebKit::StorageRequests::processNext):
+        (WebKit::StorageRequests::areBeingProcessed const):
+        (WebKit::StorageRequests::setAreBeingProcessed):
+        (WebKit::StorageRequests::StorageRequests):
+        (WebKit::StorageRequests::~StorageRequests):
+        (WebKit::StorageRequests::singleton):
+        (WebKit::WebPageProxy::forMostVisibleWebPageIfAny):
+        (WebKit::WebPageProxy::didChangeMainDocument):
+        (WebKit::WebPageProxy::exceededDatabaseQuota):
+        (WebKit::WebPageProxy::requestStorageSpace):
+        (WebKit::WebPageProxy::makeStorageSpaceRequest):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebProcessProxy.cpp:
+        (WebKit::WebProcessProxy::forWebPages):
+        * UIProcess/WebProcessProxy.h:
+        * UIProcess/WebsiteData/WebsiteDataStoreClient.h:
+        (WebKit::WebsiteDataStoreClient::implementsRequestStorageSpaceHandler const):
+
+2019-05-15  Youenn Fablet  <youenn@apple.com>
+
         Constant crashes under WebPage::isThrottleable() after r245299
         https://bugs.webkit.org/show_bug.cgi?id=197902
         <rdar://problem/50793796>
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm
index 7dd4119..26298eb 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm
@@ -43,7 +43,7 @@
 #import <wtf/URL.h>
 #import <wtf/WeakObjCPtr.h>
 
-class WebsiteDataStoreClient : public WebKit::WebsiteDataStoreClient {
+class WebsiteDataStoreClient final : public WebKit::WebsiteDataStoreClient {
 public:
     explicit WebsiteDataStoreClient(id <_WKWebsiteDataStoreDelegate> delegate)
         : m_delegate(delegate)
diff --git a/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp b/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp
index af859ae..0ee81be 100644
--- a/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp
+++ b/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp
@@ -1141,7 +1141,7 @@
 }
 #endif
 
-void NetworkProcessProxy::requestStorageSpace(PAL::SessionID sessionID, const WebCore::ClientOrigin& origin, uint64_t quota, uint64_t currentSize, uint64_t spaceRequired, CompletionHandler<void(Optional<uint64_t> quota)>&& completionHandler)
+void NetworkProcessProxy::requestStorageSpace(PAL::SessionID sessionID, const WebCore::ClientOrigin& origin, uint64_t currentQuota, uint64_t currentSize, uint64_t spaceRequired, CompletionHandler<void(Optional<uint64_t> quota)>&& completionHandler)
 {
     auto* store = websiteDataStoreFromSessionID(sessionID);
 
@@ -1150,7 +1150,28 @@
         return;
     }
 
-    store->client().requestStorageSpace(origin.topOrigin, origin.clientOrigin, quota, currentSize, spaceRequired, WTFMove(completionHandler));
+    store->client().requestStorageSpace(origin.topOrigin, origin.clientOrigin, currentQuota, currentSize, spaceRequired, [sessionID, origin, currentQuota, currentSize, spaceRequired, completionHandler = WTFMove(completionHandler)](auto quota) mutable {
+        if (quota) {
+            completionHandler(quota);
+            return;
+        }
+
+        if (origin.topOrigin != origin.clientOrigin) {
+            completionHandler({ });
+            return;
+        }
+
+        WebPageProxy::forMostVisibleWebPageIfAny(sessionID, origin.topOrigin, [completionHandler = WTFMove(completionHandler), origin, currentQuota, currentSize, spaceRequired](auto* page) mutable {
+            if (!page) {
+                completionHandler({ });
+                return;
+            }
+            String name = makeString(FileSystem::encodeForFileName(origin.topOrigin.host), " content");
+            page->requestStorageSpace(page->mainFrame()->frameID(), origin.topOrigin.databaseIdentifier(), name, name, currentQuota, currentSize, currentSize, spaceRequired, [completionHandler = WTFMove(completionHandler)](auto quota) mutable {
+                completionHandler(quota);
+            });
+        });
+    });
 }
 
 void NetworkProcessProxy::takeUploadAssertion()
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index 886ded6..9f96f9a 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -264,76 +264,43 @@
 
 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, webPageProxyCounter, ("WebPageProxy"));
 
-class ExceededDatabaseQuotaRecords {
-    WTF_MAKE_NONCOPYABLE(ExceededDatabaseQuotaRecords); WTF_MAKE_FAST_ALLOCATED;
-    friend NeverDestroyed<ExceededDatabaseQuotaRecords>;
+class StorageRequests {
+    WTF_MAKE_NONCOPYABLE(StorageRequests); WTF_MAKE_FAST_ALLOCATED;
+    friend NeverDestroyed<StorageRequests>;
 public:
-    struct Record {
-        uint64_t frameID;
-        String originIdentifier;
-        String databaseName;
-        String displayName;
-        uint64_t currentQuota;
-        uint64_t currentOriginUsage;
-        uint64_t currentDatabaseUsage;
-        uint64_t expectedUsage;
-        Messages::WebPageProxy::ExceededDatabaseQuota::DelayedReply reply;
-    };
+    static StorageRequests& singleton();
 
-    static ExceededDatabaseQuotaRecords& singleton();
+    void processOrAppend(CompletionHandler<void()>&& completionHandler)
+    {
+        if (m_requestsAreBeingProcessed) {
+            m_requests.append(WTFMove(completionHandler));
+            return;
+        }
+        m_requestsAreBeingProcessed = true;
+        completionHandler();
+    }
 
-    std::unique_ptr<Record> createRecord(uint64_t frameID, String originIdentifier,
-        String databaseName, String displayName, uint64_t currentQuota,
-        uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, 
-        Messages::WebPageProxy::ExceededDatabaseQuota::DelayedReply&&);
-
-    void add(std::unique_ptr<Record>);
-    bool areBeingProcessed() const { return !!m_currentRecord; }
-    Record* next();
+    void processNextIfAny()
+    {
+        if (m_requests.isEmpty()) {
+            m_requestsAreBeingProcessed = false;
+            return;
+        }
+        m_requests.takeFirst()();
+    }
 
 private:
-    ExceededDatabaseQuotaRecords() { }
-    ~ExceededDatabaseQuotaRecords() { }
+    StorageRequests() { }
+    ~StorageRequests() { }
 
-    Deque<std::unique_ptr<Record>> m_records;
-    std::unique_ptr<Record> m_currentRecord;
+    Deque<CompletionHandler<void()>> m_requests;
+    bool m_requestsAreBeingProcessed { false };
 };
 
-ExceededDatabaseQuotaRecords& ExceededDatabaseQuotaRecords::singleton()
+StorageRequests& StorageRequests::singleton()
 {
-    static NeverDestroyed<ExceededDatabaseQuotaRecords> records;
-    return records;
-}
-
-std::unique_ptr<ExceededDatabaseQuotaRecords::Record> ExceededDatabaseQuotaRecords::createRecord(
-    uint64_t frameID, String originIdentifier, String databaseName, String displayName,
-    uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage,
-    uint64_t expectedUsage, Messages::WebPageProxy::ExceededDatabaseQuota::DelayedReply&& reply)
-{
-    auto record = std::make_unique<Record>();
-    record->frameID = frameID;
-    record->originIdentifier = originIdentifier;
-    record->databaseName = databaseName;
-    record->displayName = displayName;
-    record->currentQuota = currentQuota;
-    record->currentOriginUsage = currentOriginUsage;
-    record->currentDatabaseUsage = currentDatabaseUsage;
-    record->expectedUsage = expectedUsage;
-    record->reply = WTFMove(reply);
-    return record;
-}
-
-void ExceededDatabaseQuotaRecords::add(std::unique_ptr<ExceededDatabaseQuotaRecords::Record> record)
-{
-    m_records.append(WTFMove(record));
-}
-
-ExceededDatabaseQuotaRecords::Record* ExceededDatabaseQuotaRecords::next()
-{
-    m_currentRecord = nullptr;
-    if (!m_records.isEmpty())
-        m_currentRecord = m_records.takeFirst();
-    return m_currentRecord.get();
+    static NeverDestroyed<StorageRequests> requests;
+    return requests;
 }
 
 #if !LOG_DISABLED
@@ -395,6 +362,25 @@
     WeakPtr<PageClient> m_pageClient;
 };
 
+void WebPageProxy::forMostVisibleWebPageIfAny(PAL::SessionID sessionID, const SecurityOriginData& origin, CompletionHandler<void(WebPageProxy*)>&& completionHandler)
+{
+    // FIXME: If not finding right away a visible page, we might want to try again for a given period of time when there is a change of visibility.
+    WebPageProxy* selectedPage = nullptr;
+    WebProcessProxy::forWebPagesWithOrigin(sessionID, origin, [&](auto& page) {
+        if (!page.mainFrame())
+            return;
+        if (page.isViewVisible() && (!selectedPage || !selectedPage->isViewVisible())) {
+            selectedPage = &page;
+            return;
+        }
+        if (page.isViewFocused() && (!selectedPage || !selectedPage->isViewFocused())) {
+            selectedPage = &page;
+            return;
+        }
+    });
+    completionHandler(selectedPage);
+}
+
 Ref<WebPageProxy> WebPageProxy::create(PageClient& pageClient, WebProcessProxy& process, uint64_t pageID, Ref<API::PageConfiguration>&& configuration)
 {
     return adoptRef(*new WebPageProxy(pageClient, process, pageID, WTFMove(configuration)));
@@ -4415,6 +4401,7 @@
 #else
     UNUSED_PARAM(frameID);
 #endif
+    m_isQuotaIncreaseDenied = false;
 }
 
 void WebPageProxy::viewIsBecomingVisible()
@@ -7263,24 +7250,41 @@
 
 void WebPageProxy::exceededDatabaseQuota(uint64_t frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, Messages::WebPageProxy::ExceededDatabaseQuota::DelayedReply&& reply)
 {
-    ExceededDatabaseQuotaRecords& records = ExceededDatabaseQuotaRecords::singleton();
-    std::unique_ptr<ExceededDatabaseQuotaRecords::Record> newRecord = records.createRecord(frameID,
-        originIdentifier, databaseName, displayName, currentQuota, currentOriginUsage,
-        currentDatabaseUsage, expectedUsage, WTFMove(reply));
-    records.add(WTFMove(newRecord));
+    requestStorageSpace(frameID, originIdentifier, databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, [reply = WTFMove(reply)](auto quota) mutable {
+        reply(quota);
+    });
+}
 
-    if (records.areBeingProcessed())
+void WebPageProxy::requestStorageSpace(uint64_t frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, CompletionHandler<void(uint64_t)>&& completionHandler)
+{
+    StorageRequests::singleton().processOrAppend([this, protectedThis = makeRef(*this), pageURL = currentURL(), frameID, originIdentifier, databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, completionHandler = WTFMove(completionHandler)]() mutable {
+        this->makeStorageSpaceRequest(frameID, originIdentifier, databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, [this, protectedThis = WTFMove(protectedThis), pageURL = WTFMove(pageURL), completionHandler = WTFMove(completionHandler), currentQuota](auto quota) mutable {
+            if (quota <= currentQuota && this->currentURL() == pageURL)
+                m_isQuotaIncreaseDenied =  true;
+            completionHandler(quota);
+            StorageRequests::singleton().processNextIfAny();
+        });
+    });
+}
+
+void WebPageProxy::makeStorageSpaceRequest(uint64_t frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, CompletionHandler<void(uint64_t)>&& completionHandler)
+{
+    if (m_isQuotaIncreaseDenied) {
+        completionHandler(currentQuota);
         return;
-
-    ExceededDatabaseQuotaRecords::Record* record = records.next();
-    while (record) {
-        WebFrameProxy* frame = m_process->webFrame(record->frameID);
-        MESSAGE_CHECK(m_process, frame);
-
-        auto origin = API::SecurityOrigin::create(SecurityOriginData::fromDatabaseIdentifier(record->originIdentifier)->securityOrigin());
-        m_uiClient->exceededDatabaseQuota(this, frame, origin.ptr(), record->databaseName, record->displayName, record->currentQuota, record->currentOriginUsage, record->currentDatabaseUsage, record->expectedUsage, WTFMove(record->reply));
-        record = records.next();
     }
+
+    WebFrameProxy* frame = m_process->webFrame(frameID);
+    MESSAGE_CHECK(m_process, frame);
+
+    auto originData = SecurityOriginData::fromDatabaseIdentifier(originIdentifier);
+    if (originData != SecurityOriginData::fromURL(URL { { }, currentURL() })) {
+        completionHandler(currentQuota);
+        return;
+    }
+
+    auto origin = API::SecurityOrigin::create(originData->securityOrigin());
+    m_uiClient->exceededDatabaseQuota(this, frame, origin.ptr(), databaseName, displayName, currentQuota, currentOriginUsage, currentDatabaseUsage, expectedUsage, WTFMove(completionHandler));
 }
 
 void WebPageProxy::reachedApplicationCacheOriginQuota(const String& originIdentifier, uint64_t currentQuota, uint64_t totalBytesNeeded, Messages::WebPageProxy::ReachedApplicationCacheOriginQuota::DelayedReply&& reply)
diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h
index 5809fcf..738b31f 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.h
+++ b/Source/WebKit/UIProcess/WebPageProxy.h
@@ -385,6 +385,8 @@
     static Ref<WebPageProxy> create(PageClient&, WebProcessProxy&, uint64_t pageID, Ref<API::PageConfiguration>&&);
     virtual ~WebPageProxy();
 
+    static void forMostVisibleWebPageIfAny(PAL::SessionID, const WebCore::SecurityOriginData&, CompletionHandler<void(WebPageProxy*)>&&);
+
     const API::PageConfiguration& configuration() const;
 
     uint64_t pageID() const { return m_pageID; }
@@ -1542,6 +1544,8 @@
     void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override;
     void didReceiveSyncMessage(IPC::Connection&, IPC::Decoder&, std::unique_ptr<IPC::Encoder>&) override;
 
+    void requestStorageSpace(uint64_t frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, WTF::CompletionHandler<void(uint64_t)>&&);
+
 private:
     WebPageProxy(PageClient&, WebProcessProxy&, uint64_t pageID, Ref<API::PageConfiguration>&&);
     void platformInitialize();
@@ -2076,6 +2080,8 @@
     static bool isInHardwareKeyboardMode();
 #endif
 
+    void makeStorageSpaceRequest(uint64_t frameID, const String& originIdentifier, const String& databaseName, const String& displayName, uint64_t currentQuota, uint64_t currentOriginUsage, uint64_t currentDatabaseUsage, uint64_t expectedUsage, CompletionHandler<void(uint64_t)>&&);
+
     WeakPtr<PageClient> m_pageClient;
     Ref<API::PageConfiguration> m_configuration;
 
@@ -2505,6 +2511,7 @@
     };
     Optional<SpeechSynthesisData> m_speechSynthesisData;
 #endif
+    bool m_isQuotaIncreaseDenied { false };
 };
 
 } // namespace WebKit
diff --git a/Source/WebKit/UIProcess/WebProcessProxy.cpp b/Source/WebKit/UIProcess/WebProcessProxy.cpp
index d642781..4248ec9 100644
--- a/Source/WebKit/UIProcess/WebProcessProxy.cpp
+++ b/Source/WebKit/UIProcess/WebProcessProxy.cpp
@@ -123,6 +123,15 @@
     return pageMap;
 }
 
+void WebProcessProxy::forWebPagesWithOrigin(PAL::SessionID sessionID, const SecurityOriginData& origin, const Function<void(WebPageProxy&)>& callback)
+{
+    for (auto* page : globalPageMap().values()) {
+        if (page->sessionID() != sessionID || SecurityOriginData::fromURL(URL { { }, page->currentURL() }) != origin)
+            continue;
+        callback(*page);
+    }
+}
+
 Ref<WebProcessProxy> WebProcessProxy::create(WebProcessPool& processPool, WebsiteDataStore* websiteDataStore, IsPrewarmed isPrewarmed, ShouldLaunchProcess shouldLaunchProcess)
 {
     auto proxy = adoptRef(*new WebProcessProxy(processPool, websiteDataStore, isPrewarmed));
diff --git a/Source/WebKit/UIProcess/WebProcessProxy.h b/Source/WebKit/UIProcess/WebProcessProxy.h
index 0a47b6c..9a6cbf1 100644
--- a/Source/WebKit/UIProcess/WebProcessProxy.h
+++ b/Source/WebKit/UIProcess/WebProcessProxy.h
@@ -110,6 +110,8 @@
     static Ref<WebProcessProxy> create(WebProcessPool&, WebsiteDataStore*, IsPrewarmed, ShouldLaunchProcess = ShouldLaunchProcess::Yes);
     ~WebProcessProxy();
 
+    static void forWebPagesWithOrigin(PAL::SessionID, const WebCore::SecurityOriginData&, const Function<void(WebPageProxy&)>&);
+
     WebConnection* webConnection() const { return m_webConnection.get(); }
 
     unsigned suspendedPageCount() const { return m_suspendedPageCount; }
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index df0cdd8..a612a37 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,5 +1,23 @@
 2019-05-15  Youenn Fablet  <youenn@apple.com>
 
+        Reuse existing WebPageProxy quota handler for NetworkProcessProxy quota requests
+        https://bugs.webkit.org/show_bug.cgi?id=197463
+        <rdar://problem/47403621>
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm: Added.
+        (-[QuotaDelegate init]):
+        (-[QuotaDelegate _webView:decideDatabaseQuotaForSecurityOrigin:currentQuota:currentOriginUsage:currentDatabaseUsage:expectedUsage:decisionHandler:]):
+        (-[QuotaDelegate quotaDelegateCalled]):
+        (-[QuotaDelegate grantQuota]):
+        (-[StorageSchemes webView:startURLSchemeTask:]):
+        (-[StorageSchemes webView:stopURLSchemeTask:]):
+        (-[QuotaMessageHandler userContentController:didReceiveScriptMessage:]):
+        (doTest):
+
+2019-05-15  Youenn Fablet  <youenn@apple.com>
+
         Constant crashes under WebPage::isThrottleable() after r245299
         https://bugs.webkit.org/show_bug.cgi?id=197902
         <rdar://problem/50793796>
diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
index 1e8eb8c..df27f12 100644
--- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
+++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
@@ -180,6 +180,7 @@
 		3FCC4FE81EC4E8CA0076E37C /* PictureInPictureDelegate.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 3FCC4FE61EC4E87E0076E37C /* PictureInPictureDelegate.html */; };
 		4135FB842011FAA700332139 /* InjectInternals_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4135FB832011FAA300332139 /* InjectInternals_Bundle.cpp */; };
 		4135FB852011FABF00332139 /* libWebCoreTestSupport.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4135FB862011FABF00332139 /* libWebCoreTestSupport.dylib */; };
+		414AD6862285D1C000777F2D /* StorageQuota.mm in Sources */ = {isa = PBXBuildFile; fileRef = 414AD6852285D1B000777F2D /* StorageQuota.mm */; };
 		41882F0321010C0D002FF288 /* ProcessPreWarming.mm in Sources */ = {isa = PBXBuildFile; fileRef = 41882F0221010A70002FF288 /* ProcessPreWarming.mm */; };
 		4433A396208044140091ED57 /* SynchronousTimeoutTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4433A395208044130091ED57 /* SynchronousTimeoutTests.mm */; };
 		44817A2F1F0486BF00003810 /* WKRequestActivatedElementInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 44817A2E1F0486BF00003810 /* WKRequestActivatedElementInfo.mm */; };
@@ -1579,6 +1580,7 @@
 		3FCC4FE61EC4E87E0076E37C /* PictureInPictureDelegate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = PictureInPictureDelegate.html; sourceTree = "<group>"; };
 		4135FB832011FAA300332139 /* InjectInternals_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = InjectInternals_Bundle.cpp; path = Tests/InjectInternals_Bundle.cpp; sourceTree = SOURCE_ROOT; };
 		4135FB862011FABF00332139 /* libWebCoreTestSupport.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libWebCoreTestSupport.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+		414AD6852285D1B000777F2D /* StorageQuota.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StorageQuota.mm; sourceTree = "<group>"; };
 		41882F0221010A70002FF288 /* ProcessPreWarming.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ProcessPreWarming.mm; sourceTree = "<group>"; };
 		41973B5C1AF22875006C7B36 /* SharedBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SharedBuffer.cpp; sourceTree = "<group>"; };
 		442BBF681C91CAD90017087F /* RefLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RefLogger.cpp; sourceTree = "<group>"; };
@@ -2688,6 +2690,7 @@
 				2D9A53AE1B31FA8D0074D5AA /* ShrinkToFit.mm */,
 				2DFF7B6C1DA487AF00814614 /* SnapshotStore.mm */,
 				CDC0932D21C993440030C4B0 /* StopSuspendResumeAllMedia.mm */,
+				414AD6852285D1B000777F2D /* StorageQuota.mm */,
 				515BE1701D428BD100DD7C68 /* StoreBlobThenDelete.mm */,
 				1C734B5220788C4800F430EA /* SystemColors.mm */,
 				2D70059521EDA0C6003463CB /* TabOutOfWebView.mm */,
@@ -4369,6 +4372,7 @@
 				7CCE7ECE1A411A7E00447C4C /* StopLoadingFromDidFinishLoading.mm in Sources */,
 				7CCE7ECF1A411A7E00447C4C /* StopLoadingFromDidReceiveResponse.mm in Sources */,
 				CDC0932E21C993440030C4B0 /* StopSuspendResumeAllMedia.mm in Sources */,
+				414AD6862285D1C000777F2D /* StorageQuota.mm in Sources */,
 				515BE1711D428E4B00DD7C68 /* StoreBlobThenDelete.mm in Sources */,
 				7CCE7ED01A411A7E00447C4C /* StringByEvaluatingJavaScriptFromString.mm in Sources */,
 				7CCE7ED11A411A7E00447C4C /* StringTruncator.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm
new file mode 100644
index 0000000..ef3161b
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/StorageQuota.mm
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2019 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 <WebKit/WebKit.h>
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKProcessPoolPrivate.h>
+#import <WebKit/WKURLSchemeHandler.h>
+#import <WebKit/WKURLSchemeTaskPrivate.h>
+#import <WebKit/WKWebViewConfigurationPrivate.h>
+#import <WebKit/WKWebsiteDataStorePrivate.h>
+#import <WebKit/WKWebsiteDataStoreRef.h>
+#import <WebKit/_WKWebsiteDataStoreConfiguration.h>
+#import <wtf/BlockPtr.h>
+#import <wtf/HashMap.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/Vector.h>
+#import <wtf/text/StringHash.h>
+#import <wtf/text/WTFString.h>
+
+using namespace TestWebKitAPI;
+
+@interface QuotaDelegate : NSObject <WKUIDelegate>
+-(bool)quotaDelegateCalled;
+-(void)grantQuota;
+-(void)denyQuota;
+@end
+
+static bool receivedQuotaDelegateCalled;
+
+@implementation QuotaDelegate {
+    bool _quotaDelegateCalled;
+    unsigned long long _currentQuota;
+    unsigned long long _expectedUsage;
+    BlockPtr<void(unsigned long long newQuota)> _decisionHandler;
+}
+
+- (instancetype)init
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _quotaDelegateCalled = false;
+    _expectedUsage = 0;
+    _currentQuota = 0;
+    
+    return self;
+}
+
+- (void)_webView:(WKWebView *)webView decideDatabaseQuotaForSecurityOrigin:(WKSecurityOrigin *)securityOrigin currentQuota:(unsigned long long)currentQuota currentOriginUsage:(unsigned long long)currentOriginUsage currentDatabaseUsage:(unsigned long long)currentUsage expectedUsage:(unsigned long long)expectedUsage decisionHandler:(void (^)(unsigned long long newQuota))decisionHandler
+{
+    receivedQuotaDelegateCalled = true;
+    _quotaDelegateCalled = true;
+    _currentQuota = currentQuota;
+    _expectedUsage = expectedUsage;
+    _decisionHandler = decisionHandler;
+}
+
+-(bool)quotaDelegateCalled {
+    return _quotaDelegateCalled;
+}
+
+-(void)grantQuota {
+    if (_quotaDelegateCalled)
+        _decisionHandler(_expectedUsage);
+    _quotaDelegateCalled = false;
+}
+
+-(void)denyQuota {
+    if (_quotaDelegateCalled)
+        _decisionHandler(_currentQuota);
+    _quotaDelegateCalled = false;
+}
+
+@end
+
+struct ResourceInfo {
+    RetainPtr<NSString> mimeType;
+    const char* data;
+};
+
+@interface StorageSchemes : NSObject <WKURLSchemeHandler> {
+@public
+    HashMap<String, ResourceInfo> resources;
+}
+@end
+
+@implementation StorageSchemes {
+}
+
+- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+    auto entry = resources.find([task.request.URL absoluteString]);
+    if (entry == resources.end()) {
+        NSLog(@"Did not find resource entry for URL %@", task.request.URL);
+        return;
+    }
+    
+    RetainPtr<NSURLResponse> response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:entry->value.mimeType.get() expectedContentLength:1 textEncodingName:nil]);
+    [task didReceiveResponse:response.get()];
+    
+    [task didReceiveData:[NSData dataWithBytesNoCopy:(void*)entry->value.data length:strlen(entry->value.data) freeWhenDone:NO]];
+    [task didFinish];
+}
+
+- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+}
+
+@end
+
+static bool receivedMessage;
+
+@interface QuotaMessageHandler : NSObject <WKScriptMessageHandler>
+-(void)setExpectedMessage:(NSString *)message;
+@end
+
+@implementation QuotaMessageHandler {
+    NSString *_expectedMessage;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+    if (_expectedMessage) {
+        EXPECT_TRUE([[message body] isEqualToString:_expectedMessage]);
+        _expectedMessage = nil;
+    }
+    receivedMessage = true;
+}
+
+-(void)setExpectedMessage:(NSString *)message {
+    _expectedMessage = message;
+}
+@end
+
+static const char* TestBytes = R"SWRESOURCE(
+<script>
+
+async function doTest()
+{
+    const cache = await window.caches.open("mycache");
+    const promise = cache.put("http://example.org/test", new Response(new ArrayBuffer(1024 * 500)));
+    window.webkit.messageHandlers.qt.postMessage("start");
+    promise.then(() => {
+        window.webkit.messageHandlers.qt.postMessage("pass");
+    }, () => {
+        window.webkit.messageHandlers.qt.postMessage("fail");
+    });
+}
+doTest();
+
+function doTestAgain()
+{
+    doTest();
+}
+</script>
+)SWRESOURCE";
+
+static bool done;
+
+static inline void setVisible(TestWKWebView *webView)
+{
+#if PLATFORM(MAC)
+    [webView.window setIsVisible:YES];
+#else
+    webView.window.hidden = NO;
+#endif
+}
+
+TEST(WebKit, QuotaDelegate)
+{
+    done = false;
+    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+
+    [[configuration websiteDataStore] _setPerOriginStorageQuota: 1024 * 400];
+    
+    auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
+    [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
+
+    auto handler1 = adoptNS([[StorageSchemes alloc] init]);
+    handler1->resources.set("qt1://test1.html", ResourceInfo { @"text/html", TestBytes });
+    [configuration setURLSchemeHandler:handler1.get() forURLScheme:@"QT1"];
+    [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt1"];
+
+    auto handler2 = adoptNS([[StorageSchemes alloc] init]);
+    handler2->resources.set("qt2://test2.html", ResourceInfo { @"text/html", TestBytes });
+    [configuration setURLSchemeHandler:handler2.get() forURLScheme:@"QT2"];
+    [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt2"];
+
+    auto webView1 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
+    auto delegate1 = adoptNS([[QuotaDelegate alloc] init]);
+    [webView1 setUIDelegate:delegate1.get()];
+    setVisible(webView1.get());
+    
+    receivedQuotaDelegateCalled = false;
+    [webView1 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt1://test1.html"]]];
+    Util::run(&receivedQuotaDelegateCalled);
+
+    auto webView2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
+    auto delegate2 = adoptNS([[QuotaDelegate alloc] init]);
+    [webView2 setUIDelegate:delegate2.get()];
+    setVisible(webView2.get());
+
+    receivedMessage = false;
+    [webView2 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt2://test2.html"]]];
+    [messageHandler setExpectedMessage: @"start"];
+    Util::run(&receivedMessage);
+
+    EXPECT_FALSE(delegate2.get().quotaDelegateCalled);
+    [delegate1 grantQuota];
+
+    [messageHandler setExpectedMessage: @"pass"];
+    receivedMessage = false;
+    Util::run(&receivedMessage);
+
+    while (!delegate2.get().quotaDelegateCalled)
+        TestWebKitAPI::Util::sleep(0.1);
+
+    [delegate2 denyQuota];
+
+    [messageHandler setExpectedMessage: @"fail"];
+    receivedMessage = false;
+    Util::run(&receivedMessage);
+}
+
+TEST(WebKit, QuotaDelegateReload)
+{
+    done = false;
+    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+    
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    
+    [[configuration websiteDataStore] _setPerOriginStorageQuota: 1024 * 400];
+    
+    auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
+    [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
+    
+    auto handler = adoptNS([[StorageSchemes alloc] init]);
+    handler->resources.set("qt://test1.html", ResourceInfo { @"text/html", TestBytes });
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"QT"];
+    [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt"];
+    
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
+    auto delegate = adoptNS([[QuotaDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+    setVisible(webView.get());
+
+    receivedQuotaDelegateCalled = false;
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html"]]];
+    Util::run(&receivedQuotaDelegateCalled);
+
+    [delegate denyQuota];
+
+    [messageHandler setExpectedMessage: @"fail"];
+    receivedMessage = false;
+    Util::run(&receivedMessage);
+
+    receivedQuotaDelegateCalled = false;
+    [webView reload];
+    Util::run(&receivedQuotaDelegateCalled);
+
+    [delegate grantQuota];
+
+    [messageHandler setExpectedMessage: @"pass"];
+    receivedMessage = false;
+    Util::run(&receivedMessage);
+}
+
+TEST(WebKit, QuotaDelegateNavigateFragment)
+{
+    done = false;
+    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+
+    [[configuration websiteDataStore] _setPerOriginStorageQuota: 1024 * 400];
+
+    auto messageHandler = adoptNS([[QuotaMessageHandler alloc] init]);
+    [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"qt"];
+
+    auto handler = adoptNS([[StorageSchemes alloc] init]);
+    handler->resources.set("qt://test1.html", ResourceInfo { @"text/html", TestBytes });
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"QT"];
+    [configuration.get().processPool _registerURLSchemeServiceWorkersCanHandle:@"qt"];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
+    auto delegate = adoptNS([[QuotaDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+    setVisible(webView.get());
+
+    receivedQuotaDelegateCalled = false;
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html"]]];
+    Util::run(&receivedQuotaDelegateCalled);
+
+    [delegate denyQuota];
+
+    [messageHandler setExpectedMessage: @"fail"];
+    receivedMessage = false;
+    Util::run(&receivedMessage);
+
+    receivedQuotaDelegateCalled = false;
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"qt://test1.html#fragment"]]];
+    [webView stringByEvaluatingJavaScript:@"doTestAgain()"];
+
+    [messageHandler setExpectedMessage: @"start"];
+    receivedMessage = false;
+    Util::run(&receivedMessage);
+
+    [messageHandler setExpectedMessage: @"fail"];
+    receivedMessage = false;
+    Util::run(&receivedMessage);
+
+    EXPECT_FALSE(receivedQuotaDelegateCalled);
+}