blob: 011483bdb8f4f761f1fbec22865489846df5c21a [file] [log] [blame]
// Copyright 2015 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/quota/storage_manager.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_storage_estimate.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_storage_usage_details.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/event_type_names.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/frame.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/modules/permissions/permission_utils.h"
#include "third_party/blink/renderer/modules/quota/quota_utils.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
using mojom::blink::PermissionName;
using mojom::blink::PermissionService;
using mojom::blink::PermissionStatus;
using mojom::blink::UsageBreakdownPtr;
namespace {
const char kUniqueOriginErrorMessage[] =
"The operation is not supported in this context.";
void QueryStorageUsageAndQuotaCallback(
ScriptPromiseResolver* resolver,
mojom::blink::QuotaStatusCode status_code,
int64_t usage_in_bytes,
int64_t quota_in_bytes,
UsageBreakdownPtr usage_breakdown) {
if (status_code != mojom::blink::QuotaStatusCode::kOk) {
// TODO(sashab): Replace this with a switch statement, and remove the enum
// values from QuotaStatusCode.
// TODO(crbug.com/1293949): Add an error message.
resolver->Reject(V8ThrowDOMException::CreateOrDie(
resolver->GetScriptState()->GetIsolate(),
static_cast<DOMExceptionCode>(status_code), ""));
return;
}
StorageEstimate* estimate = StorageEstimate::Create();
estimate->setUsage(usage_in_bytes);
estimate->setQuota(quota_in_bytes);
// We only want to show usage details for systems that are used by the app,
// this way we do not create any web compatibility issues by unecessarily
// exposing obsoleted/proprietary storage systems, but also report when
// those systems are in use.
StorageUsageDetails* details = StorageUsageDetails::Create();
if (usage_breakdown->indexedDatabase) {
details->setIndexedDB(usage_breakdown->indexedDatabase);
}
if (usage_breakdown->serviceWorkerCache) {
details->setCaches(usage_breakdown->serviceWorkerCache);
}
if (usage_breakdown->serviceWorker) {
details->setServiceWorkerRegistrations(usage_breakdown->serviceWorker);
}
if (usage_breakdown->fileSystem) {
details->setFileSystem(usage_breakdown->fileSystem);
}
estimate->setUsageDetails(details);
resolver->Resolve(estimate);
}
} // namespace
StorageManager::StorageManager(ExecutionContext* execution_context)
: ExecutionContextClient(execution_context),
permission_service_(execution_context),
quota_host_(execution_context),
change_listener_receiver_(this, execution_context) {}
StorageManager::~StorageManager() = default;
ScriptPromise StorageManager::persist(ScriptState* script_state,
ExceptionState& exception_state) {
LocalDOMWindow* window = LocalDOMWindow::From(script_state);
DCHECK(window->IsSecureContext()); // [SecureContext] in IDL
if (window->GetSecurityOrigin()->IsOpaque()) {
exception_state.ThrowTypeError(kUniqueOriginErrorMessage);
return ScriptPromise();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
GetPermissionService(window)->RequestPermission(
CreatePermissionDescriptor(PermissionName::DURABLE_STORAGE),
LocalFrame::HasTransientUserActivation(window->GetFrame()),
WTF::Bind(&StorageManager::PermissionRequestComplete,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
ScriptPromise StorageManager::persisted(ScriptState* script_state,
ExceptionState& exception_state) {
ExecutionContext* execution_context = ExecutionContext::From(script_state);
DCHECK(execution_context->IsSecureContext()); // [SecureContext] in IDL
const SecurityOrigin* security_origin =
execution_context->GetSecurityOrigin();
if (security_origin->IsOpaque()) {
exception_state.ThrowTypeError(kUniqueOriginErrorMessage);
return ScriptPromise();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
GetPermissionService(ExecutionContext::From(script_state))
->HasPermission(
CreatePermissionDescriptor(PermissionName::DURABLE_STORAGE),
WTF::Bind(&StorageManager::PermissionRequestComplete,
WrapPersistent(this), WrapPersistent(resolver)));
return promise;
}
ScriptPromise StorageManager::estimate(ScriptState* script_state,
ExceptionState& exception_state) {
ExecutionContext* execution_context = ExecutionContext::From(script_state);
DCHECK(execution_context->IsSecureContext()); // [SecureContext] in IDL
// The BlinkIDL definition for estimate() already has a [MeasureAs] attribute,
// so the kQuotaRead use counter must be explicitly updated.
UseCounter::Count(execution_context, WebFeature::kQuotaRead);
const SecurityOrigin* security_origin =
execution_context->GetSecurityOrigin();
if (security_origin->IsOpaque()) {
exception_state.ThrowTypeError(kUniqueOriginErrorMessage);
return ScriptPromise();
}
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
auto callback = resolver->WrapCallbackInScriptScope(
WTF::Bind(&QueryStorageUsageAndQuotaCallback));
GetQuotaHost(execution_context)
->QueryStorageUsageAndQuota(
mojom::blink::StorageType::kTemporary,
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), mojom::blink::QuotaStatusCode::kErrorAbort,
0, 0, nullptr));
return promise;
}
void StorageManager::Trace(Visitor* visitor) const {
visitor->Trace(change_listener_receiver_);
visitor->Trace(permission_service_);
visitor->Trace(quota_host_);
EventTargetWithInlineData::Trace(visitor);
ExecutionContextClient::Trace(visitor);
ScriptWrappable::Trace(visitor);
}
const AtomicString& StorageManager::InterfaceName() const {
return event_type_names::kQuotachange;
}
ExecutionContext* StorageManager::GetExecutionContext() const {
return ExecutionContextClient::GetExecutionContext();
}
void StorageManager::OnQuotaChange() {
DispatchEvent(*Event::Create(event_type_names::kQuotachange));
}
void StorageManager::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
if (!quota_host_.is_bound()) {
ExecutionContext* execution_context = GetExecutionContext();
if (!execution_context)
return;
// This method will bind quota_host_.
GetQuotaHost(execution_context);
}
EventTargetWithInlineData::AddedEventListener(event_type,
registered_listener);
StartObserving();
}
void StorageManager::RemovedEventListener(
const AtomicString& event_type,
const RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::RemovedEventListener(event_type,
registered_listener);
if (!HasEventListeners())
StopObserving();
}
PermissionService* StorageManager::GetPermissionService(
ExecutionContext* execution_context) {
if (!permission_service_.is_bound()) {
ConnectToPermissionService(
execution_context,
permission_service_.BindNewPipeAndPassReceiver(
execution_context->GetTaskRunner(TaskType::kMiscPlatformAPI)));
permission_service_.set_disconnect_handler(
WTF::Bind(&StorageManager::PermissionServiceConnectionError,
WrapWeakPersistent(this)));
}
return permission_service_.get();
}
void StorageManager::PermissionServiceConnectionError() {
permission_service_.reset();
}
void StorageManager::PermissionRequestComplete(ScriptPromiseResolver* resolver,
PermissionStatus status) {
if (!resolver->GetExecutionContext() ||
resolver->GetExecutionContext()->IsContextDestroyed())
return;
resolver->Resolve(status == PermissionStatus::GRANTED);
}
void StorageManager::StartObserving() {
if (change_listener_receiver_.is_bound() || !quota_host_.is_bound())
return;
ExecutionContext* execution_context = GetExecutionContext();
if (!execution_context)
return;
// Using kMiscPlatformAPI because the Storage specification does not
// specify a dedicated task queue yet.
auto task_runner =
execution_context->GetTaskRunner(TaskType::kMiscPlatformAPI);
quota_host_->AddChangeListener(
change_listener_receiver_.BindNewPipeAndPassRemote(task_runner), {});
}
void StorageManager::StopObserving() {
if (!change_listener_receiver_.is_bound())
return;
change_listener_receiver_.reset();
}
mojom::blink::QuotaManagerHost* StorageManager::GetQuotaHost(
ExecutionContext* execution_context) {
if (!quota_host_.is_bound()) {
ConnectToQuotaManagerHost(
execution_context,
quota_host_.BindNewPipeAndPassReceiver(
execution_context->GetTaskRunner(TaskType::kMiscPlatformAPI)));
}
return quota_host_.get();
}
STATIC_ASSERT_ENUM(mojom::blink::QuotaStatusCode::kErrorNotSupported,
DOMExceptionCode::kNotSupportedError);
STATIC_ASSERT_ENUM(mojom::blink::QuotaStatusCode::kErrorInvalidModification,
DOMExceptionCode::kInvalidModificationError);
STATIC_ASSERT_ENUM(mojom::blink::QuotaStatusCode::kErrorInvalidAccess,
DOMExceptionCode::kInvalidAccessError);
STATIC_ASSERT_ENUM(mojom::blink::QuotaStatusCode::kErrorAbort,
DOMExceptionCode::kAbortError);
} // namespace blink