blob: 08cb59a00ea39a8f3ce92f75cc85c2d48da72775 [file] [log] [blame]
// Copyright 2017 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 "third_party/blink/renderer/modules/background_fetch/background_fetch_registration.h"
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/fetch/request.h"
#include "third_party/blink/renderer/core/fetch/response.h"
#include "third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h"
#include "third_party/blink/renderer/modules/background_fetch/background_fetch_record.h"
#include "third_party/blink/renderer/modules/cache_storage/cache.h"
#include "third_party/blink/renderer/modules/cache_storage/cache_query_options.h"
#include "third_party/blink/renderer/modules/event_target_modules_names.h"
#include "third_party/blink/renderer/modules/manifest/image_resource.h"
#include "third_party/blink/renderer/modules/service_worker/service_worker_registration.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
namespace blink {
BackgroundFetchRegistration::BackgroundFetchRegistration(
const String& developer_id,
uint64_t upload_total,
uint64_t uploaded,
uint64_t download_total,
uint64_t downloaded,
mojom::BackgroundFetchResult result,
mojom::BackgroundFetchFailureReason failure_reason)
: developer_id_(developer_id),
upload_total_(upload_total),
uploaded_(uploaded),
download_total_(download_total),
downloaded_(downloaded),
result_(result),
failure_reason_(failure_reason) {}
BackgroundFetchRegistration::BackgroundFetchRegistration(
ServiceWorkerRegistration* service_worker_registration,
mojom::blink::BackgroundFetchRegistrationPtr registration)
: developer_id_(registration->registration_data->developer_id),
upload_total_(registration->registration_data->upload_total),
uploaded_(registration->registration_data->uploaded),
download_total_(registration->registration_data->download_total),
downloaded_(registration->registration_data->downloaded),
result_(registration->registration_data->result),
failure_reason_(registration->registration_data->failure_reason) {
DCHECK(service_worker_registration);
Initialize(service_worker_registration,
std::move(registration->registration_interface));
}
BackgroundFetchRegistration::~BackgroundFetchRegistration() = default;
void BackgroundFetchRegistration::Initialize(
ServiceWorkerRegistration* registration,
mojo::PendingRemote<mojom::blink::BackgroundFetchRegistrationService>
registration_service) {
DCHECK(!registration_);
DCHECK(registration);
DCHECK(!registration_service_);
DCHECK(registration_service);
registration_ = registration;
registration_service_.Bind(std::move(registration_service));
ExecutionContext* context = GetExecutionContext();
if (!context || context->IsContextDestroyed())
return;
auto task_runner = context->GetTaskRunner(TaskType::kBackgroundFetch);
registration_service_->AddRegistrationObserver(
observer_receiver_.BindNewPipeAndPassRemote(task_runner));
}
void BackgroundFetchRegistration::OnProgress(
uint64_t upload_total,
uint64_t uploaded,
uint64_t download_total,
uint64_t downloaded,
mojom::BackgroundFetchResult result,
mojom::BackgroundFetchFailureReason failure_reason) {
upload_total_ = upload_total;
uploaded_ = uploaded;
download_total_ = download_total;
downloaded_ = downloaded;
result_ = result;
failure_reason_ = failure_reason;
ExecutionContext* context = GetExecutionContext();
if (!context || context->IsContextDestroyed())
return;
DCHECK(context->IsContextThread());
DispatchEvent(*Event::Create(event_type_names::kProgress));
}
void BackgroundFetchRegistration::OnRecordsUnavailable() {
records_available_ = false;
}
void BackgroundFetchRegistration::OnRequestCompleted(
mojom::blink::FetchAPIRequestPtr request,
mojom::blink::FetchAPIResponsePtr response) {
for (auto* it = observers_.begin(); it != observers_.end();) {
BackgroundFetchRecord* observer = it->Get();
if (observer->ObservedUrl() == request->url) {
observer->OnRequestCompleted(response->Clone());
it = observers_.erase(it);
} else {
it++;
}
}
}
String BackgroundFetchRegistration::id() const {
return developer_id_;
}
uint64_t BackgroundFetchRegistration::uploadTotal() const {
return upload_total_;
}
uint64_t BackgroundFetchRegistration::uploaded() const {
return uploaded_;
}
uint64_t BackgroundFetchRegistration::downloadTotal() const {
return download_total_;
}
uint64_t BackgroundFetchRegistration::downloaded() const {
return downloaded_;
}
bool BackgroundFetchRegistration::recordsAvailable() const {
return records_available_;
}
const AtomicString& BackgroundFetchRegistration::InterfaceName() const {
return event_target_names::kBackgroundFetchRegistration;
}
ExecutionContext* BackgroundFetchRegistration::GetExecutionContext() const {
DCHECK(registration_);
return registration_->GetExecutionContext();
}
ScriptPromise BackgroundFetchRegistration::abort(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
DCHECK(registration_);
DCHECK(registration_service_);
registration_service_->Abort(WTF::Bind(&BackgroundFetchRegistration::DidAbort,
WrapPersistent(this),
WrapPersistent(resolver)));
return promise;
}
ScriptPromise BackgroundFetchRegistration::match(
ScriptState* script_state,
const RequestOrUSVString& request,
const CacheQueryOptions* options,
ExceptionState& exception_state) {
return MatchImpl(
script_state, base::make_optional<RequestOrUSVString>(request),
mojom::blink::CacheQueryOptions::From(options), exception_state,
/* match_all = */ false);
}
ScriptPromise BackgroundFetchRegistration::matchAll(
ScriptState* script_state,
ExceptionState& exception_state) {
return MatchImpl(script_state, /* request = */ base::nullopt,
/* cache_query_options = */ nullptr, exception_state,
/* match_all = */ true);
}
ScriptPromise BackgroundFetchRegistration::matchAll(
ScriptState* script_state,
const RequestOrUSVString& request,
const CacheQueryOptions* options,
ExceptionState& exception_state) {
return MatchImpl(
script_state, base::make_optional<RequestOrUSVString>(request),
mojom::blink::CacheQueryOptions::From(options), exception_state,
/* match_all = */ true);
}
ScriptPromise BackgroundFetchRegistration::MatchImpl(
ScriptState* script_state,
base::Optional<RequestOrUSVString> request,
mojom::blink::CacheQueryOptionsPtr cache_query_options,
ExceptionState& exception_state,
bool match_all) {
DCHECK(script_state);
UMA_HISTOGRAM_BOOLEAN("BackgroundFetch.MatchCalledFromDocumentScope",
ExecutionContext::From(script_state)->IsDocument());
UMA_HISTOGRAM_BOOLEAN("BackgroundFetch.MatchCalledWhenFetchIsIncomplete",
result_ == mojom::BackgroundFetchResult::UNSET);
if (!records_available_) {
return ScriptPromise::RejectWithDOMException(
script_state,
MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError,
"The records associated with this background fetch are no longer "
"available."));
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
// Convert |request| to mojom::blink::FetchAPIRequestPtr.
mojom::blink::FetchAPIRequestPtr request_to_match;
if (request.has_value()) {
if (request->IsRequest()) {
request_to_match = request->GetAsRequest()->CreateFetchAPIRequest();
} else {
Request* new_request = Request::Create(
script_state, request->GetAsUSVString(), exception_state);
if (exception_state.HadException())
return ScriptPromise();
request_to_match = new_request->CreateFetchAPIRequest();
}
}
DCHECK(registration_);
DCHECK(registration_service_);
registration_service_->MatchRequests(
std::move(request_to_match), std::move(cache_query_options), match_all,
WTF::Bind(&BackgroundFetchRegistration::DidGetMatchingRequests,
WrapPersistent(this), WrapPersistent(resolver), match_all));
return promise;
}
void BackgroundFetchRegistration::DidGetMatchingRequests(
ScriptPromiseResolver* resolver,
bool return_all,
Vector<mojom::blink::BackgroundFetchSettledFetchPtr> settled_fetches) {
DCHECK(resolver);
ScriptState* script_state = resolver->GetScriptState();
// Do not remove this, |scope| is needed for calling ToV8()
ScriptState::Scope scope(script_state);
HeapVector<Member<BackgroundFetchRecord>> to_return;
to_return.ReserveInitialCapacity(settled_fetches.size());
for (auto& fetch : settled_fetches) {
Request* request =
Request::Create(script_state, *(fetch->request),
Request::ForServiceWorkerFetchEvent::kFalse);
auto* record =
MakeGarbageCollected<BackgroundFetchRecord>(request, script_state);
// If this request is incomplete, enlist this record to receive updates on
// the request.
if (fetch->response.is_null() && !IsAborted())
observers_.push_back(*record);
UpdateRecord(record, fetch->response);
to_return.push_back(record);
}
if (!return_all) {
if (settled_fetches.IsEmpty()) {
// Nothing was matched. Resolve with `undefined`.
resolver->Resolve();
return;
}
DCHECK_EQ(settled_fetches.size(), 1u);
DCHECK_EQ(to_return.size(), 1u);
resolver->Resolve(to_return[0]);
return;
}
resolver->Resolve(to_return);
}
void BackgroundFetchRegistration::UpdateRecord(
BackgroundFetchRecord* record,
mojom::blink::FetchAPIResponsePtr& response) {
DCHECK(record);
if (!record->IsRecordPending())
return;
// Per the spec, resolve with a valid response, if there is one available,
// even if the fetch has been aborted.
if (!response.is_null()) {
record->SetResponseAndUpdateState(response);
return;
}
if (IsAborted()) {
record->UpdateState(BackgroundFetchRecord::State::kAborted);
return;
}
if (result_ != mojom::blink::BackgroundFetchResult::UNSET)
record->UpdateState(BackgroundFetchRecord::State::kSettled);
}
bool BackgroundFetchRegistration::IsAborted() {
return failure_reason_ ==
mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI ||
failure_reason_ ==
mojom::BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER;
}
void BackgroundFetchRegistration::DidAbort(
ScriptPromiseResolver* resolver,
mojom::blink::BackgroundFetchError error) {
switch (error) {
case mojom::blink::BackgroundFetchError::NONE:
resolver->Resolve(/* success = */ true);
return;
case mojom::blink::BackgroundFetchError::INVALID_ID:
resolver->Resolve(/* success = */ false);
return;
case mojom::blink::BackgroundFetchError::STORAGE_ERROR:
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError,
"Failed to abort registration due to I/O error."));
return;
case mojom::blink::BackgroundFetchError::SERVICE_WORKER_UNAVAILABLE:
case mojom::blink::BackgroundFetchError::DUPLICATED_DEVELOPER_ID:
case mojom::blink::BackgroundFetchError::INVALID_ARGUMENT:
case mojom::blink::BackgroundFetchError::PERMISSION_DENIED:
case mojom::blink::BackgroundFetchError::QUOTA_EXCEEDED:
case mojom::blink::BackgroundFetchError::REGISTRATION_LIMIT_EXCEEDED:
// Not applicable for this callback.
break;
}
NOTREACHED();
}
const String BackgroundFetchRegistration::result() const {
switch (result_) {
case mojom::BackgroundFetchResult::SUCCESS:
return "success";
case mojom::BackgroundFetchResult::FAILURE:
return "failure";
case mojom::BackgroundFetchResult::UNSET:
return "";
}
NOTREACHED();
}
const String BackgroundFetchRegistration::failureReason() const {
switch (failure_reason_) {
case mojom::BackgroundFetchFailureReason::NONE:
return "";
case mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI:
case mojom::BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER:
return "aborted";
case mojom::BackgroundFetchFailureReason::BAD_STATUS:
return "bad-status";
case mojom::BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE:
case mojom::BackgroundFetchFailureReason::FETCH_ERROR:
return "fetch-error";
case mojom::BackgroundFetchFailureReason::QUOTA_EXCEEDED:
return "quota-exceeded";
case mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED:
return "download-total-exceeded";
}
NOTREACHED();
}
void BackgroundFetchRegistration::Dispose() {
observer_receiver_.reset();
}
bool BackgroundFetchRegistration::HasPendingActivity() const {
if (!GetExecutionContext())
return false;
if (GetExecutionContext()->IsContextDestroyed())
return false;
return !observers_.IsEmpty();
}
void BackgroundFetchRegistration::UpdateUI(
const String& in_title,
const SkBitmap& in_icon,
mojom::blink::BackgroundFetchRegistrationService::UpdateUICallback
callback) {
DCHECK(registration_service_);
registration_service_->UpdateUI(in_title, in_icon, std::move(callback));
}
void BackgroundFetchRegistration::Trace(Visitor* visitor) {
visitor->Trace(registration_);
visitor->Trace(observers_);
EventTargetWithInlineData::Trace(visitor);
ActiveScriptWrappable::Trace(visitor);
}
} // namespace blink