diff --git a/Libraries/LibWeb/ServiceWorker/Cache.cpp b/Libraries/LibWeb/ServiceWorker/Cache.cpp index dfc7f13206..fd883a3596 100644 --- a/Libraries/LibWeb/ServiceWorker/Cache.cpp +++ b/Libraries/LibWeb/ServiceWorker/Cache.cpp @@ -42,6 +42,160 @@ void Cache::visit_edges(Visitor& visitor) visitor.visit(m_request_response_list); } +// https://w3c.github.io/ServiceWorker/#cache-match +GC::Ref Cache::match(Fetch::RequestInfo request, CacheQueryOptions options) +{ + auto& realm = HTML::relevant_realm(*this); + + // 1. Let promise be a new promise. + auto promise = WebIDL::create_promise(realm); + + // 2. Run these substeps in parallel: + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, &realm, promise, request = move(request), options]() { + HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + + // 1. Let p be the result of running the algorithm specified in matchAll(request, options) method with request and options. + // 2. Wait until p settles. + WebIDL::react_to_promise(match_all(move(request), options), + // 4. Else if p resolves with an array, responses, then: + GC::create_function(realm.heap(), [&realm, promise](JS::Value value) -> WebIDL::ExceptionOr { + HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + + // 1. If responses is an empty array, then: + if (auto& responses = value.as(); responses.indexed_array_like_size() == 0) { + // 1. Resolve promise with undefined. + WebIDL::resolve_promise(realm, promise, JS::js_undefined()); + } + // 2. Else: + else { + // 1. Resolve promise with the first element of responses. + auto first_element = responses.indexed_get(0).release_value(); + WebIDL::resolve_promise(realm, promise, first_element.value); + } + + return JS::js_undefined(); + }), + + // 3. If p rejects with an exception, then: + GC::create_function(realm.heap(), [&realm, promise](JS::Value exception) -> WebIDL::ExceptionOr { + HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + + // 1. Reject promise with that exception. + WebIDL::reject_promise(realm, promise, exception); + + return JS::js_undefined(); + })); + })); + + // 3. Return promise. + return promise; +} + +// https://w3c.github.io/ServiceWorker/#cache-matchall +GC::Ref Cache::match_all(Optional request, CacheQueryOptions options) +{ + auto& realm = HTML::relevant_realm(*this); + + // 1. Let r be null. + GC::Ptr inner_request; + + // 2. If the optional argument request is not omitted, then: + if (request.has_value()) { + TRY(request->visit( + // 1. If request is a Request object, then: + [&](GC::Root const& request) -> ErrorOr> { + // 1. Set r to request’s request. + inner_request = request->request(); + + // 2. If r’s method is not `GET` and options.ignoreMethod is false, return a promise resolved with an + // empty array. + if (inner_request->method() != "GET"sv && !options.ignore_method) + return WebIDL::create_resolved_promise(realm, MUST(JS::Array::create(realm, 0))); + + return {}; + }, + // 2. Else if request is a string, then: + [&](String const& request) -> ErrorOr> { + // 1. Set r to the associated request of the result of invoking the initial value of Request as + // constructor with request as its argument. If this throws an exception, return a promise rejected + // with that exception. + auto request_object = Fetch::Request::construct_impl(realm, request); + if (request_object.is_error()) + return WebIDL::create_rejected_promise_from_exception(realm, request_object.release_error()); + + inner_request = request_object.value()->request(); + return {}; + })); + } + + // 3. Let realm be this’s relevant realm. + // 4. Let promise be a new promise. + auto promise = WebIDL::create_promise(realm); + + // 5. Run these substeps in parallel: + Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, &realm, inner_request, promise, request = move(request), options]() { + // 1. Let responses be an empty list. + auto responses = realm.heap().allocate>>(); + + // 2. If the optional argument request is omitted, then: + if (!request.has_value()) { + // 1. For each requestResponse of the relevant request response list: + for (auto& request_response : m_request_response_list->elements()) { + // 1. Add a copy of requestResponse’s response to responses. + responses->elements().append(request_response->response->clone(realm)); + } + } + // 3. Else: + else { + // 1. Let requestResponses be the result of running Query Cache with r and options. + auto request_responses = query_cache(*inner_request, options); + + // 2. For each requestResponse of requestResponses: + for (auto request_response : request_responses->elements()) { + // 1. Add a copy of requestResponse’s response to responses. + // NB: No need to copy. Query Cache creates a copy, and the requestResponses list is dropped hereafter. + responses->elements().append(request_response->response); + } + } + + // 3. For each response of responses: + for (auto response : responses->elements()) { + // 1. If response’s type is "opaque" and cross-origin resource policy check with promise’s relevant settings + // object’s origin, promise’s relevant settings object, "", and response’s internal response returns + // blocked, then reject promise with a TypeError and abort these steps. + if (response->type() == Fetch::Infrastructure::Response::Type::Opaque) { + // FIXME: Perform the cross-origin resource policy check. + } + } + + // 4. Queue a task, on promise’s relevant settings object’s responsible event loop using the DOM manipulation + // task source, to perform the following steps: + HTML::queue_a_task( + HTML::Task::Source::DOMManipulation, + HTML::relevant_settings_object(promise->promise()).responsible_event_loop(), + {}, + GC::create_function(realm.heap(), [&realm, promise, responses]() { + HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + + // 1. Let responseList be a list. + auto response_list = realm.heap().allocate>(); + + // 2. For each response of responses: + for (auto response : responses->elements()) { + // 1. Add a new Response object associated with response and a new Headers object whose guard is + // "immutable" to responseList. + response_list->elements().append(Fetch::Response::create(realm, response, Fetch::Headers::Guard::Immutable)); + } + + // 3. Resolve promise with a frozen array created from responseList, in realm. + WebIDL::resolve_promise(realm, promise, JS::Array::create_from(realm, response_list->elements())); + })); + })); + + // 6. Return promise. + return promise; +} + // https://w3c.github.io/ServiceWorker/#cache-add GC::Ref Cache::add(Fetch::RequestInfo request) { diff --git a/Libraries/LibWeb/ServiceWorker/Cache.h b/Libraries/LibWeb/ServiceWorker/Cache.h index cb0a9c6044..4a85dbbffb 100644 --- a/Libraries/LibWeb/ServiceWorker/Cache.h +++ b/Libraries/LibWeb/ServiceWorker/Cache.h @@ -51,6 +51,8 @@ class Cache : public Bindings::PlatformObject { GC_DECLARE_ALLOCATOR(Cache); public: + GC::Ref match(Fetch::RequestInfo, CacheQueryOptions); + GC::Ref match_all(Optional, CacheQueryOptions); GC::Ref add(Fetch::RequestInfo); GC::Ref add_all(ReadonlySpan); GC::Ref put(Fetch::RequestInfo, GC::Ref); diff --git a/Libraries/LibWeb/ServiceWorker/Cache.idl b/Libraries/LibWeb/ServiceWorker/Cache.idl index dcb18d2b12..afbed24e41 100644 --- a/Libraries/LibWeb/ServiceWorker/Cache.idl +++ b/Libraries/LibWeb/ServiceWorker/Cache.idl @@ -4,8 +4,8 @@ // https://w3c.github.io/ServiceWorker/#cache-interface [SecureContext, Exposed=(Window,Worker)] interface Cache { - // [NewObject] Promise<(Response or undefined)> match(RequestInfo request, optional CacheQueryOptions options = {}); - // [NewObject] Promise> matchAll(optional RequestInfo request, optional CacheQueryOptions options = {}); + [NewObject] Promise<(Response or undefined)> match(RequestInfo request, optional CacheQueryOptions options = {}); + [NewObject] Promise> matchAll(optional RequestInfo request, optional CacheQueryOptions options = {}); [NewObject] Promise add(RequestInfo request); [NewObject] Promise addAll(sequence requests); [NewObject] Promise put(RequestInfo request, Response response); diff --git a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-add.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-add.https.any.txt index 9489157693..0929073294 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-add.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-add.https.any.txt @@ -2,10 +2,10 @@ Harness status: OK Found 22 tests -14 Pass -8 Fail +18 Pass +4 Fail Pass Cache.add called with no arguments -Fail Cache.add called with relative URL specified as a string +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 @@ -19,9 +19,9 @@ 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 Fail Cache.addAll with an empty array -Fail Cache.addAll with string URL arguments -Fail Cache.addAll with Request arguments -Fail Cache.addAll with a mix of succeeding and failing requests +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 Fail Cache.addAll should succeed when entries differ by vary header Fail Cache.addAll should reject when entries are duplicate by vary header diff --git a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-match.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-match.https.any.txt index 746885d820..ff664533b4 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-match.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-match.https.any.txt @@ -2,29 +2,30 @@ Harness status: OK Found 25 tests -25 Fail -Fail Cache.match with no matching entries -Fail Cache.match with URL -Fail Cache.match with Request -Fail Cache.match with multiple cache hits -Fail Cache.match with new Request -Fail Cache.match with HEAD -Fail Cache.match with ignoreSearch option (request with no search parameters) -Fail Cache.match with ignoreSearch option (request with search parameter) -Fail Cache.match supports ignoreMethod -Fail Cache.match supports ignoreVary -Fail Cache.match does not support cacheName option -Fail Cache.match with URL containing fragment -Fail Cache.match with string fragment "http" as query -Fail Cache.match with responses containing "Vary" header -Fail Cache.match with Request and Response objects with different URLs -Fail Cache.match invoked multiple times for the same Request/Response -Fail Cache.match blob should be sliceable -Fail Cache.match with POST Request -Fail Cache.match with a non-2xx Response -Fail Cache.match with a network error Response -Fail Cache produces large Responses that can be cloned and read correctly. +23 Pass +2 Fail +Pass Cache.match with no matching entries +Pass Cache.match with URL +Pass Cache.match with Request +Pass Cache.match with multiple cache hits +Pass Cache.match with new Request +Pass Cache.match with HEAD +Pass Cache.match with ignoreSearch option (request with no search parameters) +Pass Cache.match with ignoreSearch option (request with search parameter) +Pass Cache.match supports ignoreMethod +Pass Cache.match supports ignoreVary +Pass Cache.match does not support cacheName option +Pass Cache.match with URL containing fragment +Pass Cache.match with string fragment "http" as query +Pass Cache.match with responses containing "Vary" header +Pass Cache.match with Request and Response objects with different URLs +Pass Cache.match invoked multiple times for the same Request/Response +Pass Cache.match blob should be sliceable +Pass Cache.match with POST Request +Pass Cache.match with a non-2xx Response +Pass Cache.match with a network error Response +Pass Cache produces large Responses that can be cloned and read correctly. Fail cors-exposed header should be stored correctly. -Fail MIME type should be set from content-header correctly. -Fail MIME type should reflect Content-Type headers of response. +Pass MIME type should be set from content-header correctly. +Pass MIME type should reflect Content-Type headers of response. Fail Cache.match ignores vary headers on opaque response. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-matchAll.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-matchAll.https.any.txt index 7e2ab3ff98..fe8e0753de 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-matchAll.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-matchAll.https.any.txt @@ -2,20 +2,20 @@ Harness status: OK Found 16 tests -16 Fail -Fail Cache.matchAll with no matching entries -Fail Cache.matchAll with URL -Fail Cache.matchAll with Request -Fail Cache.matchAll with new Request -Fail Cache.matchAll with HEAD -Fail Cache.matchAll with ignoreSearch option (request with no search parameters) -Fail Cache.matchAll with ignoreSearch option (request with search parameters) -Fail Cache.matchAll supports ignoreMethod -Fail Cache.matchAll supports ignoreVary -Fail Cache.matchAll with URL containing fragment -Fail Cache.matchAll with string fragment "http" as query -Fail Cache.matchAll without parameters -Fail Cache.matchAll with explicitly undefined request -Fail Cache.matchAll with explicitly undefined request and empty options -Fail Cache.matchAll with responses containing "Vary" header -Fail Cache.matchAll with multiple vary pairs \ No newline at end of file +16 Pass +Pass Cache.matchAll with no matching entries +Pass Cache.matchAll with URL +Pass Cache.matchAll with Request +Pass Cache.matchAll with new Request +Pass Cache.matchAll with HEAD +Pass Cache.matchAll with ignoreSearch option (request with no search parameters) +Pass Cache.matchAll with ignoreSearch option (request with search parameters) +Pass Cache.matchAll supports ignoreMethod +Pass Cache.matchAll supports ignoreVary +Pass Cache.matchAll with URL containing fragment +Pass Cache.matchAll with string fragment "http" as query +Pass Cache.matchAll without parameters +Pass Cache.matchAll with explicitly undefined request +Pass Cache.matchAll with explicitly undefined request and empty options +Pass Cache.matchAll with responses containing "Vary" header +Pass Cache.matchAll with multiple vary pairs \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-put.https.any.txt b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-put.https.any.txt index 140b2f9887..726f1d9de3 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-put.https.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/service-workers/cache-storage/cache-put.https.any.txt @@ -2,24 +2,24 @@ Harness status: OK Found 27 tests -13 Pass -14 Fail +23 Pass +4 Fail Pass Cache.put called with simple Request and Response -Fail Cache.put called with Request and Response from fetch() +Pass Cache.put called with Request and Response from fetch() Pass Cache.put with Request without a body Pass Cache.put with Response without a body -Fail Cache.put with a Response containing an empty URL -Fail Cache.put with an empty response body +Pass Cache.put with a Response containing an empty URL +Pass Cache.put with an empty response body Pass Cache.put with synthetic 206 response Fail Cache.put with HTTP 206 response -Fail Cache.put with opaque-filtered HTTP 206 response +Pass Cache.put with opaque-filtered HTTP 206 response Fail Cache.put with HTTP 500 response -Fail Cache.put called twice with matching Requests and different Responses +Pass Cache.put called twice with matching Requests and different Responses Fail Cache.put called multiple times with request URLs that differ only by a fragment -Fail Cache.put with a string request +Pass Cache.put with a string request Pass Cache.put with an invalid response Pass Cache.put with a non-HTTP/HTTPS request -Fail Cache.put with a relative URL +Pass Cache.put with a relative URL Pass Cache.put with a non-GET request Pass Cache.put with a null response Pass Cache.put with a POST request @@ -28,6 +28,6 @@ Pass getReader() after Cache.put Pass Cache.put with a VARY:* Response Pass Cache.put with an embedded VARY:* Response Fail Cache.put with a VARY:* opaque response should not reject -Fail Cache.put should store Response.redirect() correctly -Fail Cache.put called with simple Request and blob Response -Fail Cache.put called with simple Request and form data Response \ No newline at end of file +Pass Cache.put should store Response.redirect() correctly +Pass Cache.put called with simple Request and blob Response +Pass Cache.put called with simple Request and form data Response \ No newline at end of file