blob: 47d0ae6c2dc18f18752a1463c563b3ecc6efae6d [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 "bindings/core/v8/CallbackPromiseAdapter.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "bindings/core/v8/ScriptState.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8ThrowException.h"
#include "bindings/modules/v8/V8Response.h"
#include "core/dom/DOMException.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/cachestorage/CacheStorageError.h"
#include "modules/fetch/BodyStreamBuffer.h"
#include "modules/fetch/FetchDataLoader.h"
#include "modules/fetch/GlobalFetch.h"
#include "modules/fetch/Request.h"
#include "modules/fetch/Response.h"
#include "platform/HTTPNames.h"
#include "platform/Histogram.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "public/platform/modules/serviceworker/WebServiceWorkerCache.h"
#include <memory>
namespace blink {
namespace {
void checkCacheQueryOptions(const CacheQueryOptions& options, ExecutionContext* context)
{
if (!RuntimeEnabledFeatures::cacheIgnoreSearchOptionEnabled() && options.ignoreSearch())
context->addConsoleMessage(ConsoleMessage::create(JSMessageSource, WarningMessageLevel, "Cache.match() does not support 'ignoreSearch' option yet. See http://crbug.com/520784"));
if (options.ignoreMethod())
context->addConsoleMessage(ConsoleMessage::create(JSMessageSource, WarningMessageLevel, "Cache.match() does not support 'ignoreMethod' option yet. See http://crbug.com/482256"));
if (options.ignoreVary())
context->addConsoleMessage(ConsoleMessage::create(JSMessageSource, WarningMessageLevel, "Cache.match() does not support 'ignoreVary' option yet. See http://crbug.com/499216"));
}
// FIXME: Consider using CallbackPromiseAdapter.
class CacheMatchCallbacks : public WebServiceWorkerCache::CacheMatchCallbacks {
WTF_MAKE_NONCOPYABLE(CacheMatchCallbacks);
public:
explicit CacheMatchCallbacks(ScriptPromiseResolver* resolver)
: m_resolver(resolver) { }
void onSuccess(const WebServiceWorkerResponse& webResponse) override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
ScriptState::Scope scope(m_resolver->getScriptState());
m_resolver->resolve(Response::create(m_resolver->getScriptState(), webResponse));
m_resolver.clear();
}
void onError(WebServiceWorkerCacheError reason) override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
if (reason == WebServiceWorkerCacheErrorNotFound)
m_resolver->resolve();
else
m_resolver->reject(CacheStorageError::createException(reason));
m_resolver.clear();
}
private:
Persistent<ScriptPromiseResolver> m_resolver;
};
// FIXME: Consider using CallbackPromiseAdapter.
class CacheWithResponsesCallbacks : public WebServiceWorkerCache::CacheWithResponsesCallbacks {
WTF_MAKE_NONCOPYABLE(CacheWithResponsesCallbacks);
public:
explicit CacheWithResponsesCallbacks(ScriptPromiseResolver* resolver)
: m_resolver(resolver) { }
void onSuccess(const WebVector<WebServiceWorkerResponse>& webResponses) override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
ScriptState::Scope scope(m_resolver->getScriptState());
HeapVector<Member<Response>> responses;
for (size_t i = 0; i < webResponses.size(); ++i)
responses.append(Response::create(m_resolver->getScriptState(), webResponses[i]));
m_resolver->resolve(responses);
m_resolver.clear();
}
void onError(WebServiceWorkerCacheError reason) override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
m_resolver->reject(CacheStorageError::createException(reason));
m_resolver.clear();
}
protected:
Persistent<ScriptPromiseResolver> m_resolver;
};
// FIXME: Consider using CallbackPromiseAdapter.
class CacheDeleteCallback : public WebServiceWorkerCache::CacheBatchCallbacks {
WTF_MAKE_NONCOPYABLE(CacheDeleteCallback);
public:
explicit CacheDeleteCallback(ScriptPromiseResolver* resolver)
: m_resolver(resolver) { }
void onSuccess() override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
m_resolver->resolve(true);
m_resolver.clear();
}
void onError(WebServiceWorkerCacheError reason) override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
if (reason == WebServiceWorkerCacheErrorNotFound)
m_resolver->resolve(false);
else
m_resolver->reject(CacheStorageError::createException(reason));
m_resolver.clear();
}
private:
Persistent<ScriptPromiseResolver> m_resolver;
};
// FIXME: Consider using CallbackPromiseAdapter.
class CacheWithRequestsCallbacks : public WebServiceWorkerCache::CacheWithRequestsCallbacks {
WTF_MAKE_NONCOPYABLE(CacheWithRequestsCallbacks);
public:
explicit CacheWithRequestsCallbacks(ScriptPromiseResolver* resolver)
: m_resolver(resolver) { }
void onSuccess(const WebVector<WebServiceWorkerRequest>& webRequests) override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
ScriptState::Scope scope(m_resolver->getScriptState());
HeapVector<Member<Request>> requests;
for (size_t i = 0; i < webRequests.size(); ++i)
requests.append(Request::create(m_resolver->getScriptState(), webRequests[i]));
m_resolver->resolve(requests);
m_resolver.clear();
}
void onError(WebServiceWorkerCacheError reason) override
{
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
m_resolver->reject(CacheStorageError::createException(reason));
m_resolver.clear();
}
private:
Persistent<ScriptPromiseResolver> m_resolver;
};
// Used for UMA. Append only.
enum class ResponseType {
BasicType,
CORSType,
DefaultType,
ErrorType,
OpaqueType,
OpaqueRedirectType,
EnumMax,
};
void RecordResponseTypeForAdd(const Member<Response>& response)
{
ResponseType type = ResponseType::EnumMax;
switch (response->response()->getType()) {
case FetchResponseData::BasicType:
type = ResponseType::BasicType;
break;
case FetchResponseData::CORSType:
type = ResponseType::CORSType;
break;
case FetchResponseData::DefaultType:
type = ResponseType::DefaultType;
break;
case FetchResponseData::ErrorType:
type = ResponseType::ErrorType;
break;
case FetchResponseData::OpaqueType:
type = ResponseType::OpaqueType;
break;
case FetchResponseData::OpaqueRedirectType:
type = ResponseType::OpaqueRedirectType;
break;
}
DEFINE_THREAD_SAFE_STATIC_LOCAL(EnumerationHistogram, responseTypeHistogram, new EnumerationHistogram("ServiceWorkerCache.Cache.AddResponseType", static_cast<int>(ResponseType::EnumMax)));
responseTypeHistogram.count(static_cast<int>(type));
};
bool varyHeaderContainsAsterisk(const Response* response)
{
const FetchHeaderList* headers = response->headers()->headerList();
for (size_t i = 0; i < headers->size(); ++i) {
const FetchHeaderList::Header& header = headers->entry(i);
if (header.first == "vary") {
Vector<String> fields;
header.second.split(',', fields);
for (size_t j = 0; j < fields.size(); ++j) {
if (fields[j].stripWhiteSpace() == "*")
return true;
}
}
}
return false;
}
} // 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* scriptState, Cache* cache, const HeapVector<Member<Request>>& requests)
{
FetchResolvedForAdd* self = new FetchResolvedForAdd(scriptState, cache, requests);
return self->bindToV8Function();
}
ScriptValue call(ScriptValue value) override
{
NonThrowableExceptionState exceptionState;
HeapVector<Member<Response>> responses = toMemberNativeArray<Response>(value.v8Value(), m_requests.size(), getScriptState()->isolate(), exceptionState);
for (const auto& response : responses) {
if (!response->ok()) {
ScriptPromise rejection = ScriptPromise::reject(getScriptState(), V8ThrowException::createTypeError(getScriptState()->isolate(), "Request failed"));
return ScriptValue(getScriptState(), rejection.v8Value());
}
if (varyHeaderContainsAsterisk(response)) {
ScriptPromise rejection = ScriptPromise::reject(getScriptState(), V8ThrowException::createTypeError(getScriptState()->isolate(), "Vary header contains *"));
return ScriptValue(getScriptState(), rejection.v8Value());
}
}
for (const auto& response : responses)
RecordResponseTypeForAdd(response);
ScriptPromise putPromise = m_cache->putImpl(getScriptState(), m_requests, responses);
return ScriptValue(getScriptState(), putPromise.v8Value());
}
DEFINE_INLINE_VIRTUAL_TRACE()
{
visitor->trace(m_cache);
visitor->trace(m_requests);
ScriptFunction::trace(visitor);
}
private:
FetchResolvedForAdd(ScriptState* scriptState, Cache* cache, const HeapVector<Member<Request>>& requests)
: ScriptFunction(scriptState)
, m_cache(cache)
, m_requests(requests)
{
}
Member<Cache> m_cache;
HeapVector<Member<Request>> m_requests;
};
class Cache::BarrierCallbackForPut final : public GarbageCollectedFinalized<BarrierCallbackForPut> {
public:
BarrierCallbackForPut(int numberOfOperations, Cache* cache, ScriptPromiseResolver* resolver)
: m_numberOfRemainingOperations(numberOfOperations)
, m_cache(cache)
, m_resolver(resolver)
{
ASSERT(0 < m_numberOfRemainingOperations);
m_batchOperations.resize(numberOfOperations);
}
void onSuccess(size_t index, const WebServiceWorkerCache::BatchOperation& batchOperation)
{
ASSERT(index < m_batchOperations.size());
if (m_completed)
return;
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
m_batchOperations[index] = batchOperation;
if (--m_numberOfRemainingOperations != 0)
return;
m_cache->webCache()->dispatchBatch(new CallbackPromiseAdapter<void, CacheStorageError>(m_resolver), m_batchOperations);
}
void onError(const String& errorMessage)
{
if (m_completed)
return;
m_completed = true;
if (!m_resolver->getExecutionContext() || m_resolver->getExecutionContext()->activeDOMObjectsAreStopped())
return;
ScriptState* state = m_resolver->getScriptState();
ScriptState::Scope scope(state);
m_resolver->reject(V8ThrowException::createTypeError(state->isolate(), errorMessage));
}
DEFINE_INLINE_VIRTUAL_TRACE()
{
visitor->trace(m_cache);
visitor->trace(m_resolver);
}
private:
bool m_completed = false;
int m_numberOfRemainingOperations;
Member<Cache> m_cache;
Member<ScriptPromiseResolver> m_resolver;
Vector<WebServiceWorkerCache::BatchOperation> m_batchOperations;
};
class Cache::BlobHandleCallbackForPut final : public GarbageCollectedFinalized<BlobHandleCallbackForPut>, public FetchDataLoader::Client {
USING_GARBAGE_COLLECTED_MIXIN(BlobHandleCallbackForPut);
public:
BlobHandleCallbackForPut(size_t index, BarrierCallbackForPut* barrierCallback, Request* request, Response* response)
: m_index(index)
, m_barrierCallback(barrierCallback)
{
request->populateWebServiceWorkerRequest(m_webRequest);
response->populateWebServiceWorkerResponse(m_webResponse);
}
~BlobHandleCallbackForPut() override { }
void didFetchDataLoadedBlobHandle(PassRefPtr<BlobDataHandle> handle) override
{
WebServiceWorkerCache::BatchOperation batchOperation;
batchOperation.operationType = WebServiceWorkerCache::OperationTypePut;
batchOperation.request = m_webRequest;
batchOperation.response = m_webResponse;
batchOperation.response.setBlobDataHandle(handle);
m_barrierCallback->onSuccess(m_index, batchOperation);
}
void didFetchDataLoadFailed() override
{
m_barrierCallback->onError("network error");
}
DEFINE_INLINE_VIRTUAL_TRACE()
{
visitor->trace(m_barrierCallback);
FetchDataLoader::Client::trace(visitor);
}
private:
const size_t m_index;
Member<BarrierCallbackForPut> m_barrierCallback;
WebServiceWorkerRequest m_webRequest;
WebServiceWorkerResponse m_webResponse;
};
Cache* Cache::create(GlobalFetch::ScopedFetcher* fetcher, std::unique_ptr<WebServiceWorkerCache> webCache)
{
return new Cache(fetcher, std::move(webCache));
}
ScriptPromise Cache::match(ScriptState* scriptState, const RequestInfo& request, const CacheQueryOptions& options, ExceptionState& exceptionState)
{
ASSERT(!request.isNull());
if (request.isRequest())
return matchImpl(scriptState, request.getAsRequest(), options);
Request* newRequest = Request::create(scriptState, request.getAsUSVString(), exceptionState);
if (exceptionState.hadException())
return ScriptPromise();
return matchImpl(scriptState, newRequest, options);
}
ScriptPromise Cache::matchAll(ScriptState* scriptState, ExceptionState& exceptionState)
{
return matchAllImpl(scriptState);
}
ScriptPromise Cache::matchAll(ScriptState* scriptState, const RequestInfo& request, const CacheQueryOptions& options, ExceptionState& exceptionState)
{
ASSERT(!request.isNull());
if (request.isRequest())
return matchAllImpl(scriptState, request.getAsRequest(), options);
Request* newRequest = Request::create(scriptState, request.getAsUSVString(), exceptionState);
if (exceptionState.hadException())
return ScriptPromise();
return matchAllImpl(scriptState, newRequest, options);
}
ScriptPromise Cache::add(ScriptState* scriptState, const RequestInfo& request, ExceptionState& exceptionState)
{
ASSERT(!request.isNull());
HeapVector<Member<Request>> requests;
if (request.isRequest()) {
requests.append(request.getAsRequest());
} else {
requests.append(Request::create(scriptState, request.getAsUSVString(), exceptionState));
if (exceptionState.hadException())
return ScriptPromise();
}
return addAllImpl(scriptState, requests, exceptionState);
}
ScriptPromise Cache::addAll(ScriptState* scriptState, const HeapVector<RequestInfo>& rawRequests, ExceptionState& exceptionState)
{
HeapVector<Member<Request>> requests;
for (RequestInfo request : rawRequests) {
if (request.isRequest()) {
requests.append(request.getAsRequest());
} else {
requests.append(Request::create(scriptState, request.getAsUSVString(), exceptionState));
if (exceptionState.hadException())
return ScriptPromise();
}
}
return addAllImpl(scriptState, requests, exceptionState);
}
ScriptPromise Cache::deleteFunction(ScriptState* scriptState, const RequestInfo& request, const CacheQueryOptions& options, ExceptionState& exceptionState)
{
ASSERT(!request.isNull());
if (request.isRequest())
return deleteImpl(scriptState, request.getAsRequest(), options);
Request* newRequest = Request::create(scriptState, request.getAsUSVString(), exceptionState);
if (exceptionState.hadException())
return ScriptPromise();
return deleteImpl(scriptState, newRequest, options);
}
ScriptPromise Cache::put(ScriptState* scriptState, const RequestInfo& request, Response* response, ExceptionState& exceptionState)
{
ASSERT(!request.isNull());
if (request.isRequest())
return putImpl(scriptState, HeapVector<Member<Request>>(1, request.getAsRequest()), HeapVector<Member<Response>>(1, response));
Request* newRequest = Request::create(scriptState, request.getAsUSVString(), exceptionState);
if (exceptionState.hadException())
return ScriptPromise();
return putImpl(scriptState, HeapVector<Member<Request>>(1, newRequest), HeapVector<Member<Response>>(1, response));
}
ScriptPromise Cache::keys(ScriptState* scriptState, ExceptionState&)
{
return keysImpl(scriptState);
}
ScriptPromise Cache::keys(ScriptState* scriptState, const RequestInfo& request, const CacheQueryOptions& options, ExceptionState& exceptionState)
{
ASSERT(!request.isNull());
if (request.isRequest())
return keysImpl(scriptState, request.getAsRequest(), options);
Request* newRequest = Request::create(scriptState, request.getAsUSVString(), exceptionState);
if (exceptionState.hadException())
return ScriptPromise();
return keysImpl(scriptState, newRequest, options);
}
// static
WebServiceWorkerCache::QueryParams Cache::toWebQueryParams(const CacheQueryOptions& options)
{
WebServiceWorkerCache::QueryParams webQueryParams;
webQueryParams.ignoreSearch = options.ignoreSearch() && RuntimeEnabledFeatures::cacheIgnoreSearchOptionEnabled();
webQueryParams.ignoreMethod = options.ignoreMethod();
webQueryParams.ignoreVary = options.ignoreVary();
webQueryParams.cacheName = options.cacheName();
return webQueryParams;
}
Cache::Cache(GlobalFetch::ScopedFetcher* fetcher, std::unique_ptr<WebServiceWorkerCache> webCache)
: m_scopedFetcher(fetcher)
, m_webCache(std::move(webCache))
{
}
DEFINE_TRACE(Cache)
{
visitor->trace(m_scopedFetcher);
}
ScriptPromise Cache::matchImpl(ScriptState* scriptState, const Request* request, const CacheQueryOptions& options)
{
WebServiceWorkerRequest webRequest;
request->populateWebServiceWorkerRequest(webRequest);
checkCacheQueryOptions(options, scriptState->getExecutionContext());
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
const ScriptPromise promise = resolver->promise();
m_webCache->dispatchMatch(new CacheMatchCallbacks(resolver), webRequest, toWebQueryParams(options));
return promise;
}
ScriptPromise Cache::matchAllImpl(ScriptState* scriptState)
{
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
const ScriptPromise promise = resolver->promise();
m_webCache->dispatchMatchAll(new CacheWithResponsesCallbacks(resolver), WebServiceWorkerRequest(), WebServiceWorkerCache::QueryParams());
return promise;
}
ScriptPromise Cache::matchAllImpl(ScriptState* scriptState, const Request* request, const CacheQueryOptions& options)
{
WebServiceWorkerRequest webRequest;
request->populateWebServiceWorkerRequest(webRequest);
checkCacheQueryOptions(options, scriptState->getExecutionContext());
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
const ScriptPromise promise = resolver->promise();
m_webCache->dispatchMatchAll(new CacheWithResponsesCallbacks(resolver), webRequest, toWebQueryParams(options));
return promise;
}
ScriptPromise Cache::addAllImpl(ScriptState* scriptState, const HeapVector<Member<Request>>& requests, ExceptionState& exceptionState)
{
if (requests.isEmpty())
return ScriptPromise::castUndefined(scriptState);
HeapVector<RequestInfo> requestInfos;
requestInfos.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(scriptState, V8ThrowException::createTypeError(scriptState->isolate(), "Add/AddAll does not support schemes other than \"http\" or \"https\""));
if (requests[i]->method() != HTTPNames::GET)
return ScriptPromise::reject(scriptState, V8ThrowException::createTypeError(scriptState->isolate(), "Add/AddAll only supports the GET request method."));
requestInfos[i].setRequest(requests[i]);
promises[i] = m_scopedFetcher->fetch(scriptState, requestInfos[i], Dictionary(), exceptionState);
}
return ScriptPromise::all(scriptState, promises).then(FetchResolvedForAdd::create(scriptState, this, requests));
}
ScriptPromise Cache::deleteImpl(ScriptState* scriptState, const Request* request, const CacheQueryOptions& options)
{
WebVector<WebServiceWorkerCache::BatchOperation> batchOperations(size_t(1));
batchOperations[0].operationType = WebServiceWorkerCache::OperationTypeDelete;
request->populateWebServiceWorkerRequest(batchOperations[0].request);
checkCacheQueryOptions(options, scriptState->getExecutionContext());
batchOperations[0].matchParams = toWebQueryParams(options);
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
const ScriptPromise promise = resolver->promise();
m_webCache->dispatchBatch(new CacheDeleteCallback(resolver), batchOperations);
return promise;
}
ScriptPromise Cache::putImpl(ScriptState* scriptState, const HeapVector<Member<Request>>& requests, const HeapVector<Member<Response>>& responses)
{
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
const ScriptPromise promise = resolver->promise();
BarrierCallbackForPut* barrierCallback = new BarrierCallbackForPut(requests.size(), this, resolver);
for (size_t i = 0; i < requests.size(); ++i) {
KURL url(KURL(), requests[i]->url());
if (!url.protocolIsInHTTPFamily()) {
barrierCallback->onError("Request scheme '" + url.protocol() + "' is unsupported");
return promise;
}
if (requests[i]->method() != HTTPNames::GET) {
barrierCallback->onError("Request method '" + requests[i]->method() + "' is unsupported");
return promise;
}
ASSERT(!requests[i]->hasBody());
if (varyHeaderContainsAsterisk(responses[i])) {
barrierCallback->onError("Vary header contains *");
return promise;
}
if (responses[i]->status() == 206) {
barrierCallback->onError("Partial response (status code 206) is unsupported");
return promise;
}
if (responses[i]->isBodyLocked() || responses[i]->bodyUsed()) {
barrierCallback->onError("Response body is already used");
return promise;
}
BodyStreamBuffer* buffer = responses[i]->internalBodyBuffer();
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, barrierCallback, requests[i], responses[i]));
continue;
}
WebServiceWorkerCache::BatchOperation batchOperation;
batchOperation.operationType = WebServiceWorkerCache::OperationTypePut;
requests[i]->populateWebServiceWorkerRequest(batchOperation.request);
responses[i]->populateWebServiceWorkerResponse(batchOperation.response);
barrierCallback->onSuccess(i, batchOperation);
}
return promise;
}
ScriptPromise Cache::keysImpl(ScriptState* scriptState)
{
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
const ScriptPromise promise = resolver->promise();
m_webCache->dispatchKeys(new CacheWithRequestsCallbacks(resolver), WebServiceWorkerRequest(), WebServiceWorkerCache::QueryParams());
return promise;
}
ScriptPromise Cache::keysImpl(ScriptState* scriptState, const Request* request, const CacheQueryOptions& options)
{
WebServiceWorkerRequest webRequest;
request->populateWebServiceWorkerRequest(webRequest);
checkCacheQueryOptions(options, scriptState->getExecutionContext());
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
const ScriptPromise promise = resolver->promise();
m_webCache->dispatchKeys(new CacheWithRequestsCallbacks(resolver), webRequest, toWebQueryParams(options));
return promise;
}
WebServiceWorkerCache* Cache::webCache() const
{
return m_webCache.get();
}
} // namespace blink