[Cache API] Add support for Cache.add/addAll
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 22 Aug 2017 02:03:24 +0000 (02:03 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 22 Aug 2017 02:03:24 +0000 (02:03 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175677

Patch by Youenn Fablet <youenn@apple.com> on 2017-08-21
Reviewed by Alex Christensen.

LayoutTests/imported/w3c:

* web-platform-tests/service-workers/cache-storage/window/cache-add.https-expected.txt:
* web-platform-tests/service-workers/cache-storage/window/cache-storage.https-expected.txt:
* web-platform-tests/service-workers/cache-storage/worker/cache-add.https-expected.txt:
* web-platform-tests/service-workers/cache-storage/worker/cache-storage.https-expected.txt:

Source/WebCore:

Covered by rebased tests.

Cache.addAll implementation is then as follow:
- Fetch the resources in parallel.
- Wait for all them to complete using FetchTaskCounter.
- If an error happens in any load or if the response is not as expected for Cache API, reject the promise.
- Call the batch put operation with all received FetchResponse objects.

FetchTaskCounter is responsible to wait for each response to arrive.
It then checks whether the response is fine,
If not, the addAll promise is rejected.
Otherwise, it waits for the body to be received.

Introducing a helper routine to create a FetchRequest from a given RequestInfo.
Introducing a helper routine to check for Vary Header '*' value in response headers.

* Modules/cache/Cache.cpp:
(WebCore::Cache::doMatch):
(WebCore::Cache::add):
(WebCore::queryCacheMatch):
(WebCore::hasResponseVaryStarHeaderValue):
(WebCore::FetchTaskCounter::FetchTaskCounter):
(WebCore::FetchTaskCounter::~FetchTaskCounter):
(WebCore::FetchTaskCounter::addRecord):
(WebCore::FetchTaskCounter::isDone const):
(WebCore::FetchTaskCounter::reject):
(WebCore::Cache::requestFromInfo):
(WebCore::Cache::addAll):
(WebCore::Cache::put):
(WebCore::Cache::remove):
(WebCore::Cache::keys):
(WebCore::toConnectionRecord):
(WebCore::Cache::batchPutOperation):
* Modules/cache/Cache.h:

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

LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-add.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/window/cache-storage.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-add.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-put.https-expected.txt
LayoutTests/imported/w3c/web-platform-tests/service-workers/cache-storage/worker/cache-storage.https-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/Modules/cache/Cache.cpp
Source/WebCore/Modules/cache/Cache.h

index ffd393db5be90d0217a61dca665fca7456f726db..a4d23805c62de589c97c9d67339967259baeda5a 100644 (file)
@@ -1,3 +1,15 @@
+2017-08-21  Youenn Fablet  <youenn@apple.com>
+
+        [Cache API] Add support for Cache.add/addAll
+        https://bugs.webkit.org/show_bug.cgi?id=175677
+
+        Reviewed by Alex Christensen.
+
+        * web-platform-tests/service-workers/cache-storage/window/cache-add.https-expected.txt:
+        * web-platform-tests/service-workers/cache-storage/window/cache-storage.https-expected.txt:
+        * web-platform-tests/service-workers/cache-storage/worker/cache-add.https-expected.txt:
+        * web-platform-tests/service-workers/cache-storage/worker/cache-storage.https-expected.txt:
+
 2017-08-21  Youenn Fablet  <youenn@apple.com>
 
         [Cache API] Add support for CacheStorage.match
 2017-08-21  Youenn Fablet  <youenn@apple.com>
 
         [Cache API] Add support for CacheStorage.match
index 318fc322a6f300b8d61b4897cd42268ea832b53f..cf74cfad6ddf2ee4d12f7f6a57745efe639b9941 100644 (file)
@@ -1,20 +1,20 @@
 
 PASS Cache.add called with no arguments 
 
 PASS Cache.add called with no arguments 
-FAIL Cache.add called with relative URL specified as a string promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add called with non-HTTP/HTTPS URL assert_throws: Cache.add should throw a TypeError for non-HTTP/HTTPS URLs. function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add called with Request object promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add called with POST request assert_throws: Cache.add should throw a TypeError for non-GET requests. function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add called twice with the same Request object promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add with request with null body (not consumed) promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add with 206 response assert_throws: Cache.add should reject on partial response function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.addAll with 206 response assert_throws: Cache.addAll should reject with TypeError if any request fails function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add with request that results in a status of 404 assert_throws: Cache.add should reject if response is !ok function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add with request that results in a status of 500 assert_throws: Cache.add should reject if response is !ok function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
+PASS Cache.add called with relative URL specified as a string 
+PASS Cache.add called with non-HTTP/HTTPS URL 
+PASS Cache.add called with Request object 
+PASS Cache.add called with POST request 
+PASS Cache.add called twice with the same Request object 
+PASS Cache.add with request with null body (not consumed) 
+PASS Cache.add with 206 response 
+PASS Cache.addAll with 206 response 
+PASS Cache.add with request that results in a status of 404 
+PASS Cache.add with request that results in a status of 500 
 PASS Cache.addAll with no arguments 
 PASS Cache.addAll with a mix of valid and undefined arguments 
 PASS Cache.addAll with no arguments 
 PASS Cache.addAll with a mix of valid and undefined arguments 
-FAIL Cache.addAll with an empty array promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.addAll with string URL arguments promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.addAll with Request arguments promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.addAll with a mix of succeeding and failing requests assert_throws: Cache.addAll should reject with TypeError if any request fails function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.addAll called with the same Request object specified twice assert_throws: Cache.addAll should throw InvalidStateError if the same request is added twice. function "function () { throw e }" threw object "NotSupportedError: Not implemented" that is not a DOMException InvalidStateError: property "code" is equal to 9, expected 11
+PASS Cache.addAll with an empty array 
+PASS Cache.addAll with string URL arguments 
+PASS Cache.addAll with Request arguments 
+PASS Cache.addAll with a mix of succeeding and failing requests 
+PASS Cache.addAll called with the same Request object specified twice 
 
 
index 709112b5892df871b0d156aaf23c6fb141939e51..4fcf543b7481a3e5f94674f45c9c1eb2e7979245 100644 (file)
@@ -1,11 +1,11 @@
 
 PASS CacheStorage.open 
 
 PASS CacheStorage.open 
-FAIL CacheStorage.delete dooms, but does not delete immediately promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
+PASS CacheStorage.delete dooms, but does not delete immediately 
 PASS CacheStorage.open with an empty name 
 PASS CacheStorage.open with no arguments 
 PASS CacheStorage.has with existing cache 
 PASS CacheStorage.has with nonexistent cache 
 PASS CacheStorage.open with an empty name 
 PASS CacheStorage.open with no arguments 
 PASS CacheStorage.has with existing cache 
 PASS CacheStorage.has with nonexistent cache 
-FAIL CacheStorage.open with existing cache promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
+PASS CacheStorage.open with existing cache 
 PASS CacheStorage.delete with existing cache 
 PASS CacheStorage.delete with nonexistent cache 
 PASS CacheStorage names are DOMStrings not USVStrings 
 PASS CacheStorage.delete with existing cache 
 PASS CacheStorage.delete with nonexistent cache 
 PASS CacheStorage names are DOMStrings not USVStrings 
index 318fc322a6f300b8d61b4897cd42268ea832b53f..cf74cfad6ddf2ee4d12f7f6a57745efe639b9941 100644 (file)
@@ -1,20 +1,20 @@
 
 PASS Cache.add called with no arguments 
 
 PASS Cache.add called with no arguments 
-FAIL Cache.add called with relative URL specified as a string promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add called with non-HTTP/HTTPS URL assert_throws: Cache.add should throw a TypeError for non-HTTP/HTTPS URLs. function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add called with Request object promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add called with POST request assert_throws: Cache.add should throw a TypeError for non-GET requests. function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add called twice with the same Request object promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add with request with null body (not consumed) promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.add with 206 response assert_throws: Cache.add should reject on partial response function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.addAll with 206 response assert_throws: Cache.addAll should reject with TypeError if any request fails function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add with request that results in a status of 404 assert_throws: Cache.add should reject if response is !ok function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.add with request that results in a status of 500 assert_throws: Cache.add should reject if response is !ok function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
+PASS Cache.add called with relative URL specified as a string 
+PASS Cache.add called with non-HTTP/HTTPS URL 
+PASS Cache.add called with Request object 
+PASS Cache.add called with POST request 
+PASS Cache.add called twice with the same Request object 
+PASS Cache.add with request with null body (not consumed) 
+PASS Cache.add with 206 response 
+PASS Cache.addAll with 206 response 
+PASS Cache.add with request that results in a status of 404 
+PASS Cache.add with request that results in a status of 500 
 PASS Cache.addAll with no arguments 
 PASS Cache.addAll with a mix of valid and undefined arguments 
 PASS Cache.addAll with no arguments 
 PASS Cache.addAll with a mix of valid and undefined arguments 
-FAIL Cache.addAll with an empty array promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.addAll with string URL arguments promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.addAll with Request arguments promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
-FAIL Cache.addAll with a mix of succeeding and failing requests assert_throws: Cache.addAll should reject with TypeError if any request fails function "function () { throw e }" threw object "NotSupportedError: Not implemented" ("NotSupportedError") expected object "TypeError" ("TypeError")
-FAIL Cache.addAll called with the same Request object specified twice assert_throws: Cache.addAll should throw InvalidStateError if the same request is added twice. function "function () { throw e }" threw object "NotSupportedError: Not implemented" that is not a DOMException InvalidStateError: property "code" is equal to 9, expected 11
+PASS Cache.addAll with an empty array 
+PASS Cache.addAll with string URL arguments 
+PASS Cache.addAll with Request arguments 
+PASS Cache.addAll with a mix of succeeding and failing requests 
+PASS Cache.addAll called with the same Request object specified twice 
 
 
index 09cd12e6546fc0e561c9d26ca0995ad00e240ffb..94492625e2fa6b3fe2a7c8335db423a145defb26 100644 (file)
@@ -1,6 +1,6 @@
 
 PASS Cache.put called with simple Request and Response 
 
 PASS Cache.put called with simple Request and Response 
-PASS Cache.put called with Request and Response from fetch() 
+FAIL Cache.put called with Request and Response from fetch() promise_test: Unhandled rejection with value: object "NotSupportedError: Caching a Response with data stored in a ReadableStream is not yet supported"
 PASS Cache.put with Request without a body 
 PASS Cache.put with Response without a body 
 PASS Cache.put with a Response containing an empty URL 
 PASS Cache.put with Request without a body 
 PASS Cache.put with Response without a body 
 PASS Cache.put with a Response containing an empty URL 
index 709112b5892df871b0d156aaf23c6fb141939e51..4fcf543b7481a3e5f94674f45c9c1eb2e7979245 100644 (file)
@@ -1,11 +1,11 @@
 
 PASS CacheStorage.open 
 
 PASS CacheStorage.open 
-FAIL CacheStorage.delete dooms, but does not delete immediately promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
+PASS CacheStorage.delete dooms, but does not delete immediately 
 PASS CacheStorage.open with an empty name 
 PASS CacheStorage.open with no arguments 
 PASS CacheStorage.has with existing cache 
 PASS CacheStorage.has with nonexistent cache 
 PASS CacheStorage.open with an empty name 
 PASS CacheStorage.open with no arguments 
 PASS CacheStorage.has with existing cache 
 PASS CacheStorage.has with nonexistent cache 
-FAIL CacheStorage.open with existing cache promise_test: Unhandled rejection with value: object "NotSupportedError: Not implemented"
+PASS CacheStorage.open with existing cache 
 PASS CacheStorage.delete with existing cache 
 PASS CacheStorage.delete with nonexistent cache 
 PASS CacheStorage names are DOMStrings not USVStrings 
 PASS CacheStorage.delete with existing cache 
 PASS CacheStorage.delete with nonexistent cache 
 PASS CacheStorage names are DOMStrings not USVStrings 
index 857df68ae5b592d13050c3c94cb892cfad54ec57..b74c012300e89f5c29467a9c4793fbb9f6aebcbd 100644 (file)
@@ -1,3 +1,45 @@
+2017-08-21  Youenn Fablet  <youenn@apple.com>
+
+        [Cache API] Add support for Cache.add/addAll
+        https://bugs.webkit.org/show_bug.cgi?id=175677
+
+        Reviewed by Alex Christensen.
+
+        Covered by rebased tests.
+
+        Cache.addAll implementation is then as follow:
+        - Fetch the resources in parallel.
+        - Wait for all them to complete using FetchTaskCounter.
+        - If an error happens in any load or if the response is not as expected for Cache API, reject the promise.
+        - Call the batch put operation with all received FetchResponse objects.
+
+        FetchTaskCounter is responsible to wait for each response to arrive.
+        It then checks whether the response is fine,
+        If not, the addAll promise is rejected.
+        Otherwise, it waits for the body to be received.
+
+        Introducing a helper routine to create a FetchRequest from a given RequestInfo.
+        Introducing a helper routine to check for Vary Header '*' value in response headers.
+
+        * Modules/cache/Cache.cpp:
+        (WebCore::Cache::doMatch):
+        (WebCore::Cache::add):
+        (WebCore::queryCacheMatch):
+        (WebCore::hasResponseVaryStarHeaderValue):
+        (WebCore::FetchTaskCounter::FetchTaskCounter):
+        (WebCore::FetchTaskCounter::~FetchTaskCounter):
+        (WebCore::FetchTaskCounter::addRecord):
+        (WebCore::FetchTaskCounter::isDone const):
+        (WebCore::FetchTaskCounter::reject):
+        (WebCore::Cache::requestFromInfo):
+        (WebCore::Cache::addAll):
+        (WebCore::Cache::put):
+        (WebCore::Cache::remove):
+        (WebCore::Cache::keys):
+        (WebCore::toConnectionRecord):
+        (WebCore::Cache::batchPutOperation):
+        * Modules/cache/Cache.h:
+
 2017-08-21  Myles C. Maxfield  <mmaxfield@apple.com>
 
         DataInteractionTests.ExternalSourceAttributedStringToContentEditable hits a debug assertion
 2017-08-21  Myles C. Maxfield  <mmaxfield@apple.com>
 
         DataInteractionTests.ExternalSourceAttributedStringToContentEditable hits a debug assertion
index 0cfa6cec9de84ae52ceaa59440fda4fed7275a93..4a1334b4e6cc3750b070db9a829f3ea20bdcef41 100644 (file)
@@ -36,6 +36,8 @@
 
 namespace WebCore {
 
 
 namespace WebCore {
 
+static CacheStorageConnection::Record toConnectionRecord(const FetchRequest&, FetchResponse&, CacheStorageConnection::ResponseBody&&);
+
 Cache::Cache(ScriptExecutionContext& context, String&& name, uint64_t identifier, Ref<CacheStorageConnection>&& connection)
     : ActiveDOMObject(&context)
     , m_name(WTFMove(name))
 Cache::Cache(ScriptExecutionContext& context, String&& name, uint64_t identifier, Ref<CacheStorageConnection>&& connection)
     : ActiveDOMObject(&context)
     , m_name(WTFMove(name))
@@ -62,20 +64,17 @@ void Cache::match(RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredP
 
 void Cache::doMatch(RequestInfo&& info, CacheQueryOptions&& options, MatchCallback&& callback)
 {
 
 void Cache::doMatch(RequestInfo&& info, CacheQueryOptions&& options, MatchCallback&& callback)
 {
-    RefPtr<FetchRequest> request;
-    if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
-        request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
-        if (request->method() != "GET" && !options.ignoreMethod) {
-            callback(nullptr);
-            return;
-        }
-    } else {
-        if (UNLIKELY(!scriptExecutionContext()))
-            return;
-        request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
+    if (UNLIKELY(!scriptExecutionContext()))
+        return;
+
+    auto requestOrException = requestFromInfo(WTFMove(info), options.ignoreMethod);
+    if (requestOrException.hasException()) {
+        callback(nullptr);
+        return;
     }
     }
+    auto request = requestOrException.releaseReturnValue();
 
 
-    queryCache(request.releaseNonNull(), WTFMove(options), [callback = WTFMove(callback)](const Vector<CacheStorageRecord>& records) mutable {
+    queryCache(request.get(), WTFMove(options), [callback = WTFMove(callback)](const Vector<CacheStorageRecord>& records) mutable {
         if (records.isEmpty()) {
             callback(nullptr);
             return;
         if (records.isEmpty()) {
             callback(nullptr);
             return;
@@ -86,19 +85,17 @@ void Cache::doMatch(RequestInfo&& info, CacheQueryOptions&& options, MatchCallba
 
 void Cache::matchAll(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, MatchAllPromise&& promise)
 {
 
 void Cache::matchAll(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, MatchAllPromise&& promise)
 {
+    if (UNLIKELY(!scriptExecutionContext()))
+        return;
+
     RefPtr<FetchRequest> request;
     if (info) {
     RefPtr<FetchRequest> request;
     if (info) {
-        if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
-            request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
-            if (request->method() != "GET" && !options.ignoreMethod) {
-                promise.resolve({ });
-                return;
-            }
-        } else {
-            if (UNLIKELY(!scriptExecutionContext()))
-                return;
-            request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
+        auto requestOrException = requestFromInfo(WTFMove(info.value()), options.ignoreMethod);
+        if (requestOrException.hasException()) {
+            promise.resolve({ });
+            return;
         }
         }
+        request = requestOrException.releaseReturnValue();
     }
 
     if (!request) {
     }
 
     if (!request) {
@@ -120,45 +117,175 @@ void Cache::matchAll(std::optional<RequestInfo>&& info, CacheQueryOptions&& opti
     });
 }
 
     });
 }
 
-void Cache::add(RequestInfo&&, DOMPromiseDeferred<void>&& promise)
+void Cache::add(RequestInfo&& info, DOMPromiseDeferred<void>&& promise)
 {
 {
-    promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
+    addAll(Vector<RequestInfo> { WTFMove(info) }, WTFMove(promise));
 }
 
 }
 
-void Cache::addAll(Vector<RequestInfo>&&, DOMPromiseDeferred<void>&& promise)
+static inline bool hasResponseVaryStarHeaderValue(const FetchResponse& response)
 {
 {
-    promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
+    auto varyValue = response.headers().internalHeaders().get(WebCore::HTTPHeaderName::Vary);
+    bool hasStar = false;
+    varyValue.split(',', false, [&](StringView view) {
+        if (!hasStar && stripLeadingAndTrailingHTTPSpaces(view.toStringWithoutCopying()) == "*")
+            hasStar = true;
+    });
+    return hasStar;
 }
 
 }
 
-void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDeferred<void>&& promise)
+class FetchTasksHandler : public RefCounted<FetchTasksHandler> {
+public:
+    explicit FetchTasksHandler(Function<void(ExceptionOr<Vector<CacheStorageConnection::Record>>&&)>&& callback)
+        : m_callback(WTFMove(callback))
+    {
+    }
+
+    ~FetchTasksHandler()
+    {
+        if (m_callback)
+            m_callback(WTFMove(m_records));
+    }
+
+    const Vector<CacheStorageConnection::Record>& records() const { return m_records; }
+
+    size_t addRecord(CacheStorageConnection::Record&& record)
+    {
+        ASSERT(!isDone());
+        m_records.append(WTFMove(record));
+        return m_records.size() - 1;
+    }
+
+    void addResponseBody(size_t position, Ref<SharedBuffer>&& data)
+    {
+        ASSERT(!isDone());
+        m_records[position].responseBody = WTFMove(data);
+    }
+
+    bool isDone() const { return !m_callback; }
+
+    void error(Exception&& exception)
+    {
+        if (auto callback = WTFMove(m_callback))
+            callback(WTFMove(exception));
+    }
+
+private:
+    Vector<CacheStorageConnection::Record> m_records;
+    Function<void(ExceptionOr<Vector<CacheStorageConnection::Record>>&&)> m_callback;
+};
+
+ExceptionOr<Ref<FetchRequest>> Cache::requestFromInfo(RequestInfo&& info, bool ignoreMethod)
 {
     RefPtr<FetchRequest> request;
     if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
         request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
 {
     RefPtr<FetchRequest> request;
     if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
         request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
-        if (request->method() != "GET") {
-            promise.reject(Exception { TypeError, ASCIILiteral("Request method is not GET") });
+        if (request->method() != "GET" && !ignoreMethod)
+            return Exception { TypeError, ASCIILiteral("Request method is not GET") };
+    } else
+        request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
+
+    if (!protocolIsInHTTPFamily(request->url()))
+        return Exception { TypeError, ASCIILiteral("Request url is not HTTP/HTTPS") };
+
+    return request.releaseNonNull();
+}
+
+void Cache::addAll(Vector<RequestInfo>&& infos, DOMPromiseDeferred<void>&& promise)
+{
+    if (UNLIKELY(!scriptExecutionContext()))
+        return;
+
+    Vector<Ref<FetchRequest>> requests;
+    requests.reserveInitialCapacity(infos.size());
+    for (auto& info : infos) {
+        bool ignoreMethod = false;
+        auto requestOrException = requestFromInfo(WTFMove(info), ignoreMethod);
+        if (requestOrException.hasException()) {
+            promise.reject(requestOrException.releaseException());
             return;
         }
             return;
         }
-    } else {
-        if (UNLIKELY(!scriptExecutionContext()))
+        requests.uncheckedAppend(requestOrException.releaseReturnValue());
+    }
+
+    auto taskHandler = adoptRef(*new FetchTasksHandler([protectedThis = makeRef(*this), this, promise = WTFMove(promise)](ExceptionOr<Vector<CacheStorageConnection::Record>>&& result) mutable {
+        if (result.hasException()) {
+            promise.reject(result.releaseException());
             return;
             return;
-        request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
+        }
+        batchPutOperation(result.releaseReturnValue(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
+            promise.settle(WTFMove(result));
+        });
+    }));
+
+    for (auto& request : requests) {
+        auto& requestReference = request.get();
+        FetchResponse::fetch(*scriptExecutionContext(), requestReference, [this, request = WTFMove(request), taskHandler = taskHandler.copyRef()](ExceptionOr<FetchResponse&>&& result) mutable {
+
+            if (taskHandler->isDone())
+                return;
+
+            if (result.hasException()) {
+                taskHandler->error(result.releaseException());
+                return;
+            }
+
+            auto& response = result.releaseReturnValue();
+
+            if (!response.ok()) {
+                taskHandler->error(Exception { TypeError, ASCIILiteral("Response is not OK") });
+                return;
+            }
+
+            if (hasResponseVaryStarHeaderValue(response)) {
+                taskHandler->error(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
+                return;
+            }
+
+            if (response.status() == 206) {
+                taskHandler->error(Exception { TypeError, ASCIILiteral("Response is a 206 partial") });
+                return;
+            }
+
+            CacheQueryOptions options;
+            for (const auto& record : taskHandler->records()) {
+                if (CacheStorageConnection::queryCacheMatch(request->resourceRequest(), record.request, record.response, options)) {
+                    taskHandler->error(Exception { InvalidStateError, ASCIILiteral("addAll cannot store several matching requests")});
+                    return;
+                }
+            }
+            size_t recordPosition = taskHandler->addRecord(toConnectionRecord(request.get(), response, nullptr));
+
+            response.consumeBodyWhenLoaded([taskHandler = WTFMove(taskHandler), recordPosition](ExceptionOr<RefPtr<SharedBuffer>>&& result) mutable {
+                if (taskHandler->isDone())
+                    return;
+
+                if (result.hasException()) {
+                    taskHandler->error(result.releaseException());
+                    return;
+                }
+                if (auto value = result.releaseReturnValue())
+                    taskHandler->addResponseBody(recordPosition, value.releaseNonNull());
+            });
+        });
     }
     }
+}
+
+void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDeferred<void>&& promise)
+{
+    if (UNLIKELY(!scriptExecutionContext()))
+        return;
 
 
-    if (!protocolIsInHTTPFamily(request->url())) {
-        promise.reject(Exception { TypeError, ASCIILiteral("Request url is not HTTP/HTTPS") });
+    bool ignoreMethod = false;
+    auto requestOrException = requestFromInfo(WTFMove(info), ignoreMethod);
+    if (requestOrException.hasException()) {
+        promise.reject(requestOrException.releaseException());
         return;
     }
         return;
     }
+    auto request = requestOrException.releaseReturnValue();
 
 
-    // FIXME: This is inefficient, we should be able to split and trim whitespaces at the same time.
-    auto varyValue = response->headers().internalHeaders().get(WebCore::HTTPHeaderName::Vary);
-    Vector<String> varyHeaderNames;
-    varyValue.split(',', false, varyHeaderNames);
-    for (auto& name : varyHeaderNames) {
-        if (stripLeadingAndTrailingHTTPSpaces(name) == "*") {
-            promise.reject(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
-            return;
-        }
+    if (hasResponseVaryStarHeaderValue(response.get())) {
+        promise.reject(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
+        return;
     }
 
     if (response->status() == 206) {
     }
 
     if (response->status() == 206) {
@@ -179,7 +306,7 @@ void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDef
 
     if (response->isLoading()) {
         setPendingActivity(this);
 
     if (response->isLoading()) {
         setPendingActivity(this);
-        response->consumeBodyWhenLoaded([promise = WTFMove(promise), request = request.releaseNonNull(), response = WTFMove(response), this](ExceptionOr<RefPtr<SharedBuffer>>&& result) mutable {
+        response->consumeBodyWhenLoaded([promise = WTFMove(promise), request = WTFMove(request), response = WTFMove(response), this](ExceptionOr<RefPtr<SharedBuffer>>&& result) mutable {
             if (result.hasException())
                 promise.reject(result.releaseException());
             else {
             if (result.hasException())
                 promise.reject(result.releaseException());
             else {
@@ -195,46 +322,40 @@ void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDef
         return;
     }
 
         return;
     }
 
-    batchPutOperation(*request, response.get(), response->consumeBody(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
+    batchPutOperation(request.get(), response.get(), response->consumeBody(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
         promise.settle(WTFMove(result));
     });
 }
 
 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
 {
         promise.settle(WTFMove(result));
     });
 }
 
 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
 {
-    RefPtr<FetchRequest> request;
-    if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
-        request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
-        if (request->method() != "GET" && !options.ignoreMethod) {
-            promise.resolve(false);
-            return;
-        }
-    } else {
-        if (UNLIKELY(!scriptExecutionContext()))
-            return;
-        request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
+    if (UNLIKELY(!scriptExecutionContext()))
+        return;
+
+    auto requestOrException = requestFromInfo(WTFMove(info), options.ignoreMethod);
+    if (requestOrException.hasException()) {
+        promise.resolve(false);
+        return;
     }
 
     }
 
-    batchDeleteOperation(*request, WTFMove(options), [promise = WTFMove(promise)](ExceptionOr<bool>&& result) mutable {
+    batchDeleteOperation(requestOrException.releaseReturnValue(), WTFMove(options), [promise = WTFMove(promise)](ExceptionOr<bool>&& result) mutable {
         promise.settle(WTFMove(result));
     });
 }
 
 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
 {
         promise.settle(WTFMove(result));
     });
 }
 
 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
 {
+    if (UNLIKELY(!scriptExecutionContext()))
+        return;
+
     RefPtr<FetchRequest> request;
     if (info) {
     RefPtr<FetchRequest> request;
     if (info) {
-        if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
-            request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
-            if (request->method() != "GET" && !options.ignoreMethod) {
-                promise.resolve(Vector<Ref<FetchRequest>> { });
-                return;
-            }
-        } else {
-            if (UNLIKELY(!scriptExecutionContext()))
-                return;
-            request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
+        auto requestOrException = requestFromInfo(WTFMove(info.value()), options.ignoreMethod);
+        if (requestOrException.hasException()) {
+            promise.resolve(Vector<Ref<FetchRequest>> { });
+            return;
         }
         }
+        request = requestOrException.releaseReturnValue();
     }
 
     if (!request) {
     }
 
     if (!request) {
@@ -306,7 +427,7 @@ void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&
     });
 }
 
     });
 }
 
-static inline CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response, CacheStorageConnection::ResponseBody&& responseBody)
+CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response, CacheStorageConnection::ResponseBody&& responseBody)
 {
     // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
     ResourceResponse cachedResponse = response.resourceResponse();
 {
     // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
     ResourceResponse cachedResponse = response.resourceResponse();
@@ -330,6 +451,11 @@ void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& respon
     Vector<CacheStorageConnection::Record> records;
     records.append(toConnectionRecord(request, response, WTFMove(responseBody)));
 
     Vector<CacheStorageConnection::Record> records;
     records.append(toConnectionRecord(request, response, WTFMove(responseBody)));
 
+    batchPutOperation(WTFMove(records), WTFMove(callback));
+}
+
+void Cache::batchPutOperation(Vector<CacheStorageConnection::Record>&& records, WTF::Function<void(ExceptionOr<void>&&)>&& callback)
+{
     setPendingActivity(this);
     m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
         if (!m_isStopped)
     setPendingActivity(this);
     m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
         if (!m_isStopped)
index 3ca082e8257b5e3d515f03bb154cb5f519810db2..efb34bbc1e9f0665ef814894082585d4dc1a3c7c 100644 (file)
@@ -62,6 +62,8 @@ public:
 private:
     Cache(ScriptExecutionContext&, String&& name, uint64_t identifier, Ref<CacheStorageConnection>&&);
 
 private:
     Cache(ScriptExecutionContext&, String&& name, uint64_t identifier, Ref<CacheStorageConnection>&&);
 
+    ExceptionOr<Ref<FetchRequest>> requestFromInfo(RequestInfo&&, bool ignoreMethod);
+
     // ActiveDOMObject
     void stop() final;
     const char* activeDOMObjectName() const final;
     // ActiveDOMObject
     void stop() final;
     const char* activeDOMObjectName() const final;
@@ -72,6 +74,7 @@ private:
     void queryCache(Ref<FetchRequest>&&, CacheQueryOptions&&, WTF::Function<void(const Vector<CacheStorageRecord>&)>&&);
     void batchDeleteOperation(const FetchRequest&, CacheQueryOptions&&, WTF::Function<void(ExceptionOr<bool>&&)>&&);
     void batchPutOperation(const FetchRequest&, FetchResponse&, CacheStorageConnection::ResponseBody&&, WTF::Function<void(ExceptionOr<void>&&)>&&);
     void queryCache(Ref<FetchRequest>&&, CacheQueryOptions&&, WTF::Function<void(const Vector<CacheStorageRecord>&)>&&);
     void batchDeleteOperation(const FetchRequest&, CacheQueryOptions&&, WTF::Function<void(ExceptionOr<bool>&&)>&&);
     void batchPutOperation(const FetchRequest&, FetchResponse&, CacheStorageConnection::ResponseBody&&, WTF::Function<void(ExceptionOr<void>&&)>&&);
+    void batchPutOperation(Vector<CacheStorageConnection::Record>&&, WTF::Function<void(ExceptionOr<void>&&)>&&);
 
     void updateRecords(Vector<CacheStorageConnection::Record>&&);
 
 
     void updateRecords(Vector<CacheStorageConnection::Record>&&);