diff --git a/BUILD.gn b/BUILD.gn index 09e0f6a15..ee55846fe 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -375,6 +375,8 @@ static_library("libcef_static") { "libcef/browser/download_manager_delegate.h", "libcef/browser/extension_impl.cc", "libcef/browser/extension_impl.h", + "libcef/browser/extensions/api/storage/sync_value_store_cache.cc", + "libcef/browser/extensions/api/storage/sync_value_store_cache.h", "libcef/browser/extensions/api/tabs/tabs_api.cc", "libcef/browser/extensions/api/tabs/tabs_api.h", "libcef/browser/extensions/browser_context_keyed_service_factories.cc", @@ -411,6 +413,10 @@ static_library("libcef_static") { "libcef/browser/extensions/pdf_extension_util.h", "libcef/browser/extensions/pdf_web_contents_helper_client.cc", "libcef/browser/extensions/pdf_web_contents_helper_client.h", + "libcef/browser/extensions/value_store/cef_value_store.cc", + "libcef/browser/extensions/value_store/cef_value_store.h", + "libcef/browser/extensions/value_store/cef_value_store_factory.cc", + "libcef/browser/extensions/value_store/cef_value_store_factory.h", "libcef/browser/file_dialog_runner.h", "libcef/browser/file_dialog_manager.cc", "libcef/browser/file_dialog_manager.h", diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 4f792576e..81824e1f5 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -431,6 +431,7 @@ 'tests/ceftests/draggable_regions_unittest.cc', 'tests/ceftests/extensions/background_unittest.cc', 'tests/ceftests/extensions/chrome_alarms_unittest.cc', + 'tests/ceftests/extensions/chrome_storage_unittest.cc', 'tests/ceftests/extensions/chrome_tabs_unittest.cc', 'tests/ceftests/extensions/extension_test_handler.cc', 'tests/ceftests/extensions/extension_test_handler.h', diff --git a/libcef/browser/extensions/api/storage/sync_value_store_cache.cc b/libcef/browser/extensions/api/storage/sync_value_store_cache.cc new file mode 100644 index 000000000..8a6ab7be9 --- /dev/null +++ b/libcef/browser/extensions/api/storage/sync_value_store_cache.cc @@ -0,0 +1,97 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. +// Portions copyright 2014 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 "libcef/browser/extensions/api/storage/sync_value_store_cache.h" + +#include + +#include + +#include "content/public/browser/browser_thread.h" +#include "extensions/browser/api/storage/backend_task_runner.h" +#include "extensions/browser/api/storage/weak_unlimited_settings_storage.h" +#include "extensions/browser/value_store/value_store_factory.h" +#include "extensions/common/api/storage.h" +#include "extensions/common/extension.h" +#include "extensions/common/permissions/permissions_data.h" + +using content::BrowserThread; + +namespace extensions { + +namespace cef { + +namespace { + +// Returns the quota limit for local storage, taken from the schema in +// extensions/common/api/storage.json. +SettingsStorageQuotaEnforcer::Limits GetLocalQuotaLimits() { + SettingsStorageQuotaEnforcer::Limits limits = { + static_cast(api::storage::local::QUOTA_BYTES), + std::numeric_limits::max(), std::numeric_limits::max()}; + return limits; +} + +} // namespace + +SyncValueStoreCache::SyncValueStoreCache( + const scoped_refptr& factory) + : storage_factory_(factory), quota_(GetLocalQuotaLimits()) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); +} + +SyncValueStoreCache::~SyncValueStoreCache() { + DCHECK(IsOnBackendSequence()); +} + +void SyncValueStoreCache::RunWithValueStoreForExtension( + const StorageCallback& callback, + scoped_refptr extension) { + DCHECK(IsOnBackendSequence()); + + ValueStore* storage = GetStorage(extension.get()); + + // A neat way to implement unlimited storage; if the extension has the + // unlimited storage permission, force through all calls to Set(). + if (extension->permissions_data()->HasAPIPermission( + APIPermission::kUnlimitedStorage)) { + WeakUnlimitedSettingsStorage unlimited_storage(storage); + callback.Run(&unlimited_storage); + } else { + callback.Run(storage); + } +} + +void SyncValueStoreCache::DeleteStorageSoon(const std::string& extension_id) { + DCHECK(IsOnBackendSequence()); + storage_map_.erase(extension_id); + storage_factory_->DeleteSettings(settings_namespace::SYNC, + ValueStoreFactory::ModelType::APP, + extension_id); + storage_factory_->DeleteSettings(settings_namespace::SYNC, + ValueStoreFactory::ModelType::EXTENSION, + extension_id); +} + +ValueStore* SyncValueStoreCache::GetStorage(const Extension* extension) { + StorageMap::iterator iter = storage_map_.find(extension->id()); + if (iter != storage_map_.end()) + return iter->second.get(); + + ValueStoreFactory::ModelType model_type = + extension->is_app() ? ValueStoreFactory::ModelType::APP + : ValueStoreFactory::ModelType::EXTENSION; + std::unique_ptr store = storage_factory_->CreateSettingsStore( + settings_namespace::SYNC, model_type, extension->id()); + std::unique_ptr storage( + new SettingsStorageQuotaEnforcer(quota_, std::move(store))); + DCHECK(storage.get()); + + ValueStore* storage_ptr = storage.get(); + storage_map_[extension->id()] = std::move(storage); + return storage_ptr; +} +} // namespace cef +} // namespace extensions diff --git a/libcef/browser/extensions/api/storage/sync_value_store_cache.h b/libcef/browser/extensions/api/storage/sync_value_store_cache.h new file mode 100644 index 000000000..269fc7cd0 --- /dev/null +++ b/libcef/browser/extensions/api/storage/sync_value_store_cache.h @@ -0,0 +1,56 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. +// Portions copyright 2014 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. + +#ifndef CEF_LIBCEF_BROWSER_EXTENSIONS_API_STORAGE_SYNC_VALUE_STORE_CACHE_H_ +#define CEF_LIBCEF_BROWSER_EXTENSIONS_API_STORAGE_SYNC_VALUE_STORE_CACHE_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h" +#include "extensions/browser/api/storage/value_store_cache.h" + +namespace extensions { + +class ValueStoreFactory; + +namespace cef { + +// Based on LocalValueStoreCache +// ValueStoreCache for the SYNC namespace. It owns a backend for apps and +// another for extensions. Each backend takes care of persistence. +class SyncValueStoreCache : public ValueStoreCache { + public: + explicit SyncValueStoreCache(const scoped_refptr& factory); + ~SyncValueStoreCache() override; + + // ValueStoreCache implementation: + void RunWithValueStoreForExtension( + const StorageCallback& callback, + scoped_refptr extension) override; + void DeleteStorageSoon(const std::string& extension_id) override; + + private: + using StorageMap = std::map>; + + ValueStore* GetStorage(const Extension* extension); + + // The Factory to use for creating new ValueStores. + const scoped_refptr storage_factory_; + + // Quota limits (see SettingsStorageQuotaEnforcer). + const SettingsStorageQuotaEnforcer::Limits quota_; + + // The collection of ValueStores for local storage. + StorageMap storage_map_; + + DISALLOW_COPY_AND_ASSIGN(SyncValueStoreCache); +}; +} // namespace cef +} // namespace extensions + +#endif // CEF_LIBCEF_BROWSER_EXTENSIONS_API_STORAGE_SYNC_VALUE_STORE_CACHE_H_ diff --git a/libcef/browser/extensions/browser_context_keyed_service_factories.cc b/libcef/browser/extensions/browser_context_keyed_service_factories.cc index 3e91c3853..c5bd27c45 100644 --- a/libcef/browser/extensions/browser_context_keyed_service_factories.cc +++ b/libcef/browser/extensions/browser_context_keyed_service_factories.cc @@ -8,6 +8,7 @@ #include "chrome/browser/extensions/api/streams_private/streams_private_api.h" #include "chrome/browser/ui/prefs/prefs_tab_helper.h" #include "extensions/browser/api/alarms/alarm_manager.h" +#include "extensions/browser/api/storage/storage_frontend.h" #include "extensions/browser/renderer_startup_helper.h" namespace extensions { @@ -18,6 +19,7 @@ void EnsureBrowserContextKeyedServiceFactoriesBuilt() { CookieSettingsFactory::GetInstance(); PrefsTabHelper::GetServiceInstance(); RendererStartupHelperFactory::GetInstance(); + StorageFrontend::GetFactoryInstance(); StreamsPrivateAPI::GetFactoryInstance(); } diff --git a/libcef/browser/extensions/chrome_api_registration.cc b/libcef/browser/extensions/chrome_api_registration.cc index e5a8d60a1..bd202e0c3 100644 --- a/libcef/browser/extensions/chrome_api_registration.cc +++ b/libcef/browser/extensions/chrome_api_registration.cc @@ -13,6 +13,7 @@ #include "chrome/browser/extensions/api/resources_private/resources_private_api.h" #include "chrome/browser/extensions/api/streams_private/streams_private_api.h" #include "extensions/browser/api/alarms/alarms_api.h" +#include "extensions/browser/api/storage/storage_api.h" #include "extensions/browser/extension_function_registry.h" namespace extensions { @@ -36,6 +37,12 @@ const char* const kSupportedAPIs[] = { EXTENSION_FUNCTION_NAME(AlarmsGetAllFunction), EXTENSION_FUNCTION_NAME(AlarmsClearFunction), EXTENSION_FUNCTION_NAME(AlarmsClearAllFunction), + "storage", + EXTENSION_FUNCTION_NAME(StorageStorageAreaGetFunction), + EXTENSION_FUNCTION_NAME(StorageStorageAreaSetFunction), + EXTENSION_FUNCTION_NAME(StorageStorageAreaRemoveFunction), + EXTENSION_FUNCTION_NAME(StorageStorageAreaClearFunction), + EXTENSION_FUNCTION_NAME(StorageStorageAreaGetBytesInUseFunction), "tabs", EXTENSION_FUNCTION_NAME(cefimpl::TabsGetFunction), EXTENSION_FUNCTION_NAME(cefimpl::TabsExecuteScriptFunction), @@ -67,6 +74,11 @@ void ChromeFunctionRegistry::RegisterAll(ExtensionFunctionRegistry* registry) { registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); + registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); diff --git a/libcef/browser/extensions/extension_system.cc b/libcef/browser/extensions/extension_system.cc index bae75bc20..8e65279a0 100644 --- a/libcef/browser/extensions/extension_system.cc +++ b/libcef/browser/extensions/extension_system.cc @@ -9,6 +9,7 @@ #include "libcef/browser/extension_impl.h" #include "libcef/browser/extensions/pdf_extension_util.h" +#include "libcef/browser/extensions/value_store/cef_value_store_factory.h" #include "libcef/browser/thread_util.h" #include "libcef/common/extensions/extensions_util.h" @@ -21,6 +22,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/task_scheduler/post_task.h" #include "base/threading/thread_restrictions.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_paths.h" #include "components/crx_file/id_util.h" #include "content/public/browser/browser_context.h" @@ -40,7 +42,7 @@ #include "extensions/browser/renderer_startup_helper.h" #include "extensions/browser/runtime_data.h" #include "extensions/browser/service_worker_manager.h" -#include "extensions/browser/value_store/value_store_factory.h" +#include "extensions/browser/state_store.h" #include "extensions/common/constants.h" #include "extensions/common/extension_messages.h" #include "extensions/common/file_util.h" @@ -153,7 +155,9 @@ CefExtensionSystem::CefExtensionSystem(BrowserContext* browser_context) renderer_helper_( extensions::RendererStartupHelperFactory::GetForBrowserContext( browser_context)), - weak_ptr_factory_(this) {} + weak_ptr_factory_(this) { + InitPrefs(); +} CefExtensionSystem::~CefExtensionSystem() {} @@ -384,15 +388,15 @@ SharedUserScriptMaster* CefExtensionSystem::shared_user_script_master() { } StateStore* CefExtensionSystem::state_store() { - return nullptr; + return state_store_.get(); } StateStore* CefExtensionSystem::rules_store() { - return nullptr; + return rules_store_.get(); } scoped_refptr CefExtensionSystem::store_factory() { - return nullptr; + return store_factory_; } InfoMap* CefExtensionSystem::info_map() { @@ -467,6 +471,21 @@ CefExtensionSystem::ComponentExtensionInfo::ComponentExtensionInfo( } } +void CefExtensionSystem::InitPrefs() { + store_factory_ = new CefValueStoreFactory(browser_context_->GetPath()); + + Profile* profile = Profile::FromBrowserContext(browser_context_); + + // Two state stores. The latter, which contains declarative rules, must be + // loaded immediately so that the rules are ready before we issue network + // requests. + state_store_.reset(new StateStore( + profile, store_factory_, ValueStoreFrontend::BackendType::STATE, true)); + + rules_store_.reset(new StateStore( + profile, store_factory_, ValueStoreFrontend::BackendType::RULES, false)); +} + // Implementation based on ComponentLoader::CreateExtension. scoped_refptr CefExtensionSystem::CreateExtension( const ComponentExtensionInfo& info, diff --git a/libcef/browser/extensions/extension_system.h b/libcef/browser/extensions/extension_system.h index e2c6d4516..2f054c886 100644 --- a/libcef/browser/extensions/extension_system.h +++ b/libcef/browser/extensions/extension_system.h @@ -116,6 +116,8 @@ class CefExtensionSystem : public ExtensionSystem { bool initialized() const { return initialized_; } private: + virtual void InitPrefs(); + // Information about a registered component extension. struct ComponentExtensionInfo { ComponentExtensionInfo(const base::DictionaryValue* manifest, @@ -169,6 +171,10 @@ class CefExtensionSystem : public ExtensionSystem { std::unique_ptr quota_service_; std::unique_ptr app_sorting_; + std::unique_ptr state_store_; + std::unique_ptr rules_store_; + scoped_refptr store_factory_; + // Signaled when the extension system has completed its startup tasks. OneShotEvent ready_; diff --git a/libcef/browser/extensions/extensions_api_client.cc b/libcef/browser/extensions/extensions_api_client.cc index cc8bd8b24..0f17020a6 100644 --- a/libcef/browser/extensions/extensions_api_client.cc +++ b/libcef/browser/extensions/extensions_api_client.cc @@ -7,6 +7,7 @@ #include "include/internal/cef_types_wrappers.h" #include "libcef/browser/browser_context_impl.h" +#include "libcef/browser/extensions/api/storage/sync_value_store_cache.h" #include "libcef/browser/extensions/extension_web_contents_observer.h" #include "libcef/browser/extensions/mime_handler_view_guest_delegate.h" #include "libcef/browser/extensions/pdf_web_contents_helper_client.h" @@ -66,4 +67,17 @@ void CefExtensionsAPIClient::AttachWebContentsHelpers( zoom::ZoomController::CreateForWebContents(web_contents); } +void CefExtensionsAPIClient::AddAdditionalValueStoreCaches( + content::BrowserContext* context, + const scoped_refptr& factory, + const scoped_refptr>& + observers, + std::map* caches) { + // Add support for chrome.storage.sync. + // Because we don't support syncing with Google, we follow the behavior of + // chrome.storage.sync as if Chrome were permanently offline, by using a local + // store see: https://developer.chrome.com/apps/storage for more information + (*caches)[settings_namespace::SYNC] = new cef::SyncValueStoreCache(factory); +} + } // namespace extensions diff --git a/libcef/browser/extensions/extensions_api_client.h b/libcef/browser/extensions/extensions_api_client.h index 5e83d963b..821f0b67b 100644 --- a/libcef/browser/extensions/extensions_api_client.h +++ b/libcef/browser/extensions/extensions_api_client.h @@ -24,6 +24,18 @@ class CefExtensionsAPIClient : public ExtensionsAPIClient { MimeHandlerViewGuest* guest) const override; void AttachWebContentsHelpers( content::WebContents* web_contents) const override; + + // Storage API support. + + // Add any additional value store caches (e.g. for chrome.storage.managed) + // to |caches|. By default adds nothing. + void AddAdditionalValueStoreCaches( + content::BrowserContext* context, + const scoped_refptr& factory, + const scoped_refptr>& + observers, + std::map* caches) + override; }; } // namespace extensions diff --git a/libcef/browser/extensions/value_store/cef_value_store.cc b/libcef/browser/extensions/value_store/cef_value_store.cc new file mode 100644 index 000000000..0f4240762 --- /dev/null +++ b/libcef/browser/extensions/value_store/cef_value_store.cc @@ -0,0 +1,110 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. +// Portions copyright 2016 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 "libcef/browser/extensions/value_store/cef_value_store.h" + +#include + +#include "base/logging.h" +#include "base/memory/ptr_util.h" + +CefValueStore::CefValueStore() : read_count_(0), write_count_(0) {} + +CefValueStore::~CefValueStore() {} + +size_t CefValueStore::GetBytesInUse(const std::string& key) { + // Let SettingsStorageQuotaEnforcer implement this. + NOTREACHED(); + return 0; +} + +size_t CefValueStore::GetBytesInUse(const std::vector& keys) { + // Let SettingsStorageQuotaEnforcer implement this. + NOTREACHED(); + return 0; +} + +size_t CefValueStore::GetBytesInUse() { + // Let SettingsStorageQuotaEnforcer implement this. + NOTREACHED(); + return 0; +} + +ValueStore::ReadResult CefValueStore::Get(const std::string& key) { + return Get(std::vector(1, key)); +} + +ValueStore::ReadResult CefValueStore::Get( + const std::vector& keys) { + read_count_++; + base::DictionaryValue* settings = new base::DictionaryValue(); + for (std::vector::const_iterator it = keys.begin(); + it != keys.end(); ++it) { + base::Value* value = NULL; + if (storage_.GetWithoutPathExpansion(*it, &value)) { + settings->SetWithoutPathExpansion(*it, value->CreateDeepCopy()); + } + } + return MakeReadResult(base::WrapUnique(settings), status_); +} + +ValueStore::ReadResult CefValueStore::Get() { + read_count_++; + return MakeReadResult(storage_.CreateDeepCopy(), status_); +} + +ValueStore::WriteResult CefValueStore::Set(WriteOptions options, + const std::string& key, + const base::Value& value) { + base::DictionaryValue settings; + settings.SetWithoutPathExpansion(key, value.CreateDeepCopy()); + return Set(options, settings); +} + +ValueStore::WriteResult CefValueStore::Set( + WriteOptions options, + const base::DictionaryValue& settings) { + write_count_++; + std::unique_ptr changes(new ValueStoreChangeList()); + for (base::DictionaryValue::Iterator it(settings); !it.IsAtEnd(); + it.Advance()) { + base::Value* old_value = NULL; + if (!storage_.GetWithoutPathExpansion(it.key(), &old_value) || + !old_value->Equals(&it.value())) { + changes->push_back(ValueStoreChange( + it.key(), old_value ? old_value->CreateDeepCopy() : nullptr, + it.value().CreateDeepCopy())); + storage_.SetWithoutPathExpansion(it.key(), it.value().CreateDeepCopy()); + } + } + return MakeWriteResult(std::move(changes), status_); +} + +ValueStore::WriteResult CefValueStore::Remove(const std::string& key) { + return Remove(std::vector(1, key)); +} + +ValueStore::WriteResult CefValueStore::Remove( + const std::vector& keys) { + write_count_++; + std::unique_ptr changes(new ValueStoreChangeList()); + for (std::vector::const_iterator it = keys.begin(); + it != keys.end(); ++it) { + std::unique_ptr old_value; + if (storage_.RemoveWithoutPathExpansion(*it, &old_value)) { + changes->push_back(ValueStoreChange(*it, std::move(old_value), nullptr)); + } + } + return MakeWriteResult(std::move(changes), status_); +} + +ValueStore::WriteResult CefValueStore::Clear() { + std::vector keys; + for (base::DictionaryValue::Iterator it(storage_); !it.IsAtEnd(); + it.Advance()) { + keys.push_back(it.key()); + } + return Remove(keys); +} diff --git a/libcef/browser/extensions/value_store/cef_value_store.h b/libcef/browser/extensions/value_store/cef_value_store.h new file mode 100644 index 000000000..c9fc6332e --- /dev/null +++ b/libcef/browser/extensions/value_store/cef_value_store.h @@ -0,0 +1,59 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. +// Portions copyright 2016 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. + +#ifndef CEF_LIBCEF_BROWSER_EXTENSIONS_VALUE_STORE_CEF_VALUE_STORE_H_ +#define CEF_LIBCEF_BROWSER_EXTENSIONS_VALUE_STORE_CEF_VALUE_STORE_H_ + +#include + +#include +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "extensions/browser/value_store/value_store.h" + +// Implementation Based on TestingValueStore +// ValueStore with an in-memory storage but the ability to +// optionally fail all operations. +class CefValueStore : public ValueStore { + public: + CefValueStore(); + ~CefValueStore() override; + + // Accessors for the number of reads/writes done by this value store. Each + // Get* operation (except for the BytesInUse ones) counts as one read, and + // each Set*/Remove/Clear operation counts as one write. This is useful in + // tests seeking to assert that some number of reads/writes to their + // underlying value store have (or have not) happened. + int read_count() const { return read_count_; } + int write_count() const { return write_count_; } + + // ValueStore implementation. + size_t GetBytesInUse(const std::string& key) override; + size_t GetBytesInUse(const std::vector& keys) override; + size_t GetBytesInUse() override; + ReadResult Get(const std::string& key) override; + ReadResult Get(const std::vector& keys) override; + ReadResult Get() override; + WriteResult Set(WriteOptions options, + const std::string& key, + const base::Value& value) override; + WriteResult Set(WriteOptions options, + const base::DictionaryValue& values) override; + WriteResult Remove(const std::string& key) override; + WriteResult Remove(const std::vector& keys) override; + WriteResult Clear() override; + + private: + base::DictionaryValue storage_; + int read_count_; + int write_count_; + ValueStore::Status status_; + + DISALLOW_COPY_AND_ASSIGN(CefValueStore); +}; + +#endif // CEF_LIBCEF_BROWSER_EXTENSIONS_VALUE_STORE_CEF_VALUE_STORE_H_ diff --git a/libcef/browser/extensions/value_store/cef_value_store_factory.cc b/libcef/browser/extensions/value_store/cef_value_store_factory.cc new file mode 100644 index 000000000..cd52d615d --- /dev/null +++ b/libcef/browser/extensions/value_store/cef_value_store_factory.cc @@ -0,0 +1,191 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. +// Portions copyright 2016 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 "libcef/browser/extensions/value_store/cef_value_store_factory.h" + +#include "base/memory/ptr_util.h" +#include "extensions/browser/value_store/leveldb_value_store.h" +#include "libcef/browser/extensions/value_store/cef_value_store.h" + +namespace { + +const char kUMAClientName[] = "Cef"; + +} // namespace + +namespace extensions { + +using SettingsNamespace = settings_namespace::Namespace; + +CefValueStoreFactory::StorageHelper::StorageHelper() = default; + +CefValueStoreFactory::StorageHelper::~StorageHelper() = default; + +std::set CefValueStoreFactory::StorageHelper::GetKnownExtensionIDs( + ModelType model_type) const { + std::set ids; + switch (model_type) { + case ValueStoreFactory::ModelType::APP: + for (const auto& key : app_stores_) + ids.insert(key.first); + break; + case ValueStoreFactory::ModelType::EXTENSION: + for (const auto& key : extension_stores_) + ids.insert(key.first); + break; + } + return ids; +} + +void CefValueStoreFactory::StorageHelper::Reset() { + app_stores_.clear(); + extension_stores_.clear(); +} + +ValueStore* CefValueStoreFactory::StorageHelper::AddValueStore( + const ExtensionId& extension_id, + ValueStore* value_store, + ModelType model_type) { + if (model_type == ValueStoreFactory::ModelType::APP) { + DCHECK(app_stores_.find(extension_id) == app_stores_.end()); + app_stores_[extension_id] = value_store; + } else { + DCHECK(extension_stores_.find(extension_id) == extension_stores_.end()); + extension_stores_[extension_id] = value_store; + } + return value_store; +} + +void CefValueStoreFactory::StorageHelper::DeleteSettings( + const ExtensionId& extension_id, + ModelType model_type) { + switch (model_type) { + case ValueStoreFactory::ModelType::APP: + app_stores_.erase(extension_id); + break; + case ValueStoreFactory::ModelType::EXTENSION: + extension_stores_.erase(extension_id); + break; + } +} + +bool CefValueStoreFactory::StorageHelper::HasSettings( + const ExtensionId& extension_id, + ModelType model_type) const { + switch (model_type) { + case ValueStoreFactory::ModelType::APP: + return app_stores_.find(extension_id) != app_stores_.end(); + case ValueStoreFactory::ModelType::EXTENSION: + return extension_stores_.find(extension_id) != extension_stores_.end(); + } + NOTREACHED(); + return false; +} + +ValueStore* CefValueStoreFactory::StorageHelper::GetExisting( + const ExtensionId& extension_id) const { + auto it = app_stores_.find(extension_id); + if (it != app_stores_.end()) + return it->second; + it = extension_stores_.find(extension_id); + if (it != extension_stores_.end()) + return it->second; + return nullptr; +} + +CefValueStoreFactory::CefValueStoreFactory() = default; + +CefValueStoreFactory::CefValueStoreFactory(const base::FilePath& db_path) + : db_path_(db_path) {} + +CefValueStoreFactory::~CefValueStoreFactory() {} + +std::unique_ptr CefValueStoreFactory::CreateRulesStore() { + if (db_path_.empty()) + last_created_store_ = new CefValueStore(); + else + last_created_store_ = new LeveldbValueStore(kUMAClientName, db_path_); + return base::WrapUnique(last_created_store_); +} + +std::unique_ptr CefValueStoreFactory::CreateStateStore() { + return CreateRulesStore(); +} + +CefValueStoreFactory::StorageHelper& CefValueStoreFactory::GetStorageHelper( + SettingsNamespace settings_namespace) { + switch (settings_namespace) { + case settings_namespace::LOCAL: + return local_helper_; + case settings_namespace::SYNC: + return sync_helper_; + case settings_namespace::MANAGED: + return managed_helper_; + case settings_namespace::INVALID: + break; + } + NOTREACHED(); + return local_helper_; +} + +std::unique_ptr CefValueStoreFactory::CreateSettingsStore( + SettingsNamespace settings_namespace, + ModelType model_type, + const ExtensionId& extension_id) { + std::unique_ptr settings_store(CreateRulesStore()); + // Note: This factory is purposely keeping the raw pointers to each ValueStore + // created. Tests using CefValueStoreFactory must be careful to keep + // those ValueStore's alive for the duration of their test. + GetStorageHelper(settings_namespace) + .AddValueStore(extension_id, settings_store.get(), model_type); + return settings_store; +} + +ValueStore* CefValueStoreFactory::LastCreatedStore() const { + return last_created_store_; +} + +void CefValueStoreFactory::DeleteSettings(SettingsNamespace settings_namespace, + ModelType model_type, + const ExtensionId& extension_id) { + GetStorageHelper(settings_namespace).DeleteSettings(extension_id, model_type); +} + +bool CefValueStoreFactory::HasSettings(SettingsNamespace settings_namespace, + ModelType model_type, + const ExtensionId& extension_id) { + return GetStorageHelper(settings_namespace) + .HasSettings(extension_id, model_type); +} + +std::set CefValueStoreFactory::GetKnownExtensionIDs( + SettingsNamespace settings_namespace, + ModelType model_type) const { + return const_cast(this) + ->GetStorageHelper(settings_namespace) + .GetKnownExtensionIDs(model_type); +} + +ValueStore* CefValueStoreFactory::GetExisting( + const ExtensionId& extension_id) const { + ValueStore* existing_store = local_helper_.GetExisting(extension_id); + if (existing_store) + return existing_store; + existing_store = sync_helper_.GetExisting(extension_id); + if (existing_store) + return existing_store; + existing_store = managed_helper_.GetExisting(extension_id); + DCHECK(existing_store != nullptr); + return existing_store; +} + +void CefValueStoreFactory::Reset() { + last_created_store_ = nullptr; + local_helper_.Reset(); + sync_helper_.Reset(); + managed_helper_.Reset(); +} + +} // namespace extensions diff --git a/libcef/browser/extensions/value_store/cef_value_store_factory.h b/libcef/browser/extensions/value_store/cef_value_store_factory.h new file mode 100644 index 000000000..073b5f713 --- /dev/null +++ b/libcef/browser/extensions/value_store/cef_value_store_factory.h @@ -0,0 +1,97 @@ +// Copyright 2017 The Chromium Embedded Framework Authors. +// Portions copyright 2016 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. + +#ifndef CEF_LIBCEF_BROWSER_EXTENSIONS_VALUE_STORE_CEF_VALUE_STORE_FACTORY_H_ +#define CEF_LIBCEF_BROWSER_EXTENSIONS_VALUE_STORE_CEF_VALUE_STORE_FACTORY_H_ + +#include +#include +#include + +#include "base/files/file_path.h" +#include "extensions/browser/value_store/value_store_factory.h" +#include "extensions/common/extension_id.h" + +class ValueStore; + +namespace extensions { + +// Will either open a database on disk (if path provided) returning a +// |LeveldbValueStore|. Otherwise a new |CefValueStore| instance will be +// returned. +class CefValueStoreFactory : public ValueStoreFactory { + public: + CefValueStoreFactory(); + explicit CefValueStoreFactory(const base::FilePath& db_path); + + // ValueStoreFactory + std::unique_ptr CreateRulesStore() override; + std::unique_ptr CreateStateStore() override; + std::unique_ptr CreateSettingsStore( + settings_namespace::Namespace settings_namespace, + ModelType model_type, + const ExtensionId& extension_id) override; + void DeleteSettings(settings_namespace::Namespace settings_namespace, + ModelType model_type, + const ExtensionId& extension_id) override; + bool HasSettings(settings_namespace::Namespace settings_namespace, + ModelType model_type, + const ExtensionId& extension_id) override; + std::set GetKnownExtensionIDs( + settings_namespace::Namespace settings_namespace, + ModelType model_type) const override; + + // Return the last created |ValueStore|. Use with caution as this may return + // a dangling pointer since the creator now owns the ValueStore which can be + // deleted at any time. + ValueStore* LastCreatedStore() const; + // Return a previously created |ValueStore| for an extension. + ValueStore* GetExisting(const ExtensionId& extension_id) const; + // Reset this class (as if just created). + void Reset(); + + private: + // Manages a collection of |ValueStore|'s created for an app/extension. + // One of these exists for each setting type. + class StorageHelper { + public: + StorageHelper(); + ~StorageHelper(); + std::set GetKnownExtensionIDs(ModelType model_type) const; + ValueStore* AddValueStore(const ExtensionId& extension_id, + ValueStore* value_store, + ModelType model_type); + void DeleteSettings(const ExtensionId& extension_id, ModelType model_type); + bool HasSettings(const ExtensionId& extension_id, + ModelType model_type) const; + void Reset(); + ValueStore* GetExisting(const ExtensionId& extension_id) const; + + private: + std::map app_stores_; + std::map extension_stores_; + + DISALLOW_COPY_AND_ASSIGN(StorageHelper); + }; + + StorageHelper& GetStorageHelper( + settings_namespace::Namespace settings_namespace); + + ~CefValueStoreFactory() override; + base::FilePath db_path_; + ValueStore* last_created_store_ = nullptr; + + // None of these value stores are owned by this factory, so care must be + // taken when calling GetExisting. + StorageHelper local_helper_; + StorageHelper sync_helper_; + StorageHelper managed_helper_; + + DISALLOW_COPY_AND_ASSIGN(CefValueStoreFactory); +}; + +} // namespace extensions + +#endif // CEF_LIBCEF_BROWSER_EXTENSIONS_VALUE_STORE_CEF_VALUE_STORE_FACTORY_H_ diff --git a/tests/ceftests/extensions/chrome_storage_unittest.cc b/tests/ceftests/extensions/chrome_storage_unittest.cc new file mode 100644 index 000000000..68b5f4692 --- /dev/null +++ b/tests/ceftests/extensions/chrome_storage_unittest.cc @@ -0,0 +1,472 @@ +// Copyright (c) 2017 The Chromium Embedded Framework 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 "tests/ceftests/extensions/extension_test_handler.h" +#include "tests/ceftests/test_util.h" +#include "tests/shared/browser/extension_util.h" + +#define STORAGE_TEST_GROUP_ALL(name, test_class) \ + EXTENSION_TEST_GROUP_ALL(ChromeStorage##name, test_class) +#define STORAGE_TEST_GROUP_MINIMAL(name, test_class) \ + EXTENSION_TEST_GROUP_MINIMAL(ChromeStorage##name, test_class) + +namespace { + +const char kExtensionPath[] = "storage-extension"; +const char kSuccessMessage[] = "success"; + +// Base class for testing chrome.storage methods. +// See https://developer.chrome.com/extensions/storage +class StorageTestHandler : public ExtensionTestHandler { + public: + explicit StorageTestHandler(RequestContextType request_context_type) + : ExtensionTestHandler(request_context_type) { + // Only creating the extension browser. + set_create_main_browser(false); + } + + // CefExtensionHandler methods: + void OnExtensionLoaded(CefRefPtr extension) override { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_FALSE(got_loaded_); + got_loaded_.yes(); + + // Verify |extension| contents. + EXPECT_FALSE(extension->GetIdentifier().empty()); + EXPECT_STREQ(("extensions/" + std::string(kExtensionPath)).c_str(), + client::extension_util::GetInternalExtensionResourcePath( + extension->GetPath()) + .c_str()); + TestDictionaryEqual(CreateManifest(), extension->GetManifest()); + + EXPECT_FALSE(extension_); + extension_ = extension; + + CreateBrowserForExtension(); + } + + void OnExtensionUnloaded(CefRefPtr extension) override { + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + EXPECT_TRUE(extension_); + EXPECT_TRUE(extension_->IsSame(extension)); + EXPECT_FALSE(got_unloaded_); + got_unloaded_.yes(); + extension_ = NULL; + + // Execute asynchronously so call stacks have a chance to unwind. + // Will close the browser windows. + CefPostTask(TID_UI, base::Bind(&StorageTestHandler::DestroyTest, this)); + } + + // CefLoadHandler methods: + void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override { + CefRefPtr extension = browser->GetHost()->GetExtension(); + EXPECT_TRUE(extension); + EXPECT_TRUE(extension_->IsSame(extension)); + + if (isLoading) { + EXPECT_FALSE(extension_browser_); + extension_browser_ = browser; + } else { + EXPECT_TRUE(browser->IsSame(extension_browser_)); + + const std::string& url = browser->GetMainFrame()->GetURL(); + EXPECT_STREQ(extension_url_.c_str(), url.c_str()); + } + } + + // CefRequestHandler methods: + CefRefPtr GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override { + EXPECT_TRUE(browser->IsSame(extension_browser_)); + + CefRefPtr extension = browser->GetHost()->GetExtension(); + EXPECT_TRUE(extension); + EXPECT_TRUE(extension_->IsSame(extension)); + + const std::string& url = request->GetURL(); + EXPECT_STREQ(extension_url_.c_str(), url.c_str()); + + EXPECT_FALSE(got_url_request_); + got_url_request_.yes(); + + // Handle the resource request. + return RoutingTestHandler::GetResourceHandler(browser, frame, request); + } + + protected: + void OnLoadExtensions() override { + LoadExtension(kExtensionPath, CreateManifest()); + } + + bool OnMessage(CefRefPtr browser, + const std::string& message) override { + if (message == "extension_onload") { + // From body onLoad in the extension browser. + EXPECT_TRUE(browser->IsSame(extension_browser_)); + EXPECT_FALSE(got_body_onload_); + got_body_onload_.yes(); + TriggerStorageApiJSFunction(); + return true; + } + EXPECT_TRUE(browser->IsSame(extension_browser_)); + EXPECT_FALSE(got_success_message_); + got_success_message_.yes(); + EXPECT_STREQ(kSuccessMessage, message.c_str()); + TriggerDestroyTest(); + return true; + } + + void OnDestroyTest() override { + extension_browser_ = NULL; + + EXPECT_TRUE(got_loaded_); + EXPECT_TRUE(got_url_request_); + EXPECT_TRUE(got_body_onload_); + EXPECT_TRUE(got_trigger_api_function_); + EXPECT_TRUE(got_success_message_); + EXPECT_TRUE(got_unloaded_); + } + + // Create a manifest that grants access to the storage API. + virtual CefRefPtr CreateManifest() const { + ApiPermissionsList api_permissions; + api_permissions.push_back("storage"); + return CreateDefaultManifest(api_permissions); + } + + // Add resources in the extension browser. + virtual void OnAddExtensionResources(const std::string& origin) { + extension_url_ = origin + "extension.html"; + AddResource(extension_url_, GetExtensionHTML(), "text/html"); + } + + // Returns the chrome.storage.* JS that is executed in the extension browser + // when the triggerStorageApi() JS function is called. + virtual std::string GetStorageApiJS() const = 0; + + // Returns the JS that will be loaded in the extension browser. This + // implements the triggerStorageApi() JS function called from + // TriggerStorageApiJSFunction(). + virtual std::string GetExtensionJS() const { + return "function triggerStorageApi() {" + GetStorageApiJS() + "}"; + } + + // Returns the HTML that will be loaded in the extension browser. + virtual std::string GetExtensionHTML() const { + return "Extension"; + } + + virtual void TriggerDestroyTest() { + // Execute asynchronously so call stacks have a chance to unwind. + CefPostTask(TID_UI, base::Bind(&StorageTestHandler::UnloadExtension, this, + extension_)); + } + + CefRefPtr extension() const { return extension_; } + std::string extension_url() const { return extension_url_; } + CefRefPtr extension_browser() const { return extension_browser_; } + + bool got_success_message() const { return got_success_message_; } + + private: + void CreateBrowserForExtension() { + const std::string& identifier = extension_->GetIdentifier(); + EXPECT_FALSE(identifier.empty()); + const std::string& origin = + client::extension_util::GetExtensionOrigin(identifier); + EXPECT_FALSE(origin.empty()); + + // Add extension resources. + OnAddExtensionResources(origin); + + // Create a browser to host the extension. + CreateBrowser(extension_url_, request_context()); + } + + void TriggerStorageApiJSFunction() { + EXPECT_FALSE(got_trigger_api_function_); + got_trigger_api_function_.yes(); + + extension_browser_->GetMainFrame()->ExecuteJavaScript( + "triggerStorageApi();", extension_url_, 0); + } + + CefRefPtr extension_; + std::string extension_url_; + CefRefPtr extension_browser_; + + TrackCallback got_loaded_; + TrackCallback got_url_request_; + TrackCallback got_body_onload_; + TrackCallback got_trigger_api_function_; + TrackCallback got_success_message_; + TrackCallback got_unloaded_; +}; +} // namespace + +namespace { + +// Test for chrome.storage.local.set(object items, function callback) +// and for chrome.storage.local.get(string or array of string or object keys, +// function callback) +class LocalStorageTestHandler : public StorageTestHandler { + public: + explicit LocalStorageTestHandler(RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "chrome.storage.local.set({\"local_key_1\": \"local_value_1\"}, function() {" + "chrome.storage.local.get(\"local_key_1\", function (items) {" + "if(items[\"local_key_1\"] == \"local_value_1\") {" + + GetMessageJS(kSuccessMessage) + + "}});" + "});"; + } + + private: + IMPLEMENT_REFCOUNTING(LocalStorageTestHandler); + DISALLOW_COPY_AND_ASSIGN(LocalStorageTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_ALL(LocalStorage, LocalStorageTestHandler); + +namespace { + +// Test for chrome.storage.local.getBytesInUse(string or array of string keys, +// function callback) +class LocalStorageGetBytesInUseTestHandler : public StorageTestHandler { + public: + explicit LocalStorageGetBytesInUseTestHandler( + RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "chrome.storage.local.set({\"local_key_2\": \"local_value_2\"}, function() {" + "chrome.storage.local.getBytesInUse(\"local_key_2\", function (bytesInUse) {" + "if (bytesInUse == 26) {" + + GetMessageJS(kSuccessMessage) + + "}});" + "});"; + } + + private: + IMPLEMENT_REFCOUNTING(LocalStorageGetBytesInUseTestHandler); + DISALLOW_COPY_AND_ASSIGN(LocalStorageGetBytesInUseTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_MINIMAL(LocalStorageGetBytesInUse, + LocalStorageGetBytesInUseTestHandler); + +namespace { + +// Test for chrome.storage.local.remove(string or array of string keys, function +// callback) +class LocalStorageRemoveTestHandler : public StorageTestHandler { + public: + explicit LocalStorageRemoveTestHandler( + RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "chrome.storage.local.set({\"local_key_3\": \"local_value_3\"}, function() {" + "chrome.storage.local.remove(\"local_key_3\", function () {" + "chrome.storage.local.get(\"local_key_3\", function(items) {" + "if (items[\"local_key_3\"] == undefined) {" + + GetMessageJS(kSuccessMessage) + + "}})})" + "});"; + } + + private: + IMPLEMENT_REFCOUNTING(LocalStorageRemoveTestHandler); + DISALLOW_COPY_AND_ASSIGN(LocalStorageRemoveTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_MINIMAL(LocalStorageRemove, LocalStorageRemoveTestHandler); + +namespace { + +// Test for chrome.storage.local.clear(function callback) +class LocalStorageClearTestHandler : public StorageTestHandler { + public: + explicit LocalStorageClearTestHandler(RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "var value1Cleared = false;" + "var value2Cleared = false;" + "function checkCleared() {" + "if (value1Cleared && value2Cleared) {" + + GetMessageJS(kSuccessMessage) + + "}}" + "chrome.storage.local.set({\"local_key_4\": \"local_value_4\"," + "\"local_key_5\": \"local_value_5\"}, function() {" + "chrome.storage.local.clear(function () {" + + "chrome.storage.local.get(\"local_key_4\", function(items) {" + "if (items[\"local_key_4\"] == undefined) {" + "value1Cleared = true;" + "checkCleared();" + "}});" + + "chrome.storage.local.get(\"local_key_5\", function(items) {" + "if (items[\"local_key_5\"] == undefined) {" + "value2Cleared = true;" + "checkCleared();" + "}});" + "})});"; + } + + private: + IMPLEMENT_REFCOUNTING(LocalStorageClearTestHandler); + DISALLOW_COPY_AND_ASSIGN(LocalStorageClearTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_MINIMAL(LocalStorageClear, LocalStorageClearTestHandler); + +namespace { + +// Test for chrome.storage.sync.set(object items, function callback) +// and for chrome.storage.sync.get(string or array of string or object keys, +// function callback) +class SyncStorageTestHandler : public StorageTestHandler { + public: + explicit SyncStorageTestHandler(RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "chrome.storage.sync.set({\"sync_key_1\": \"sync_value_1\"}, function() {" + "chrome.storage.sync.get(\"sync_key_1\", function (items) {" + "if (items[\"sync_key_1\"] == \"sync_value_1\") {" + + GetMessageJS(kSuccessMessage) + + "}});" + "});"; + } + + private: + IMPLEMENT_REFCOUNTING(SyncStorageTestHandler); + DISALLOW_COPY_AND_ASSIGN(SyncStorageTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_ALL(SyncStorage, SyncStorageTestHandler); + +namespace { + +// Test for chrome.storage.sync.getBytesInUse(string or array of string keys, +// function callback) +class SyncStorageGetBytesInUseTestHandler : public StorageTestHandler { + public: + explicit SyncStorageGetBytesInUseTestHandler( + RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "chrome.storage.sync.set({\"sync_key_2\": \"sync_value_2\"}, function() {" + "chrome.storage.sync.getBytesInUse(\"sync_key_2\", function (bytesInUse) {" + "if (bytesInUse == 24) {" + + GetMessageJS(kSuccessMessage) + + "}});" + "});"; + } + + private: + IMPLEMENT_REFCOUNTING(SyncStorageGetBytesInUseTestHandler); + DISALLOW_COPY_AND_ASSIGN(SyncStorageGetBytesInUseTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_MINIMAL(SyncStorageGetBytesInUse, + SyncStorageGetBytesInUseTestHandler); + +namespace { + +// Test for chrome.storage.sync.remove(string or array of string keys, function +// callback) +class SyncStorageRemoveTestHandler : public StorageTestHandler { + public: + explicit SyncStorageRemoveTestHandler(RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "chrome.storage.sync.set({\"sync_key_3\": \"sync_value_3\"}, function() {" + "chrome.storage.sync.remove(\"sync_key_3\", function () {" + "chrome.storage.sync.get(\"sync_key_3\", function(items) {" + "if (items[\"sync_key_3\"] == undefined) {" + + GetMessageJS(kSuccessMessage) + + "}})})" + "});"; + } + + private: + IMPLEMENT_REFCOUNTING(SyncStorageRemoveTestHandler); + DISALLOW_COPY_AND_ASSIGN(SyncStorageRemoveTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_MINIMAL(SyncStorageRemove, SyncStorageRemoveTestHandler); + +namespace { + +// Test for chrome.storage.sync.clear(function callback) +class SyncStorageClearTestHandler : public StorageTestHandler { + public: + explicit SyncStorageClearTestHandler(RequestContextType request_context_type) + : StorageTestHandler(request_context_type) {} + + protected: + std::string GetStorageApiJS() const override { + return "var value1Cleared = false;" + "var value2Cleared = false;" + + "function checkCleared() {" + "if (value1Cleared && value2Cleared) {" + + GetMessageJS(kSuccessMessage) + + "}}" + + "chrome.storage.sync.set({\"sync_key_4\": \"sync_value_4\"," + "\"sync_key_5\": \"sync_value_5\"}, function() {" + "chrome.storage.sync.clear(function () {" + + "chrome.storage.sync.get(\"sync_key_4\", function(items) {" + "if (items[\"sync_key_4\"] == undefined) {" + "value1Cleared = true;" + "checkCleared();" + "}});" + + "chrome.storage.sync.get(\"sync_key_5\", function(items) {" + "if (items[\"sync_key_5\"] == undefined) {" + "value2Cleared = true;" + "checkCleared();" + "}});" + + "})});"; + } + + private: + IMPLEMENT_REFCOUNTING(SyncStorageClearTestHandler); + DISALLOW_COPY_AND_ASSIGN(SyncStorageClearTestHandler); +}; +} // namespace + +STORAGE_TEST_GROUP_MINIMAL(SyncStorageClear, SyncStorageClearTestHandler);