| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/run_loop.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/extensions/api/storage/settings_sync_util.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/extensions/extension_system_factory.h" |
| #include "chrome/browser/policy/schema_registry_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "components/policy/core/browser/browser_policy_connector.h" |
| #include "components/policy/core/common/mock_configuration_policy_provider.h" |
| #include "components/policy/core/common/policy_bundle.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/core/common/policy_namespace.h" |
| #include "components/policy/core/common/policy_types.h" |
| #include "components/policy/core/common/schema.h" |
| #include "components/policy/core/common/schema_map.h" |
| #include "components/policy/core/common/schema_registry.h" |
| #include "components/sync/model/sync_change.h" |
| #include "components/sync/model/sync_change_processor.h" |
| #include "components/sync/model/sync_error_factory.h" |
| #include "components/sync/model/syncable_service.h" |
| #include "components/sync/test/fake_sync_change_processor.h" |
| #include "components/sync/test/sync_change_processor_wrapper_for_test.h" |
| #include "components/sync/test/sync_error_factory_mock.h" |
| #include "components/version_info/channel.h" |
| #include "content/public/test/browser_test.h" |
| #include "extensions/browser/api/storage/backend_task_runner.h" |
| #include "extensions/browser/api/storage/settings_namespace.h" |
| #include "extensions/browser/api/storage/storage_area_namespace.h" |
| #include "extensions/browser/api/storage/storage_frontend.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/common/features/feature_channel.h" |
| #include "extensions/common/value_builder.h" |
| #include "extensions/test/extension_test_message_listener.h" |
| #include "extensions/test/result_catcher.h" |
| #include "extensions/test/test_extension_dir.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| namespace extensions { |
| |
| using testing::NiceMock; |
| |
| namespace { |
| |
| // TODO(kalman): test both EXTENSION_SETTINGS and APP_SETTINGS. |
| const syncer::ModelType kModelType = syncer::EXTENSION_SETTINGS; |
| |
| // The managed_storage extension has a key defined in its manifest, so that |
| // its extension ID is well-known and the policy system can push policies for |
| // the extension. |
| const char kManagedStorageExtensionId[] = "kjmkgkdkpedkejedfhmfcenooemhbpbo"; |
| |
| class TestSchemaRegistryObserver : public policy::SchemaRegistry::Observer { |
| public: |
| TestSchemaRegistryObserver() = default; |
| ~TestSchemaRegistryObserver() override = default; |
| TestSchemaRegistryObserver(const TestSchemaRegistryObserver&) = delete; |
| TestSchemaRegistryObserver& operator=(const TestSchemaRegistryObserver&) = |
| delete; |
| |
| void OnSchemaRegistryUpdated(bool has_new_schemas) override { |
| has_new_schemas_ = has_new_schemas; |
| run_loop_.Quit(); |
| } |
| |
| void WaitForSchemaRegistryUpdated() { run_loop_.Run(); } |
| |
| bool has_new_schemas() const { return has_new_schemas_; } |
| |
| private: |
| bool has_new_schemas_ = false; |
| base::RunLoop run_loop_; |
| }; |
| |
| } // namespace |
| |
| class ExtensionSettingsApiTest : public ExtensionApiTest { |
| public: |
| explicit ExtensionSettingsApiTest( |
| ContextType context_type = ContextType::kNone) |
| : ExtensionApiTest(context_type) {} |
| ~ExtensionSettingsApiTest() override = default; |
| ExtensionSettingsApiTest(const ExtensionSettingsApiTest&) = delete; |
| ExtensionSettingsApiTest& operator=(const ExtensionSettingsApiTest&) = delete; |
| |
| protected: |
| void SetUpInProcessBrowserTestFixture() override { |
| ExtensionApiTest::SetUpInProcessBrowserTestFixture(); |
| |
| policy_provider_.SetDefaultReturns( |
| /*is_initialization_complete_return=*/true, |
| /*is_first_policy_load_complete_return=*/true); |
| policy_provider_.SetAutoRefresh(); |
| policy::BrowserPolicyConnector::SetPolicyProviderForTesting( |
| &policy_provider_); |
| } |
| |
| void ReplyWhenSatisfied(StorageAreaNamespace storage_area, |
| const std::string& normal_action, |
| const std::string& incognito_action) { |
| MaybeLoadAndReplyWhenSatisfied(storage_area, normal_action, |
| incognito_action, nullptr, false); |
| } |
| |
| const Extension* LoadAndReplyWhenSatisfied( |
| StorageAreaNamespace storage_area, |
| const std::string& normal_action, |
| const std::string& incognito_action, |
| const std::string& extension_dir) { |
| return MaybeLoadAndReplyWhenSatisfied( |
| storage_area, normal_action, incognito_action, &extension_dir, false); |
| } |
| |
| void FinalReplyWhenSatisfied(StorageAreaNamespace storage_area, |
| const std::string& normal_action, |
| const std::string& incognito_action) { |
| MaybeLoadAndReplyWhenSatisfied(storage_area, normal_action, |
| incognito_action, nullptr, true); |
| } |
| |
| static void InitSyncOnBackgroundSequence( |
| base::OnceCallback<base::WeakPtr<syncer::SyncableService>()> |
| syncable_service_provider, |
| syncer::SyncChangeProcessor* sync_processor) { |
| DCHECK(GetBackendTaskRunner()->RunsTasksInCurrentSequence()); |
| |
| base::WeakPtr<syncer::SyncableService> syncable_service = |
| std::move(syncable_service_provider).Run(); |
| DCHECK(syncable_service.get()); |
| EXPECT_FALSE( |
| syncable_service |
| ->MergeDataAndStartSyncing( |
| kModelType, syncer::SyncDataList(), |
| std::make_unique<syncer::SyncChangeProcessorWrapperForTest>( |
| sync_processor), |
| std::make_unique<NiceMock<syncer::SyncErrorFactoryMock>>()) |
| .has_value()); |
| } |
| |
| void InitSync(syncer::SyncChangeProcessor* sync_processor) { |
| base::RunLoop().RunUntilIdle(); |
| |
| base::RunLoop loop; |
| GetBackendTaskRunner()->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(&InitSyncOnBackgroundSequence, |
| settings_sync_util::GetSyncableServiceProvider( |
| profile(), kModelType), |
| sync_processor), |
| loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| static void SendChangesOnBackgroundSequence( |
| base::OnceCallback<base::WeakPtr<syncer::SyncableService>()> |
| syncable_service_provider, |
| const syncer::SyncChangeList& change_list) { |
| DCHECK(GetBackendTaskRunner()->RunsTasksInCurrentSequence()); |
| |
| base::WeakPtr<syncer::SyncableService> syncable_service = |
| std::move(syncable_service_provider).Run(); |
| DCHECK(syncable_service.get()); |
| EXPECT_FALSE(syncable_service->ProcessSyncChanges(FROM_HERE, change_list) |
| .has_value()); |
| } |
| |
| void SendChanges(const syncer::SyncChangeList& change_list) { |
| base::RunLoop().RunUntilIdle(); |
| |
| base::RunLoop loop; |
| GetBackendTaskRunner()->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(&SendChangesOnBackgroundSequence, |
| settings_sync_util::GetSyncableServiceProvider( |
| profile(), kModelType), |
| change_list), |
| loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| void SetPolicies(const base::Value::Dict& policies) { |
| std::unique_ptr<policy::PolicyBundle> bundle(new policy::PolicyBundle()); |
| policy::PolicyMap& policy_map = bundle->Get(policy::PolicyNamespace( |
| policy::POLICY_DOMAIN_EXTENSIONS, kManagedStorageExtensionId)); |
| policy_map.LoadFrom(policies, policy::POLICY_LEVEL_MANDATORY, |
| policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD); |
| policy_provider_.UpdatePolicy(std::move(bundle)); |
| } |
| |
| private: |
| const Extension* MaybeLoadAndReplyWhenSatisfied( |
| StorageAreaNamespace storage_area, |
| const std::string& normal_action, |
| const std::string& incognito_action, |
| // May be NULL to imply not loading the extension. |
| const std::string* extension_dir, |
| bool is_final_action) { |
| ExtensionTestMessageListener listener("waiting", ReplyBehavior::kWillReply); |
| ExtensionTestMessageListener listener_incognito("waiting_incognito", |
| ReplyBehavior::kWillReply); |
| |
| // Only load the extension after the listeners have been set up, to avoid |
| // initialisation race conditions. |
| const Extension* extension = nullptr; |
| if (extension_dir) { |
| extension = LoadExtension( |
| test_data_dir_.AppendASCII("settings").AppendASCII(*extension_dir), |
| {.allow_in_incognito = true}); |
| EXPECT_TRUE(extension); |
| } |
| |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(listener_incognito.WaitUntilSatisfied()); |
| |
| listener.Reply(CreateMessage(storage_area, normal_action, is_final_action)); |
| listener_incognito.Reply( |
| CreateMessage(storage_area, incognito_action, is_final_action)); |
| return extension; |
| } |
| |
| std::string CreateMessage(StorageAreaNamespace storage_area, |
| const std::string& action, |
| bool is_final_action) { |
| base::DictionaryValue message; |
| message.SetStringKey("namespace", StorageAreaToString(storage_area)); |
| message.SetStringKey("action", action); |
| message.SetBoolKey("isFinalAction", is_final_action); |
| std::string message_json; |
| base::JSONWriter::Write(message, &message_json); |
| return message_json; |
| } |
| |
| void SendChangesToSyncableService( |
| const syncer::SyncChangeList& change_list, |
| syncer::SyncableService* settings_service) { |
| } |
| |
| protected: |
| testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, |
| SessionInUnsupportedExtension) { |
| constexpr char kManifest[] = |
| R"({ |
| "name": "Unsupported manifest version for Storage API", |
| "manifest_version": 2, |
| "version": "0.1", |
| "background": {"scripts": ["script.js"]}, |
| "permissions": ["storage"] |
| })"; |
| constexpr char kScript[] = |
| R"({ |
| chrome.test.runTests([ |
| function unsupported() { |
| chrome.test.assertEq(undefined, chrome.storage.session), |
| chrome.test.assertTrue(!!chrome.storage.local); |
| chrome.test.succeed(); |
| } |
| ]) |
| })"; |
| |
| TestExtensionDir test_dir; |
| test_dir.WriteManifest(kManifest); |
| test_dir.WriteFile(FILE_PATH_LITERAL("script.js"), kScript); |
| |
| ResultCatcher catcher; |
| const Extension* extension = LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, SimpleTest) { |
| ASSERT_TRUE(RunExtensionTest("settings/simple_test")) << message_; |
| } |
| |
| // Structure of this test taken from IncognitoSplitMode. |
| // Note that only split-mode incognito is tested, because spanning mode |
| // incognito looks the same as normal mode when the only API activity comes |
| // from background pages. |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, SplitModeIncognito) { |
| // We need 2 ResultCatchers because we'll be running the same test in both |
| // regular and incognito mode. |
| ResultCatcher catcher; |
| ResultCatcher catcher_incognito; |
| catcher.RestrictToBrowserContext(browser()->profile()); |
| catcher_incognito.RestrictToBrowserContext( |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true)); |
| |
| // Sync, local and managed follow the same storage flow (RunWithStorage), |
| // whereas session follows a separate flow (RunWithSession). For the purpose |
| // of this test we can just test sync and session. |
| StorageAreaNamespace storage_areas[2] = {StorageAreaNamespace::kSync, |
| StorageAreaNamespace::kSession}; |
| LoadAndReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertEmpty", |
| "assertEmpty", "split_incognito"); |
| for (const StorageAreaNamespace& storage_area : storage_areas) { |
| ReplyWhenSatisfied(storage_area, "assertEmpty", "assertEmpty"); |
| ReplyWhenSatisfied(storage_area, "noop", "setFoo"); |
| ReplyWhenSatisfied(storage_area, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(storage_area, "clear", "noop"); |
| ReplyWhenSatisfied(storage_area, "assertEmpty", "assertEmpty"); |
| ReplyWhenSatisfied(storage_area, "setFoo", "noop"); |
| ReplyWhenSatisfied(storage_area, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(storage_area, "noop", "removeFoo"); |
| ReplyWhenSatisfied(storage_area, "assertEmpty", "assertEmpty"); |
| } |
| FinalReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertEmpty", |
| "assertEmpty"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); |
| } |
| |
| // TODO(crbug.com/1229351): Service worker extension listener should receive an |
| // event before the callback is made. Current workaround: wait for the event to |
| // be received by the extension before checking for it. Potential solution: once |
| // browser-side observation of SW lifetime work is finished, check if it fixes |
| // this test. |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, |
| OnChangedNotificationsBetweenBackgroundPages) { |
| // We need 2 ResultCatchers because we'll be running the same test in both |
| // regular and incognito mode. |
| ResultCatcher catcher; |
| ResultCatcher catcher_incognito; |
| catcher.RestrictToBrowserContext(browser()->profile()); |
| catcher_incognito.RestrictToBrowserContext( |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true)); |
| |
| StorageAreaNamespace storage_areas[2] = {StorageAreaNamespace::kSync, |
| StorageAreaNamespace::kSession}; |
| |
| for (const StorageAreaNamespace& storage_area : storage_areas) { |
| // We need to load the extension when it's the first reply. |
| // kSync is the first storage area to run. |
| if (storage_area == StorageAreaNamespace::kSync) { |
| LoadAndReplyWhenSatisfied(StorageAreaNamespace::kSync, |
| "assertNoNotifications", |
| "assertNoNotifications", "split_incognito"); |
| } else { |
| ReplyWhenSatisfied(storage_area, "assertNoNotifications", |
| "assertNoNotifications"); |
| } |
| |
| ReplyWhenSatisfied(storage_area, "noop", "setFoo"); |
| ReplyWhenSatisfied(storage_area, "assertAddFooNotification", |
| "assertAddFooNotification"); |
| ReplyWhenSatisfied(storage_area, "clearNotifications", |
| "clearNotifications"); |
| ReplyWhenSatisfied(storage_area, "removeFoo", "noop"); |
| |
| // We need to end the test with a final reply when it's the last reply. |
| // kSession is the last storage area to run. |
| if (storage_area == StorageAreaNamespace::kSession) { |
| FinalReplyWhenSatisfied(StorageAreaNamespace::kSession, |
| "assertDeleteFooNotification", |
| "assertDeleteFooNotification"); |
| } else { |
| ReplyWhenSatisfied(storage_area, "assertDeleteFooNotification", |
| "assertDeleteFooNotification"); |
| } |
| } |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, |
| SyncLocalAndSessionAreasAreSeparate) { |
| // We need 2 ResultCatchers because we'll be running the same test in both |
| // regular and incognito mode. |
| ResultCatcher catcher; |
| ResultCatcher catcher_incognito; |
| catcher.RestrictToBrowserContext(browser()->profile()); |
| catcher_incognito.RestrictToBrowserContext( |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true)); |
| |
| LoadAndReplyWhenSatisfied(StorageAreaNamespace::kSync, |
| "assertNoNotifications", "assertNoNotifications", |
| "split_incognito"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "noop", "setFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertAddFooNotification", |
| "assertAddFooNotification"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertEmpty", |
| "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications", |
| "assertNoNotifications"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertEmpty", |
| "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "clearNotifications", |
| "clearNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "setFoo", "noop"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertAddFooNotification", |
| "assertAddFooNotification"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertNoNotifications", |
| "assertNoNotifications"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertEmpty", |
| "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "clearNotifications", |
| "clearNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "setFoo", "noop"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertAddFooNotification", |
| "assertAddFooNotification"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertNoNotifications", |
| "assertNoNotifications"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "clearNotifications", |
| "clearNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "noop", "removeFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertEmpty", |
| "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, |
| "assertDeleteFooNotification", |
| "assertDeleteFooNotification"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertNoNotifications", |
| "assertNoNotifications"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "clearNotifications", |
| "clearNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "removeFoo", "noop"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertEmpty", "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertDeleteFooNotification", |
| "assertDeleteFooNotification"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertEmpty", |
| "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications", |
| "assertNoNotifications"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertFoo", "assertFoo"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "clearNotifications", |
| "clearNotifications"); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "removeFoo", "noop"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, "assertEmpty", |
| "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSession, |
| "assertDeleteFooNotification", |
| "assertDeleteFooNotification"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertEmpty", "assertEmpty"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertNoNotifications", |
| "assertNoNotifications"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertEmpty", |
| "assertEmpty"); |
| FinalReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); |
| } |
| |
| // TODO(crbug.com/1229351): Service worker extension listener should receive an |
| // event before the callback is made. Current workaround: wait for the event to |
| // be received by the extension before checking for it. Potential solution: once |
| // browser-side observation of SW lifetime work is finished, check if it fixes |
| // this test. |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, |
| OnChangedNotificationsFromSync) { |
| // We need 2 ResultCatchers because we'll be running the same test in both |
| // regular and incognito mode. |
| ResultCatcher catcher, catcher_incognito; |
| catcher.RestrictToBrowserContext(browser()->profile()); |
| catcher_incognito.RestrictToBrowserContext( |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true)); |
| |
| const Extension* extension = LoadAndReplyWhenSatisfied( |
| StorageAreaNamespace::kSync, "assertNoNotifications", |
| "assertNoNotifications", "split_incognito"); |
| const std::string& extension_id = extension->id(); |
| |
| syncer::FakeSyncChangeProcessor sync_processor; |
| InitSync(&sync_processor); |
| |
| // Set "foo" to "bar" via sync. |
| syncer::SyncChangeList sync_changes; |
| base::Value bar("bar"); |
| sync_changes.push_back(settings_sync_util::CreateAdd( |
| extension_id, "foo", bar, kModelType)); |
| SendChanges(sync_changes); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "assertAddFooNotification", |
| "assertAddFooNotification"); |
| ReplyWhenSatisfied(StorageAreaNamespace::kSync, "clearNotifications", |
| "clearNotifications"); |
| |
| // Remove "foo" via sync. |
| sync_changes.clear(); |
| sync_changes.push_back(settings_sync_util::CreateDelete( |
| extension_id, "foo", kModelType)); |
| SendChanges(sync_changes); |
| |
| FinalReplyWhenSatisfied(StorageAreaNamespace::kSync, |
| "assertDeleteFooNotification", |
| "assertDeleteFooNotification"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); |
| } |
| |
| // TODO: boring test, already done in the unit tests. What we really should be |
| // be testing is that the areas don't overlap. |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, |
| OnChangedNotificationsFromSyncNotSentToLocal) { |
| // We need 2 ResultCatchers because we'll be running the same test in both |
| // regular and incognito mode. |
| ResultCatcher catcher, catcher_incognito; |
| catcher.RestrictToBrowserContext(browser()->profile()); |
| catcher_incognito.RestrictToBrowserContext( |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true)); |
| |
| const Extension* extension = LoadAndReplyWhenSatisfied( |
| StorageAreaNamespace::kLocal, "assertNoNotifications", |
| "assertNoNotifications", "split_incognito"); |
| const std::string& extension_id = extension->id(); |
| |
| syncer::FakeSyncChangeProcessor sync_processor; |
| InitSync(&sync_processor); |
| |
| // Set "foo" to "bar" via sync. |
| syncer::SyncChangeList sync_changes; |
| base::Value bar("bar"); |
| sync_changes.push_back(settings_sync_util::CreateAdd( |
| extension_id, "foo", bar, kModelType)); |
| SendChanges(sync_changes); |
| |
| ReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| // Remove "foo" via sync. |
| sync_changes.clear(); |
| sync_changes.push_back(settings_sync_util::CreateDelete( |
| extension_id, "foo", kModelType)); |
| SendChanges(sync_changes); |
| |
| FinalReplyWhenSatisfied(StorageAreaNamespace::kLocal, "assertNoNotifications", |
| "assertNoNotifications"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| EXPECT_TRUE(catcher_incognito.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, IsStorageEnabled) { |
| StorageFrontend* frontend = StorageFrontend::Get(browser()->profile()); |
| EXPECT_TRUE(frontend->IsStorageEnabled(settings_namespace::LOCAL)); |
| EXPECT_TRUE(frontend->IsStorageEnabled(settings_namespace::SYNC)); |
| |
| EXPECT_TRUE(frontend->IsStorageEnabled(settings_namespace::MANAGED)); |
| } |
| |
| using ContextType = ExtensionBrowserTest::ContextType; |
| |
| class ExtensionSettingsManagedStorageApiTest |
| : public ExtensionSettingsApiTest, |
| public testing::WithParamInterface<ContextType> { |
| public: |
| ExtensionSettingsManagedStorageApiTest() |
| : ExtensionSettingsApiTest(GetParam()) {} |
| ~ExtensionSettingsManagedStorageApiTest() override = default; |
| ExtensionSettingsManagedStorageApiTest( |
| const ExtensionSettingsManagedStorageApiTest& other) = delete; |
| ExtensionSettingsManagedStorageApiTest& operator=( |
| const ExtensionSettingsManagedStorageApiTest& other) = delete; |
| |
| // TODO(crbug.com/1247323): Remove this. |
| // The ManagedStorageEvents test has a PRE_ step loads an extension which |
| // then runs in the main step. Since the extension immediately starts |
| // running the tests, constructing a ResultCatcher in the body of the |
| // fixture will occasionally miss the result from the JS test, leading |
| // to a flaky result. This ResultCatcher will be always be constructed |
| // before the test starts running. |
| ResultCatcher events_result_catcher_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(PersistentBackground, |
| ExtensionSettingsManagedStorageApiTest, |
| ::testing::Values(ContextType::kPersistentBackground)); |
| |
| INSTANTIATE_TEST_SUITE_P(ServiceWorker, |
| ExtensionSettingsManagedStorageApiTest, |
| ::testing::Values(ContextType::kServiceWorker)); |
| |
| IN_PROC_BROWSER_TEST_P(ExtensionSettingsManagedStorageApiTest, |
| ExtensionsSchemas) { |
| // Verifies that the Schemas for the extensions domain are created on startup. |
| Profile* profile = browser()->profile(); |
| ExtensionSystem* extension_system = ExtensionSystem::Get(profile); |
| if (!extension_system->ready().is_signaled()) { |
| // Wait until the extension system is ready. |
| base::RunLoop run_loop; |
| extension_system->ready().Post(FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| ASSERT_TRUE(extension_system->ready().is_signaled()); |
| } |
| |
| // This test starts without any test extensions installed. |
| EXPECT_FALSE(GetSingleLoadedExtension()); |
| message_.clear(); |
| |
| policy::SchemaRegistry* registry = |
| profile->GetPolicySchemaRegistryService()->registry(); |
| ASSERT_TRUE(registry); |
| EXPECT_FALSE(registry->schema_map()->GetSchema(policy::PolicyNamespace( |
| policy::POLICY_DOMAIN_EXTENSIONS, kManagedStorageExtensionId))); |
| |
| TestSchemaRegistryObserver observer; |
| registry->AddObserver(&observer); |
| |
| // Install a managed extension. |
| ExtensionTestMessageListener listener("ready"); |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("settings/managed_storage_schemas")); |
| ASSERT_TRUE(listener.WaitUntilSatisfied()); |
| ASSERT_TRUE(extension); |
| observer.WaitForSchemaRegistryUpdated(); |
| |
| // Verify the schemas were installed. |
| EXPECT_TRUE(observer.has_new_schemas()); |
| |
| registry->RemoveObserver(&observer); |
| |
| // Verify that its schema has been published, and verify its contents. |
| const policy::Schema* schema = |
| registry->schema_map()->GetSchema(policy::PolicyNamespace( |
| policy::POLICY_DOMAIN_EXTENSIONS, kManagedStorageExtensionId)); |
| ASSERT_TRUE(schema); |
| |
| ASSERT_TRUE(schema->valid()); |
| ASSERT_EQ(base::Value::Type::DICTIONARY, schema->type()); |
| ASSERT_TRUE(schema->GetKnownProperty("string-policy").valid()); |
| EXPECT_EQ(base::Value::Type::STRING, |
| schema->GetKnownProperty("string-policy").type()); |
| ASSERT_TRUE(schema->GetKnownProperty("string-enum-policy").valid()); |
| EXPECT_EQ(base::Value::Type::STRING, |
| schema->GetKnownProperty("string-enum-policy").type()); |
| ASSERT_TRUE(schema->GetKnownProperty("int-policy").valid()); |
| EXPECT_EQ(base::Value::Type::INTEGER, |
| schema->GetKnownProperty("int-policy").type()); |
| ASSERT_TRUE(schema->GetKnownProperty("int-enum-policy").valid()); |
| EXPECT_EQ(base::Value::Type::INTEGER, |
| schema->GetKnownProperty("int-enum-policy").type()); |
| ASSERT_TRUE(schema->GetKnownProperty("double-policy").valid()); |
| EXPECT_EQ(base::Value::Type::DOUBLE, |
| schema->GetKnownProperty("double-policy").type()); |
| ASSERT_TRUE(schema->GetKnownProperty("boolean-policy").valid()); |
| EXPECT_EQ(base::Value::Type::BOOLEAN, |
| schema->GetKnownProperty("boolean-policy").type()); |
| |
| policy::Schema list = schema->GetKnownProperty("list-policy"); |
| ASSERT_TRUE(list.valid()); |
| ASSERT_EQ(base::Value::Type::LIST, list.type()); |
| ASSERT_TRUE(list.GetItems().valid()); |
| EXPECT_EQ(base::Value::Type::STRING, list.GetItems().type()); |
| |
| policy::Schema dict = schema->GetKnownProperty("dict-policy"); |
| ASSERT_TRUE(dict.valid()); |
| ASSERT_EQ(base::Value::Type::DICTIONARY, dict.type()); |
| list = dict.GetKnownProperty("list"); |
| ASSERT_TRUE(list.valid()); |
| ASSERT_EQ(base::Value::Type::LIST, list.type()); |
| dict = list.GetItems(); |
| ASSERT_TRUE(dict.valid()); |
| ASSERT_EQ(base::Value::Type::DICTIONARY, dict.type()); |
| ASSERT_TRUE(dict.GetProperty("anything").valid()); |
| EXPECT_EQ(base::Value::Type::INTEGER, dict.GetProperty("anything").type()); |
| } |
| |
| // TODO(crbug.com/1247323): This test should be rewritten. See the bug for more |
| // details. |
| IN_PROC_BROWSER_TEST_P(ExtensionSettingsManagedStorageApiTest, ManagedStorage) { |
| // Set policies for the test extension. |
| std::unique_ptr<base::DictionaryValue> policy = |
| extensions::DictionaryBuilder() |
| .Set("string-policy", "value") |
| .Set("string-enum-policy", "value-1") |
| .Set("another-string-policy", 123) // Test invalid policy value. |
| .Set("int-policy", -123) |
| .Set("int-enum-policy", 1) |
| .Set("double-policy", 456e7) |
| .Set("boolean-policy", true) |
| .Set("list-policy", extensions::ListBuilder() |
| .Append("one") |
| .Append("two") |
| .Append("three") |
| .Build()) |
| .Set("dict-policy", |
| extensions::DictionaryBuilder() |
| .Set("list", extensions::ListBuilder() |
| .Append(extensions::DictionaryBuilder() |
| .Set("one", 1) |
| .Set("two", 2) |
| .Build()) |
| .Append(extensions::DictionaryBuilder() |
| .Set("three", 3) |
| .Build()) |
| .Build()) |
| .Build()) |
| .Build(); |
| SetPolicies(policy->GetDict()); |
| // Now run the extension. |
| ASSERT_TRUE(RunExtensionTest("settings/managed_storage")) << message_; |
| } |
| |
| // TODO(crbug.com/1247323): This test should be rewritten. See the bug for more |
| // details. |
| IN_PROC_BROWSER_TEST_P(ExtensionSettingsManagedStorageApiTest, |
| PRE_ManagedStorageEvents) { |
| // This test starts without any test extensions installed. |
| EXPECT_FALSE(GetSingleLoadedExtension()); |
| message_.clear(); |
| |
| // Set policies for the test extension. |
| std::unique_ptr<base::DictionaryValue> policy = |
| extensions::DictionaryBuilder() |
| .Set("constant-policy", "aaa") |
| .Set("changes-policy", "bbb") |
| .Set("deleted-policy", "ccc") |
| .Build(); |
| SetPolicies(policy->GetDict()); |
| |
| ExtensionTestMessageListener ready_listener("ready"); |
| // Load the extension to install the event listener and wait for the |
| // extension's registration to be stored since it must persist after |
| // this PRE_ step exits. Otherwise, the test will be flaky, since the |
| // extension's service worker registration might not get stored. |
| const Extension* extension = LoadExtension( |
| test_data_dir_.AppendASCII("settings/managed_storage_events"), |
| {.wait_for_registration_stored = true}); |
| ASSERT_TRUE(extension); |
| // Wait until the extension sends the "ready" message. |
| ASSERT_TRUE(ready_listener.WaitUntilSatisfied()); |
| |
| // Now change the policies and wait until the extension is done. |
| policy = extensions::DictionaryBuilder() |
| .Set("constant-policy", "aaa") |
| .Set("changes-policy", "ddd") |
| .Set("new-policy", "eee") |
| .Build(); |
| SetPolicies(policy->GetDict()); |
| EXPECT_TRUE(events_result_catcher_.GetNextResult()) |
| << events_result_catcher_.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ExtensionSettingsManagedStorageApiTest, |
| ManagedStorageEvents) { |
| // This test runs after PRE_ManagedStorageEvents without having deleted the |
| // profile, so the extension is still around. While the browser restarted the |
| // policy went back to the empty default, and so the extension should receive |
| // the corresponding change events. |
| |
| // Verify that the test extension is still installed. |
| const Extension* extension = GetSingleLoadedExtension(); |
| ASSERT_TRUE(extension); |
| EXPECT_EQ(kManagedStorageExtensionId, extension->id()); |
| |
| // Running the test again skips the onInstalled callback, and just triggers |
| // the onChanged notification. |
| EXPECT_TRUE(events_result_catcher_.GetNextResult()) |
| << events_result_catcher_.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(ExtensionSettingsManagedStorageApiTest, |
| ManagedStorageDisabled) { |
| // Disable the 'managed' namespace. |
| StorageFrontend* frontend = StorageFrontend::Get(browser()->profile()); |
| frontend->DisableStorageForTesting(settings_namespace::MANAGED); |
| EXPECT_FALSE(frontend->IsStorageEnabled(settings_namespace::MANAGED)); |
| // Now run the extension. |
| ASSERT_TRUE(RunExtensionTest("settings/managed_storage_disabled")) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionSettingsApiTest, StorageAreaOnChanged) { |
| ASSERT_TRUE(RunExtensionTest("settings/storage_area")) << message_; |
| } |
| |
| } // namespace extensions |