1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/cache_storage/cache_storage.h"
6
7 #include <utility>
8
9 #include "base/memory/ptr_util.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
12 #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
13 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
14 #include "third_party/blink/public/platform/web_content_settings_client.h"
15 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
16 #include "third_party/blink/renderer/bindings/modules/v8/v8_multi_cache_query_options.h"
17 #include "third_party/blink/renderer/core/dom/document.h"
18 #include "third_party/blink/renderer/core/dom/dom_exception.h"
19 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
20 #include "third_party/blink/renderer/core/fetch/request.h"
21 #include "third_party/blink/renderer/core/fetch/response.h"
22 #include "third_party/blink/renderer/core/frame/local_frame.h"
23 #include "third_party/blink/renderer/core/inspector/console_message.h"
24 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
25 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_error.h"
26 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h"
27 #include "third_party/blink/renderer/modules/cache_storage/cache_utils.h"
28 #include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h"
29 #include "third_party/blink/renderer/platform/bindings/script_state.h"
30 #include "third_party/blink/renderer/platform/heap/heap.h"
31 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
32 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
33 #include "third_party/blink/renderer/platform/network/http_names.h"
34
35 namespace mojo {
36
37 using blink::mojom::blink::CacheQueryOptions;
38 using blink::mojom::blink::CacheQueryOptionsPtr;
39 using blink::mojom::blink::MultiCacheQueryOptions;
40 using blink::mojom::blink::MultiCacheQueryOptionsPtr;
41
42 template <>
43 struct TypeConverter<MultiCacheQueryOptionsPtr,
44 const blink::MultiCacheQueryOptions*> {
Convertmojo::TypeConverter45 static MultiCacheQueryOptionsPtr Convert(
46 const blink::MultiCacheQueryOptions* input) {
47 CacheQueryOptionsPtr query_options = CacheQueryOptions::New();
48 query_options->ignore_search = input->ignoreSearch();
49 query_options->ignore_method = input->ignoreMethod();
50 query_options->ignore_vary = input->ignoreVary();
51
52 MultiCacheQueryOptionsPtr output = MultiCacheQueryOptions::New();
53 output->query_options = std::move(query_options);
54 output->cache_name = input->cacheName();
55 return output;
56 }
57 };
58
59 } // namespace mojo
60
61 namespace blink {
62
63 namespace {
64
IsCacheStorageAllowed(ScriptState * script_state)65 bool IsCacheStorageAllowed(ScriptState* script_state) {
66 ExecutionContext* context = ExecutionContext::From(script_state);
67
68 if (auto* document = Document::DynamicFrom(context)) {
69 LocalFrame* frame = document->GetFrame();
70 if (!frame)
71 return false;
72 if (auto* settings_client = frame->GetContentSettingsClient()) {
73 // This triggers a sync IPC.
74 return settings_client->AllowCacheStorage();
75 }
76 return true;
77 }
78
79 WebContentSettingsClient* content_settings_client =
80 To<WorkerGlobalScope>(context)->ContentSettingsClient();
81 if (!content_settings_client)
82 return true;
83 // This triggers a sync IPC.
84 return content_settings_client->AllowCacheStorage();
85 }
86
87 } // namespace
88
open(ScriptState * script_state,const String & cache_name)89 ScriptPromise CacheStorage::open(ScriptState* script_state,
90 const String& cache_name) {
91 int64_t trace_id = blink::cache_storage::CreateTraceId();
92 TRACE_EVENT_WITH_FLOW1("CacheStorage", "CacheStorage::Open",
93 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT,
94 "name", CacheStorageTracedValue(cache_name));
95
96 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
97 ScriptPromise promise = resolver->Promise();
98
99 if (!IsAllowed(script_state)) {
100 resolver->Reject(
101 MakeGarbageCollected<DOMException>(DOMExceptionCode::kSecurityError));
102 return promise;
103 }
104
105 // The context may be destroyed and the mojo connection unbound. However the
106 // object may live on, reject any requests after the context is destroyed.
107 if (!cache_storage_remote_) {
108 resolver->Reject(MakeGarbageCollected<DOMException>(
109 DOMExceptionCode::kInvalidStateError));
110 return promise;
111 }
112
113 ever_used_ = true;
114
115 // Make sure to bind the CacheStorage object to keep the mojo interface
116 // pointer alive during the operation. Otherwise GC might prevent the
117 // callback from ever being executed.
118 cache_storage_remote_->Open(
119 cache_name, trace_id,
120 WTF::Bind(
121 [](ScriptPromiseResolver* resolver,
122 GlobalFetch::ScopedFetcher* fetcher, base::TimeTicks start_time,
123 int64_t trace_id, mojom::blink::OpenResultPtr result) {
124 UMA_HISTOGRAM_TIMES("ServiceWorkerCache.CacheStorage.Renderer.Open",
125 base::TimeTicks::Now() - start_time);
126 if (!resolver->GetExecutionContext() ||
127 resolver->GetExecutionContext()->IsContextDestroyed()) {
128 return;
129 }
130 if (result->is_status()) {
131 TRACE_EVENT_WITH_FLOW1(
132 "CacheStorage", "CacheStorage::Open::Callback",
133 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status",
134 CacheStorageTracedValue(result->get_status()));
135 switch (result->get_status()) {
136 case mojom::blink::CacheStorageError::kErrorNotFound:
137 case mojom::blink::CacheStorageError::kErrorStorage:
138 resolver->Resolve();
139 break;
140 default:
141 RejectCacheStorageWithError(resolver, result->get_status());
142 break;
143 }
144 } else {
145 TRACE_EVENT_WITH_FLOW1(
146 "CacheStorage", "CacheStorage::Open::Callback",
147 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status",
148 "success");
149 // See https://bit.ly/2S0zRAS for task types.
150 resolver->Resolve(MakeGarbageCollected<Cache>(
151 fetcher, std::move(result->get_cache()),
152 resolver->GetExecutionContext()->GetTaskRunner(
153 blink::TaskType::kMiscPlatformAPI)));
154 }
155 },
156 WrapPersistent(resolver), WrapPersistent(scoped_fetcher_.Get()),
157 base::TimeTicks::Now(), trace_id));
158
159 return promise;
160 }
161
has(ScriptState * script_state,const String & cache_name)162 ScriptPromise CacheStorage::has(ScriptState* script_state,
163 const String& cache_name) {
164 int64_t trace_id = blink::cache_storage::CreateTraceId();
165 TRACE_EVENT_WITH_FLOW1("CacheStorage", "CacheStorage::Has",
166 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT,
167 "name", CacheStorageTracedValue(cache_name));
168
169 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
170 ScriptPromise promise = resolver->Promise();
171
172 if (!IsAllowed(script_state)) {
173 resolver->Reject(
174 MakeGarbageCollected<DOMException>(DOMExceptionCode::kSecurityError));
175 return promise;
176 }
177
178 // The context may be destroyed and the mojo connection unbound. However the
179 // object may live on, reject any requests after the context is destroyed.
180 if (!cache_storage_remote_) {
181 resolver->Reject(MakeGarbageCollected<DOMException>(
182 DOMExceptionCode::kInvalidStateError));
183 return promise;
184 }
185
186 ever_used_ = true;
187
188 // Make sure to bind the CacheStorage object to keep the mojo interface
189 // pointer alive during the operation. Otherwise GC might prevent the
190 // callback from ever being executed.
191 cache_storage_remote_->Has(
192 cache_name, trace_id,
193 WTF::Bind(
194 [](ScriptPromiseResolver* resolver, base::TimeTicks start_time,
195 int64_t trace_id, mojom::blink::CacheStorageError result) {
196 UMA_HISTOGRAM_TIMES("ServiceWorkerCache.CacheStorage.Renderer.Has",
197 base::TimeTicks::Now() - start_time);
198 TRACE_EVENT_WITH_FLOW1(
199 "CacheStorage", "CacheStorage::Has::Callback",
200 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status",
201 CacheStorageTracedValue(result));
202 if (!resolver->GetExecutionContext() ||
203 resolver->GetExecutionContext()->IsContextDestroyed())
204 return;
205 switch (result) {
206 case mojom::blink::CacheStorageError::kSuccess:
207 resolver->Resolve(true);
208 break;
209 case mojom::blink::CacheStorageError::kErrorNotFound:
210 resolver->Resolve(false);
211 break;
212 default:
213 RejectCacheStorageWithError(resolver, result);
214 break;
215 }
216 },
217 WrapPersistent(resolver), base::TimeTicks::Now(), trace_id));
218
219 return promise;
220 }
221
Delete(ScriptState * script_state,const String & cache_name)222 ScriptPromise CacheStorage::Delete(ScriptState* script_state,
223 const String& cache_name) {
224 int64_t trace_id = blink::cache_storage::CreateTraceId();
225 TRACE_EVENT_WITH_FLOW1("CacheStorage", "CacheStorage::Delete",
226 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT,
227 "name", CacheStorageTracedValue(cache_name));
228
229 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
230 ScriptPromise promise = resolver->Promise();
231
232 if (!IsAllowed(script_state)) {
233 resolver->Reject(
234 MakeGarbageCollected<DOMException>(DOMExceptionCode::kSecurityError));
235 return promise;
236 }
237
238 // The context may be destroyed and the mojo connection unbound. However the
239 // object may live on, reject any requests after the context is destroyed.
240 if (!cache_storage_remote_) {
241 resolver->Reject(MakeGarbageCollected<DOMException>(
242 DOMExceptionCode::kInvalidStateError));
243 return promise;
244 }
245
246 ever_used_ = true;
247
248 // Make sure to bind the CacheStorage object to keep the mojo interface
249 // pointer alive during the operation. Otherwise GC might prevent the
250 // callback from ever being executed.
251 cache_storage_remote_->Delete(
252 cache_name, trace_id,
253 WTF::Bind(
254 [](ScriptPromiseResolver* resolver, base::TimeTicks start_time,
255 int64_t trace_id, mojom::blink::CacheStorageError result) {
256 UMA_HISTOGRAM_TIMES(
257 "ServiceWorkerCache.CacheStorage.Renderer.Delete",
258 base::TimeTicks::Now() - start_time);
259 TRACE_EVENT_WITH_FLOW1(
260 "CacheStorage", "CacheStorage::Delete::Callback",
261 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status",
262 CacheStorageTracedValue(result));
263 if (!resolver->GetExecutionContext() ||
264 resolver->GetExecutionContext()->IsContextDestroyed())
265 return;
266 switch (result) {
267 case mojom::blink::CacheStorageError::kSuccess:
268 resolver->Resolve(true);
269 break;
270 case mojom::blink::CacheStorageError::kErrorStorage:
271 case mojom::blink::CacheStorageError::kErrorNotFound:
272 resolver->Resolve(false);
273 break;
274 default:
275 RejectCacheStorageWithError(resolver, result);
276 break;
277 }
278 },
279 WrapPersistent(resolver), base::TimeTicks::Now(), trace_id));
280
281 return promise;
282 }
283
keys(ScriptState * script_state)284 ScriptPromise CacheStorage::keys(ScriptState* script_state) {
285 int64_t trace_id = blink::cache_storage::CreateTraceId();
286 TRACE_EVENT_WITH_FLOW0("CacheStorage", "CacheStorage::Keys",
287 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT);
288
289 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
290 ScriptPromise promise = resolver->Promise();
291
292 if (!IsAllowed(script_state)) {
293 resolver->Reject(
294 MakeGarbageCollected<DOMException>(DOMExceptionCode::kSecurityError));
295 return promise;
296 }
297
298 // The context may be destroyed and the mojo connection unbound. However the
299 // object may live on, reject any requests after the context is destroyed.
300 if (!cache_storage_remote_) {
301 resolver->Reject(MakeGarbageCollected<DOMException>(
302 DOMExceptionCode::kInvalidStateError));
303 return promise;
304 }
305
306 ever_used_ = true;
307
308 // Make sure to bind the CacheStorage object to keep the mojo interface
309 // pointer alive during the operation. Otherwise GC might prevent the
310 // callback from ever being executed.
311 cache_storage_remote_->Keys(
312 trace_id,
313 WTF::Bind(
314 [](ScriptPromiseResolver* resolver, base::TimeTicks start_time,
315 int64_t trace_id, const Vector<String>& keys) {
316 UMA_HISTOGRAM_TIMES("ServiceWorkerCache.CacheStorage.Renderer.Keys",
317 base::TimeTicks::Now() - start_time);
318 TRACE_EVENT_WITH_FLOW1(
319 "CacheStorage", "CacheStorage::Keys::Callback",
320 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "key_list",
321 CacheStorageTracedValue(keys));
322 if (!resolver->GetExecutionContext() ||
323 resolver->GetExecutionContext()->IsContextDestroyed())
324 return;
325 resolver->Resolve(keys);
326 },
327 WrapPersistent(resolver), base::TimeTicks::Now(), trace_id));
328
329 return promise;
330 }
331
match(ScriptState * script_state,const RequestInfo & request,const MultiCacheQueryOptions * options,ExceptionState & exception_state)332 ScriptPromise CacheStorage::match(ScriptState* script_state,
333 const RequestInfo& request,
334 const MultiCacheQueryOptions* options,
335 ExceptionState& exception_state) {
336 DCHECK(!request.IsNull());
337
338 if (request.IsRequest())
339 return MatchImpl(script_state, request.GetAsRequest(), options);
340 Request* new_request =
341 Request::Create(script_state, request.GetAsUSVString(), exception_state);
342 if (exception_state.HadException())
343 return ScriptPromise();
344 return MatchImpl(script_state, new_request, options);
345 }
346
MatchImpl(ScriptState * script_state,const Request * request,const MultiCacheQueryOptions * options)347 ScriptPromise CacheStorage::MatchImpl(ScriptState* script_state,
348 const Request* request,
349 const MultiCacheQueryOptions* options) {
350 int64_t trace_id = blink::cache_storage::CreateTraceId();
351 mojom::blink::FetchAPIRequestPtr mojo_request =
352 request->CreateFetchAPIRequest();
353 mojom::blink::MultiCacheQueryOptionsPtr mojo_options =
354 mojom::blink::MultiCacheQueryOptions::From(options);
355
356 ExecutionContext* context = ExecutionContext::From(script_state);
357 bool in_related_fetch_event = false;
358 if (auto* global_scope = DynamicTo<ServiceWorkerGlobalScope>(context))
359 in_related_fetch_event = global_scope->HasRelatedFetchEvent(request->url());
360
361 TRACE_EVENT_WITH_FLOW2("CacheStorage", "CacheStorage::MatchImpl",
362 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT,
363 "request", CacheStorageTracedValue(mojo_request),
364 "options", CacheStorageTracedValue(mojo_options));
365
366 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
367 const ScriptPromise promise = resolver->Promise();
368
369 if (!IsAllowed(script_state)) {
370 resolver->Reject(
371 MakeGarbageCollected<DOMException>(DOMExceptionCode::kSecurityError));
372 return promise;
373 }
374
375 // The context may be destroyed and the mojo connection unbound. However the
376 // object may live on, reject any requests after the context is destroyed.
377 if (!cache_storage_remote_) {
378 resolver->Reject(MakeGarbageCollected<DOMException>(
379 DOMExceptionCode::kInvalidStateError));
380 return promise;
381 }
382
383 if (request->method() != http_names::kGET && !options->ignoreMethod()) {
384 resolver->Resolve();
385 return promise;
386 }
387
388 ever_used_ = true;
389
390 // Make sure to bind the CacheStorage object to keep the mojo interface
391 // pointer alive during the operation. Otherwise GC might prevent the
392 // callback from ever being executed.
393 cache_storage_remote_->Match(
394 std::move(mojo_request), std::move(mojo_options), in_related_fetch_event,
395 trace_id,
396 WTF::Bind(
397 [](ScriptPromiseResolver* resolver, base::TimeTicks start_time,
398 const MultiCacheQueryOptions* options, int64_t trace_id,
399 CacheStorage* self, mojom::blink::MatchResultPtr result) {
400 base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
401 if (!options->hasCacheName() || options->cacheName().IsEmpty()) {
402 UMA_HISTOGRAM_LONG_TIMES(
403 "ServiceWorkerCache.CacheStorage.Renderer.MatchAllCaches",
404 elapsed);
405 } else {
406 UMA_HISTOGRAM_LONG_TIMES(
407 "ServiceWorkerCache.CacheStorage.Renderer.MatchOneCache",
408 elapsed);
409 }
410 if (!resolver->GetExecutionContext() ||
411 resolver->GetExecutionContext()->IsContextDestroyed())
412 return;
413 if (result->is_status()) {
414 TRACE_EVENT_WITH_FLOW1(
415 "CacheStorage", "CacheStorage::MatchImpl::Callback",
416 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN, "status",
417 CacheStorageTracedValue(result->get_status()));
418 switch (result->get_status()) {
419 case mojom::CacheStorageError::kErrorNotFound:
420 case mojom::CacheStorageError::kErrorStorage:
421 case mojom::CacheStorageError::kErrorCacheNameNotFound:
422 resolver->Resolve();
423 break;
424 default:
425 RejectCacheStorageWithError(resolver, result->get_status());
426 break;
427 }
428 } else {
429 ScriptState::Scope scope(resolver->GetScriptState());
430 if (result->is_eager_response()) {
431 TRACE_EVENT_WITH_FLOW1(
432 "CacheStorage", "CacheStorage::MatchImpl::Callback",
433 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN,
434 "eager_response",
435 CacheStorageTracedValue(
436 result->get_eager_response()->response));
437 resolver->Resolve(
438 CreateEagerResponse(resolver->GetScriptState(),
439 std::move(result->get_eager_response()),
440 self->blob_client_list_));
441 } else {
442 TRACE_EVENT_WITH_FLOW1(
443 "CacheStorage", "CacheStorage::MatchImpl::Callback",
444 TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN,
445 "response",
446 CacheStorageTracedValue(result->get_response()));
447 resolver->Resolve(Response::Create(resolver->GetScriptState(),
448 *result->get_response()));
449 }
450 }
451 },
452 WrapPersistent(resolver), base::TimeTicks::Now(),
453 WrapPersistent(options), trace_id, WrapPersistent(this)));
454
455 return promise;
456 }
457
CacheStorage(ExecutionContext * context,GlobalFetch::ScopedFetcher * fetcher)458 CacheStorage::CacheStorage(ExecutionContext* context,
459 GlobalFetch::ScopedFetcher* fetcher)
460 : ExecutionContextLifecycleObserver(context),
461 scoped_fetcher_(fetcher),
462 blob_client_list_(MakeGarbageCollected<CacheStorageBlobClientList>()),
463 ever_used_(false) {
464 // See https://bit.ly/2S0zRAS for task types.
465 scoped_refptr<base::SingleThreadTaskRunner> task_runner =
466 context->GetTaskRunner(blink::TaskType::kMiscPlatformAPI);
467
468 // Service workers may already have a CacheStoragePtr provided as an
469 // optimization.
470 if (auto* service_worker = DynamicTo<ServiceWorkerGlobalScope>(context)) {
471 mojo::PendingRemote<mojom::blink::CacheStorage> info =
472 service_worker->TakeCacheStorage();
473 if (info) {
474 cache_storage_remote_ = mojo::Remote<mojom::blink::CacheStorage>(
475 std::move(info), task_runner);
476 return;
477 }
478 }
479
480 context->GetBrowserInterfaceBroker().GetInterface(
481 cache_storage_remote_.BindNewPipeAndPassReceiver(task_runner));
482 }
483
484 CacheStorage::~CacheStorage() = default;
485
HasPendingActivity() const486 bool CacheStorage::HasPendingActivity() const {
487 // Once the CacheStorage has been used once we keep it alive until the
488 // context goes away. This allows us to use the existence of this
489 // context as a hint to optimizations such as keeping backend disk_caches
490 // open in the browser process.
491 //
492 // Note, this also keeps the CacheStorage alive during active Cache and
493 // CacheStorage operations.
494 return ever_used_;
495 }
496
Trace(Visitor * visitor)497 void CacheStorage::Trace(Visitor* visitor) {
498 visitor->Trace(scoped_fetcher_);
499 visitor->Trace(blob_client_list_);
500 ScriptWrappable::Trace(visitor);
501 ExecutionContextLifecycleObserver::Trace(visitor);
502 }
503
IsAllowed(ScriptState * script_state)504 bool CacheStorage::IsAllowed(ScriptState* script_state) {
505 if (!allowed_.has_value()) {
506 // Cache the IsCacheStorageAllowed() because it triggers a sync IPC.
507 allowed_.emplace(IsCacheStorageAllowed(script_state));
508 }
509 return allowed_.value();
510 }
511
ContextDestroyed()512 void CacheStorage::ContextDestroyed() {
513 cache_storage_remote_.reset();
514 }
515
516 } // namespace blink
517