910e62b5创建于 1月15日历史提交
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/api/storage/storage_frontend.h"

#include <memory>
#include <utility>

#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/debug/alias.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "components/value_store/value_store_factory.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/storage/backend_task_runner.h"
#include "extensions/browser/api/storage/local_value_store_cache.h"
#include "extensions/browser/api/storage/storage_area_namespace.h"
#include "extensions/browser/api/storage/storage_utils.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/api/storage.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/mojom/context_type.mojom.h"

using content::BrowserContext;
using content::BrowserThread;
using value_store::ValueStore;

namespace extensions {

namespace {

base::LazyInstance<BrowserContextKeyedAPIFactory<StorageFrontend>>::
    DestructorAtExit g_factory = LAZY_INSTANCE_INITIALIZER;

events::HistogramValue StorageAreaToEventHistogram(
    StorageAreaNamespace storage_area) {
  switch (storage_area) {
    case StorageAreaNamespace::kLocal:
      return events::STORAGE_LOCAL_ON_CHANGE;
    case StorageAreaNamespace::kSync:
      return events::STORAGE_SYNC_ON_CHANGE;
    case StorageAreaNamespace::kManaged:
      return events::STORAGE_MANAGED_ON_CHANGE;
    case StorageAreaNamespace::kSession:
      return events::STORAGE_SESSION_ON_CHANGE;
    case StorageAreaNamespace::kInvalid:
      NOTREACHED();
  }
}

void GetKeysWithValueStore(
    base::OnceCallback<void(ValueStore::ReadResult)> callback,
    ValueStore* store) {
  ValueStore::ReadResult result = store->GetKeys();

  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
}

void GetWithValueStore(
    std::optional<std::vector<std::string>> keys,
    base::OnceCallback<void(ValueStore::ReadResult)> callback,
    ValueStore* store) {
  ValueStore::ReadResult result =
      keys.has_value() ? store->Get(keys.value()) : store->Get();

  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
}

void GetBytesInUseWithValueStore(std::optional<std::vector<std::string>> keys,
                                 base::OnceCallback<void(size_t)> callback,
                                 ValueStore* store) {
  size_t size = keys.has_value() ? store->GetBytesInUse(keys.value())
                                 : store->GetBytesInUse();

  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), size));
}

void SetWithValueStore(
    const base::Value::Dict& values,
    base::OnceCallback<void(ValueStore::WriteResult)> callback,
    ValueStore* store) {
  ValueStore::WriteResult result = store->Set(ValueStore::DEFAULTS, values);
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
}

void RemoveWithValueStore(
    std::vector<std::string> keys,
    base::OnceCallback<void(ValueStore::WriteResult)> callback,
    ValueStore* store) {
  ValueStore::WriteResult result = store->Remove(keys);
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
}

void ClearWithValueStore(
    base::OnceCallback<void(ValueStore::WriteResult)> callback,
    ValueStore* store) {
  ValueStore::WriteResult result = store->Clear();
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
}

base::Value::List KeysFromDict(base::Value::Dict dict) {
  base::Value::List list = base::Value::List::with_capacity(dict.size());
  for (auto item : dict) {
    list.Append(std::move(item.first));
  }
  return list;
}

}  // namespace

// static
StorageFrontend* StorageFrontend::Get(BrowserContext* context) {
  return BrowserContextKeyedAPIFactory<StorageFrontend>::Get(context);
}

// static
std::unique_ptr<StorageFrontend> StorageFrontend::CreateForTesting(
    scoped_refptr<value_store::ValueStoreFactory> storage_factory,
    BrowserContext* context) {
  return base::WrapUnique(
      new StorageFrontend(std::move(storage_factory), context));
}

// Implementation of ResultStatus.

StorageFrontend::ResultStatus::ResultStatus() = default;

StorageFrontend::ResultStatus::ResultStatus(const ResultStatus&) = default;

StorageFrontend::ResultStatus::~ResultStatus() = default;

// Implementation of GetKeysResult.

StorageFrontend::GetKeysResult::GetKeysResult() = default;

StorageFrontend::GetKeysResult::GetKeysResult(GetKeysResult&& other) = default;

StorageFrontend::GetKeysResult::~GetKeysResult() = default;

// Implementation of GetResult.

StorageFrontend::GetResult::GetResult() = default;

StorageFrontend::GetResult::GetResult(GetResult&& other) = default;

StorageFrontend::GetResult::~GetResult() = default;

// Implementation of StorageFrontend.

StorageFrontend::StorageFrontend(BrowserContext* context)
    : StorageFrontend(ExtensionSystem::Get(context)->store_factory(), context) {
}

StorageFrontend::StorageFrontend(
    scoped_refptr<value_store::ValueStoreFactory> factory,
    BrowserContext* context)
    : browser_context_(context) {
  Init(std::move(factory));
}

void StorageFrontend::Init(
    scoped_refptr<value_store::ValueStoreFactory> factory) {
  TRACE_EVENT0("browser,startup", "StorageFrontend::Init");

  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!browser_context_->IsOffTheRecord());

  caches_[settings_namespace::LOCAL] = new LocalValueStoreCache(factory);

  // Add any additional caches the embedder supports (for example, caches
  // for chrome.storage.managed and chrome.storage.sync).
  ExtensionsAPIClient::Get()->AddAdditionalValueStoreCaches(
      browser_context_, factory, GetObserver(), &caches_);
}

StorageFrontend::~StorageFrontend() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  for (auto it = caches_.begin(); it != caches_.end(); ++it) {
    ValueStoreCache* cache = it->second;
    cache->ShutdownOnUI();
    GetBackendTaskRunner()->DeleteSoon(FROM_HERE, cache);
  }
}

void StorageFrontend::OnReadFinished(
    const ExtensionId& extension_id,
    StorageAreaNamespace storage_area,
    base::OnceCallback<void(StorageFrontend::GetResult)> callback,
    ValueStore::ReadResult result) {
  bool success = result.status().ok();

  GetResult get_result;

  get_result.status.success = success;
  get_result.status.error =
      success ? std::nullopt : std::optional(result.status().message);

  if (success) {
    get_result.data = result.PassSettings();
  }

  std::move(callback).Run(std::move(get_result));
}

void StorageFrontend::OnReadKeysFinished(
    base::OnceCallback<void(GetKeysResult)> callback,
    ValueStore::ReadResult result) {
  bool success = result.status().ok();

  GetKeysResult get_keys_result;

  get_keys_result.status.success = success;
  get_keys_result.status.error =
      success ? std::nullopt : std::optional(result.status().message);

  if (success) {
    get_keys_result.data = KeysFromDict(result.PassSettings());
  }

  std::move(callback).Run(std::move(get_keys_result));
}

void StorageFrontend::OnWriteFinished(
    const ExtensionId& extension_id,
    StorageAreaNamespace storage_area,
    base::OnceCallback<void(StorageFrontend::ResultStatus)> callback,
    ValueStore::WriteResult result) {
  bool success = result.status().ok();

  if (success && !result.changes().empty()) {
    OnSettingsChanged(
        extension_id, storage_area,
        storage_utils::GetAccessLevelForArea(extension_id, *browser_context_,
                                             storage_area),
        value_store::ValueStoreChange::ToValue(result.PassChanges()));
  }

  ResultStatus status;
  status.success = success;
  status.error =
      success ? std::nullopt : std::optional(result.status().message);
  std::move(callback).Run(status);
}

void StorageFrontend::GetValues(scoped_refptr<const Extension> extension,
                                StorageAreaNamespace storage_area,
                                std::optional<std::vector<std::string>> keys,
                                base::OnceCallback<void(GetResult)> callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (storage_area == StorageAreaNamespace::kSession) {
    SessionStorageManager* storage_manager =
        SessionStorageManager::GetForBrowserContext(browser_context_);

    std::map<std::string, const base::Value*> result =
        keys.has_value() ? storage_manager->Get(extension->id(), keys.value())
                         : storage_manager->GetAll(extension->id());

    GetResult get_result;
    get_result.data = base::Value::Dict();

    for (auto item : result) {
      get_result.data->Set(std::move(item.first), item.second->Clone());
    }

    // Using a task here is important since we want to consistently fire the
    // callback asynchronously.
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), std::move(get_result)));
    return;
  }

  settings_namespace::Namespace settings_namespace =
      StorageAreaToSettingsNamespace(storage_area);

  CHECK(StorageFrontend::IsStorageEnabled(settings_namespace));

  RunWithStorage(
      extension, settings_namespace,
      base::BindOnce(&GetWithValueStore, std::move(keys),
                     base::BindOnce(&StorageFrontend::OnReadFinished,
                                    weak_factory_.GetWeakPtr(), extension->id(),
                                    storage_area, std::move(callback))));
}

void StorageFrontend::GetKeys(
    scoped_refptr<const Extension> extension,
    StorageAreaNamespace storage_area,
    base::OnceCallback<void(GetKeysResult)> callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (storage_area == StorageAreaNamespace::kSession) {
    SessionStorageManager* storage_manager =
        SessionStorageManager::GetForBrowserContext(browser_context_);

    std::vector<std::string> keys = storage_manager->GetKeys(extension->id());

    base::Value::List list = base::Value::List::with_capacity(keys.size());
    for (const std::string& key : keys) {
      list.Append(key);
    }

    GetKeysResult get_keys_result;
    get_keys_result.data = std::move(list);

    // Using a task here is important since we want to consistently fire the
    // callback asynchronously.
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), std::move(get_keys_result)));
    return;
  }

  settings_namespace::Namespace settings_namespace =
      StorageAreaToSettingsNamespace(storage_area);

  CHECK(StorageFrontend::IsStorageEnabled(settings_namespace));

  base::OnceCallback<void(ValueStore::ReadResult)> test =
      base::BindOnce(&StorageFrontend::OnReadKeysFinished,
                     weak_factory_.GetWeakPtr(), std::move(callback));
  RunWithStorage(extension, settings_namespace,
                 base::BindOnce(&GetKeysWithValueStore, std::move(test)));
}

void StorageFrontend::GetBytesInUse(
    scoped_refptr<const Extension> extension,
    StorageAreaNamespace storage_area,
    std::optional<std::vector<std::string>> keys,
    base::OnceCallback<void(size_t)> callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (storage_area == StorageAreaNamespace::kSession) {
    SessionStorageManager* storage_manager =
        SessionStorageManager::GetForBrowserContext(browser_context_);

    size_t bytes_in_use =
        keys.has_value()
            ? storage_manager->GetBytesInUse(extension->id(), keys.value())
            : storage_manager->GetTotalBytesInUse(extension->id());

    // Using a task here is important since we want to consistently fire the
    // callback asynchronously.
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), bytes_in_use));
    return;
  }

  extensions::settings_namespace::Namespace settings_namespace =
      extensions::StorageAreaToSettingsNamespace(storage_area);

  CHECK(StorageFrontend::IsStorageEnabled(settings_namespace));

  RunWithStorage(extension, settings_namespace,
                 base::BindOnce(&GetBytesInUseWithValueStore, std::move(keys),
                                std::move(callback)));
}

void StorageFrontend::Set(scoped_refptr<const Extension> extension,
                          StorageAreaNamespace storage_area,
                          base::Value::Dict values,
                          base::OnceCallback<void(ResultStatus)> callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (storage_area == StorageAreaNamespace::kSession) {
    SessionStorageManager* storage_manager =
        SessionStorageManager::GetForBrowserContext(browser_context_);

    std::map<std::string, base::Value> values_map;
    for (auto item : values) {
      values_map.emplace(std::move(item.first), std::move(item.second));
    }

    std::vector<SessionStorageManager::ValueChange> changes;
    std::string error;
    bool success = storage_manager->Set(extension->id(), std::move(values_map),
                                        changes, &error);

    if (success && !changes.empty()) {
      OnSettingsChanged(extension->id(), storage_area,
                        storage_utils::GetAccessLevelForArea(
                            extension->id(), *browser_context_, storage_area),
                        storage_utils::ValueChangeToValue(std::move(changes)));
    }

    ResultStatus status;
    status.success = success;
    status.error = success ? std::nullopt : std::optional(error);

    // Using a task here is important since we want to consistently fire the
    // callback asynchronously.
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), status));
    return;
  }

  settings_namespace::Namespace settings_namespace =
      StorageAreaToSettingsNamespace(storage_area);

  CHECK(StorageFrontend::IsStorageEnabled(settings_namespace));

  RunWithStorage(
      extension, settings_namespace,
      base::BindOnce(&SetWithValueStore, std::move(values),
                     base::BindOnce(&StorageFrontend::OnWriteFinished,
                                    weak_factory_.GetWeakPtr(), extension->id(),
                                    storage_area, std::move(callback))));
}

void StorageFrontend::Remove(scoped_refptr<const Extension> extension,
                             StorageAreaNamespace storage_area,
                             const std::vector<std::string>& keys,
                             base::OnceCallback<void(ResultStatus)> callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (storage_area == StorageAreaNamespace::kSession) {
    SessionStorageManager* storage_manager =
        SessionStorageManager::GetForBrowserContext(browser_context_);

    std::vector<SessionStorageManager::ValueChange> changes;
    storage_manager->Remove(extension->id(), keys, changes);

    if (!changes.empty()) {
      OnSettingsChanged(extension->id(), storage_area,
                        storage_utils::GetAccessLevelForArea(
                            extension->id(), *browser_context_, storage_area),
                        storage_utils::ValueChangeToValue(std::move(changes)));
    }

    // Using a task here is important since we want to consistently fire the
    // callback asynchronously.
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), StorageFrontend::ResultStatus()));
    return;
  }

  settings_namespace::Namespace settings_namespace =
      StorageAreaToSettingsNamespace(storage_area);

  CHECK(StorageFrontend::IsStorageEnabled(settings_namespace));

  RunWithStorage(
      extension, settings_namespace,
      base::BindOnce(&RemoveWithValueStore, keys,
                     base::BindOnce(&StorageFrontend::OnWriteFinished,
                                    weak_factory_.GetWeakPtr(), extension->id(),
                                    storage_area, std::move(callback))));
}

void StorageFrontend::Clear(
    scoped_refptr<const Extension> extension,
    StorageAreaNamespace storage_area,
    base::OnceCallback<void(StorageFrontend::ResultStatus)> callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (storage_area == StorageAreaNamespace::kSession) {
    SessionStorageManager* storage_manager =
        SessionStorageManager::GetForBrowserContext(browser_context_);

    std::vector<SessionStorageManager::ValueChange> changes;
    storage_manager->Clear(extension->id(), changes);

    if (!changes.empty()) {
      OnSettingsChanged(extension->id(), storage_area,
                        storage_utils::GetAccessLevelForArea(
                            extension->id(), *browser_context_, storage_area),
                        storage_utils::ValueChangeToValue(std::move(changes)));
    }

    // Using a task here is important since we want to consistently fire the
    // callback asynchronously.
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), StorageFrontend::ResultStatus()));
    return;
  }

  settings_namespace::Namespace settings_namespace =
      StorageAreaToSettingsNamespace(storage_area);

  CHECK(StorageFrontend::IsStorageEnabled(settings_namespace));

  RunWithStorage(
      extension, settings_namespace,
      base::BindOnce(&ClearWithValueStore,
                     base::BindOnce(&StorageFrontend::OnWriteFinished,
                                    weak_factory_.GetWeakPtr(), extension->id(),
                                    storage_area, std::move(callback))));
}

ValueStoreCache* StorageFrontend::GetValueStoreCache(
    settings_namespace::Namespace settings_namespace) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto it = caches_.find(settings_namespace);
  if (it != caches_.end()) {
    return it->second;
  }
  return nullptr;
}

bool StorageFrontend::IsStorageEnabled(
    settings_namespace::Namespace settings_namespace) const {
  return base::Contains(caches_, settings_namespace);
}

void StorageFrontend::RunWithStorage(
    scoped_refptr<const Extension> extension,
    settings_namespace::Namespace settings_namespace,
    ValueStoreCache::StorageCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(extension.get());

  ValueStoreCache* cache = caches_[settings_namespace];
  CHECK(cache);

  GetBackendTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(&ValueStoreCache::RunWithValueStoreForExtension,
                     base::Unretained(cache), std::move(callback), extension));
}

void StorageFrontend::DeleteStorageSoon(const ExtensionId& extension_id,
                                        base::OnceClosure done_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  auto subtask_done_callback =
      base::BarrierClosure(caches_.size(), std::move(done_callback));
  for (auto& cache_map : caches_) {
    ValueStoreCache* cache = cache_map.second;
    GetBackendTaskRunner()->PostTaskAndReply(
        FROM_HERE,
        base::BindOnce(&ValueStoreCache::DeleteStorageSoon,
                       base::Unretained(cache), extension_id),
        subtask_done_callback);
  }
}

SettingsChangedCallback StorageFrontend::GetObserver() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  return base::BindRepeating(&StorageFrontend::OnSettingsChanged,
                             weak_factory_.GetWeakPtr());
}

void StorageFrontend::SetCacheForTesting(
    settings_namespace::Namespace settings_namespace,
    std::unique_ptr<ValueStoreCache> cache) {
  DisableStorageForTesting(settings_namespace);  // IN-TEST
  caches_[settings_namespace] = cache.release();
}

void StorageFrontend::DisableStorageForTesting(
    settings_namespace::Namespace settings_namespace) {
  auto it = caches_.find(settings_namespace);
  if (it != caches_.end()) {
    ValueStoreCache* cache = it->second;
    cache->ShutdownOnUI();
    GetBackendTaskRunner()->DeleteSoon(FROM_HERE, cache);
    caches_.erase(it);
  }
}

// Forwards changes on to the extension processes for |browser_context_| and its
// incognito partner if it exists.
void StorageFrontend::OnSettingsChanged(
    const ExtensionId& extension_id,
    StorageAreaNamespace storage_area,
    std::optional<api::storage::AccessLevel> access_level,
    base::Value changes) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  TRACE_EVENT1("browser", "SettingsObserver:OnSettingsChanged", "extension_id",
               extension_id);

  // Alias extension_id for investigation of shutdown hangs. crbug.com/1154997
  // Extension IDs are exactly 32 characters in length.
  constexpr size_t kExtensionsIdLength = 32;
  char extension_id_str[kExtensionsIdLength + 1];
  base::strlcpy(extension_id_str, extension_id.c_str(),
                std::size(extension_id_str));
  base::debug::Alias(extension_id_str);

  const std::string namespace_string = StorageAreaToString(storage_area);
  EventRouter* event_router = EventRouter::Get(browser_context_);

  bool has_event_changed_listener = event_router->ExtensionHasEventListener(
      extension_id, api::storage::OnChanged::kEventName);

  // Event for StorageArea.
  auto area_event_name =
      base::StringPrintf("storage.%s.onChanged", namespace_string.c_str());
  bool has_area_changed_event_listener =
      event_router->ExtensionHasEventListener(extension_id, area_event_name);

  // Restrict event to privileged context if access level is set only to trusted
  // contexts.
  std::optional<mojom::ContextType> restrict_to_context_type = std::nullopt;
  if (access_level.has_value() &&
      access_level.value() == api::storage::AccessLevel::kTrustedContexts) {
    restrict_to_context_type = mojom::ContextType::kPrivilegedExtension;
  }

  auto make_changed_event = [&namespace_string,
                             restrict_to_context_type](base::Value changes) {
    base::Value::List args;
    args.Append(std::move(changes));
    args.Append(namespace_string);

    return std::make_unique<Event>(
        events::STORAGE_ON_CHANGED, api::storage::OnChanged::kEventName,
        std::move(args), /*restrict_to_browser_context=*/nullptr,
        restrict_to_context_type);
  };
  auto make_area_changed_event = [&storage_area, &area_event_name,
                                  restrict_to_context_type](
                                     base::Value changes) {
    base::Value::List args;
    args.Append(std::move(changes));
    return std::make_unique<Event>(StorageAreaToEventHistogram(storage_area),
                                   area_event_name, std::move(args), nullptr,
                                   restrict_to_context_type);
  };
  // We only dispatch the events if there's a valid listener (even though
  // EventRouter would handle the no-listener case) since copying `changes`
  // can be expensive.
  // Event for each storage(sync, local, managed).
  if (has_event_changed_listener && has_area_changed_event_listener) {
    event_router->DispatchEventToExtension(
        extension_id, make_area_changed_event(changes.Clone()));
  }
  if (has_event_changed_listener) {
    event_router->DispatchEventToExtension(
        extension_id, make_changed_event(std::move(changes)));
  } else if (has_area_changed_event_listener) {
    event_router->DispatchEventToExtension(
        extension_id, make_area_changed_event(std::move(changes)));
  }
}

// BrowserContextKeyedAPI implementation.

// static
BrowserContextKeyedAPIFactory<StorageFrontend>*
StorageFrontend::GetFactoryInstance() {
  return g_factory.Pointer();
}

// static
const char* StorageFrontend::service_name() { return "StorageFrontend"; }

}  // namespace extensions