blob: 3360d0dcbc36f8390e8bfc7f0a32f3db3212e175 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/cachestorage/Cache.h"
#include <memory>
#include <utility>
#include "bindings/core/v8/CallbackPromiseAdapter.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/IDLTypes.h"
#include "bindings/core/v8/NativeValueTraitsImpl.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "bindings/core/v8/V8BindingForCore.h"
#include "bindings/core/v8/V8Response.h"
#include "bindings/core/v8/V8ScriptRunner.h"
#include "core/dom/DOMException.h"
#include "core/dom/ExecutionContext.h"
#include "core/fetch/BodyStreamBuffer.h"
#include "core/fetch/FetchDataLoader.h"
#include "core/fetch/Request.h"
#include "core/fetch/Response.h"
#include "core/html/parser/TextResourceDecoder.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/cachestorage/CacheStorageError.h"
#include "modules/serviceworkers/ServiceWorkerGlobalScope.h"
#include "platform/Histogram.h"
#include "platform/bindings/ScriptState.h"
#include "platform/bindings/V8ThrowException.h"
#include "platform/loader/fetch/CachedMetadata.h"
#include "platform/network/http_names.h"
#include "platform/network/mime/MIMETypeRegistry.h"
#include "platform/runtime_enabled_features.h"
#include "public/platform/modules/cache_storage/cache_storage.mojom-blink.h"
#include "public/platform/modules/serviceworker/WebServiceWorkerCache.h"
#include "services/network/public/interfaces/fetch_api.mojom-blink.h"
namespace blink {
namespace {
// FIXME: Consider using CallbackPromiseAdapter.
class CacheMatchCallbacks : public WebServiceWorkerCache::CacheMatchCallbacks {
WTF_MAKE_NONCOPYABLE(CacheMatchCallbacks);
public:
explicit CacheMatchCallbacks(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
void OnSuccess(const WebServiceWorkerResponse& web_response) override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
ScriptState::Scope scope(resolver_->GetScriptState());
resolver_->Resolve(
Response::Create(resolver_->GetScriptState(), web_response));
resolver_.Clear();
}
void OnError(mojom::CacheStorageError reason) override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
if (reason == mojom::CacheStorageError::kErrorNotFound)
resolver_->Resolve();
else
resolver_->Reject(CacheStorageError::CreateException(reason));
resolver_.Clear();
}
private:
Persistent<ScriptPromiseResolver> resolver_;
};
// FIXME: Consider using CallbackPromiseAdapter.
class CacheWithResponsesCallbacks
: public WebServiceWorkerCache::CacheWithResponsesCallbacks {
WTF_MAKE_NONCOPYABLE(CacheWithResponsesCallbacks);
public:
explicit CacheWithResponsesCallbacks(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
void OnSuccess(
const WebVector<WebServiceWorkerResponse>& web_responses) override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
ScriptState::Scope scope(resolver_->GetScriptState());
HeapVector<Member<Response>> responses;
for (size_t i = 0; i < web_responses.size(); ++i)
responses.push_back(
Response::Create(resolver_->GetScriptState(), web_responses[i]));
resolver_->Resolve(responses);
resolver_.Clear();
}
void OnError(mojom::CacheStorageError reason) override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
resolver_->Reject(CacheStorageError::CreateException(reason));
resolver_.Clear();
}
protected:
Persistent<ScriptPromiseResolver> resolver_;
};
// FIXME: Consider using CallbackPromiseAdapter.
class CacheDeleteCallback : public WebServiceWorkerCache::CacheBatchCallbacks {
WTF_MAKE_NONCOPYABLE(CacheDeleteCallback);
public:
explicit CacheDeleteCallback(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
void OnSuccess() override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
resolver_->Resolve(true);
resolver_.Clear();
}
void OnError(mojom::CacheStorageError reason) override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
if (reason == mojom::CacheStorageError::kErrorNotFound)
resolver_->Resolve(false);
else
resolver_->Reject(CacheStorageError::CreateException(reason));
resolver_.Clear();
}
private:
Persistent<ScriptPromiseResolver> resolver_;
};
// FIXME: Consider using CallbackPromiseAdapter.
class CacheWithRequestsCallbacks
: public WebServiceWorkerCache::CacheWithRequestsCallbacks {
WTF_MAKE_NONCOPYABLE(CacheWithRequestsCallbacks);
public:
explicit CacheWithRequestsCallbacks(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
void OnSuccess(
const WebVector<WebServiceWorkerRequest>& web_requests) override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
ScriptState::Scope scope(resolver_->GetScriptState());
HeapVector<Member<Request>> requests;
for (size_t i = 0; i < web_requests.size(); ++i)
requests.push_back(
Request::Create(resolver_->GetScriptState(), web_requests[i]));
resolver_->Resolve(requests);
resolver_.Clear();
}
void OnError(mojom::CacheStorageError reason) override {
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
resolver_->Reject(CacheStorageError::CreateException(reason));
resolver_.Clear();
}
private:
Persistent<ScriptPromiseResolver> resolver_;
};
void RecordResponseTypeForAdd(const Member<Response>& response) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
EnumerationHistogram, response_type_histogram,
("ServiceWorkerCache.Cache.AddResponseType",
static_cast<int>(network::mojom::FetchResponseType::kLast) + 1));
response_type_histogram.Count(
static_cast<int>(response->GetResponse()->GetType()));
};
bool VaryHeaderContainsAsterisk(const Response* response) {
const FetchHeaderList* headers = response->headers()->HeaderList();
String varyHeader;
if (headers->Get("vary", varyHeader)) {
Vector<String> fields;
varyHeader.Split(',', fields);
return std::any_of(fields.begin(), fields.end(), [](const String& field) {
return field.StripWhiteSpace() == "*";
});
}
return false;
}
bool ShouldGenerateV8CodeCache(ScriptState* script_state,
const Response* response) {
if (!RuntimeEnabledFeatures::PWAFullCodeCacheEnabled())
return false;
ExecutionContext* context = ExecutionContext::From(script_state);
if (!context->IsServiceWorkerGlobalScope())
return false;
if (!ToServiceWorkerGlobalScope(context)->IsInstalling())
return false;
if (!MIMETypeRegistry::IsSupportedJavaScriptMIMEType(
response->InternalMIMEType())) {
return false;
}
if (!response->InternalBodyBuffer())
return false;
return true;
}
} // namespace
// TODO(nhiroki): Unfortunately, we have to go through V8 to wait for the fetch
// promise. It should be better to achieve this only within C++ world.
class Cache::FetchResolvedForAdd final : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(
ScriptState* script_state,
Cache* cache,
const HeapVector<Member<Request>>& requests) {
FetchResolvedForAdd* self =
new FetchResolvedForAdd(script_state, cache, requests);
return self->BindToV8Function();
}
ScriptValue Call(ScriptValue value) override {
NonThrowableExceptionState exception_state;
HeapVector<Member<Response>> responses =
NativeValueTraits<IDLSequence<Response>>::NativeValue(
GetScriptState()->GetIsolate(), value.V8Value(), exception_state);
for (const auto& response : responses) {
if (!response->ok()) {
ScriptPromise rejection = ScriptPromise::Reject(
GetScriptState(),
V8ThrowException::CreateTypeError(GetScriptState()->GetIsolate(),
"Request failed"));
return ScriptValue(GetScriptState(), rejection.V8Value());
}
if (VaryHeaderContainsAsterisk(response)) {
ScriptPromise rejection = ScriptPromise::Reject(
GetScriptState(),
V8ThrowException::CreateTypeError(GetScriptState()->GetIsolate(),
"Vary header contains *"));
return ScriptValue(GetScriptState(), rejection.V8Value());
}
}
for (const auto& response : responses)
RecordResponseTypeForAdd(response);
ScriptPromise put_promise =
cache_->PutImpl(GetScriptState(), requests_, responses);
return ScriptValue(GetScriptState(), put_promise.V8Value());
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(cache_);
visitor->Trace(requests_);
ScriptFunction::Trace(visitor);
}
private:
FetchResolvedForAdd(ScriptState* script_state,
Cache* cache,
const HeapVector<Member<Request>>& requests)
: ScriptFunction(script_state), cache_(cache), requests_(requests) {}
Member<Cache> cache_;
HeapVector<Member<Request>> requests_;
};
class Cache::BarrierCallbackForPut final
: public GarbageCollectedFinalized<BarrierCallbackForPut> {
public:
BarrierCallbackForPut(int number_of_operations,
Cache* cache,
ScriptPromiseResolver* resolver)
: number_of_remaining_operations_(number_of_operations),
cache_(cache),
resolver_(resolver) {
DCHECK_LT(0, number_of_remaining_operations_);
batch_operations_.resize(number_of_operations);
}
void OnSuccess(size_t index,
const WebServiceWorkerCache::BatchOperation& batch_operation) {
DCHECK_LT(index, batch_operations_.size());
if (completed_)
return;
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
batch_operations_[index] = batch_operation;
if (--number_of_remaining_operations_ != 0)
return;
MaybeReportInstalledScripts();
cache_->WebCache()->DispatchBatch(
std::make_unique<CallbackPromiseAdapter<void, CacheStorageError>>(
resolver_),
batch_operations_);
}
void OnError(const String& error_message) {
if (completed_)
return;
completed_ = true;
if (!resolver_->GetExecutionContext() ||
resolver_->GetExecutionContext()->IsContextDestroyed())
return;
ScriptState* state = resolver_->GetScriptState();
ScriptState::Scope scope(state);
resolver_->Reject(
V8ThrowException::CreateTypeError(state->GetIsolate(), error_message));
}
virtual void Trace(blink::Visitor* visitor) {
visitor->Trace(cache_);
visitor->Trace(resolver_);
}
private:
// Report the script stats if this cache storage is for service worker
// execution context and it's in installation phase.
void MaybeReportInstalledScripts() {
ExecutionContext* context = resolver_->GetExecutionContext();
if (!context || !context->IsServiceWorkerGlobalScope())
return;
ServiceWorkerGlobalScope* global_scope =
ToServiceWorkerGlobalScope(context);
if (!global_scope->IsInstalling())
return;
for (const auto& operation : batch_operations_) {
scoped_refptr<BlobDataHandle> blob_data_handle =
operation.response.GetBlobDataHandle();
if (!blob_data_handle)
continue;
if (!MIMETypeRegistry::IsSupportedJavaScriptMIMEType(
blob_data_handle->GetType())) {
continue;
}
global_scope->CountCacheStorageInstalledScript(
blob_data_handle->size(), operation.response.SideDataBlobSize());
}
}
bool completed_ = false;
int number_of_remaining_operations_;
Member<Cache> cache_;
Member<ScriptPromiseResolver> resolver_;
Vector<WebServiceWorkerCache::BatchOperation> batch_operations_;
};
class Cache::BlobHandleCallbackForPut final
: public GarbageCollectedFinalized<BlobHandleCallbackForPut>,
public FetchDataLoader::Client {
USING_GARBAGE_COLLECTED_MIXIN(BlobHandleCallbackForPut);
public:
BlobHandleCallbackForPut(size_t index,
BarrierCallbackForPut* barrier_callback,
Request* request,
Response* response)
: index_(index), barrier_callback_(barrier_callback) {
request->PopulateWebServiceWorkerRequest(web_request_);
response->PopulateWebServiceWorkerResponse(web_response_);
}
~BlobHandleCallbackForPut() override = default;
void DidFetchDataLoadedBlobHandle(
scoped_refptr<BlobDataHandle> handle) override {
WebServiceWorkerCache::BatchOperation batch_operation;
batch_operation.operation_type = WebServiceWorkerCache::kOperationTypePut;
batch_operation.request = web_request_;
batch_operation.response = web_response_;
batch_operation.response.SetBlobDataHandle(std::move(handle));
barrier_callback_->OnSuccess(index_, batch_operation);
}
void DidFetchDataLoadFailed() override {
barrier_callback_->OnError("network error");
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(barrier_callback_);
FetchDataLoader::Client::Trace(visitor);
}
private:
const size_t index_;
Member<BarrierCallbackForPut> barrier_callback_;
WebServiceWorkerRequest web_request_;
WebServiceWorkerResponse web_response_;
};
class Cache::CodeCacheHandleCallbackForPut final
: public GarbageCollectedFinalized<CodeCacheHandleCallbackForPut>,
public FetchDataLoader::Client {
USING_GARBAGE_COLLECTED_MIXIN(CodeCacheHandleCallbackForPut);
public:
CodeCacheHandleCallbackForPut(ScriptState* script_state,
size_t index,
BarrierCallbackForPut* barrier_callback,
Request* request,
Response* response)
: script_state_(script_state),
index_(index),
barrier_callback_(barrier_callback),
mime_type_(response->InternalMIMEType()) {
request->PopulateWebServiceWorkerRequest(web_request_);
response->PopulateWebServiceWorkerResponse(web_response_);
}
~CodeCacheHandleCallbackForPut() override = default;
void DidFetchDataLoadedArrayBuffer(DOMArrayBuffer* array_buffer) override {
WebServiceWorkerCache::BatchOperation batch_operation;
batch_operation.operation_type = WebServiceWorkerCache::kOperationTypePut;
batch_operation.request = web_request_;
batch_operation.response = web_response_;
std::unique_ptr<BlobData> blob_data = BlobData::Create();
blob_data->SetContentType(mime_type_);
blob_data->AppendBytes(array_buffer->Data(), array_buffer->ByteLength());
batch_operation.response.SetBlobDataHandle(BlobDataHandle::Create(
std::move(blob_data), array_buffer->ByteLength()));
// Currently we only support UTF8 encoding.
// TODO(horo): Use the charset in Content-type header of the response.
// See crbug.com/743311.
std::unique_ptr<TextResourceDecoder> text_decoder =
TextResourceDecoder::Create(
TextResourceDecoderOptions::CreateAlwaysUseUTF8ForText());
scoped_refptr<CachedMetadata> cached_metadata =
V8ScriptRunner::GenerateFullCodeCache(
script_state_.get(),
text_decoder->Decode(static_cast<const char*>(array_buffer->Data()),
array_buffer->ByteLength()),
web_request_.Url().GetString(), text_decoder->Encoding(),
web_response_.ResponseType() ==
network::mojom::FetchResponseType::kOpaque
? V8ScriptRunner::OpaqueMode::kOpaque
: V8ScriptRunner::OpaqueMode::kNotOpaque);
if (!cached_metadata) {
barrier_callback_->OnSuccess(index_, batch_operation);
return;
}
const Vector<char>& serialized_data = cached_metadata->SerializedData();
std::unique_ptr<BlobData> side_data_blob_data = BlobData::Create();
side_data_blob_data->AppendBytes(serialized_data.data(),
serialized_data.size());
batch_operation.response.SetSideDataBlobDataHandle(BlobDataHandle::Create(
std::move(side_data_blob_data), serialized_data.size()));
barrier_callback_->OnSuccess(index_, batch_operation);
}
void DidFetchDataLoadFailed() override {
barrier_callback_->OnError("network error");
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(barrier_callback_);
FetchDataLoader::Client::Trace(visitor);
}
private:
const scoped_refptr<ScriptState> script_state_;
const size_t index_;
Member<BarrierCallbackForPut> barrier_callback_;
const String mime_type_;
WebServiceWorkerRequest web_request_;
WebServiceWorkerResponse web_response_;
};
Cache* Cache::Create(GlobalFetch::ScopedFetcher* fetcher,
std::unique_ptr<WebServiceWorkerCache> web_cache) {
return new Cache(fetcher, std::move(web_cache));
}
ScriptPromise Cache::match(ScriptState* script_state,
const RequestInfo& request,
const CacheQueryOptions& options,
ExceptionState& exception_state) {
DCHECK(!request.IsNull());
if (request.IsRequest())
return MatchImpl(script_state, request.GetAsRequest(), options);
Request* new_request =
Request::Create(script_state, request.GetAsUSVString(), exception_state);
if (exception_state.HadException())
return ScriptPromise();
return MatchImpl(script_state, new_request, options);
}
ScriptPromise Cache::matchAll(ScriptState* script_state,
ExceptionState& exception_state) {
return MatchAllImpl(script_state);
}
ScriptPromise Cache::matchAll(ScriptState* script_state,
const RequestInfo& request,
const CacheQueryOptions& options,
ExceptionState& exception_state) {
DCHECK(!request.IsNull());
if (request.IsRequest())
return MatchAllImpl(script_state, request.GetAsRequest(), options);
Request* new_request =
Request::Create(script_state, request.GetAsUSVString(), exception_state);
if (exception_state.HadException())
return ScriptPromise();
return MatchAllImpl(script_state, new_request, options);
}
ScriptPromise Cache::add(ScriptState* script_state,
const RequestInfo& request,
ExceptionState& exception_state) {
DCHECK(!request.IsNull());
HeapVector<Member<Request>> requests;
if (request.IsRequest()) {
requests.push_back(request.GetAsRequest());
} else {
requests.push_back(Request::Create(script_state, request.GetAsUSVString(),
exception_state));
if (exception_state.HadException())
return ScriptPromise();
}
return AddAllImpl(script_state, requests, exception_state);
}
ScriptPromise Cache::addAll(ScriptState* script_state,
const HeapVector<RequestInfo>& raw_requests,
ExceptionState& exception_state) {
HeapVector<Member<Request>> requests;
for (RequestInfo request : raw_requests) {
if (request.IsRequest()) {
requests.push_back(request.GetAsRequest());
} else {
requests.push_back(Request::Create(script_state, request.GetAsUSVString(),
exception_state));
if (exception_state.HadException())
return ScriptPromise();
}
}
return AddAllImpl(script_state, requests, exception_state);
}
ScriptPromise Cache::Delete(ScriptState* script_state,
const RequestInfo& request,
const CacheQueryOptions& options,
ExceptionState& exception_state) {
DCHECK(!request.IsNull());
if (request.IsRequest())
return DeleteImpl(script_state, request.GetAsRequest(), options);
Request* new_request =
Request::Create(script_state, request.GetAsUSVString(), exception_state);
if (exception_state.HadException())
return ScriptPromise();
return DeleteImpl(script_state, new_request, options);
}
ScriptPromise Cache::put(ScriptState* script_state,
const RequestInfo& request,
Response* response,
ExceptionState& exception_state) {
DCHECK(!request.IsNull());
if (request.IsRequest())
return PutImpl(script_state,
HeapVector<Member<Request>>(1, request.GetAsRequest()),
HeapVector<Member<Response>>(1, response));
Request* new_request =
Request::Create(script_state, request.GetAsUSVString(), exception_state);
if (exception_state.HadException())
return ScriptPromise();
return PutImpl(script_state, HeapVector<Member<Request>>(1, new_request),
HeapVector<Member<Response>>(1, response));
}
ScriptPromise Cache::keys(ScriptState* script_state, ExceptionState&) {
return KeysImpl(script_state);
}
ScriptPromise Cache::keys(ScriptState* script_state,
const RequestInfo& request,
const CacheQueryOptions& options,
ExceptionState& exception_state) {
DCHECK(!request.IsNull());
if (request.IsRequest())
return KeysImpl(script_state, request.GetAsRequest(), options);
Request* new_request =
Request::Create(script_state, request.GetAsUSVString(), exception_state);
if (exception_state.HadException())
return ScriptPromise();
return KeysImpl(script_state, new_request, options);
}
// static
WebServiceWorkerCache::QueryParams Cache::ToWebQueryParams(
const CacheQueryOptions& options) {
WebServiceWorkerCache::QueryParams web_query_params;
web_query_params.ignore_search = options.ignoreSearch();
web_query_params.ignore_method = options.ignoreMethod();
web_query_params.ignore_vary = options.ignoreVary();
web_query_params.cache_name = options.cacheName();
return web_query_params;
}
Cache::Cache(GlobalFetch::ScopedFetcher* fetcher,
std::unique_ptr<WebServiceWorkerCache> web_cache)
: scoped_fetcher_(fetcher), web_cache_(std::move(web_cache)) {}
void Cache::Trace(blink::Visitor* visitor) {
visitor->Trace(scoped_fetcher_);
ScriptWrappable::Trace(visitor);
}
ScriptPromise Cache::MatchImpl(ScriptState* script_state,
const Request* request,
const CacheQueryOptions& options) {
WebServiceWorkerRequest web_request;
request->PopulateWebServiceWorkerRequest(web_request);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ScriptPromise promise = resolver->Promise();
if (request->method() != HTTPNames::GET && !options.ignoreMethod()) {
resolver->Resolve();
return promise;
}
web_cache_->DispatchMatch(std::make_unique<CacheMatchCallbacks>(resolver),
web_request, ToWebQueryParams(options));
return promise;
}
ScriptPromise Cache::MatchAllImpl(ScriptState* script_state) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ScriptPromise promise = resolver->Promise();
web_cache_->DispatchMatchAll(
std::make_unique<CacheWithResponsesCallbacks>(resolver),
WebServiceWorkerRequest(), WebServiceWorkerCache::QueryParams());
return promise;
}
ScriptPromise Cache::MatchAllImpl(ScriptState* script_state,
const Request* request,
const CacheQueryOptions& options) {
WebServiceWorkerRequest web_request;
request->PopulateWebServiceWorkerRequest(web_request);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ScriptPromise promise = resolver->Promise();
if (request->method() != HTTPNames::GET && !options.ignoreMethod()) {
resolver->Resolve(HeapVector<Member<Response>>());
return promise;
}
web_cache_->DispatchMatchAll(
std::make_unique<CacheWithResponsesCallbacks>(resolver), web_request,
ToWebQueryParams(options));
return promise;
}
ScriptPromise Cache::AddAllImpl(ScriptState* script_state,
const HeapVector<Member<Request>>& requests,
ExceptionState& exception_state) {
if (requests.IsEmpty())
return ScriptPromise::CastUndefined(script_state);
HeapVector<RequestInfo> request_infos;
request_infos.resize(requests.size());
Vector<ScriptPromise> promises;
promises.resize(requests.size());
for (size_t i = 0; i < requests.size(); ++i) {
if (!requests[i]->url().ProtocolIsInHTTPFamily())
return ScriptPromise::Reject(script_state,
V8ThrowException::CreateTypeError(
script_state->GetIsolate(),
"Add/AddAll does not support schemes "
"other than \"http\" or \"https\""));
if (requests[i]->method() != HTTPNames::GET)
return ScriptPromise::Reject(
script_state,
V8ThrowException::CreateTypeError(
script_state->GetIsolate(),
"Add/AddAll only supports the GET request method."));
request_infos[i].SetRequest(requests[i]);
promises[i] = scoped_fetcher_->Fetch(script_state, request_infos[i],
Dictionary(), exception_state);
}
return ScriptPromise::All(script_state, promises)
.Then(FetchResolvedForAdd::Create(script_state, this, requests));
}
ScriptPromise Cache::DeleteImpl(ScriptState* script_state,
const Request* request,
const CacheQueryOptions& options) {
WebVector<WebServiceWorkerCache::BatchOperation> batch_operations(size_t(1));
batch_operations[0].operation_type =
WebServiceWorkerCache::kOperationTypeDelete;
request->PopulateWebServiceWorkerRequest(batch_operations[0].request);
batch_operations[0].match_params = ToWebQueryParams(options);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ScriptPromise promise = resolver->Promise();
if (request->method() != HTTPNames::GET && !options.ignoreMethod()) {
resolver->Resolve(false);
return promise;
}
web_cache_->DispatchBatch(std::make_unique<CacheDeleteCallback>(resolver),
batch_operations);
return promise;
}
ScriptPromise Cache::PutImpl(ScriptState* script_state,
const HeapVector<Member<Request>>& requests,
const HeapVector<Member<Response>>& responses) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ScriptPromise promise = resolver->Promise();
BarrierCallbackForPut* barrier_callback =
new BarrierCallbackForPut(requests.size(), this, resolver);
for (size_t i = 0; i < requests.size(); ++i) {
KURL url(NullURL(), requests[i]->url());
if (!url.ProtocolIsInHTTPFamily()) {
barrier_callback->OnError("Request scheme '" + url.Protocol() +
"' is unsupported");
return promise;
}
if (requests[i]->method() != HTTPNames::GET) {
barrier_callback->OnError("Request method '" + requests[i]->method() +
"' is unsupported");
return promise;
}
DCHECK(!requests[i]->HasBody());
if (VaryHeaderContainsAsterisk(responses[i])) {
barrier_callback->OnError("Vary header contains *");
return promise;
}
if (responses[i]->status() == 206) {
barrier_callback->OnError(
"Partial response (status code 206) is unsupported");
return promise;
}
if (responses[i]->IsBodyLocked() || responses[i]->bodyUsed()) {
barrier_callback->OnError("Response body is already used");
return promise;
}
BodyStreamBuffer* buffer = responses[i]->InternalBodyBuffer();
if (ShouldGenerateV8CodeCache(script_state, responses[i])) {
FetchDataLoader* loader = FetchDataLoader::CreateLoaderAsArrayBuffer();
buffer->StartLoading(loader, new CodeCacheHandleCallbackForPut(
script_state, i, barrier_callback,
requests[i], responses[i]));
continue;
}
if (buffer) {
// If the response has body, read the all data and create
// the blob handle and dispatch the put batch asynchronously.
FetchDataLoader* loader = FetchDataLoader::CreateLoaderAsBlobHandle(
responses[i]->InternalMIMEType());
buffer->StartLoading(
loader, new BlobHandleCallbackForPut(i, barrier_callback, requests[i],
responses[i]));
continue;
}
WebServiceWorkerCache::BatchOperation batch_operation;
batch_operation.operation_type = WebServiceWorkerCache::kOperationTypePut;
requests[i]->PopulateWebServiceWorkerRequest(batch_operation.request);
responses[i]->PopulateWebServiceWorkerResponse(batch_operation.response);
barrier_callback->OnSuccess(i, batch_operation);
}
return promise;
}
ScriptPromise Cache::KeysImpl(ScriptState* script_state) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ScriptPromise promise = resolver->Promise();
web_cache_->DispatchKeys(
std::make_unique<CacheWithRequestsCallbacks>(resolver),
WebServiceWorkerRequest(), WebServiceWorkerCache::QueryParams());
return promise;
}
ScriptPromise Cache::KeysImpl(ScriptState* script_state,
const Request* request,
const CacheQueryOptions& options) {
WebServiceWorkerRequest web_request;
request->PopulateWebServiceWorkerRequest(web_request);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
const ScriptPromise promise = resolver->Promise();
if (request->method() != HTTPNames::GET && !options.ignoreMethod()) {
resolver->Resolve(HeapVector<Member<Request>>());
return promise;
}
web_cache_->DispatchKeys(
std::make_unique<CacheWithRequestsCallbacks>(resolver), web_request,
ToWebQueryParams(options));
return promise;
}
WebServiceWorkerCache* Cache::WebCache() const {
return web_cache_.get();
}
} // namespace blink