Commit bc660fc7 authored by Ben Kelly's avatar Ben Kelly Committed by Commit Bot

CacheStorage: Make addAll() support throttled network requests.

Cache.addAll() must wait for the all requests to succeed before starting
to store the results in CacheStorage.  Previously this was done by
waiting for all the Response objects to become available and only then
would it begin loading the bodies into blobs.  This does not work with
throttling since throttling requires bodies to be loaded in order to
make progress.

The main change in this CL is to refactor things so we now immediately
load the body to a blob when a Response becomes available.  We then wait
for all Responses and their bodies before proceeding to store the
result.  This should not use too much memory since loading a Response to
a blob streams data to the BlobRegistry, which may then subsequently
stream to disk if necessary.

One notable change here is that code cache generation now requires
loading the body into an ArrayBuffer from a blob.  Previously we loaded
the response to the ArrayBuffer and then converted that into a blob.
This will be slightly slower for putting scripts in CacheStorage, but
that should be fine since generally we do not optimize for write
performance.

Other changes in this CL:

* Adds a cache.addAll() test for the throttled service worker case.
* Adds `cache:no-store` to the existing throttled service worker test
  since that more easily triggers true throttling behavior.  This in
  turn also required draining the response bodies in the test.
* Refactored cache add(), addAll(), and put() to all go through the
  same set of objects where appropriate.
* FetchHandler is used in add/addAll to wait for each individual fetch()
  to complete.
* ResponseBodyLoader is used in add/addAll/put to take a Response and
  load its body as a blob.
* BarrierCallbackForPutResponse waits for a list of Response and blob
  objects to become available before calling PutImpl().
* BarrierCallbackForPutComplete waits for any code cache generation and
  the final cache operation to complete.

Bug: 1035448
Change-Id: I8c9b6b5831cfd21bd266e526a138de4cbf03171f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2404267
Commit-Queue: Ben Kelly <wanderview@chromium.org>
Reviewed-by: default avatarMarijn Kruisselbrink <mek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811379}
parent 7da4aa18
......@@ -2967,6 +2967,7 @@ class ServiceWorkerThrottlingTest : public ServiceWorkerBrowserTest {
"Connection: close\r\n"
"Content-Length: 32\r\n"
"Content-Type: text/html\r\n"
"Cache-Control: no-store\r\n"
"\r\n"
"<title>ERROR</title>Hello world.";
std::move(send_).Run(kPageResponse, std::move(done_));
......@@ -3042,4 +3043,43 @@ IN_PROC_BROWSER_TEST_F(ServiceWorkerThrottlingTest, ThrottleInstalling) {
observer->Wait();
}
IN_PROC_BROWSER_TEST_F(ServiceWorkerThrottlingTest,
ThrottleInstallingWithCacheAddAll) {
// Register a service worker that loads 3 resources in its install
// handler via cache.addAll(). The test server will cause these loads
// to block which should trigger throttling on the third request.
RegisterServiceWorkerAndWaitForState(
"/service_worker/throttling_blocking_cache_addall_sw.js",
"/service_worker/throttling_blocking_cache_addall",
ServiceWorkerVersion::INSTALLING);
// Register a second service worker that also loads 3 resources in
// its install handler using cache.addAll(). The test server will not
// block these loads and the worker should progress to the activated state.
//
// This second service worker is used to wait for the first worker
// to potentially request its resources. By the time the second worker
// activates the first worker should have requested its resources and
// triggered throttling. This avoids the need for an arbitrary timeout.
RegisterServiceWorkerAndWaitForState(
"/service_worker/throttling_non_blocking_cache_addall_sw.js",
"/service_worker/throttling_non_blocking_cache_addall",
ServiceWorkerVersion::ACTIVATED);
// If throttling worked correctly then there should only be 2 outstanding
// requests blocked by the test server.
EXPECT_EQ(2, GetBlockingResponseCount());
auto observer = base::MakeRefCounted<WorkerStateObserver>(
wrapper(), ServiceWorkerVersion::ACTIVATED);
observer->Init();
// Stop blocking the resources loaded by the first service worker.
StopBlocking();
// Verify that throttling correctly notes when resources can load and
// the first service worker fully activates.
observer->Wait();
}
} // namespace content
// Copyright 2020 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.
self.addEventListener('install', evt => {
evt.waitUntil(async function() {
const c = await caches.open('foo');
return c.addAll([
'./empty.js?1&block',
'./empty.js?2&block',
'./empty.js?3&block',
]);
}());
});
......@@ -5,9 +5,9 @@
self.addEventListener('install', evt => {
evt.waitUntil(async function() {
return Promise.all([
fetch('./foo/1?block'),
fetch('./foo/2?block'),
fetch('./foo/3?block'),
fetch('./foo/1?block').then(r => r.blob()),
fetch('./foo/2?block').then(r => r.blob()),
fetch('./foo/3?block').then(r => r.blob()),
]);
}());
});
// Copyright 2020 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.
self.addEventListener('install', evt => {
evt.waitUntil(async function() {
const c = await caches.open('bar');
return c.addAll([
'./empty.js?1',
'./empty.js?2',
'./empty.js?3',
]);
}());
});
......@@ -5,9 +5,9 @@
self.addEventListener('install', evt => {
evt.waitUntil(async function() {
return Promise.all([
fetch('./foo/1'),
fetch('./foo/2'),
fetch('./foo/3'),
fetch('./foo/1').then(r => r.blob()),
fetch('./foo/2').then(r => r.blob()),
fetch('./foo/3').then(r => r.blob()),
]);
}());
});
......@@ -22,8 +22,10 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_code_cache.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_request_init.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_response.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fetch/blob_bytes_consumer.h"
#include "third_party/blink/renderer/core/fetch/body_stream_buffer.h"
#include "third_party/blink/renderer/core/fetch/fetch_data_loader.h"
#include "third_party/blink/renderer/core/fetch/request.h"
......@@ -76,6 +78,39 @@ bool HasJavascriptMimeType(const Response* response) {
return MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type);
}
void ValidateRequestForPut(const Request* request,
ExceptionState& exception_state) {
KURL url(NullURL(), request->url());
if (!url.ProtocolIsInHTTPFamily()) {
exception_state.ThrowTypeError("Request scheme '" + url.Protocol() +
"' is unsupported");
return;
}
if (request->method() != http_names::kGET) {
exception_state.ThrowTypeError("Request method '" + request->method() +
"' is unsupported");
return;
}
DCHECK(!request->HasBody());
}
void ValidateResponseForPut(const Response* response,
ExceptionState& exception_state) {
if (VaryHeaderContainsAsterisk(response)) {
exception_state.ThrowTypeError("Vary header contains *");
return;
}
if (response->GetResponse()->InternalStatus() == 206) {
exception_state.ThrowTypeError(
"Partial response (status code 206) is unsupported");
return;
}
if (response->IsBodyLocked() || response->IsBodyUsed()) {
exception_state.ThrowTypeError("Response body is already used");
return;
}
}
enum class CodeCachePolicy {
// Use the default policy. Currently that policy generates full code cache
// when a script is stored during service worker install.
......@@ -138,105 +173,200 @@ bool ShouldGenerateV8CodeCache(ScriptState* script_state,
} // 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 {
// Waits for all expected Responses and their blob bodies to be available.
class Cache::BarrierCallbackForPutResponse final
: public GarbageCollected<BarrierCallbackForPutResponse> {
public:
// |exception_state| is passed so that the context_type, interface_name and
// property_name can be copied and then used to construct a new ExceptionState
// object asynchronously later.
static v8::Local<v8::Function> Create(
ScriptState* script_state,
Cache* cache,
const String& method_name,
const HeapVector<Member<Request>>& requests,
const ExceptionState& exception_state,
int64_t trace_id) {
FetchResolvedForAdd* self = MakeGarbageCollected<FetchResolvedForAdd>(
script_state, cache, method_name, requests, exception_state, trace_id);
return self->BindToV8Function();
}
FetchResolvedForAdd(ScriptState* script_state,
Cache* cache,
const String& method_name,
const HeapVector<Member<Request>>& requests,
const ExceptionState& exception_state,
int64_t trace_id)
: ScriptFunction(script_state),
BarrierCallbackForPutResponse(ScriptState* script_state,
Cache* cache,
const String& method_name,
const HeapVector<Member<Request>>& request_list,
const ExceptionState& exception_state,
int64_t trace_id)
: resolver_(MakeGarbageCollected<ScriptPromiseResolver>(script_state)),
cache_(cache),
method_name_(method_name),
requests_(requests),
request_list_(request_list),
context_type_(exception_state.Context()),
property_name_(exception_state.PropertyName()),
interface_name_(exception_state.InterfaceName()),
trace_id_(trace_id),
response_list_(request_list_.size()),
blob_list_(request_list_.size()) {}
// Must be called prior to starting the load of any response.
ScriptPromise Promise() const { return resolver_->Promise(); }
void CompletedResponse(int index,
Response* response,
scoped_refptr<BlobDataHandle> blob) {
DCHECK(!response_list_[index]);
DCHECK(!blob_list_[index]);
DCHECK_LT(num_complete_, request_list_.size());
if (stopped_)
return;
response_list_[index] = response;
blob_list_[index] = std::move(blob);
num_complete_ += 1;
if (num_complete_ == request_list_.size()) {
ScriptState* script_state = resolver_->GetScriptState();
ExceptionState exception_state(script_state->GetIsolate(), context_type_,
property_name_, interface_name_);
cache_->PutImpl(resolver_, method_name_, request_list_, response_list_,
blob_list_, exception_state, trace_id_);
}
}
void FailedResponse() {
ScriptState* state = resolver_->GetScriptState();
ScriptState::Scope scope(state);
resolver_->Reject(V8ThrowDOMException::CreateOrEmpty(
state->GetIsolate(), DOMExceptionCode::kNetworkError,
method_name_ + " encountered a network error"));
Stop();
}
void AbortedResponse() {
ScriptState* state = resolver_->GetScriptState();
ScriptState::Scope scope(state);
resolver_->Reject(V8ThrowDOMException::CreateOrEmpty(
state->GetIsolate(), DOMExceptionCode::kAbortError,
method_name_ + " was aborted"));
Stop();
}
void OnError(ScriptValue value) {
resolver_->Reject(value);
Stop();
}
void OnError(ExceptionState& exception_state) {
resolver_->Reject(exception_state);
Stop();
}
void Trace(Visitor* visitor) const {
visitor->Trace(resolver_);
visitor->Trace(cache_);
visitor->Trace(request_list_);
visitor->Trace(response_list_);
}
private:
void Stop() {
// TODO(crbug.com/1130781): abort outstanding requests
stopped_ = true;
}
Member<ScriptPromiseResolver> resolver_;
Member<Cache> cache_;
const String method_name_;
const HeapVector<Member<Request>> request_list_;
ExceptionState::ContextType context_type_;
const char* property_name_;
const char* interface_name_;
const int64_t trace_id_;
HeapVector<Member<Response>> response_list_;
WTF::Vector<scoped_refptr<BlobDataHandle>> blob_list_;
size_t num_complete_ = 0;
bool stopped_ = false;
};
// Waits for a single Response and then loads its body as a blob. This class
// also performs validation on the Response and triggers a failure if
// necessary. Passing true for |require_response_ok| will also trigger a
// failure if the Response status code is not ok. This is necessary for the
// add/addAll case, but is not used in the put case.
class Cache::ResponseBodyLoader final
: public GarbageCollected<Cache::ResponseBodyLoader>,
public FetchDataLoader::Client {
public:
ResponseBodyLoader(ScriptState* script_state,
BarrierCallbackForPutResponse* barrier_callback,
int index,
bool require_ok_response,
int64_t trace_id)
: script_state_(script_state),
barrier_callback_(barrier_callback),
index_(index),
require_ok_response_(require_ok_response),
trace_id_(trace_id) {}
ScriptValue Call(ScriptValue value) override {
void OnResponse(Response* response, ExceptionState& exception_state) {
TRACE_EVENT_WITH_FLOW0(
"CacheStorage", "Cache::FetchResolverForAdd::Call",
"CacheStorage", "Cache::ResponseBodyLoader::OnResponse",
TRACE_ID_GLOBAL(trace_id_),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
ExceptionState exception_state(GetScriptState()->GetIsolate(),
context_type_, property_name_,
interface_name_);
HeapVector<Member<Response>> responses =
NativeValueTraits<IDLSequence<Response>>::NativeValue(
GetScriptState()->GetIsolate(), value.V8Value(), exception_state);
if (require_ok_response_ && !response->ok()) {
exception_state.ThrowTypeError("Request failed");
barrier_callback_->OnError(exception_state);
return;
}
ValidateResponseForPut(response, exception_state);
if (exception_state.HadException()) {
ScriptPromise rejection =
ScriptPromise::Reject(GetScriptState(), exception_state);
return ScriptValue(GetScriptState()->GetIsolate(), rejection.V8Value());
barrier_callback_->OnError(exception_state);
return;
}
for (const auto& response : responses) {
if (!response->ok()) {
ScriptPromise rejection = ScriptPromise::Reject(
GetScriptState(),
V8ThrowException::CreateTypeError(GetScriptState()->GetIsolate(),
"Request failed"));
return ScriptValue(GetScriptState()->GetIsolate(), rejection.V8Value());
}
if (VaryHeaderContainsAsterisk(response)) {
ScriptPromise rejection = ScriptPromise::Reject(
GetScriptState(),
V8ThrowException::CreateTypeError(GetScriptState()->GetIsolate(),
"Vary header contains *"));
return ScriptValue(GetScriptState()->GetIsolate(), rejection.V8Value());
}
BodyStreamBuffer* buffer = response->InternalBodyBuffer();
if (!buffer) {
barrier_callback_->CompletedResponse(index_, response, nullptr);
return;
}
ScriptPromise put_promise =
cache_->PutImpl(GetScriptState(), method_name_, requests_, responses,
exception_state, trace_id_);
return ScriptValue(GetScriptState()->GetIsolate(), put_promise.V8Value());
response_ = response;
ExecutionContext* context = ExecutionContext::From(script_state_);
fetch_loader_ = FetchDataLoader::CreateLoaderAsBlobHandle(
response_->InternalMIMEType(),
context->GetTaskRunner(TaskType::kNetworking));
buffer->StartLoading(fetch_loader_, this, exception_state);
}
void Trace(Visitor* visitor) const override {
visitor->Trace(cache_);
visitor->Trace(requests_);
ScriptFunction::Trace(visitor);
visitor->Trace(script_state_);
visitor->Trace(barrier_callback_);
visitor->Trace(response_);
visitor->Trace(fetch_loader_);
FetchDataLoader::Client::Trace(visitor);
}
private:
Member<Cache> cache_;
const String method_name_;
HeapVector<Member<Request>> requests_;
ExceptionState::ContextType context_type_;
const char* property_name_;
const char* interface_name_;
void DidFetchDataLoadedBlobHandle(
scoped_refptr<BlobDataHandle> handle) override {
barrier_callback_->CompletedResponse(index_, response_, std::move(handle));
}
void DidFetchDataLoadFailed() override {
barrier_callback_->FailedResponse();
}
void Abort() override { barrier_callback_->AbortedResponse(); }
Member<ScriptState> script_state_;
Member<BarrierCallbackForPutResponse> barrier_callback_;
const int index_;
const bool require_ok_response_;
const int64_t trace_id_;
Member<Response> response_;
Member<FetchDataLoader> fetch_loader_;
};
class Cache::BarrierCallbackForPut final
: public GarbageCollected<BarrierCallbackForPut> {
// Waits for code cache to be generated and writing to cache_storage to
// complete.
class Cache::BarrierCallbackForPutComplete final
: public GarbageCollected<BarrierCallbackForPutComplete> {
public:
BarrierCallbackForPut(wtf_size_t number_of_operations,
Cache* cache,
const String& method_name,
ScriptPromiseResolver* resolver,
int64_t trace_id)
BarrierCallbackForPutComplete(wtf_size_t number_of_operations,
Cache* cache,
const String& method_name,
ScriptPromiseResolver* resolver,
int64_t trace_id)
: number_of_remaining_operations_(number_of_operations),
cache_(cache),
method_name_(method_name),
......@@ -250,7 +380,7 @@ class Cache::BarrierCallbackForPut final
mojom::blink::BatchOperationPtr batch_operation) {
DCHECK_LT(index, batch_operations_.size());
TRACE_EVENT_WITH_FLOW1(
"CacheStorage", "Cache::BarrierCallbackForPut::OnSuccess",
"CacheStorage", "Cache::BarrierCallbackForPutComplete::OnSuccess",
TRACE_ID_GLOBAL(trace_id_),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "batch_operation",
CacheStorageTracedValue(batch_operation));
......@@ -275,7 +405,7 @@ class Cache::BarrierCallbackForPut final
base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
TRACE_EVENT_WITH_FLOW1(
"CacheStorage",
"Cache::BarrierCallbackForPut::OnSuccess::Callback",
"Cache::BarrierCallbackForPutComplete::OnSuccess::Callback",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status",
CacheStorageTracedValue(error->value));
if (operation_count > 1) {
......@@ -307,6 +437,13 @@ class Cache::BarrierCallbackForPut final
WrapPersistent(cache_.Get())));
}
void OnError(ExceptionState& exception_state) {
if (!StillActive())
return;
completed_ = true;
resolver_->Reject(exception_state);
}
void OnError(const String& error_message) {
if (!StillActive())
return;
......@@ -321,9 +458,11 @@ class Cache::BarrierCallbackForPut final
if (!StillActive())
return;
completed_ = true;
ScriptState::Scope scope(resolver_->GetScriptState());
resolver_->Reject(
MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError));
ScriptState* state = resolver_->GetScriptState();
ScriptState::Scope scope(state);
resolver_->Reject(V8ThrowDOMException::CreateOrEmpty(
state->GetIsolate(), DOMExceptionCode::kAbortError,
method_name_ + " was aborted"));
}
virtual void Trace(Visitor* visitor) const {
......@@ -379,48 +518,86 @@ class Cache::BarrierCallbackForPut final
const int64_t trace_id_;
};
class Cache::BlobHandleCallbackForPut final
: public GarbageCollected<BlobHandleCallbackForPut>,
public FetchDataLoader::Client {
// Used to handle the ScopedFetcher::Fetch promise in AddAllImpl.
// 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::FetchHandler final : public ScriptFunction {
public:
BlobHandleCallbackForPut(wtf_size_t index,
BarrierCallbackForPut* barrier_callback,
Request* request,
Response* response)
: index_(index), barrier_callback_(barrier_callback) {
fetch_api_request_ = request->CreateFetchAPIRequest();
fetch_api_response_ = response->PopulateFetchAPIResponse(request->url());
// |exception_state| is passed so that the context_type, interface_name and
// property_name can be copied and then used to construct a new ExceptionState
// object asynchronously later.
static v8::Local<v8::Function> CreateForResolve(
ScriptState* script_state,
ResponseBodyLoader* response_loader,
BarrierCallbackForPutResponse* barrier_callback,
const ExceptionState& exception_state) {
FetchHandler* self = MakeGarbageCollected<FetchHandler>(
script_state, response_loader, barrier_callback, exception_state);
return self->BindToV8Function();
}
~BlobHandleCallbackForPut() override = default;
void DidFetchDataLoadedBlobHandle(
scoped_refptr<BlobDataHandle> handle) override {
mojom::blink::BatchOperationPtr batch_operation =
mojom::blink::BatchOperation::New();
batch_operation->operation_type = mojom::blink::OperationType::kPut;
batch_operation->request = std::move(fetch_api_request_);
batch_operation->response = std::move(fetch_api_response_);
batch_operation->response->blob = handle;
barrier_callback_->OnSuccess(index_, std::move(batch_operation));
static v8::Local<v8::Function> CreateForReject(
ScriptState* script_state,
BarrierCallbackForPutResponse* barrier_callback,
const ExceptionState& exception_state) {
FetchHandler* self = MakeGarbageCollected<FetchHandler>(
script_state, /*response_loader=*/nullptr, barrier_callback,
exception_state);
return self->BindToV8Function();
}
void DidFetchDataLoadFailed() override {
barrier_callback_->OnError("network error");
}
FetchHandler(ScriptState* script_state,
ResponseBodyLoader* response_loader,
BarrierCallbackForPutResponse* barrier_callback,
const ExceptionState& exception_state)
: ScriptFunction(script_state),
response_loader_(response_loader),
barrier_callback_(barrier_callback),
context_type_(exception_state.Context()),
property_name_(exception_state.PropertyName()),
interface_name_(exception_state.InterfaceName()) {}
void Abort() override { barrier_callback_->Abort(); }
ScriptValue Call(ScriptValue value) override {
// We always resolve undefined from this promise handler since the
// promise is never returned to script or chained to another handler.
// If we return our real result and an exception occurs then unhandled
// promise errors will occur.
ScriptValue rtn =
ScriptPromise::CastUndefined(GetScriptState()).GetScriptValue();
// If there is no loader, we were created as a reject handler.
if (!response_loader_) {
barrier_callback_->OnError(value);
return rtn;
}
ExceptionState exception_state(GetScriptState()->GetIsolate(),
context_type_, property_name_,
interface_name_);
// Resolve handler, so try to process a Response.
Response* response = NativeValueTraits<Response>::NativeValue(
GetScriptState()->GetIsolate(), value.V8Value(), exception_state);
if (exception_state.HadException())
barrier_callback_->OnError(exception_state);
else
response_loader_->OnResponse(response, exception_state);
return rtn;
}
void Trace(Visitor* visitor) const override {
visitor->Trace(response_loader_);
visitor->Trace(barrier_callback_);
FetchDataLoader::Client::Trace(visitor);
ScriptFunction::Trace(visitor);
}
private:
const wtf_size_t index_;
Member<BarrierCallbackForPut> barrier_callback_;
mojom::blink::FetchAPIRequestPtr fetch_api_request_;
mojom::blink::FetchAPIResponsePtr fetch_api_response_;
Member<ResponseBodyLoader> response_loader_;
Member<BarrierCallbackForPutResponse> barrier_callback_;
ExceptionState::ContextType context_type_;
const char* property_name_;
const char* interface_name_;
};
class Cache::CodeCacheHandleCallbackForPut final
......@@ -429,14 +606,16 @@ class Cache::CodeCacheHandleCallbackForPut final
public:
CodeCacheHandleCallbackForPut(ScriptState* script_state,
wtf_size_t index,
BarrierCallbackForPut* barrier_callback,
BarrierCallbackForPutComplete* barrier_callback,
Request* request,
Response* response,
scoped_refptr<BlobDataHandle> blob_handle,
int64_t trace_id)
: script_state_(script_state),
index_(index),
barrier_callback_(barrier_callback),
mime_type_(response->InternalMIMEType()),
blob_handle_(std::move(blob_handle)),
trace_id_(trace_id) {
fetch_api_request_ = request->CreateFetchAPIRequest();
fetch_api_response_ = response->PopulateFetchAPIResponse(request->url());
......@@ -460,13 +639,7 @@ class Cache::CodeCacheHandleCallbackForPut final
batch_operation->operation_type = mojom::blink::OperationType::kPut;
batch_operation->request = std::move(fetch_api_request_);
batch_operation->response = std::move(fetch_api_response_);
auto blob_data = std::make_unique<BlobData>();
blob_data->SetContentType(mime_type_);
blob_data->AppendBytes(array_buffer->Data(),
array_buffer->ByteLengthAsSizeT());
batch_operation->response->blob = BlobDataHandle::Create(
std::move(blob_data), array_buffer->ByteLengthAsSizeT());
batch_operation->response->blob = std::move(blob_handle_);
scoped_refptr<CachedMetadata> cached_metadata =
GenerateFullCodeCache(array_buffer);
......@@ -532,8 +705,9 @@ class Cache::CodeCacheHandleCallbackForPut final
const Member<ScriptState> script_state_;
const wtf_size_t index_;
Member<BarrierCallbackForPut> barrier_callback_;
Member<BarrierCallbackForPutComplete> barrier_callback_;
const String mime_type_;
scoped_refptr<BlobDataHandle> blob_handle_;
KURL url_;
V8CodeCache::OpaqueMode opaque_mode_;
const int64_t trace_id_;
......@@ -625,26 +799,38 @@ ScriptPromise Cache::Delete(ScriptState* script_state,
}
ScriptPromise Cache::put(ScriptState* script_state,
const RequestInfo& request,
const RequestInfo& request_info,
Response* response,
ExceptionState& exception_state) {
DCHECK(!request.IsNull());
DCHECK(!request_info.IsNull());
int64_t trace_id = blink::cache_storage::CreateTraceId();
TRACE_EVENT_WITH_FLOW0("CacheStorage", "Cache::put",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT);
if (request.IsRequest()) {
return PutImpl(script_state, "Cache.put()",
HeapVector<Member<Request>>(1, request.GetAsRequest()),
HeapVector<Member<Response>>(1, response), exception_state,
trace_id);
}
Request* new_request =
Request::Create(script_state, request.GetAsUSVString(), exception_state);
Request* request =
request_info.IsRequest()
? request_info.GetAsRequest()
: Request::Create(script_state, request_info.GetAsUSVString(),
exception_state);
if (exception_state.HadException())
return ScriptPromise();
return PutImpl(
script_state, "Cache.put()", HeapVector<Member<Request>>(1, new_request),
HeapVector<Member<Response>>(1, response), exception_state, trace_id);
ValidateRequestForPut(request, exception_state);
if (exception_state.HadException())
return ScriptPromise();
auto* barrier_callback = MakeGarbageCollected<BarrierCallbackForPutResponse>(
script_state, this, "Cache.put()",
HeapVector<Member<Request>>(1, request), exception_state, trace_id);
// We must get the promise before any rejections can happen during loading.
ScriptPromise promise = barrier_callback->Promise();
auto* loader = MakeGarbageCollected<ResponseBodyLoader>(
script_state, barrier_callback, /*index=*/0,
/*require_ok_response=*/false, trace_id);
loader->OnResponse(response, exception_state);
return promise;
}
ScriptPromise Cache::keys(ScriptState* script_state, ExceptionState&) {
......@@ -843,40 +1029,44 @@ ScriptPromise Cache::MatchAllImpl(ScriptState* script_state,
ScriptPromise Cache::AddAllImpl(ScriptState* script_state,
const String& method_name,
const HeapVector<Member<Request>>& requests,
const HeapVector<Member<Request>>& request_list,
ExceptionState& exception_state) {
int64_t trace_id = blink::cache_storage::CreateTraceId();
TRACE_EVENT_WITH_FLOW0("CacheStorage", "Cache::AddAllImpl",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT);
if (requests.IsEmpty())
if (request_list.IsEmpty())
return ScriptPromise::CastUndefined(script_state);
HeapVector<RequestInfo> request_infos;
request_infos.resize(requests.size());
HeapVector<ScriptPromise> promises;
promises.resize(requests.size());
for (wtf_size_t i = 0; i < requests.size(); ++i) {
if (!requests[i]->url().ProtocolIsInHTTPFamily()) {
exception_state.ThrowTypeError(
"Add/AddAll does not support schemes "
"other than \"http\" or \"https\"");
return ScriptPromise();
}
if (requests[i]->method() != http_names::kGET) {
exception_state.ThrowTypeError(
"Add/AddAll only supports the GET request method.");
// Validate all requests before starting to load or store any of them.
for (wtf_size_t i = 0; i < request_list.size(); ++i) {
ValidateRequestForPut(request_list[i], exception_state);
if (exception_state.HadException())
return ScriptPromise();
}
request_infos[i].SetRequest(requests[i]);
}
promises[i] = scoped_fetcher_->Fetch(
script_state, request_infos[i], RequestInit::Create(), exception_state);
auto* barrier_callback = MakeGarbageCollected<BarrierCallbackForPutResponse>(
script_state, this, method_name, request_list, exception_state, trace_id);
// We must get the promise before any rejections can happen during loading.
ScriptPromise promise = barrier_callback->Promise();
// Begin loading each of the requests.
for (wtf_size_t i = 0; i < request_list.size(); ++i) {
RequestInfo info;
info.SetRequest(request_list[i]);
auto* response_loader = MakeGarbageCollected<ResponseBodyLoader>(
script_state, barrier_callback, i, /*require_ok_response=*/true,
trace_id);
scoped_fetcher_
->Fetch(script_state, info, RequestInit::Create(), exception_state)
.Then(FetchHandler::CreateForResolve(script_state, response_loader,
barrier_callback, exception_state),
FetchHandler::CreateForReject(script_state, barrier_callback,
exception_state));
}
return ScriptPromise::All(script_state, promises)
.Then(FetchResolvedForAdd::Create(script_state, this, method_name,
requests, exception_state, trace_id));
return promise;
}
ScriptPromise Cache::DeleteImpl(ScriptState* script_state,
......@@ -947,93 +1137,57 @@ ScriptPromise Cache::DeleteImpl(ScriptState* script_state,
return promise;
}
ScriptPromise Cache::PutImpl(ScriptState* script_state,
const String& method_name,
const HeapVector<Member<Request>>& requests,
const HeapVector<Member<Response>>& responses,
ExceptionState& exception_state,
int64_t trace_id) {
void Cache::PutImpl(ScriptPromiseResolver* resolver,
const String& method_name,
const HeapVector<Member<Request>>& requests,
const HeapVector<Member<Response>>& responses,
const WTF::Vector<scoped_refptr<BlobDataHandle>>& blob_list,
ExceptionState& exception_state,
int64_t trace_id) {
DCHECK_EQ(requests.size(), responses.size());
DCHECK_EQ(requests.size(), blob_list.size());
TRACE_EVENT_WITH_FLOW0("CacheStorage", "Cache::PutImpl",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
const ScriptPromise promise = resolver->Promise();
BarrierCallbackForPut* barrier_callback =
MakeGarbageCollected<BarrierCallbackForPut>(
requests.size(), this, method_name, resolver, trace_id);
for (wtf_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() != http_names::kGET) {
barrier_callback->OnError("Request method '" + requests[i]->method() +
"' is unsupported");
return promise;
}
DCHECK(!requests[i]->HasBody());
ScriptState* script_state = resolver->GetScriptState();
ScriptState::Scope scope(script_state);
ExecutionContext* context = ExecutionContext::From(script_state);
if (VaryHeaderContainsAsterisk(responses[i])) {
barrier_callback->OnError("Vary header contains *");
return promise;
}
if (responses[i]->GetResponse()->InternalStatus() == 206) {
barrier_callback->OnError(
"Partial response (status code 206) is unsupported");
return promise;
}
if (responses[i]->IsBodyLocked() || responses[i]->IsBodyUsed()) {
barrier_callback->OnError("Response body is already used");
return promise;
}
BarrierCallbackForPutComplete* barrier_callback =
MakeGarbageCollected<BarrierCallbackForPutComplete>(
requests.size(), this, method_name, resolver, trace_id);
BodyStreamBuffer* buffer = responses[i]->InternalBodyBuffer();
if (ShouldGenerateV8CodeCache(script_state, responses[i])) {
FetchDataLoader* loader = FetchDataLoader::CreateLoaderAsArrayBuffer();
buffer->StartLoading(loader,
MakeGarbageCollected<CodeCacheHandleCallbackForPut>(
script_state, i, barrier_callback, requests[i],
responses[i], trace_id),
exception_state);
if (exception_state.HadException()) {
barrier_callback->OnError("Could not inspect response body state");
return promise;
}
for (wtf_size_t i = 0; i < requests.size(); ++i) {
if (!blob_list[i] ||
!ShouldGenerateV8CodeCache(script_state, responses[i])) {
mojom::blink::BatchOperationPtr batch_operation =
mojom::blink::BatchOperation::New();
batch_operation->operation_type = mojom::blink::OperationType::kPut;
batch_operation->request = requests[i]->CreateFetchAPIRequest();
batch_operation->response =
responses[i]->PopulateFetchAPIResponse(requests[i]->url());
batch_operation->response->blob = std::move(blob_list[i]);
barrier_callback->OnSuccess(i, std::move(batch_operation));
continue;
}
if (buffer) {
ExecutionContext* context = ExecutionContext::From(script_state);
// 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(),
context->GetTaskRunner(TaskType::kNetworking));
buffer->StartLoading(loader,
MakeGarbageCollected<BlobHandleCallbackForPut>(
i, barrier_callback, requests[i], responses[i]),
exception_state);
if (exception_state.HadException()) {
barrier_callback->OnError("Could not inspect response body state");
return promise;
}
continue;
BytesConsumer* consumer =
MakeGarbageCollected<BlobBytesConsumer>(context, blob_list[i]);
BodyStreamBuffer* buffer =
BodyStreamBuffer::Create(script_state, consumer, /*signal=*/nullptr);
FetchDataLoader* loader = FetchDataLoader::CreateLoaderAsArrayBuffer();
buffer->StartLoading(loader,
MakeGarbageCollected<CodeCacheHandleCallbackForPut>(
script_state, i, barrier_callback, requests[i],
responses[i], std::move(blob_list[i]), trace_id),
exception_state);
if (exception_state.HadException()) {
barrier_callback->OnError("Could not inspect response body state");
return;
}
mojom::blink::BatchOperationPtr batch_operation =
mojom::blink::BatchOperation::New();
batch_operation->operation_type = mojom::blink::OperationType::kPut;
batch_operation->request = requests[i]->CreateFetchAPIRequest();
batch_operation->response =
responses[i]->PopulateFetchAPIResponse(requests[i]->url());
barrier_callback->OnSuccess(i, std::move(batch_operation));
}
return promise;
}
ScriptPromise Cache::KeysImpl(ScriptState* script_state,
......
......@@ -44,6 +44,7 @@ class CacheStorageBlobClientList;
class ExceptionState;
class Response;
class Request;
class ScriptPromiseResolver;
class ScriptState;
typedef RequestOrUSVString RequestInfo;
......@@ -87,11 +88,11 @@ class MODULES_EXPORT Cache final : public ScriptWrappable {
void Trace(Visitor*) const override;
private:
class BarrierCallbackForPut;
class BlobHandleCallbackForPut;
class BarrierCallbackForPutResponse;
class BarrierCallbackForPutComplete;
class CodeCacheHandleCallbackForPut;
class FetchResolvedForAdd;
friend class FetchResolvedForAdd;
class ResponseBodyLoader;
class FetchHandler;
ScriptPromise MatchImpl(ScriptState*,
const Request*,
......@@ -106,12 +107,13 @@ class MODULES_EXPORT Cache final : public ScriptWrappable {
ScriptPromise DeleteImpl(ScriptState*,
const Request*,
const CacheQueryOptions*);
ScriptPromise PutImpl(ScriptState*,
const String& method_name,
const HeapVector<Member<Request>>&,
const HeapVector<Member<Response>>&,
ExceptionState&,
int64_t trace_id);
void PutImpl(ScriptPromiseResolver*,
const String& method_name,
const HeapVector<Member<Request>>&,
const HeapVector<Member<Response>>&,
const WTF::Vector<scoped_refptr<BlobDataHandle>>& blob_list,
ExceptionState&,
int64_t trace_id);
ScriptPromise KeysImpl(ScriptState*,
const Request*,
const CacheQueryOptions*);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment