blob: d445554b02b9eb05f38e016198805340325bd033 [file] [log] [blame]
// Copyright 2023 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/storage_access/storage_access_handle.h"
#include "base/types/pass_key.h"
#include "third_party/blink/public/common/features.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/fileapi/blob.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/workers/shared_worker.h"
#include "third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.h"
#include "third_party/blink/renderer/modules/file_system_access/storage_manager_file_system_access.h"
#include "third_party/blink/renderer/modules/storage/storage_controller.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
namespace blink {
using PassKey = base::PassKey<StorageAccessHandle>;
// static
const char StorageAccessHandle::kSupplementName[] = "StorageAccessHandle";
// static
const char StorageAccessHandle::kSessionStorageNotRequested[] =
"Session storage not requested when storage access handle was initialized.";
// static
const char StorageAccessHandle::kLocalStorageNotRequested[] =
"Local storage not requested when storage access handle was initialized.";
// static
const char StorageAccessHandle::kIndexedDBNotRequested[] =
"IndexedDB not requested when storage access handle was initialized.";
// static
const char StorageAccessHandle::kLocksNotRequested[] =
"Web Locks not requested when storage access handle was initialized.";
// static
const char StorageAccessHandle::kCachesNotRequested[] =
"Cache Storage not requested when storage access handle was initialized.";
// static
const char StorageAccessHandle::kGetDirectoryNotRequested[] =
"Origin Private File System not requested when storage access handle was "
"initialized.";
// static
const char StorageAccessHandle::kEstimateNotRequested[] =
"The estimate function for Quota was not requested when storage access "
"handle was initialized.";
// static
const char StorageAccessHandle::kCreateObjectURLNotRequested[] =
"The createObjectURL function for Blob Stoage was not requested when "
"storage access handle was initialized.";
// static
const char StorageAccessHandle::kRevokeObjectURLNotRequested[] =
"The revokeObjectURL function for Blob Stoage was not requested when "
"storage access handle was initialized.";
// static
const char StorageAccessHandle::kBroadcastChannelNotRequested[] =
"Broadcast Channel was not requested when storage access handle was "
"initialized.";
// static
const char StorageAccessHandle::kSharedWorkerNotRequested[] =
"Shared Worker was not requested when storage access handle was "
"initialized.";
namespace {
void EstimateImplAfterRemoteEstimate(
ScriptPromiseResolverTyped<StorageEstimate>* resolver,
int64_t current_usage,
int64_t current_quota,
bool success) {
ScriptState* script_state = resolver->GetScriptState();
if (!script_state->ContextIsValid()) {
return;
}
ScriptState::Scope scope(script_state);
if (!success) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kUnknownError,
"Unknown error occurred while getting estimate."));
return;
}
StorageEstimate* estimate = StorageEstimate::Create();
estimate->setUsage(current_usage);
estimate->setQuota(current_quota);
estimate->setUsageDetails(StorageUsageDetails::Create());
resolver->Resolve(estimate);
}
} // namespace
StorageAccessHandle::StorageAccessHandle(
LocalDOMWindow& window,
const StorageAccessTypes* storage_access_types)
: Supplement<LocalDOMWindow>(window),
storage_access_types_(storage_access_types),
remote_(window.GetExecutionContext()),
broadcast_channel_(window.GetExecutionContext()),
shared_worker_(window.GetExecutionContext()) {
window.CountUse(
WebFeature::kStorageAccessAPI_requestStorageAccess_BeyondCookies);
if (storage_access_types_->all()) {
window.CountUse(
WebFeature::kStorageAccessAPI_requestStorageAccess_BeyondCookies_all);
}
if (storage_access_types_->cookies()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_cookies);
}
if (storage_access_types_->sessionStorage()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_sessionStorage);
}
if (storage_access_types_->localStorage()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_localStorage);
}
if (storage_access_types_->indexedDB()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_indexedDB);
}
if (storage_access_types_->locks()) {
window.CountUse(
WebFeature::kStorageAccessAPI_requestStorageAccess_BeyondCookies_locks);
}
if (storage_access_types_->caches()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_caches);
}
if (storage_access_types_->getDirectory()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_getDirectory);
}
if (storage_access_types_->estimate()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_estimate);
}
if (storage_access_types_->createObjectURL()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_createObjectURL);
}
if (storage_access_types_->revokeObjectURL()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_revokeObjectURL);
}
if (storage_access_types_->broadcastChannel()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_BroadcastChannel);
}
if (storage_access_types_->sharedWorker()) {
window.CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_SharedWorker);
}
if (storage_access_types_->all() || storage_access_types_->sessionStorage()) {
InitSessionStorage();
}
if (storage_access_types_->all() || storage_access_types_->localStorage()) {
InitLocalStorage();
}
if (storage_access_types_->all() || storage_access_types_->indexedDB()) {
InitIndexedDB();
}
if (storage_access_types_->all() || storage_access_types_->locks()) {
InitLocks();
}
if (storage_access_types_->all() || storage_access_types_->caches()) {
InitCaches();
}
if (storage_access_types_->all() || storage_access_types_->getDirectory()) {
InitGetDirectory();
}
if (storage_access_types_->all() || storage_access_types_->estimate()) {
InitQuota();
}
if (storage_access_types_->all() ||
storage_access_types_->createObjectURL() ||
storage_access_types_->revokeObjectURL() ||
storage_access_types_->sharedWorker()) {
InitBlobStorage();
}
if (storage_access_types_->all() ||
storage_access_types_->broadcastChannel()) {
InitBroadcastChannel();
}
if (storage_access_types_->all() || storage_access_types_->sharedWorker()) {
InitSharedWorker();
}
}
void StorageAccessHandle::Trace(Visitor* visitor) const {
visitor->Trace(storage_access_types_);
visitor->Trace(session_storage_);
visitor->Trace(local_storage_);
visitor->Trace(remote_);
visitor->Trace(indexed_db_);
visitor->Trace(locks_);
visitor->Trace(caches_);
visitor->Trace(blob_storage_);
visitor->Trace(broadcast_channel_);
visitor->Trace(shared_worker_);
ScriptWrappable::Trace(visitor);
Supplement<LocalDOMWindow>::Trace(visitor);
}
StorageArea* StorageAccessHandle::sessionStorage(
ExceptionState& exception_state) const {
if (!storage_access_types_->all() &&
!storage_access_types_->sessionStorage()) {
exception_state.ThrowSecurityError(kSessionStorageNotRequested);
return nullptr;
}
LocalDOMWindow* window = GetSupplementable();
window->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_sessionStorage_Use);
if (!session_storage_) {
return nullptr;
}
if (window->GetSecurityOrigin()->IsLocal()) {
window->CountUse(WebFeature::kFileAccessedSessionStorage);
}
if (!session_storage_->CanAccessStorage()) {
exception_state.ThrowSecurityError(StorageArea::kAccessDeniedMessage);
return nullptr;
}
return session_storage_;
}
StorageArea* StorageAccessHandle::localStorage(
ExceptionState& exception_state) const {
if (!storage_access_types_->all() && !storage_access_types_->localStorage()) {
exception_state.ThrowSecurityError(kLocalStorageNotRequested);
return nullptr;
}
LocalDOMWindow* window = GetSupplementable();
window->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_localStorage_Use);
if (!local_storage_) {
return nullptr;
}
if (window->GetSecurityOrigin()->IsLocal()) {
window->CountUse(WebFeature::kFileAccessedLocalStorage);
}
if (!local_storage_->CanAccessStorage()) {
exception_state.ThrowSecurityError(StorageArea::kAccessDeniedMessage);
return nullptr;
}
return local_storage_;
}
IDBFactory* StorageAccessHandle::indexedDB(
ExceptionState& exception_state) const {
if (!storage_access_types_->all() && !storage_access_types_->indexedDB()) {
exception_state.ThrowSecurityError(kIndexedDBNotRequested);
return nullptr;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_indexedDB_Use);
return indexed_db_;
}
LockManager* StorageAccessHandle::locks(ExceptionState& exception_state) const {
if (!storage_access_types_->all() && !storage_access_types_->locks()) {
exception_state.ThrowSecurityError(kLocksNotRequested);
return nullptr;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_locks_Use);
return locks_;
}
CacheStorage* StorageAccessHandle::caches(
ExceptionState& exception_state) const {
if (!storage_access_types_->all() && !storage_access_types_->caches()) {
exception_state.ThrowSecurityError(kCachesNotRequested);
return nullptr;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_caches_Use);
return caches_;
}
ScriptPromiseTyped<FileSystemDirectoryHandle> StorageAccessHandle::getDirectory(
ScriptState* script_state,
ExceptionState& exception_state) const {
if (!storage_access_types_->all() && !storage_access_types_->getDirectory()) {
auto* resolver = MakeGarbageCollected<
ScriptPromiseResolverTyped<FileSystemDirectoryHandle>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
resolver->RejectWithSecurityError(kGetDirectoryNotRequested,
kGetDirectoryNotRequested);
return promise;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_getDirectory_Use);
return StorageManagerFileSystemAccess::CheckGetDirectoryIsAllowed(
script_state, exception_state,
WTF::BindOnce(&StorageAccessHandle::GetDirectoryImpl,
WrapWeakPersistent(this)));
}
void StorageAccessHandle::GetDirectoryImpl(
ScriptPromiseResolverTyped<FileSystemDirectoryHandle>* resolver) const {
if (!remote_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError));
return;
}
remote_->GetDirectory(
WTF::BindOnce(&StorageManagerFileSystemAccess::DidGetSandboxedFileSystem,
WrapPersistent(resolver)));
}
ScriptPromiseTyped<StorageEstimate> StorageAccessHandle::estimate(
ScriptState* script_state,
ExceptionState& exception_state) const {
auto* resolver =
MakeGarbageCollected<ScriptPromiseResolverTyped<StorageEstimate>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
if (!storage_access_types_->all() && !storage_access_types_->estimate()) {
resolver->RejectWithSecurityError(kEstimateNotRequested,
kEstimateNotRequested);
return promise;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_estimate_Use);
if (!remote_) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError));
return promise;
}
remote_->Estimate(WTF::BindOnce(&EstimateImplAfterRemoteEstimate,
WrapPersistent(resolver)));
return promise;
}
String StorageAccessHandle::createObjectURL(
Blob* blob,
ExceptionState& exception_state) const {
if (!storage_access_types_->all() &&
!storage_access_types_->createObjectURL()) {
exception_state.ThrowSecurityError(kCreateObjectURLNotRequested);
return "";
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_createObjectURL_Use);
GetSupplementable()->CountUse(WebFeature::kCreateObjectURLBlob);
CHECK(blob);
return blob_storage_->RegisterURL(blob);
}
void StorageAccessHandle::revokeObjectURL(
const String& url,
ExceptionState& exception_state) const {
if (!storage_access_types_->all() &&
!storage_access_types_->revokeObjectURL()) {
exception_state.ThrowSecurityError(kRevokeObjectURLNotRequested);
return;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_revokeObjectURL_Use);
KURL resolved_url(NullURL(), url);
GetSupplementable()->GetExecutionContext()->RemoveURLFromMemoryCache(
resolved_url);
blob_storage_->Revoke(resolved_url);
}
BroadcastChannel* StorageAccessHandle::BroadcastChannel(
ExecutionContext* execution_context,
const String& name,
ExceptionState& exception_state) const {
if (!storage_access_types_->all() &&
!storage_access_types_->broadcastChannel()) {
exception_state.ThrowSecurityError(kBroadcastChannelNotRequested);
return nullptr;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_BroadcastChannel_Use);
return MakeGarbageCollected<blink::BroadcastChannel>(
PassKey(), execution_context, name, broadcast_channel_.get());
}
blink::SharedWorker* StorageAccessHandle::SharedWorker(
ExecutionContext* context,
const String& url,
const V8UnionSharedWorkerOptionsOrString* name_or_options,
ExceptionState& exception_state) const {
if (!storage_access_types_->all() && !storage_access_types_->sharedWorker()) {
exception_state.ThrowSecurityError(kSharedWorkerNotRequested);
return nullptr;
}
GetSupplementable()->CountUse(
WebFeature::
kStorageAccessAPI_requestStorageAccess_BeyondCookies_SharedWorker_Use);
return SharedWorker::Create(PassKey(), context, url, name_or_options,
exception_state, blob_storage_, &shared_worker_);
}
void StorageAccessHandle::InitSessionStorage() {
LocalDOMWindow* window = GetSupplementable();
if (!window->GetSecurityOrigin()->CanAccessSessionStorage()) {
return;
}
if (!window->GetFrame()) {
return;
}
StorageNamespace* storage_namespace =
StorageNamespace::From(window->GetFrame()->GetPage());
if (!storage_namespace) {
return;
}
session_storage_ = StorageArea::Create(
window,
storage_namespace->GetCachedArea(
window, {}, StorageNamespace::StorageContext::kStorageAccessAPI),
StorageArea::StorageType::kSessionStorage);
}
void StorageAccessHandle::InitLocalStorage() {
LocalDOMWindow* window = GetSupplementable();
if (!window->GetSecurityOrigin()->CanAccessLocalStorage()) {
return;
}
if (!window->GetFrame()) {
return;
}
if (!window->GetFrame()->GetSettings()->GetLocalStorageEnabled()) {
return;
}
auto storage_area = StorageController::GetInstance()->GetLocalStorageArea(
window, {}, StorageNamespace::StorageContext::kStorageAccessAPI);
local_storage_ = StorageArea::Create(window, std::move(storage_area),
StorageArea::StorageType::kLocalStorage);
}
HeapMojoRemote<mojom::blink::StorageAccessHandle>&
StorageAccessHandle::InitRemote() {
if (!remote_) {
mojo::PendingRemote<mojom::blink::StorageAccessHandle> remote;
GetSupplementable()
->GetExecutionContext()
->GetBrowserInterfaceBroker()
.GetInterface(remote.InitWithNewPipeAndPassReceiver());
remote_.Bind(std::move(remote),
GetSupplementable()->GetExecutionContext()->GetTaskRunner(
TaskType::kMiscPlatformAPI));
}
return remote_;
}
void StorageAccessHandle::InitIndexedDB() {
if (!GetSupplementable()->GetSecurityOrigin()->CanAccessDatabase()) {
return;
}
if (!InitRemote()) {
return;
}
mojo::PendingRemote<mojom::blink::IDBFactory> indexed_db_remote;
remote_->BindIndexedDB(indexed_db_remote.InitWithNewPipeAndPassReceiver());
indexed_db_ = MakeGarbageCollected<IDBFactory>(GetSupplementable());
indexed_db_->SetRemote(std::move(indexed_db_remote));
}
void StorageAccessHandle::InitLocks() {
if (!GetSupplementable()->GetSecurityOrigin()->CanAccessLocks()) {
return;
}
if (!InitRemote()) {
return;
}
mojo::PendingRemote<mojom::blink::LockManager> locks_remote;
remote_->BindLocks(locks_remote.InitWithNewPipeAndPassReceiver());
locks_ = MakeGarbageCollected<LockManager>(*GetSupplementable()->navigator());
locks_->SetManager(std::move(locks_remote),
GetSupplementable()->GetExecutionContext());
}
void StorageAccessHandle::InitCaches() {
if (!GetSupplementable()->GetSecurityOrigin()->CanAccessCacheStorage()) {
return;
}
if (!InitRemote()) {
return;
}
mojo::PendingRemote<mojom::blink::CacheStorage> cache_remote;
remote_->BindCaches(cache_remote.InitWithNewPipeAndPassReceiver());
caches_ = MakeGarbageCollected<CacheStorage>(
GetSupplementable()->GetExecutionContext(),
GlobalFetch::ScopedFetcher::From(*GetSupplementable()),
std::move(cache_remote));
}
void StorageAccessHandle::InitGetDirectory() {
if (!GetSupplementable()->GetSecurityOrigin()->CanAccessFileSystem()) {
return;
}
InitRemote();
// Nothing else to init as getDirectory is an async function not a handle.
}
void StorageAccessHandle::InitQuota() {
if (GetSupplementable()->GetSecurityOrigin()->IsOpaque()) {
return;
}
InitRemote();
// Nothing else to init as all Quota usage is via async functions.
}
void StorageAccessHandle::InitBlobStorage() {
if (GetSupplementable()->GetSecurityOrigin()->IsOpaque()) {
return;
}
if (!InitRemote()) {
return;
}
mojo::PendingAssociatedRemote<mojom::blink::BlobURLStore> blob_storage_remote;
remote_->BindBlobStorage(
blob_storage_remote.InitWithNewEndpointAndPassReceiver());
blob_storage_ = MakeGarbageCollected<PublicURLManager>(
PassKey(), GetSupplementable()->GetExecutionContext(),
std::move(blob_storage_remote));
}
void StorageAccessHandle::InitBroadcastChannel() {
if (GetSupplementable()->GetSecurityOrigin()->IsOpaque()) {
return;
}
if (!InitRemote()) {
return;
}
remote_->BindBroadcastChannel(
broadcast_channel_.BindNewEndpointAndPassReceiver(
GetSupplementable()->GetExecutionContext()->GetTaskRunner(
TaskType::kInternalDefault)));
}
void StorageAccessHandle::InitSharedWorker() {
if (!GetSupplementable()->GetSecurityOrigin()->CanAccessSharedWorkers()) {
return;
}
if (!InitRemote()) {
return;
}
remote_->BindSharedWorker(shared_worker_.BindNewPipeAndPassReceiver(
GetSupplementable()->GetExecutionContext()->GetTaskRunner(
TaskType::kDOMManipulation)));
}
namespace bindings {
ExecutionContext* ExecutionContextFromV8Wrappable(
const StorageAccessHandle* storage_access_handle) {
return storage_access_handle->GetSupplementable();
}
} // namespace bindings
} // namespace blink