1 // Copyright 2017 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/background_fetch/background_fetch_registration.h"
6
7 #include <utility>
8
9 #include "base/metrics/histogram_macros.h"
10 #include "base/optional.h"
11 #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h"
12 #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
13 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
14 #include "third_party/blink/renderer/bindings/modules/v8/v8_cache_query_options.h"
15 #include "third_party/blink/renderer/bindings/modules/v8/v8_image_resource.h"
16 #include "third_party/blink/renderer/core/dom/dom_exception.h"
17 #include "third_party/blink/renderer/core/dom/events/event.h"
18 #include "third_party/blink/renderer/core/fetch/request.h"
19 #include "third_party/blink/renderer/core/fetch/response.h"
20 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
21 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h"
22 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_record.h"
23 #include "third_party/blink/renderer/modules/cache_storage/cache.h"
24 #include "third_party/blink/renderer/modules/event_target_modules_names.h"
25 #include "third_party/blink/renderer/modules/service_worker/service_worker_registration.h"
26 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
27 #include "third_party/blink/renderer/platform/bindings/script_state.h"
28 #include "third_party/blink/renderer/platform/heap/heap.h"
29 #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
30
31 namespace blink {
32
BackgroundFetchRegistration(ServiceWorkerRegistration * service_worker_registration,mojom::blink::BackgroundFetchRegistrationPtr registration)33 BackgroundFetchRegistration::BackgroundFetchRegistration(
34 ServiceWorkerRegistration* service_worker_registration,
35 mojom::blink::BackgroundFetchRegistrationPtr registration)
36 : developer_id_(registration->registration_data->developer_id),
37 upload_total_(registration->registration_data->upload_total),
38 uploaded_(registration->registration_data->uploaded),
39 download_total_(registration->registration_data->download_total),
40 downloaded_(registration->registration_data->downloaded),
41 result_(registration->registration_data->result),
42 failure_reason_(registration->registration_data->failure_reason),
43 observer_receiver_(this,
44 service_worker_registration->GetExecutionContext()) {
45 DCHECK(service_worker_registration);
46 registration_ = service_worker_registration;
47 registration_service_.Bind(std::move(registration->registration_interface));
48
49 ExecutionContext* context = GetExecutionContext();
50 if (!context || context->IsContextDestroyed())
51 return;
52
53 auto task_runner = context->GetTaskRunner(TaskType::kBackgroundFetch);
54 registration_service_->AddRegistrationObserver(
55 observer_receiver_.BindNewPipeAndPassRemote(task_runner));
56 }
57
58 BackgroundFetchRegistration::~BackgroundFetchRegistration() = default;
59
OnProgress(uint64_t upload_total,uint64_t uploaded,uint64_t download_total,uint64_t downloaded,mojom::BackgroundFetchResult result,mojom::BackgroundFetchFailureReason failure_reason)60 void BackgroundFetchRegistration::OnProgress(
61 uint64_t upload_total,
62 uint64_t uploaded,
63 uint64_t download_total,
64 uint64_t downloaded,
65 mojom::BackgroundFetchResult result,
66 mojom::BackgroundFetchFailureReason failure_reason) {
67 upload_total_ = upload_total;
68 uploaded_ = uploaded;
69 download_total_ = download_total;
70 downloaded_ = downloaded;
71 result_ = result;
72 failure_reason_ = failure_reason;
73
74 ExecutionContext* context = GetExecutionContext();
75 if (!context || context->IsContextDestroyed())
76 return;
77
78 DCHECK(context->IsContextThread());
79 DispatchEvent(*Event::Create(event_type_names::kProgress));
80 }
81
OnRecordsUnavailable()82 void BackgroundFetchRegistration::OnRecordsUnavailable() {
83 records_available_ = false;
84 }
85
OnRequestCompleted(mojom::blink::FetchAPIRequestPtr request,mojom::blink::FetchAPIResponsePtr response)86 void BackgroundFetchRegistration::OnRequestCompleted(
87 mojom::blink::FetchAPIRequestPtr request,
88 mojom::blink::FetchAPIResponsePtr response) {
89 for (auto* it = observers_.begin(); it != observers_.end();) {
90 BackgroundFetchRecord* observer = it->Get();
91 if (observer->ObservedUrl() == request->url) {
92 observer->OnRequestCompleted(response->Clone());
93 it = observers_.erase(it);
94 } else {
95 it++;
96 }
97 }
98 }
99
id() const100 String BackgroundFetchRegistration::id() const {
101 return developer_id_;
102 }
103
uploadTotal() const104 uint64_t BackgroundFetchRegistration::uploadTotal() const {
105 return upload_total_;
106 }
107
uploaded() const108 uint64_t BackgroundFetchRegistration::uploaded() const {
109 return uploaded_;
110 }
111
downloadTotal() const112 uint64_t BackgroundFetchRegistration::downloadTotal() const {
113 return download_total_;
114 }
115
downloaded() const116 uint64_t BackgroundFetchRegistration::downloaded() const {
117 return downloaded_;
118 }
119
recordsAvailable() const120 bool BackgroundFetchRegistration::recordsAvailable() const {
121 return records_available_;
122 }
123
InterfaceName() const124 const AtomicString& BackgroundFetchRegistration::InterfaceName() const {
125 return event_target_names::kBackgroundFetchRegistration;
126 }
127
GetExecutionContext() const128 ExecutionContext* BackgroundFetchRegistration::GetExecutionContext() const {
129 DCHECK(registration_);
130 return registration_->GetExecutionContext();
131 }
132
abort(ScriptState * script_state)133 ScriptPromise BackgroundFetchRegistration::abort(ScriptState* script_state) {
134 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
135 ScriptPromise promise = resolver->Promise();
136
137 DCHECK(registration_);
138 DCHECK(registration_service_);
139
140 registration_service_->Abort(WTF::Bind(&BackgroundFetchRegistration::DidAbort,
141 WrapPersistent(this),
142 WrapPersistent(resolver)));
143
144 return promise;
145 }
146
match(ScriptState * script_state,const RequestOrUSVString & request,const CacheQueryOptions * options,ExceptionState & exception_state)147 ScriptPromise BackgroundFetchRegistration::match(
148 ScriptState* script_state,
149 const RequestOrUSVString& request,
150 const CacheQueryOptions* options,
151 ExceptionState& exception_state) {
152 return MatchImpl(
153 script_state, base::make_optional<RequestOrUSVString>(request),
154 mojom::blink::CacheQueryOptions::From(options), exception_state,
155 /* match_all = */ false);
156 }
157
matchAll(ScriptState * script_state,ExceptionState & exception_state)158 ScriptPromise BackgroundFetchRegistration::matchAll(
159 ScriptState* script_state,
160 ExceptionState& exception_state) {
161 return MatchImpl(script_state, /* request = */ base::nullopt,
162 /* cache_query_options = */ nullptr, exception_state,
163 /* match_all = */ true);
164 }
165
matchAll(ScriptState * script_state,const RequestOrUSVString & request,const CacheQueryOptions * options,ExceptionState & exception_state)166 ScriptPromise BackgroundFetchRegistration::matchAll(
167 ScriptState* script_state,
168 const RequestOrUSVString& request,
169 const CacheQueryOptions* options,
170 ExceptionState& exception_state) {
171 return MatchImpl(
172 script_state, base::make_optional<RequestOrUSVString>(request),
173 mojom::blink::CacheQueryOptions::From(options), exception_state,
174 /* match_all = */ true);
175 }
176
MatchImpl(ScriptState * script_state,base::Optional<RequestOrUSVString> request,mojom::blink::CacheQueryOptionsPtr cache_query_options,ExceptionState & exception_state,bool match_all)177 ScriptPromise BackgroundFetchRegistration::MatchImpl(
178 ScriptState* script_state,
179 base::Optional<RequestOrUSVString> request,
180 mojom::blink::CacheQueryOptionsPtr cache_query_options,
181 ExceptionState& exception_state,
182 bool match_all) {
183 DCHECK(script_state);
184 UMA_HISTOGRAM_BOOLEAN("BackgroundFetch.MatchCalledFromDocumentScope",
185 LocalDOMWindow::From(script_state));
186 UMA_HISTOGRAM_BOOLEAN("BackgroundFetch.MatchCalledWhenFetchIsIncomplete",
187 result_ == mojom::BackgroundFetchResult::UNSET);
188
189 if (!records_available_) {
190 exception_state.ThrowDOMException(
191 DOMExceptionCode::kInvalidStateError,
192 "The records associated with this background fetch are no longer "
193 "available.");
194 return ScriptPromise();
195 }
196
197 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
198 ScriptPromise promise = resolver->Promise();
199
200 // Convert |request| to mojom::blink::FetchAPIRequestPtr.
201 mojom::blink::FetchAPIRequestPtr request_to_match;
202 if (request.has_value()) {
203 if (request->IsRequest()) {
204 request_to_match = request->GetAsRequest()->CreateFetchAPIRequest();
205 } else {
206 Request* new_request = Request::Create(
207 script_state, request->GetAsUSVString(), exception_state);
208 if (exception_state.HadException())
209 return ScriptPromise();
210 request_to_match = new_request->CreateFetchAPIRequest();
211 }
212 }
213
214 DCHECK(registration_);
215 DCHECK(registration_service_);
216
217 registration_service_->MatchRequests(
218 std::move(request_to_match), std::move(cache_query_options), match_all,
219 WTF::Bind(&BackgroundFetchRegistration::DidGetMatchingRequests,
220 WrapPersistent(this), WrapPersistent(resolver), match_all));
221
222 return promise;
223 }
224
DidGetMatchingRequests(ScriptPromiseResolver * resolver,bool return_all,Vector<mojom::blink::BackgroundFetchSettledFetchPtr> settled_fetches)225 void BackgroundFetchRegistration::DidGetMatchingRequests(
226 ScriptPromiseResolver* resolver,
227 bool return_all,
228 Vector<mojom::blink::BackgroundFetchSettledFetchPtr> settled_fetches) {
229 DCHECK(resolver);
230
231 ScriptState* script_state = resolver->GetScriptState();
232 // Do not remove this, |scope| is needed for calling ToV8()
233 ScriptState::Scope scope(script_state);
234 HeapVector<Member<BackgroundFetchRecord>> to_return;
235 to_return.ReserveInitialCapacity(settled_fetches.size());
236
237 for (auto& fetch : settled_fetches) {
238 Request* request =
239 Request::Create(script_state, std::move(fetch->request),
240 Request::ForServiceWorkerFetchEvent::kFalse);
241 auto* record =
242 MakeGarbageCollected<BackgroundFetchRecord>(request, script_state);
243
244 // If this request is incomplete, enlist this record to receive updates on
245 // the request.
246 if (fetch->response.is_null() && !IsAborted())
247 observers_.push_back(*record);
248
249 UpdateRecord(record, fetch->response);
250 to_return.push_back(record);
251 }
252
253 if (!return_all) {
254 if (settled_fetches.IsEmpty()) {
255 // Nothing was matched. Resolve with `undefined`.
256 resolver->Resolve();
257 return;
258 }
259
260 DCHECK_EQ(settled_fetches.size(), 1u);
261 DCHECK_EQ(to_return.size(), 1u);
262 resolver->Resolve(to_return[0]);
263 return;
264 }
265
266 resolver->Resolve(to_return);
267 }
268
UpdateRecord(BackgroundFetchRecord * record,mojom::blink::FetchAPIResponsePtr & response)269 void BackgroundFetchRegistration::UpdateRecord(
270 BackgroundFetchRecord* record,
271 mojom::blink::FetchAPIResponsePtr& response) {
272 DCHECK(record);
273
274 if (!record->IsRecordPending())
275 return;
276
277 // Per the spec, resolve with a valid response, if there is one available,
278 // even if the fetch has been aborted.
279 if (!response.is_null()) {
280 record->SetResponseAndUpdateState(response);
281 return;
282 }
283
284 if (IsAborted()) {
285 record->UpdateState(BackgroundFetchRecord::State::kAborted);
286 return;
287 }
288
289 if (result_ != mojom::blink::BackgroundFetchResult::UNSET)
290 record->UpdateState(BackgroundFetchRecord::State::kSettled);
291 }
292
IsAborted()293 bool BackgroundFetchRegistration::IsAborted() {
294 return failure_reason_ ==
295 mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI ||
296 failure_reason_ ==
297 mojom::BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER;
298 }
299
DidAbort(ScriptPromiseResolver * resolver,mojom::blink::BackgroundFetchError error)300 void BackgroundFetchRegistration::DidAbort(
301 ScriptPromiseResolver* resolver,
302 mojom::blink::BackgroundFetchError error) {
303 switch (error) {
304 case mojom::blink::BackgroundFetchError::NONE:
305 resolver->Resolve(/* success = */ true);
306 return;
307 case mojom::blink::BackgroundFetchError::INVALID_ID:
308 resolver->Resolve(/* success = */ false);
309 return;
310 case mojom::blink::BackgroundFetchError::STORAGE_ERROR:
311 resolver->Reject(MakeGarbageCollected<DOMException>(
312 DOMExceptionCode::kAbortError,
313 "Failed to abort registration due to I/O error."));
314 return;
315 case mojom::blink::BackgroundFetchError::SERVICE_WORKER_UNAVAILABLE:
316 case mojom::blink::BackgroundFetchError::DUPLICATED_DEVELOPER_ID:
317 case mojom::blink::BackgroundFetchError::INVALID_ARGUMENT:
318 case mojom::blink::BackgroundFetchError::PERMISSION_DENIED:
319 case mojom::blink::BackgroundFetchError::QUOTA_EXCEEDED:
320 case mojom::blink::BackgroundFetchError::REGISTRATION_LIMIT_EXCEEDED:
321 // Not applicable for this callback.
322 break;
323 }
324
325 NOTREACHED();
326 }
327
result() const328 const String BackgroundFetchRegistration::result() const {
329 switch (result_) {
330 case mojom::BackgroundFetchResult::SUCCESS:
331 return "success";
332 case mojom::BackgroundFetchResult::FAILURE:
333 return "failure";
334 case mojom::BackgroundFetchResult::UNSET:
335 return "";
336 }
337 NOTREACHED();
338 }
339
failureReason() const340 const String BackgroundFetchRegistration::failureReason() const {
341 blink::IdentifiabilityMetricBuilder(GetExecutionContext()->UkmSourceID())
342 .Set(
343 blink::IdentifiableSurface::FromTypeAndToken(
344 blink::IdentifiableSurface::Type::kWebFeature,
345 WebFeature::
346 kV8BackgroundFetchRegistration_FailureReason_AttributeGetter),
347 failure_reason_ ==
348 mojom::BackgroundFetchFailureReason::QUOTA_EXCEEDED)
349 .Record(GetExecutionContext()->UkmRecorder());
350 switch (failure_reason_) {
351 case mojom::BackgroundFetchFailureReason::NONE:
352 return "";
353 case mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI:
354 case mojom::BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER:
355 return "aborted";
356 case mojom::BackgroundFetchFailureReason::BAD_STATUS:
357 return "bad-status";
358 case mojom::BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE:
359 case mojom::BackgroundFetchFailureReason::FETCH_ERROR:
360 return "fetch-error";
361 case mojom::BackgroundFetchFailureReason::QUOTA_EXCEEDED:
362 return "quota-exceeded";
363 case mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED:
364 return "download-total-exceeded";
365 }
366 NOTREACHED();
367 }
368
HasPendingActivity() const369 bool BackgroundFetchRegistration::HasPendingActivity() const {
370 if (!GetExecutionContext())
371 return false;
372 if (GetExecutionContext()->IsContextDestroyed())
373 return false;
374
375 return !observers_.IsEmpty();
376 }
377
UpdateUI(const String & in_title,const SkBitmap & in_icon,mojom::blink::BackgroundFetchRegistrationService::UpdateUICallback callback)378 void BackgroundFetchRegistration::UpdateUI(
379 const String& in_title,
380 const SkBitmap& in_icon,
381 mojom::blink::BackgroundFetchRegistrationService::UpdateUICallback
382 callback) {
383 DCHECK(registration_service_);
384 registration_service_->UpdateUI(in_title, in_icon, std::move(callback));
385 }
386
Trace(Visitor * visitor) const387 void BackgroundFetchRegistration::Trace(Visitor* visitor) const {
388 visitor->Trace(registration_);
389 visitor->Trace(observers_);
390 visitor->Trace(observer_receiver_);
391 EventTargetWithInlineData::Trace(visitor);
392 ActiveScriptWrappable::Trace(visitor);
393 }
394
395 } // namespace blink
396