/* * Copyright (c) 2025, Aliaksandr Kalenik * Copyright (c) 2026, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace Web::ServiceWorker { GC_DEFINE_ALLOCATOR(CacheStorage); CacheStorage::CacheStorage(JS::Realm& realm) : Bindings::PlatformObject(realm) { } void CacheStorage::initialize(JS::Realm& realm) { Base::initialize(realm); WEB_SET_PROTOTYPE_FOR_INTERFACE(CacheStorage); } void CacheStorage::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_relevant_name_to_cache_map); } // https://w3c.github.io/ServiceWorker/#cache-storage-has GC::Ref CacheStorage::has(String const& cache_name) { auto& realm = HTML::relevant_realm(*this); // 1. Let promise be a new promise. auto promise = WebIDL::create_promise(realm); // 2. Run the following substeps in parallel: Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, &realm, promise, cache_name]() { HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; // 1. For each key → value of the relevant name to cache map: // 1. If cacheName matches key, resolve promise with true and abort these steps. // 2. Resolve promise with false. WebIDL::resolve_promise(realm, promise, JS::Value { relevant_name_to_cache_map().contains(cache_name) }); })); // 3. Return promise. return promise; } // https://w3c.github.io/ServiceWorker/#cache-storage-open GC::Ref CacheStorage::open(String const& cache_name) { auto& realm = HTML::relevant_realm(*this); // 1. Let promise be a new promise. auto promise = WebIDL::create_promise(realm); // 2. Run the following substeps in parallel: Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, &realm, promise, cache_name]() { HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; auto& relevant_name_to_cache_map = this->relevant_name_to_cache_map(); // 1. For each key → value of the relevant name to cache map: // 1. If cacheName matches key, then: if (auto value = relevant_name_to_cache_map.get(cache_name); value.has_value()) { // 1. Resolve promise with a new Cache object that represents value. WebIDL::resolve_promise(realm, promise, realm.create(realm, value.release_value())); // 2. Abort these steps. return; } // 2. Let cache be a new request response list. auto cache = realm.heap().allocate(); // 3. Set the relevant name to cache map[cacheName] to cache. If this cache write operation failed due to // exceeding the granted quota limit, reject promise with a QuotaExceededError and abort these steps. // FIXME: Handle cache quotas. relevant_name_to_cache_map.set(cache_name, cache); // 4. Resolve promise with a new Cache object that represents cache. WebIDL::resolve_promise(realm, promise, realm.create(realm, cache)); })); // 3. Return promise. return promise; } // https://w3c.github.io/ServiceWorker/#cache-storage-open GC::Ref CacheStorage::delete_(String const& cache_name) { auto& realm = HTML::relevant_realm(*this); // 1. Let promise be the result of running the algorithm specified in has(cacheName) method with cacheName. auto promise = has(cache_name); // 2. Return the result of reacting to promise with a fulfillment handler that, when called with argument cacheExists, // performs the following substeps: return WebIDL::upon_fulfillment(promise, GC::create_function(realm.heap(), [this, &realm, cache_name](JS::Value cache_exists) mutable -> WebIDL::ExceptionOr { // 1. If cacheExists is false, then: if (!cache_exists.as_bool()) { // 1. Return false. return false; } HTML::TemporaryExecutionContext context { realm }; // 1. Let cacheJobPromise be a new promise. auto cache_job_promise = WebIDL::create_promise(realm); // 2. Run the following substeps in parallel: Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, &realm, cache_job_promise, cache_name]() { HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; // 1. Remove the relevant name to cache map[cacheName]. relevant_name_to_cache_map().remove(cache_name); // 2. Resolve cacheJobPromise with true. WebIDL::resolve_promise(realm, cache_job_promise, JS::Value { true }); // Note: After this step, the existing DOM objects (i.e. the currently referenced Cache, Request, and // Response objects) should remain functional. })); // 3. Return cacheJobPromise. return cache_job_promise->promise(); })); } // https://w3c.github.io/ServiceWorker/#cache-storage-keys GC::Ref CacheStorage::keys() { auto& realm = HTML::relevant_realm(*this); // 1. Let promise be a new promise. auto promise = WebIDL::create_promise(realm); // 2. Run the following substeps in parallel: Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, &realm, promise]() { HTML::TemporaryExecutionContext context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; // 1. Let cacheKeys be the result of getting the keys of the relevant name to cache map. auto cache_keys = relevant_name_to_cache_map().keys(); // Note: The items in the result ordered set are in the order that their corresponding entry was added to the // name to cache map. // 2. Resolve promise with cacheKeys. WebIDL::resolve_promise(realm, promise, JS::Array::create_from(realm, cache_keys, [&](String const& cache_key) { return JS::PrimitiveString::create(realm.vm(), cache_key); })); })); // 3. Return promise. return promise; } // https://w3c.github.io/ServiceWorker/#relevant-name-to-cache-map NameToCacheMap& CacheStorage::relevant_name_to_cache_map() { // The relevant name to cache map for a CacheStorage object is the name to cache map associated with the result of // running obtain a local storage bottle map with the object’s relevant settings object and "caches". // FIXME: We don't yet have a way to serialize request/response pairs for storage in the UI process. For now, we // use an ephemeral name to cache map. return m_relevant_name_to_cache_map; } }