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 { ...@@ -2967,6 +2967,7 @@ class ServiceWorkerThrottlingTest : public ServiceWorkerBrowserTest {
"Connection: close\r\n" "Connection: close\r\n"
"Content-Length: 32\r\n" "Content-Length: 32\r\n"
"Content-Type: text/html\r\n" "Content-Type: text/html\r\n"
"Cache-Control: no-store\r\n"
"\r\n" "\r\n"
"<title>ERROR</title>Hello world."; "<title>ERROR</title>Hello world.";
std::move(send_).Run(kPageResponse, std::move(done_)); std::move(send_).Run(kPageResponse, std::move(done_));
...@@ -3042,4 +3043,43 @@ IN_PROC_BROWSER_TEST_F(ServiceWorkerThrottlingTest, ThrottleInstalling) { ...@@ -3042,4 +3043,43 @@ IN_PROC_BROWSER_TEST_F(ServiceWorkerThrottlingTest, ThrottleInstalling) {
observer->Wait(); 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 } // 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 @@ ...@@ -5,9 +5,9 @@
self.addEventListener('install', evt => { self.addEventListener('install', evt => {
evt.waitUntil(async function() { evt.waitUntil(async function() {
return Promise.all([ return Promise.all([
fetch('./foo/1?block'), fetch('./foo/1?block').then(r => r.blob()),
fetch('./foo/2?block'), fetch('./foo/2?block').then(r => r.blob()),
fetch('./foo/3?block'), 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 @@ ...@@ -5,9 +5,9 @@
self.addEventListener('install', evt => { self.addEventListener('install', evt => {
evt.waitUntil(async function() { evt.waitUntil(async function() {
return Promise.all([ return Promise.all([
fetch('./foo/1'), fetch('./foo/1').then(r => r.blob()),
fetch('./foo/2'), fetch('./foo/2').then(r => r.blob()),
fetch('./foo/3'), fetch('./foo/3').then(r => r.blob()),
]); ]);
}()); }());
}); });
...@@ -22,8 +22,10 @@ ...@@ -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_code_cache.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_request_init.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_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/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.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/body_stream_buffer.h"
#include "third_party/blink/renderer/core/fetch/fetch_data_loader.h" #include "third_party/blink/renderer/core/fetch/fetch_data_loader.h"
#include "third_party/blink/renderer/core/fetch/request.h" #include "third_party/blink/renderer/core/fetch/request.h"
...@@ -76,6 +78,39 @@ bool HasJavascriptMimeType(const Response* response) { ...@@ -76,6 +78,39 @@ bool HasJavascriptMimeType(const Response* response) {
return MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type); 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 { enum class CodeCachePolicy {
// Use the default policy. Currently that policy generates full code cache // Use the default policy. Currently that policy generates full code cache
// when a script is stored during service worker install. // when a script is stored during service worker install.
...@@ -138,105 +173,200 @@ bool ShouldGenerateV8CodeCache(ScriptState* script_state, ...@@ -138,105 +173,200 @@ bool ShouldGenerateV8CodeCache(ScriptState* script_state,
} // namespace } // namespace
// TODO(nhiroki): Unfortunately, we have to go through V8 to wait for the fetch // Waits for all expected Responses and their blob bodies to be available.
// promise. It should be better to achieve this only within C++ world. class Cache::BarrierCallbackForPutResponse final
class Cache::FetchResolvedForAdd final : public ScriptFunction { : public GarbageCollected<BarrierCallbackForPutResponse> {
public: public:
// |exception_state| is passed so that the context_type, interface_name and BarrierCallbackForPutResponse(ScriptState* script_state,
// property_name can be copied and then used to construct a new ExceptionState Cache* cache,
// object asynchronously later. const String& method_name,
static v8::Local<v8::Function> Create( const HeapVector<Member<Request>>& request_list,
ScriptState* script_state, const ExceptionState& exception_state,
Cache* cache, int64_t trace_id)
const String& method_name, : resolver_(MakeGarbageCollected<ScriptPromiseResolver>(script_state)),
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),
cache_(cache), cache_(cache),
method_name_(method_name), method_name_(method_name),
requests_(requests), request_list_(request_list),
context_type_(exception_state.Context()), context_type_(exception_state.Context()),
property_name_(exception_state.PropertyName()), property_name_(exception_state.PropertyName()),
interface_name_(exception_state.InterfaceName()), 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) {} trace_id_(trace_id) {}
ScriptValue Call(ScriptValue value) override { void OnResponse(Response* response, ExceptionState& exception_state) {
TRACE_EVENT_WITH_FLOW0( TRACE_EVENT_WITH_FLOW0(
"CacheStorage", "Cache::FetchResolverForAdd::Call", "CacheStorage", "Cache::ResponseBodyLoader::OnResponse",
TRACE_ID_GLOBAL(trace_id_), TRACE_ID_GLOBAL(trace_id_),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
ExceptionState exception_state(GetScriptState()->GetIsolate(), if (require_ok_response_ && !response->ok()) {
context_type_, property_name_, exception_state.ThrowTypeError("Request failed");
interface_name_); barrier_callback_->OnError(exception_state);
HeapVector<Member<Response>> responses = return;
NativeValueTraits<IDLSequence<Response>>::NativeValue( }
GetScriptState()->GetIsolate(), value.V8Value(), exception_state);
ValidateResponseForPut(response, exception_state);
if (exception_state.HadException()) { if (exception_state.HadException()) {
ScriptPromise rejection = barrier_callback_->OnError(exception_state);
ScriptPromise::Reject(GetScriptState(), exception_state); return;
return ScriptValue(GetScriptState()->GetIsolate(), rejection.V8Value());
} }
for (const auto& response : responses) { BodyStreamBuffer* buffer = response->InternalBodyBuffer();
if (!response->ok()) { if (!buffer) {
ScriptPromise rejection = ScriptPromise::Reject( barrier_callback_->CompletedResponse(index_, response, nullptr);
GetScriptState(), return;
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());
}
} }
ScriptPromise put_promise = response_ = response;
cache_->PutImpl(GetScriptState(), method_name_, requests_, responses,
exception_state, trace_id_); ExecutionContext* context = ExecutionContext::From(script_state_);
return ScriptValue(GetScriptState()->GetIsolate(), put_promise.V8Value()); fetch_loader_ = FetchDataLoader::CreateLoaderAsBlobHandle(
response_->InternalMIMEType(),
context->GetTaskRunner(TaskType::kNetworking));
buffer->StartLoading(fetch_loader_, this, exception_state);
} }
void Trace(Visitor* visitor) const override { void Trace(Visitor* visitor) const override {
visitor->Trace(cache_); visitor->Trace(script_state_);
visitor->Trace(requests_); visitor->Trace(barrier_callback_);
ScriptFunction::Trace(visitor); visitor->Trace(response_);
visitor->Trace(fetch_loader_);
FetchDataLoader::Client::Trace(visitor);
} }
private: private:
Member<Cache> cache_; void DidFetchDataLoadedBlobHandle(
const String method_name_; scoped_refptr<BlobDataHandle> handle) override {
HeapVector<Member<Request>> requests_; barrier_callback_->CompletedResponse(index_, response_, std::move(handle));
ExceptionState::ContextType context_type_; }
const char* property_name_;
const char* interface_name_; 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_; const int64_t trace_id_;
Member<Response> response_;
Member<FetchDataLoader> fetch_loader_;
}; };
class Cache::BarrierCallbackForPut final // Waits for code cache to be generated and writing to cache_storage to
: public GarbageCollected<BarrierCallbackForPut> { // complete.
class Cache::BarrierCallbackForPutComplete final
: public GarbageCollected<BarrierCallbackForPutComplete> {
public: public:
BarrierCallbackForPut(wtf_size_t number_of_operations, BarrierCallbackForPutComplete(wtf_size_t number_of_operations,
Cache* cache, Cache* cache,
const String& method_name, const String& method_name,
ScriptPromiseResolver* resolver, ScriptPromiseResolver* resolver,
int64_t trace_id) int64_t trace_id)
: number_of_remaining_operations_(number_of_operations), : number_of_remaining_operations_(number_of_operations),
cache_(cache), cache_(cache),
method_name_(method_name), method_name_(method_name),
...@@ -250,7 +380,7 @@ class Cache::BarrierCallbackForPut final ...@@ -250,7 +380,7 @@ class Cache::BarrierCallbackForPut final
mojom::blink::BatchOperationPtr batch_operation) { mojom::blink::BatchOperationPtr batch_operation) {
DCHECK_LT(index, batch_operations_.size()); DCHECK_LT(index, batch_operations_.size());
TRACE_EVENT_WITH_FLOW1( TRACE_EVENT_WITH_FLOW1(
"CacheStorage", "Cache::BarrierCallbackForPut::OnSuccess", "CacheStorage", "Cache::BarrierCallbackForPutComplete::OnSuccess",
TRACE_ID_GLOBAL(trace_id_), TRACE_ID_GLOBAL(trace_id_),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "batch_operation", TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "batch_operation",
CacheStorageTracedValue(batch_operation)); CacheStorageTracedValue(batch_operation));
...@@ -275,7 +405,7 @@ class Cache::BarrierCallbackForPut final ...@@ -275,7 +405,7 @@ class Cache::BarrierCallbackForPut final
base::TimeDelta elapsed = base::TimeTicks::Now() - start_time; base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
TRACE_EVENT_WITH_FLOW1( TRACE_EVENT_WITH_FLOW1(
"CacheStorage", "CacheStorage",
"Cache::BarrierCallbackForPut::OnSuccess::Callback", "Cache::BarrierCallbackForPutComplete::OnSuccess::Callback",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status",
CacheStorageTracedValue(error->value)); CacheStorageTracedValue(error->value));
if (operation_count > 1) { if (operation_count > 1) {
...@@ -307,6 +437,13 @@ class Cache::BarrierCallbackForPut final ...@@ -307,6 +437,13 @@ class Cache::BarrierCallbackForPut final
WrapPersistent(cache_.Get()))); WrapPersistent(cache_.Get())));
} }
void OnError(ExceptionState& exception_state) {
if (!StillActive())
return;
completed_ = true;
resolver_->Reject(exception_state);
}
void OnError(const String& error_message) { void OnError(const String& error_message) {
if (!StillActive()) if (!StillActive())
return; return;
...@@ -321,9 +458,11 @@ class Cache::BarrierCallbackForPut final ...@@ -321,9 +458,11 @@ class Cache::BarrierCallbackForPut final
if (!StillActive()) if (!StillActive())
return; return;
completed_ = true; completed_ = true;
ScriptState::Scope scope(resolver_->GetScriptState()); ScriptState* state = resolver_->GetScriptState();
resolver_->Reject( ScriptState::Scope scope(state);
MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError)); resolver_->Reject(V8ThrowDOMException::CreateOrEmpty(
state->GetIsolate(), DOMExceptionCode::kAbortError,
method_name_ + " was aborted"));
} }
virtual void Trace(Visitor* visitor) const { virtual void Trace(Visitor* visitor) const {
...@@ -379,48 +518,86 @@ class Cache::BarrierCallbackForPut final ...@@ -379,48 +518,86 @@ class Cache::BarrierCallbackForPut final
const int64_t trace_id_; const int64_t trace_id_;
}; };
class Cache::BlobHandleCallbackForPut final // Used to handle the ScopedFetcher::Fetch promise in AddAllImpl.
: public GarbageCollected<BlobHandleCallbackForPut>, // TODO(nhiroki): Unfortunately, we have to go through V8 to wait for the fetch
public FetchDataLoader::Client { // promise. It should be better to achieve this only within C++ world.
class Cache::FetchHandler final : public ScriptFunction {
public: public:
BlobHandleCallbackForPut(wtf_size_t index, // |exception_state| is passed so that the context_type, interface_name and
BarrierCallbackForPut* barrier_callback, // property_name can be copied and then used to construct a new ExceptionState
Request* request, // object asynchronously later.
Response* response) static v8::Local<v8::Function> CreateForResolve(
: index_(index), barrier_callback_(barrier_callback) { ScriptState* script_state,
fetch_api_request_ = request->CreateFetchAPIRequest(); ResponseBodyLoader* response_loader,
fetch_api_response_ = response->PopulateFetchAPIResponse(request->url()); 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( static v8::Local<v8::Function> CreateForReject(
scoped_refptr<BlobDataHandle> handle) override { ScriptState* script_state,
mojom::blink::BatchOperationPtr batch_operation = BarrierCallbackForPutResponse* barrier_callback,
mojom::blink::BatchOperation::New(); const ExceptionState& exception_state) {
batch_operation->operation_type = mojom::blink::OperationType::kPut; FetchHandler* self = MakeGarbageCollected<FetchHandler>(
batch_operation->request = std::move(fetch_api_request_); script_state, /*response_loader=*/nullptr, barrier_callback,
batch_operation->response = std::move(fetch_api_response_); exception_state);
batch_operation->response->blob = handle; return self->BindToV8Function();
barrier_callback_->OnSuccess(index_, std::move(batch_operation));
} }
void DidFetchDataLoadFailed() override { FetchHandler(ScriptState* script_state,
barrier_callback_->OnError("network error"); 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 { void Trace(Visitor* visitor) const override {
visitor->Trace(response_loader_);
visitor->Trace(barrier_callback_); visitor->Trace(barrier_callback_);
FetchDataLoader::Client::Trace(visitor); ScriptFunction::Trace(visitor);
} }
private: private:
const wtf_size_t index_; Member<ResponseBodyLoader> response_loader_;
Member<BarrierCallbackForPut> barrier_callback_; Member<BarrierCallbackForPutResponse> barrier_callback_;
ExceptionState::ContextType context_type_;
mojom::blink::FetchAPIRequestPtr fetch_api_request_; const char* property_name_;
mojom::blink::FetchAPIResponsePtr fetch_api_response_; const char* interface_name_;
}; };
class Cache::CodeCacheHandleCallbackForPut final class Cache::CodeCacheHandleCallbackForPut final
...@@ -429,14 +606,16 @@ class Cache::CodeCacheHandleCallbackForPut final ...@@ -429,14 +606,16 @@ class Cache::CodeCacheHandleCallbackForPut final
public: public:
CodeCacheHandleCallbackForPut(ScriptState* script_state, CodeCacheHandleCallbackForPut(ScriptState* script_state,
wtf_size_t index, wtf_size_t index,
BarrierCallbackForPut* barrier_callback, BarrierCallbackForPutComplete* barrier_callback,
Request* request, Request* request,
Response* response, Response* response,
scoped_refptr<BlobDataHandle> blob_handle,
int64_t trace_id) int64_t trace_id)
: script_state_(script_state), : script_state_(script_state),
index_(index), index_(index),
barrier_callback_(barrier_callback), barrier_callback_(barrier_callback),
mime_type_(response->InternalMIMEType()), mime_type_(response->InternalMIMEType()),
blob_handle_(std::move(blob_handle)),
trace_id_(trace_id) { trace_id_(trace_id) {
fetch_api_request_ = request->CreateFetchAPIRequest(); fetch_api_request_ = request->CreateFetchAPIRequest();
fetch_api_response_ = response->PopulateFetchAPIResponse(request->url()); fetch_api_response_ = response->PopulateFetchAPIResponse(request->url());
...@@ -460,13 +639,7 @@ class Cache::CodeCacheHandleCallbackForPut final ...@@ -460,13 +639,7 @@ class Cache::CodeCacheHandleCallbackForPut final
batch_operation->operation_type = mojom::blink::OperationType::kPut; batch_operation->operation_type = mojom::blink::OperationType::kPut;
batch_operation->request = std::move(fetch_api_request_); batch_operation->request = std::move(fetch_api_request_);
batch_operation->response = std::move(fetch_api_response_); batch_operation->response = std::move(fetch_api_response_);
batch_operation->response->blob = std::move(blob_handle_);
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());
scoped_refptr<CachedMetadata> cached_metadata = scoped_refptr<CachedMetadata> cached_metadata =
GenerateFullCodeCache(array_buffer); GenerateFullCodeCache(array_buffer);
...@@ -532,8 +705,9 @@ class Cache::CodeCacheHandleCallbackForPut final ...@@ -532,8 +705,9 @@ class Cache::CodeCacheHandleCallbackForPut final
const Member<ScriptState> script_state_; const Member<ScriptState> script_state_;
const wtf_size_t index_; const wtf_size_t index_;
Member<BarrierCallbackForPut> barrier_callback_; Member<BarrierCallbackForPutComplete> barrier_callback_;
const String mime_type_; const String mime_type_;
scoped_refptr<BlobDataHandle> blob_handle_;
KURL url_; KURL url_;
V8CodeCache::OpaqueMode opaque_mode_; V8CodeCache::OpaqueMode opaque_mode_;
const int64_t trace_id_; const int64_t trace_id_;
...@@ -625,26 +799,38 @@ ScriptPromise Cache::Delete(ScriptState* script_state, ...@@ -625,26 +799,38 @@ ScriptPromise Cache::Delete(ScriptState* script_state,
} }
ScriptPromise Cache::put(ScriptState* script_state, ScriptPromise Cache::put(ScriptState* script_state,
const RequestInfo& request, const RequestInfo& request_info,
Response* response, Response* response,
ExceptionState& exception_state) { ExceptionState& exception_state) {
DCHECK(!request.IsNull()); DCHECK(!request_info.IsNull());
int64_t trace_id = blink::cache_storage::CreateTraceId(); int64_t trace_id = blink::cache_storage::CreateTraceId();
TRACE_EVENT_WITH_FLOW0("CacheStorage", "Cache::put", TRACE_EVENT_WITH_FLOW0("CacheStorage", "Cache::put",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT);
if (request.IsRequest()) { Request* request =
return PutImpl(script_state, "Cache.put()", request_info.IsRequest()
HeapVector<Member<Request>>(1, request.GetAsRequest()), ? request_info.GetAsRequest()
HeapVector<Member<Response>>(1, response), exception_state, : Request::Create(script_state, request_info.GetAsUSVString(),
trace_id); exception_state);
}
Request* new_request =
Request::Create(script_state, request.GetAsUSVString(), exception_state);
if (exception_state.HadException()) if (exception_state.HadException())
return ScriptPromise(); return ScriptPromise();
return PutImpl(
script_state, "Cache.put()", HeapVector<Member<Request>>(1, new_request), ValidateRequestForPut(request, exception_state);
HeapVector<Member<Response>>(1, response), exception_state, trace_id); 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&) { ScriptPromise Cache::keys(ScriptState* script_state, ExceptionState&) {
...@@ -843,40 +1029,44 @@ ScriptPromise Cache::MatchAllImpl(ScriptState* script_state, ...@@ -843,40 +1029,44 @@ ScriptPromise Cache::MatchAllImpl(ScriptState* script_state,
ScriptPromise Cache::AddAllImpl(ScriptState* script_state, ScriptPromise Cache::AddAllImpl(ScriptState* script_state,
const String& method_name, const String& method_name,
const HeapVector<Member<Request>>& requests, const HeapVector<Member<Request>>& request_list,
ExceptionState& exception_state) { ExceptionState& exception_state) {
int64_t trace_id = blink::cache_storage::CreateTraceId(); int64_t trace_id = blink::cache_storage::CreateTraceId();
TRACE_EVENT_WITH_FLOW0("CacheStorage", "Cache::AddAllImpl", TRACE_EVENT_WITH_FLOW0("CacheStorage", "Cache::AddAllImpl",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT);
if (requests.IsEmpty()) if (request_list.IsEmpty())
return ScriptPromise::CastUndefined(script_state); return ScriptPromise::CastUndefined(script_state);
HeapVector<RequestInfo> request_infos; // Validate all requests before starting to load or store any of them.
request_infos.resize(requests.size()); for (wtf_size_t i = 0; i < request_list.size(); ++i) {
HeapVector<ScriptPromise> promises; ValidateRequestForPut(request_list[i], exception_state);
promises.resize(requests.size()); if (exception_state.HadException())
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.");
return ScriptPromise(); return ScriptPromise();
} }
request_infos[i].SetRequest(requests[i]);
promises[i] = scoped_fetcher_->Fetch( auto* barrier_callback = MakeGarbageCollected<BarrierCallbackForPutResponse>(
script_state, request_infos[i], RequestInit::Create(), exception_state); 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) return promise;
.Then(FetchResolvedForAdd::Create(script_state, this, method_name,
requests, exception_state, trace_id));
} }
ScriptPromise Cache::DeleteImpl(ScriptState* script_state, ScriptPromise Cache::DeleteImpl(ScriptState* script_state,
...@@ -947,93 +1137,57 @@ ScriptPromise Cache::DeleteImpl(ScriptState* script_state, ...@@ -947,93 +1137,57 @@ ScriptPromise Cache::DeleteImpl(ScriptState* script_state,
return promise; return promise;
} }
ScriptPromise Cache::PutImpl(ScriptState* script_state, void Cache::PutImpl(ScriptPromiseResolver* resolver,
const String& method_name, const String& method_name,
const HeapVector<Member<Request>>& requests, const HeapVector<Member<Request>>& requests,
const HeapVector<Member<Response>>& responses, const HeapVector<Member<Response>>& responses,
ExceptionState& exception_state, const WTF::Vector<scoped_refptr<BlobDataHandle>>& blob_list,
int64_t trace_id) { 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_EVENT_WITH_FLOW0("CacheStorage", "Cache::PutImpl",
TRACE_ID_GLOBAL(trace_id), TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); 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) { ScriptState* script_state = resolver->GetScriptState();
KURL url(NullURL(), requests[i]->url()); ScriptState::Scope scope(script_state);
if (!url.ProtocolIsInHTTPFamily()) { ExecutionContext* context = ExecutionContext::From(script_state);
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());
if (VaryHeaderContainsAsterisk(responses[i])) { BarrierCallbackForPutComplete* barrier_callback =
barrier_callback->OnError("Vary header contains *"); MakeGarbageCollected<BarrierCallbackForPutComplete>(
return promise; requests.size(), this, method_name, resolver, trace_id);
}
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;
}
BodyStreamBuffer* buffer = responses[i]->InternalBodyBuffer(); for (wtf_size_t i = 0; i < requests.size(); ++i) {
if (!blob_list[i] ||
if (ShouldGenerateV8CodeCache(script_state, responses[i])) { !ShouldGenerateV8CodeCache(script_state, responses[i])) {
FetchDataLoader* loader = FetchDataLoader::CreateLoaderAsArrayBuffer(); mojom::blink::BatchOperationPtr batch_operation =
buffer->StartLoading(loader, mojom::blink::BatchOperation::New();
MakeGarbageCollected<CodeCacheHandleCallbackForPut>( batch_operation->operation_type = mojom::blink::OperationType::kPut;
script_state, i, barrier_callback, requests[i], batch_operation->request = requests[i]->CreateFetchAPIRequest();
responses[i], trace_id), batch_operation->response =
exception_state); responses[i]->PopulateFetchAPIResponse(requests[i]->url());
if (exception_state.HadException()) { batch_operation->response->blob = std::move(blob_list[i]);
barrier_callback->OnError("Could not inspect response body state"); barrier_callback->OnSuccess(i, std::move(batch_operation));
return promise;
}
continue; continue;
} }
if (buffer) { BytesConsumer* consumer =
ExecutionContext* context = ExecutionContext::From(script_state); MakeGarbageCollected<BlobBytesConsumer>(context, blob_list[i]);
// If the response has body, read the all data and create BodyStreamBuffer* buffer =
// the blob handle and dispatch the put batch asynchronously. BodyStreamBuffer::Create(script_state, consumer, /*signal=*/nullptr);
FetchDataLoader* loader = FetchDataLoader::CreateLoaderAsBlobHandle( FetchDataLoader* loader = FetchDataLoader::CreateLoaderAsArrayBuffer();
responses[i]->InternalMIMEType(), buffer->StartLoading(loader,
context->GetTaskRunner(TaskType::kNetworking)); MakeGarbageCollected<CodeCacheHandleCallbackForPut>(
buffer->StartLoading(loader, script_state, i, barrier_callback, requests[i],
MakeGarbageCollected<BlobHandleCallbackForPut>( responses[i], std::move(blob_list[i]), trace_id),
i, barrier_callback, requests[i], responses[i]), exception_state);
exception_state); if (exception_state.HadException()) {
if (exception_state.HadException()) { barrier_callback->OnError("Could not inspect response body state");
barrier_callback->OnError("Could not inspect response body state"); return;
return promise;
}
continue;
} }
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, ScriptPromise Cache::KeysImpl(ScriptState* script_state,
......
...@@ -44,6 +44,7 @@ class CacheStorageBlobClientList; ...@@ -44,6 +44,7 @@ class CacheStorageBlobClientList;
class ExceptionState; class ExceptionState;
class Response; class Response;
class Request; class Request;
class ScriptPromiseResolver;
class ScriptState; class ScriptState;
typedef RequestOrUSVString RequestInfo; typedef RequestOrUSVString RequestInfo;
...@@ -87,11 +88,11 @@ class MODULES_EXPORT Cache final : public ScriptWrappable { ...@@ -87,11 +88,11 @@ class MODULES_EXPORT Cache final : public ScriptWrappable {
void Trace(Visitor*) const override; void Trace(Visitor*) const override;
private: private:
class BarrierCallbackForPut; class BarrierCallbackForPutResponse;
class BlobHandleCallbackForPut; class BarrierCallbackForPutComplete;
class CodeCacheHandleCallbackForPut; class CodeCacheHandleCallbackForPut;
class FetchResolvedForAdd; class ResponseBodyLoader;
friend class FetchResolvedForAdd; class FetchHandler;
ScriptPromise MatchImpl(ScriptState*, ScriptPromise MatchImpl(ScriptState*,
const Request*, const Request*,
...@@ -106,12 +107,13 @@ class MODULES_EXPORT Cache final : public ScriptWrappable { ...@@ -106,12 +107,13 @@ class MODULES_EXPORT Cache final : public ScriptWrappable {
ScriptPromise DeleteImpl(ScriptState*, ScriptPromise DeleteImpl(ScriptState*,
const Request*, const Request*,
const CacheQueryOptions*); const CacheQueryOptions*);
ScriptPromise PutImpl(ScriptState*, void PutImpl(ScriptPromiseResolver*,
const String& method_name, const String& method_name,
const HeapVector<Member<Request>>&, const HeapVector<Member<Request>>&,
const HeapVector<Member<Response>>&, const HeapVector<Member<Response>>&,
ExceptionState&, const WTF::Vector<scoped_refptr<BlobDataHandle>>& blob_list,
int64_t trace_id); ExceptionState&,
int64_t trace_id);
ScriptPromise KeysImpl(ScriptState*, ScriptPromise KeysImpl(ScriptState*,
const Request*, const Request*,
const CacheQueryOptions*); 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