ladybird/Libraries/LibWeb/ServiceWorker/CacheStorage.cpp
2026-04-03 11:04:12 +02:00

179 lines
7.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2026, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Array.h>
#include <LibWeb/Bindings/CacheStoragePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/ServiceWorker/Cache.h>
#include <LibWeb/ServiceWorker/CacheStorage.h>
#include <LibWeb/WebIDL/Promise.h>
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<WebIDL::Promise> 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<WebIDL::Promise> 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<Cache>(realm, value.release_value()));
// 2. Abort these steps.
return;
}
// 2. Let cache be a new request response list.
auto cache = realm.heap().allocate<RequestResponseList>();
// 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<Cache>(realm, cache));
}));
// 3. Return promise.
return promise;
}
// https://w3c.github.io/ServiceWorker/#cache-storage-open
GC::Ref<WebIDL::Promise> 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<JS::Value> {
// 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<WebIDL::Promise> 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<String>(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 objects 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;
}
}