| // Copyright 2015 The Chromium Authors |
| // 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/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/bindings/v8_throw_exception.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::UsageBreakdownPtr; |
| |
| namespace { |
| |
| const char kUniqueOriginErrorMessage[] = |
| "The operation is not supported in this context."; |
| const char kGenericErrorMessage[] = |
| "Internal error when calculating storage usage."; |
| const char kAbortErrorMessage[] = "The operation was aborted due to shutdown."; |
| |
| void QueryStorageUsageAndQuotaCallback( |
| ScriptPromiseResolver<StorageEstimate>* resolver, |
| mojom::blink::QuotaStatusCode status_code, |
| int64_t usage_in_bytes, |
| int64_t quota_in_bytes, |
| UsageBreakdownPtr usage_breakdown) { |
| const char* error_message = nullptr; |
| switch (status_code) { |
| case mojom::blink::QuotaStatusCode::kOk: |
| break; |
| case mojom::blink::QuotaStatusCode::kErrorNotSupported: |
| case mojom::blink::QuotaStatusCode::kErrorInvalidModification: |
| case mojom::blink::QuotaStatusCode::kErrorInvalidAccess: |
| NOTREACHED(); |
| case mojom::blink::QuotaStatusCode::kUnknown: |
| error_message = kGenericErrorMessage; |
| break; |
| case mojom::blink::QuotaStatusCode::kErrorAbort: |
| error_message = kAbortErrorMessage; |
| break; |
| } |
| if (error_message) { |
| resolver->Reject(V8ThrowException::CreateTypeError( |
| resolver->GetScriptState()->GetIsolate(), error_message)); |
| 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) |
| : permission_service_(execution_context), quota_host_(execution_context) {} |
| |
| StorageManager::~StorageManager() = default; |
| |
| ScriptPromise<IDLBoolean> 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 EmptyPromise(); |
| } |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLBoolean>>( |
| script_state, exception_state.GetContext()); |
| auto promise = resolver->Promise(); |
| |
| GetPermissionService(window)->RequestPermission( |
| CreatePermissionDescriptor(PermissionName::DURABLE_STORAGE), |
| LocalFrame::HasTransientUserActivation(window->GetFrame()), |
| BindOnce(&StorageManager::PermissionRequestComplete, WrapPersistent(this), |
| WrapPersistent(resolver))); |
| |
| return promise; |
| } |
| |
| ScriptPromise<IDLBoolean> 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 EmptyPromise(); |
| } |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLBoolean>>( |
| script_state, exception_state.GetContext()); |
| auto promise = resolver->Promise(); |
| |
| GetPermissionService(ExecutionContext::From(script_state)) |
| ->HasPermission( |
| CreatePermissionDescriptor(PermissionName::DURABLE_STORAGE), |
| BindOnce(&StorageManager::PermissionRequestComplete, |
| WrapPersistent(this), WrapPersistent(resolver))); |
| return promise; |
| } |
| |
| ScriptPromise<StorageEstimate> 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 EmptyPromise(); |
| } |
| |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<StorageEstimate>>( |
| script_state, exception_state.GetContext()); |
| auto promise = resolver->Promise(); |
| |
| auto callback = resolver->WrapCallbackInScriptScope( |
| BindOnce(&QueryStorageUsageAndQuotaCallback)); |
| GetQuotaHost(execution_context) |
| ->QueryStorageUsageAndQuota(mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| std::move(callback), mojom::blink::QuotaStatusCode::kErrorAbort, 0, 0, |
| nullptr)); |
| return promise; |
| } |
| |
| void StorageManager::Trace(Visitor* visitor) const { |
| visitor->Trace(permission_service_); |
| visitor->Trace(quota_host_); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| 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( |
| BindOnce(&StorageManager::PermissionServiceConnectionError, |
| WrapWeakPersistent(this))); |
| } |
| return permission_service_.get(); |
| } |
| |
| void StorageManager::PermissionServiceConnectionError() { |
| permission_service_.reset(); |
| } |
| |
| void StorageManager::PermissionRequestComplete( |
| ScriptPromiseResolver<IDLBoolean>* resolver, |
| mojom::blink::PermissionStatus status) { |
| if (!resolver->GetExecutionContext() || |
| resolver->GetExecutionContext()->IsContextDestroyed()) |
| return; |
| resolver->Resolve(status == mojom::blink::PermissionStatus::GRANTED); |
| } |
| |
| 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(); |
| } |
| |
| } // namespace blink |