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