diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-20 23:33:40 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-20 23:33:40 +0000 |
commit | 0dd6f20318b09983933fa8aab1573eb057cc1088 (patch) | |
tree | 4b561c910850f3df5f51b1916164969f6f46b0c4 /components | |
parent | b55475898c3d74ec5810e4b60bccce4183cb17d9 (diff) | |
download | chromium_src-0dd6f20318b09983933fa8aab1573eb057cc1088.zip chromium_src-0dd6f20318b09983933fa8aab1573eb057cc1088.tar.gz chromium_src-0dd6f20318b09983933fa8aab1573eb057cc1088.tar.bz2 |
Move ProfileKeyedService infrastructure to a component
Renames of the classes and methods will follow in separate patch(es).
BUG=227219
R=erg@chromium.org, joi@chromium.org
Review URL: https://codereview.chromium.org/14743010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@201167 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
20 files changed, 1698 insertions, 0 deletions
diff --git a/components/browser_context_keyed_service.gypi b/components/browser_context_keyed_service.gypi new file mode 100644 index 0000000..03bd8f2 --- /dev/null +++ b/components/browser_context_keyed_service.gypi @@ -0,0 +1,38 @@ +# Copyright 2013 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. + +{ + 'targets': [ + { + 'target_name': 'browser_context_keyed_service', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_prefs', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../content/content.gyp:content_common', + 'user_prefs', + ], + 'sources': [ + 'browser_context_keyed_service/browser_context_dependency_manager.cc', + 'browser_context_keyed_service/browser_context_dependency_manager.h', + 'browser_context_keyed_service/browser_context_keyed_base_factory.h', + 'browser_context_keyed_service/browser_context_keyed_base_factory.cc', + 'browser_context_keyed_service/browser_context_keyed_service.h', + 'browser_context_keyed_service/browser_context_keyed_service_factory.cc', + 'browser_context_keyed_service/browser_context_keyed_service_factory.h', + 'browser_context_keyed_service/dependency_graph.cc', + 'browser_context_keyed_service/dependency_graph.h', + 'browser_context_keyed_service/dependency_node.h', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service.cc', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service.h', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h', + ], + }, + ], +} diff --git a/components/browser_context_keyed_service/DEPS b/components/browser_context_keyed_service/DEPS new file mode 100644 index 0000000..1c35d9c --- /dev/null +++ b/components/browser_context_keyed_service/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/browser", +] diff --git a/components/browser_context_keyed_service/browser_context_dependency_manager.cc b/components/browser_context_keyed_service/browser_context_dependency_manager.cc new file mode 100644 index 0000000..44d40b4 --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_dependency_manager.cc @@ -0,0 +1,149 @@ +// Copyright (c) 2013 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 "components/browser_context_keyed_service/browser_context_dependency_manager.h" + +#include <algorithm> +#include <deque> +#include <iterator> + +#include "base/bind.h" +#include "components/browser_context_keyed_service/browser_context_keyed_base_factory.h" +#include "content/public/browser/browser_context.h" + +#ifndef NDEBUG +#include "base/command_line.h" +#include "base/file_util.h" +#include "content/public/common/content_switches.h" +#endif + +class Profile; + +void ProfileDependencyManager::AddComponent( + ProfileKeyedBaseFactory* component) { + dependency_graph_.AddNode(component); +} + +void ProfileDependencyManager::RemoveComponent( + ProfileKeyedBaseFactory* component) { + dependency_graph_.RemoveNode(component); +} + +void ProfileDependencyManager::AddEdge(ProfileKeyedBaseFactory* depended, + ProfileKeyedBaseFactory* dependee) { + dependency_graph_.AddEdge(depended, dependee); +} + +void ProfileDependencyManager::CreateProfileServices( + content::BrowserContext* profile, bool is_testing_profile) { +#ifndef NDEBUG + // Unmark |profile| as dead. This exists because of unit tests, which will + // often have similar stack structures. 0xWhatever might be created, go out + // of scope, and then a new Profile object might be created at 0xWhatever. + dead_profile_pointers_.erase(profile); +#endif + + std::vector<DependencyNode*> construction_order; + if (!dependency_graph_.GetConstructionOrder(&construction_order)) { + NOTREACHED(); + } + +#ifndef NDEBUG + DumpProfileDependencies(profile); +#endif + + for (size_t i = 0; i < construction_order.size(); i++) { + ProfileKeyedBaseFactory* factory = + static_cast<ProfileKeyedBaseFactory*>(construction_order[i]); + + if (!profile->IsOffTheRecord()) { + // We only register preferences on normal profiles because the incognito + // profile shares the pref service with the normal one. + factory->RegisterUserPrefsOnProfile(profile); + } + + if (is_testing_profile && factory->ServiceIsNULLWhileTesting()) { + factory->SetEmptyTestingFactory(profile); + } else if (factory->ServiceIsCreatedWithProfile()) { + // Create the service. + factory->CreateServiceNow(profile); + } + } +} + +void ProfileDependencyManager::DestroyProfileServices( + content::BrowserContext* profile) { + std::vector<DependencyNode*> destruction_order; + if (!dependency_graph_.GetDestructionOrder(&destruction_order)) { + NOTREACHED(); + } + +#ifndef NDEBUG + DumpProfileDependencies(profile); +#endif + + for (size_t i = 0; i < destruction_order.size(); i++) { + ProfileKeyedBaseFactory* factory = + static_cast<ProfileKeyedBaseFactory*>(destruction_order[i]); + factory->ProfileShutdown(profile); + } + +#ifndef NDEBUG + // The profile is now dead to the rest of the program. + dead_profile_pointers_.insert(profile); +#endif + + for (size_t i = 0; i < destruction_order.size(); i++) { + ProfileKeyedBaseFactory* factory = + static_cast<ProfileKeyedBaseFactory*>(destruction_order[i]); + factory->ProfileDestroyed(profile); + } +} + +#ifndef NDEBUG +void ProfileDependencyManager::AssertProfileWasntDestroyed( + content::BrowserContext* profile) { + if (dead_profile_pointers_.find(profile) != dead_profile_pointers_.end()) { + NOTREACHED() << "Attempted to access a Profile that was ShutDown(). This " + << "is most likely a heap smasher in progress. After " + << "ProfileKeyedService::Shutdown() completes, your service " + << "MUST NOT refer to depended Profile services again."; + } +} +#endif + +// static +ProfileDependencyManager* ProfileDependencyManager::GetInstance() { + return Singleton<ProfileDependencyManager>::get(); +} + +ProfileDependencyManager::ProfileDependencyManager() { +} + +ProfileDependencyManager::~ProfileDependencyManager() { +} + +#ifndef NDEBUG +namespace { + +std::string ProfileKeyedBaseFactoryGetNodeName(DependencyNode* node) { + return static_cast<ProfileKeyedBaseFactory*>(node)->name(); +} + +} // namespace + +void ProfileDependencyManager::DumpProfileDependencies( + content::BrowserContext* profile) { + // Whenever we try to build a destruction ordering, we should also dump a + // dependency graph to "/path/to/profile/profile-dependencies.dot". + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDumpBrowserContextDependencyGraph)) { + base::FilePath dot_file = + profile->GetPath().AppendASCII("browser-context-dependencies.dot"); + std::string contents = dependency_graph_.DumpAsGraphviz( + "Profile", base::Bind(&ProfileKeyedBaseFactoryGetNodeName)); + file_util::WriteFile(dot_file, contents.c_str(), contents.size()); + } +} +#endif // NDEBUG diff --git a/components/browser_context_keyed_service/browser_context_dependency_manager.h b/components/browser_context_keyed_service/browser_context_dependency_manager.h new file mode 100644 index 0000000..2b5ba0e --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_dependency_manager.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_DEPENDENCY_MANAGER_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_DEPENDENCY_MANAGER_H_ + +#include "base/memory/singleton.h" +#include "components/browser_context_keyed_service/dependency_graph.h" + +#ifndef NDEBUG +#include <set> +#endif + +class ProfileKeyedBaseFactory; + +namespace content { +class BrowserContext; +} + +// A singleton that listens for profile destruction notifications and +// rebroadcasts them to each ProfileKeyedBaseFactory in a safe order based +// on the stated dependencies by each service. +class ProfileDependencyManager { + public: + // Adds/Removes a component from our list of live components. Removing will + // also remove live dependency links. + void AddComponent(ProfileKeyedBaseFactory* component); + void RemoveComponent(ProfileKeyedBaseFactory* component); + + // Adds a dependency between two factories. + void AddEdge(ProfileKeyedBaseFactory* depended, + ProfileKeyedBaseFactory* dependee); + + // Called by each Profile to alert us of its creation. Several services want + // to be started when a profile is created. Testing configuration is also + // done at this time. (If you want your ProfileKeyedService to be started + // with the Profile, override ProfileKeyedBaseFactory:: + // ServiceIsCreatedWithProfile() to return true.) + void CreateProfileServices(content::BrowserContext* profile, + bool is_testing_profile); + + // Called by each Profile to alert us that we should destroy services + // associated with it. + // + // Why not use the existing PROFILE_DESTROYED notification? + // + // - Because we need to do everything here after the application has handled + // being notified about PROFILE_DESTROYED. + // - Because this class is a singleton and Singletons can't rely on + // NotificationService in unit tests because NotificationService is + // replaced in many tests. + void DestroyProfileServices(content::BrowserContext* profile); + +#ifndef NDEBUG + // Debugging assertion called as part of GetServiceForProfile in debug + // mode. This will NOTREACHED() whenever the user is trying to access a stale + // Profile*. + void AssertProfileWasntDestroyed(content::BrowserContext* profile); +#endif + + static ProfileDependencyManager* GetInstance(); + + private: + friend class ProfileDependencyManagerUnittests; + friend struct DefaultSingletonTraits<ProfileDependencyManager>; + + ProfileDependencyManager(); + virtual ~ProfileDependencyManager(); + +#ifndef NDEBUG + void DumpProfileDependencies(content::BrowserContext* profile); +#endif + + DependencyGraph dependency_graph_; + +#ifndef NDEBUG + // A list of profile objects that have gone through the Shutdown() + // phase. These pointers are most likely invalid, but we keep track of their + // locations in memory so we can nicely assert if we're asked to do anything + // with them. + std::set<content::BrowserContext*> dead_profile_pointers_; +#endif +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_DEPENDENCY_MANAGER_H_ diff --git a/components/browser_context_keyed_service/browser_context_dependency_manager_unittest.cc b/components/browser_context_keyed_service/browser_context_dependency_manager_unittest.cc new file mode 100644 index 0000000..a0c03ff --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_dependency_manager_unittest.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2012 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 "testing/gtest/include/gtest/gtest.h" + +#include "components/browser_context_keyed_service/browser_context_dependency_manager.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h" + +class ProfileDependencyManagerUnittests : public ::testing::Test { + protected: + // To get around class access: + void DependOn(ProfileKeyedServiceFactory* child, + ProfileKeyedServiceFactory* parent) { + child->DependsOn(parent); + } + + ProfileDependencyManager* manager() { return &dependency_manager_; } + + std::vector<std::string>* shutdown_order() { return &shutdown_order_; } + + private: + ProfileDependencyManager dependency_manager_; + + std::vector<std::string> shutdown_order_; +}; + +class TestService : public ProfileKeyedServiceFactory { + public: + TestService(const std::string& name, + std::vector<std::string>* fill_on_shutdown, + ProfileDependencyManager* manager) + : ProfileKeyedServiceFactory("TestService", manager), + name_(name), + fill_on_shutdown_(fill_on_shutdown) { + } + + virtual ProfileKeyedService* BuildServiceInstanceFor( + content::BrowserContext* profile) const OVERRIDE { + ADD_FAILURE() << "This isn't part of the tests!"; + return NULL; + } + + virtual void ProfileShutdown(content::BrowserContext* profile) OVERRIDE { + fill_on_shutdown_->push_back(name_); + } + + private: + const std::string name_; + std::vector<std::string>* fill_on_shutdown_; +}; + +// Tests that we can deal with a single component. +TEST_F(ProfileDependencyManagerUnittests, SingleCase) { + TestService service("service", shutdown_order(), manager()); + + manager()->DestroyProfileServices(NULL); + + ASSERT_EQ(1U, shutdown_order()->size()); + EXPECT_STREQ("service", (*shutdown_order())[0].c_str()); +} + +// Tests that we get a simple one component depends on the other case. +TEST_F(ProfileDependencyManagerUnittests, SimpleDependency) { + TestService parent("parent", shutdown_order(), manager()); + TestService child("child", shutdown_order(), manager()); + DependOn(&child, &parent); + + manager()->DestroyProfileServices(NULL); + + ASSERT_EQ(2U, shutdown_order()->size()); + EXPECT_STREQ("child", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("parent", (*shutdown_order())[1].c_str()); +} + +// Tests two children, one parent +TEST_F(ProfileDependencyManagerUnittests, TwoChildrenOneParent) { + TestService parent("parent", shutdown_order(), manager()); + TestService child1("child1", shutdown_order(), manager()); + TestService child2("child2", shutdown_order(), manager()); + DependOn(&child1, &parent); + DependOn(&child2, &parent); + + manager()->DestroyProfileServices(NULL); + + ASSERT_EQ(3U, shutdown_order()->size()); + EXPECT_STREQ("child2", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("child1", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("parent", (*shutdown_order())[2].c_str()); +} + +// Tests an M configuration +TEST_F(ProfileDependencyManagerUnittests, MConfiguration) { + TestService parent1("parent1", shutdown_order(), manager()); + TestService parent2("parent2", shutdown_order(), manager()); + + TestService child_of_1("child_of_1", shutdown_order(), manager()); + DependOn(&child_of_1, &parent1); + + TestService child_of_12("child_of_12", shutdown_order(), manager()); + DependOn(&child_of_12, &parent1); + DependOn(&child_of_12, &parent2); + + TestService child_of_2("child_of_2", shutdown_order(), manager()); + DependOn(&child_of_2, &parent2); + + manager()->DestroyProfileServices(NULL); + + ASSERT_EQ(5U, shutdown_order()->size()); + EXPECT_STREQ("child_of_2", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("child_of_12", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("child_of_1", (*shutdown_order())[2].c_str()); + EXPECT_STREQ("parent2", (*shutdown_order())[3].c_str()); + EXPECT_STREQ("parent1", (*shutdown_order())[4].c_str()); +} + +// Tests that it can deal with a simple diamond. +TEST_F(ProfileDependencyManagerUnittests, DiamondConfiguration) { + TestService parent("parent", shutdown_order(), manager()); + + TestService middle_row_1("middle_row_1", shutdown_order(), manager()); + DependOn(&middle_row_1, &parent); + + TestService middle_row_2("middle_row_2", shutdown_order(), manager()); + DependOn(&middle_row_2, &parent); + + TestService bottom("bottom", shutdown_order(), manager()); + DependOn(&bottom, &middle_row_1); + DependOn(&bottom, &middle_row_2); + + manager()->DestroyProfileServices(NULL); + + ASSERT_EQ(4U, shutdown_order()->size()); + EXPECT_STREQ("bottom", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("middle_row_2", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("middle_row_1", (*shutdown_order())[2].c_str()); + EXPECT_STREQ("parent", (*shutdown_order())[3].c_str()); +} + +// A final test that works with a more complex graph. +TEST_F(ProfileDependencyManagerUnittests, ComplexGraph) { + TestService everything_depends_on_me("everything_depends_on_me", + shutdown_order(), manager()); + + TestService intermediary_service("intermediary_service", + shutdown_order(), manager()); + DependOn(&intermediary_service, &everything_depends_on_me); + + TestService specialized_service("specialized_service", + shutdown_order(), manager()); + DependOn(&specialized_service, &everything_depends_on_me); + DependOn(&specialized_service, &intermediary_service); + + TestService other_root("other_root", shutdown_order(), manager()); + + TestService other_intermediary("other_intermediary", + shutdown_order(), manager()); + DependOn(&other_intermediary, &other_root); + + TestService bottom("bottom", shutdown_order(), manager()); + DependOn(&bottom, &specialized_service); + DependOn(&bottom, &other_intermediary); + + manager()->DestroyProfileServices(NULL); + + ASSERT_EQ(6U, shutdown_order()->size()); + EXPECT_STREQ("bottom", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("specialized_service", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("other_intermediary", (*shutdown_order())[2].c_str()); + EXPECT_STREQ("intermediary_service", (*shutdown_order())[3].c_str()); + EXPECT_STREQ("other_root", (*shutdown_order())[4].c_str()); + EXPECT_STREQ("everything_depends_on_me", (*shutdown_order())[5].c_str()); +} diff --git a/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc b/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc new file mode 100644 index 0000000..9228400 --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2012 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 "components/browser_context_keyed_service/browser_context_keyed_base_factory.h" + +#include "base/prefs/pref_service.h" +#include "components/browser_context_keyed_service/browser_context_dependency_manager.h" +#include "components/user_prefs/pref_registry_syncable.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" + +ProfileKeyedBaseFactory::ProfileKeyedBaseFactory( + const char* name, ProfileDependencyManager* manager) + : dependency_manager_(manager) +#ifndef NDEBUG + , service_name_(name) +#endif +{ + dependency_manager_->AddComponent(this); +} + +ProfileKeyedBaseFactory::~ProfileKeyedBaseFactory() { + dependency_manager_->RemoveComponent(this); +} + +void ProfileKeyedBaseFactory::DependsOn(ProfileKeyedBaseFactory* rhs) { + dependency_manager_->AddEdge(rhs, this); +} + +content::BrowserContext* ProfileKeyedBaseFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + DCHECK(CalledOnValidThread()); + +#ifndef NDEBUG + dependency_manager_->AssertProfileWasntDestroyed(context); +#endif + + // Safe default for the Incognito mode: no service. + if (context->IsOffTheRecord()) + return NULL; + + return context; +} + +void ProfileKeyedBaseFactory::RegisterUserPrefsOnProfile( + content::BrowserContext* profile) { + // Safe timing for pref registration is hard. Previously, we made Profile + // responsible for all pref registration on every service that used + // Profile. Now we don't and there are timing issues. + // + // With normal profiles, prefs can simply be registered at + // ProfileDependencyManager::CreateProfileServices time. With incognito + // profiles, we just never register since incognito profiles share the same + // pref services with their parent profiles. + // + // TestingProfiles throw a wrench into the mix, in that some tests will + // swap out the PrefService after we've registered user prefs on the original + // PrefService. Test code that does this is responsible for either manually + // invoking RegisterUserPrefs() on the appropriate ProfileKeyedServiceFactory + // associated with the prefs they need, or they can use SetTestingFactory() + // and create a service (since service creation with a factory method causes + // registration to happen at service creation time). + // + // Now that services are responsible for declaring their preferences, we have + // to enforce a uniquenes check here because some tests create one profile and + // multiple services of the same type attached to that profile (serially, not + // parallel) and we don't want to register multiple times on the same profile. + DCHECK(!profile->IsOffTheRecord()); + + std::set<content::BrowserContext*>::iterator it = + registered_preferences_.find(profile); + if (it == registered_preferences_.end()) { + PrefService* prefs = components::UserPrefs::Get(profile); + user_prefs::PrefRegistrySyncable* registry = + static_cast<user_prefs::PrefRegistrySyncable*>( + prefs->DeprecatedGetPrefRegistry()); + RegisterUserPrefs(registry); + registered_preferences_.insert(profile); + } +} + +bool ProfileKeyedBaseFactory::ServiceIsCreatedWithProfile() const { + return false; +} + +bool ProfileKeyedBaseFactory::ServiceIsNULLWhileTesting() const { + return false; +} + +void ProfileKeyedBaseFactory::ProfileDestroyed( + content::BrowserContext* profile) { + // While object destruction can be customized in ways where the object is + // only dereferenced, this still must run on the UI thread. + DCHECK(CalledOnValidThread()); + + registered_preferences_.erase(profile); +} + +bool ProfileKeyedBaseFactory::ArePreferencesSetOn( + content::BrowserContext* profile) const { + return registered_preferences_.find(profile) != + registered_preferences_.end(); +} + +void ProfileKeyedBaseFactory::MarkPreferencesSetOn( + content::BrowserContext* profile) { + DCHECK(!ArePreferencesSetOn(profile)); + registered_preferences_.insert(profile); +} diff --git a/components/browser_context_keyed_service/browser_context_keyed_base_factory.h b/components/browser_context_keyed_service/browser_context_keyed_base_factory.h new file mode 100644 index 0000000..30a0c32 --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_keyed_base_factory.h @@ -0,0 +1,133 @@ +// Copyright (c) 2012 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_BASE_FACTORY_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_BASE_FACTORY_H_ + +#include <set> + +#include "base/threading/non_thread_safe.h" +#include "components/browser_context_keyed_service/dependency_node.h" + +class PrefService; +class ProfileDependencyManager; + +namespace content { +class BrowserContext; +} + +namespace user_prefs { +class PrefRegistrySyncable; +} +// Base class for Factories that take a Profile object and return some service. +// +// Unless you're trying to make a new type of Factory, you probably don't want +// this class, but its subclasses: ProfileKeyedServiceFactory and +// RefcountedProfileKeyedServiceFactory. This object describes general +// dependency management between Factories; subclasses react to lifecycle +// events and implement memory management. +class ProfileKeyedBaseFactory : public base::NonThreadSafe, + public DependencyNode { + public: + // Registers preferences used in this service on the pref service of + // |profile|. This is the public interface and is safe to be called multiple + // times because testing code can have multiple services of the same type + // attached to a single |profile|. + void RegisterUserPrefsOnProfile(content::BrowserContext* profile); + +#ifndef NDEBUG + // Returns our name. We don't keep track of this in release mode. + const char* name() const { return service_name_; } +#endif + + protected: + ProfileKeyedBaseFactory(const char* name, + ProfileDependencyManager* manager); + virtual ~ProfileKeyedBaseFactory(); + + // The main public interface for declaring dependencies between services + // created by factories. + void DependsOn(ProfileKeyedBaseFactory* rhs); + + // Interface for people building a concrete FooServiceFactory: -------------- + + // Finds which browser context (if any) to use. + virtual content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const; + + // Register any user preferences on this service. This is called during + // CreateProfileService() since preferences are registered on a per Profile + // basis. + virtual void RegisterUserPrefs(user_prefs::PrefRegistrySyncable* registry) {} + + // By default, we create instances of a service lazily and wait until + // GetForProfile() is called on our subclass. Some services need to be + // created as soon as the Profile has been brought up. + virtual bool ServiceIsCreatedWithProfile() const; + + // By default, TestingProfiles will be treated like normal profiles. You can + // override this so that by default, the service associated with the + // TestingProfile is NULL. (This is just a shortcut around + // SetTestingFactory() to make sure our profiles don't directly refer to the + // services they use.) + virtual bool ServiceIsNULLWhileTesting() const; + + // Interface for people building a type of ProfileKeyedFactory: ------------- + + // A helper object actually listens for notifications about Profile + // destruction, calculates the order in which things are destroyed and then + // does a two pass shutdown. + // + // It is up to the individual factory types to determine what this two pass + // shutdown means. The general framework guarantees the following: + // + // - Each ProfileShutdown() is called in dependency order (and you may reach + // out to other services during this phase). + // + // - Each ProfileDestroyed() is called in dependency order. We will + // NOTREACHED() if you attempt to GetForProfile() any other service. You + // should delete/deref/do other final memory management things during this + // phase. You must also call the base class method as the last thing you + // do. + virtual void ProfileShutdown(content::BrowserContext* profile) = 0; + virtual void ProfileDestroyed(content::BrowserContext* profile); + + // Returns whether we've registered the preferences on this profile. + bool ArePreferencesSetOn(content::BrowserContext* profile) const; + + // Mark profile as Preferences set. + void MarkPreferencesSetOn(content::BrowserContext* profile); + + private: + friend class ProfileDependencyManager; + friend class ProfileDependencyManagerUnittests; + + // These two methods are for tight integration with the + // ProfileDependencyManager. + + // Because of ServiceIsNULLWhileTesting(), we need a way to tell different + // subclasses that they should disable testing. + virtual void SetEmptyTestingFactory(content::BrowserContext* profile) = 0; + + // We also need a generalized, non-returning method that generates the object + // now for when we're creating the profile. + virtual void CreateServiceNow(content::BrowserContext* profile) = 0; + + // Which ProfileDependencyManager we should communicate with. In real code, + // this will always be ProfileDependencyManager::GetInstance(), but unit + // tests will want to use their own copy. + ProfileDependencyManager* dependency_manager_; + + // Profiles that have this service's preferences registered on them. + std::set<content::BrowserContext*> registered_preferences_; + +#if !defined(NDEBUG) + // A static string passed in to our constructor. Should be unique across all + // services. This is used only for debugging in debug mode. (We can print + // pretty graphs with GraphViz with this information.) + const char* service_name_; +#endif +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_BASE_FACTORY_H_ diff --git a/components/browser_context_keyed_service/browser_context_keyed_service.h b/components/browser_context_keyed_service/browser_context_keyed_service.h new file mode 100644 index 0000000..b87aeac --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_keyed_service.h @@ -0,0 +1,30 @@ +// Copyright (c) 2012 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_H_ + +class ProfileKeyedServiceFactory; + +// Base class for all ProfileKeyedServices to allow for correct destruction +// order. +// +// Many services that hang off Profile have a two-pass shutdown. Many +// subsystems need a first pass shutdown phase where they drop references. Not +// all services will need this, so there's a default implementation. Only once +// every system has been given a chance to drop references do we start deleting +// objects. +class ProfileKeyedService { + public: + // The first pass is to call Shutdown on a ProfileKeyedService. + virtual void Shutdown() {} + + protected: + friend class ProfileKeyedServiceFactory; + + // The second pass is the actual deletion of each object. + virtual ~ProfileKeyedService() {} +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_H_ diff --git a/components/browser_context_keyed_service/browser_context_keyed_service_factory.cc b/components/browser_context_keyed_service/browser_context_keyed_service_factory.cc new file mode 100644 index 0000000..bfc1c74 --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_keyed_service_factory.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2012 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 "components/browser_context_keyed_service/browser_context_keyed_service_factory.h" + +#include <map> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "components/browser_context_keyed_service/browser_context_dependency_manager.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service.h" +#include "content/public/browser/browser_context.h" + +void ProfileKeyedServiceFactory::SetTestingFactory( + content::BrowserContext* profile, FactoryFunction factory) { + // Destroying the profile may cause us to lose data about whether |profile| + // has our preferences registered on it (since the profile object itself + // isn't dead). See if we need to readd it once we've gone through normal + // destruction. + bool add_profile = ArePreferencesSetOn(profile); + + // We have to go through the shutdown and destroy mechanisms because there + // are unit tests that create a service on a profile and then change the + // testing service mid-test. + ProfileShutdown(profile); + ProfileDestroyed(profile); + + if (add_profile) + MarkPreferencesSetOn(profile); + + factories_[profile] = factory; +} + +ProfileKeyedService* ProfileKeyedServiceFactory::SetTestingFactoryAndUse( + content::BrowserContext* profile, + FactoryFunction factory) { + DCHECK(factory); + SetTestingFactory(profile, factory); + return GetServiceForProfile(profile, true); +} + +ProfileKeyedServiceFactory::ProfileKeyedServiceFactory( + const char* name, ProfileDependencyManager* manager) + : ProfileKeyedBaseFactory(name, manager) { +} + +ProfileKeyedServiceFactory::~ProfileKeyedServiceFactory() { + DCHECK(mapping_.empty()); +} + +ProfileKeyedService* ProfileKeyedServiceFactory::GetServiceForProfile( + content::BrowserContext* profile, + bool create) { + profile = GetBrowserContextToUse(profile); + if (!profile) + return NULL; + + // NOTE: If you modify any of the logic below, make sure to update the + // refcounted version in refcounted_profile_keyed_service_factory.cc! + ProfileKeyedServices::const_iterator it = mapping_.find(profile); + if (it != mapping_.end()) + return it->second; + + // Object not found. + if (!create) + return NULL; // And we're forbidden from creating one. + + // Create new object. + // Check to see if we have a per-Profile testing factory that we should use + // instead of default behavior. + ProfileKeyedService* service = NULL; + ProfileOverriddenFunctions::const_iterator jt = factories_.find(profile); + if (jt != factories_.end()) { + if (jt->second) { + if (!profile->IsOffTheRecord()) + RegisterUserPrefsOnProfile(profile); + service = jt->second(profile); + } + } else { + service = BuildServiceInstanceFor(profile); + } + + Associate(profile, service); + return service; +} + +void ProfileKeyedServiceFactory::Associate(content::BrowserContext* profile, + ProfileKeyedService* service) { + DCHECK(!ContainsKey(mapping_, profile)); + mapping_.insert(std::make_pair(profile, service)); +} + +void ProfileKeyedServiceFactory::ProfileShutdown( + content::BrowserContext* profile) { + ProfileKeyedServices::iterator it = mapping_.find(profile); + if (it != mapping_.end() && it->second) + it->second->Shutdown(); +} + +void ProfileKeyedServiceFactory::ProfileDestroyed( + content::BrowserContext* profile) { + ProfileKeyedServices::iterator it = mapping_.find(profile); + if (it != mapping_.end()) { + delete it->second; + mapping_.erase(it); + } + + // For unit tests, we also remove the factory function both so we don't + // maintain a big map of dead pointers, but also since we may have a second + // object that lives at the same address (see other comments about unit tests + // in this file). + factories_.erase(profile); + + ProfileKeyedBaseFactory::ProfileDestroyed(profile); +} + +void ProfileKeyedServiceFactory::SetEmptyTestingFactory( + content::BrowserContext* profile) { + SetTestingFactory(profile, NULL); +} + +void ProfileKeyedServiceFactory::CreateServiceNow( + content::BrowserContext* profile) { + GetServiceForProfile(profile, true); +} diff --git a/components/browser_context_keyed_service/browser_context_keyed_service_factory.h b/components/browser_context_keyed_service/browser_context_keyed_service_factory.h new file mode 100644 index 0000000..f222c42 --- /dev/null +++ b/components/browser_context_keyed_service/browser_context_keyed_service_factory.h @@ -0,0 +1,115 @@ +// Copyright (c) 2012 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/browser_context_keyed_service/browser_context_keyed_base_factory.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service.h" + +class ProfileDependencyManager; +class ProfileKeyedService; + +// Base class for Factories that take a Profile object and return some service +// on a one-to-one mapping. Each factory that derives from this class *must* +// be a Singleton (only unit tests don't do that). See ThemeServiceFactory as +// an example of how to derive from this class. +// +// We do this because services depend on each other and we need to control +// shutdown/destruction order. In each derived classes' constructors, the +// implementors must explicitly state which services are depended on. +class ProfileKeyedServiceFactory : public ProfileKeyedBaseFactory { + public: + // A function that supplies the instance of a ProfileKeyedService for a given + // Profile. This is used primarily for testing, where we want to feed a + // specific mock into the PKSF system. + typedef ProfileKeyedService* + (*FactoryFunction)(content::BrowserContext* profile); + + // Associates |factory| with |profile| so that |factory| is used to create + // the ProfileKeyedService when requested. |factory| can be NULL to signal + // that ProfileKeyedService should be NULL. Multiple calls to + // SetTestingFactory() are allowed; previous services will be shut down. + void SetTestingFactory(content::BrowserContext* profile, + FactoryFunction factory); + + // Associates |factory| with |profile| and immediately returns the created + // ProfileKeyedService. Since the factory will be used immediately, it may + // not be NULL. + ProfileKeyedService* SetTestingFactoryAndUse(content::BrowserContext* profile, + FactoryFunction factory); + + protected: + // ProfileKeyedServiceFactories must communicate with a + // ProfileDependencyManager. For all non-test code, write your subclass + // constructors like this: + // + // MyServiceFactory::MyServiceFactory() + // : ProfileKeyedServiceFactory( + // "MyService", + // ProfileDependencyManager::GetInstance()) + // {} + ProfileKeyedServiceFactory(const char* name, + ProfileDependencyManager* manager); + virtual ~ProfileKeyedServiceFactory(); + + // Common implementation that maps |profile| to some service object. Deals + // with incognito profiles per subclass instructions with + // ServiceRedirectedInIncognito() and ServiceHasOwnInstanceInIncognito() + // through the GetProfileToUse() method on the base. If |create| is true, + // the service will be created using BuildServiceInstanceFor() if it doesn't + // already exist. + ProfileKeyedService* GetServiceForProfile(content::BrowserContext* profile, + bool create); + + // Maps |profile| to |service| with debug checks to prevent duplication. + void Associate(content::BrowserContext* profile, + ProfileKeyedService* service); + + // All subclasses of ProfileKeyedServiceFactory must return a + // ProfileKeyedService instead of just a ProfileKeyedBase. + virtual ProfileKeyedService* BuildServiceInstanceFor( + content::BrowserContext* profile) const = 0; + + // A helper object actually listens for notifications about Profile + // destruction, calculates the order in which things are destroyed and then + // does a two pass shutdown. + // + // First, ProfileShutdown() is called on every ServiceFactory and will + // usually call ProfileKeyedService::Shutdown(), which gives each + // ProfileKeyedService a chance to remove dependencies on other services that + // it may be holding. + // + // Secondly, ProfileDestroyed() is called on every ServiceFactory and the + // default implementation removes it from |mapping_| and deletes the pointer. + virtual void ProfileShutdown(content::BrowserContext* profile) OVERRIDE; + virtual void ProfileDestroyed(content::BrowserContext* profile) OVERRIDE; + + virtual void SetEmptyTestingFactory( + content::BrowserContext* profile) OVERRIDE; + virtual void CreateServiceNow(content::BrowserContext* profile) OVERRIDE; + + private: + friend class ProfileDependencyManager; + friend class ProfileDependencyManagerUnittests; + + typedef std::map<content::BrowserContext*, ProfileKeyedService*> + ProfileKeyedServices; + typedef std::map<content::BrowserContext*, FactoryFunction> + ProfileOverriddenFunctions; + + // The mapping between a Profile and its service. + std::map<content::BrowserContext*, ProfileKeyedService*> mapping_; + + // The mapping between a Profile and its overridden FactoryFunction. + std::map<content::BrowserContext*, FactoryFunction> factories_; + + DISALLOW_COPY_AND_ASSIGN(ProfileKeyedServiceFactory); +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ diff --git a/components/browser_context_keyed_service/dependency_graph.cc b/components/browser_context_keyed_service/dependency_graph.cc new file mode 100644 index 0000000..253f5dd --- /dev/null +++ b/components/browser_context_keyed_service/dependency_graph.cc @@ -0,0 +1,166 @@ +// Copyright 2013 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 "components/browser_context_keyed_service/dependency_graph.h" + +#include <algorithm> +#include <deque> +#include <iterator> + +DependencyGraph::DependencyGraph() { +} + +DependencyGraph::~DependencyGraph() { +} + +void DependencyGraph::AddNode(DependencyNode* node) { + all_nodes_.push_back(node); + construction_order_.clear(); +} + +void DependencyGraph::RemoveNode(DependencyNode* node) { + all_nodes_.erase(std::remove(all_nodes_.begin(), + all_nodes_.end(), + node), + all_nodes_.end()); + + // Remove all dependency edges that contain this node. + EdgeMap::iterator it = edges_.begin(); + while (it != edges_.end()) { + EdgeMap::iterator temp = it; + ++it; + + if (temp->first == node || temp->second == node) + edges_.erase(temp); + } + + construction_order_.clear(); +} + +void DependencyGraph::AddEdge(DependencyNode* depended, + DependencyNode* dependee) { + edges_.insert(std::make_pair(depended, dependee)); + construction_order_.clear(); +} + +bool DependencyGraph::GetConstructionOrder( + std::vector<DependencyNode*>* order) { + if (construction_order_.empty() && !BuildConstructionOrder()) + return false; + + *order = construction_order_; + return true; +} + +bool DependencyGraph::GetDestructionOrder( + std::vector<DependencyNode*>* order) { + if (construction_order_.empty() && !BuildConstructionOrder()) + return false; + + *order = construction_order_; + + // Destroy nodes in reverse order. + std::reverse(order->begin(), order->end()); + + return true; +} + +bool DependencyGraph::BuildConstructionOrder() { + // Step 1: Build a set of nodes with no incoming edges. + std::deque<DependencyNode*> queue; + std::copy(all_nodes_.begin(), + all_nodes_.end(), + std::back_inserter(queue)); + + std::deque<DependencyNode*>::iterator queue_end = queue.end(); + for (EdgeMap::const_iterator it = edges_.begin(); + it != edges_.end(); ++it) { + queue_end = std::remove(queue.begin(), queue_end, it->second); + } + queue.erase(queue_end, queue.end()); + + // Step 2: Do the Kahn topological sort. + std::vector<DependencyNode*> output; + EdgeMap edges(edges_); + while (!queue.empty()) { + DependencyNode* node = queue.front(); + queue.pop_front(); + output.push_back(node); + + std::pair<EdgeMap::iterator, EdgeMap::iterator> range = + edges.equal_range(node); + EdgeMap::iterator it = range.first; + while (it != range.second) { + DependencyNode* dest = it->second; + EdgeMap::iterator temp = it; + it++; + edges.erase(temp); + + bool has_incoming_edges = false; + for (EdgeMap::iterator jt = edges.begin(); jt != edges.end(); ++jt) { + if (jt->second == dest) { + has_incoming_edges = true; + break; + } + } + + if (!has_incoming_edges) + queue.push_back(dest); + } + } + + if (!edges.empty()) { + // Dependency graph has a cycle. + return false; + } + + construction_order_ = output; + return true; +} + +std::string DependencyGraph::DumpAsGraphviz( + const std::string& toplevel_name, + const base::Callback<std::string(DependencyNode*)>& + node_name_callback) const { + std::string result("digraph {\n"); + + // Make a copy of all nodes. + std::deque<DependencyNode*> nodes; + std::copy(all_nodes_.begin(), all_nodes_.end(), std::back_inserter(nodes)); + + // State all dependencies and remove |second| so we don't generate an + // implicit dependency on the top level node. + std::deque<DependencyNode*>::iterator nodes_end(nodes.end()); + result.append(" /* Dependencies */\n"); + for (EdgeMap::const_iterator it = edges_.begin(); it != edges_.end(); ++it) { + result.append(" "); + result.append(node_name_callback.Run(it->second)); + result.append(" -> "); + result.append(node_name_callback.Run(it->first)); + result.append(";\n"); + + nodes_end = std::remove(nodes.begin(), nodes_end, it->second); + } + nodes.erase(nodes_end, nodes.end()); + + // Every node that doesn't depend on anything else will implicitly depend on + // the top level node. + result.append("\n /* Toplevel attachments */\n"); + for (std::deque<DependencyNode*>::const_iterator it = + nodes.begin(); it != nodes.end(); ++it) { + result.append(" "); + result.append(node_name_callback.Run(*it)); + result.append(" -> "); + result.append(toplevel_name); + result.append(";\n"); + } + + result.append("\n /* Toplevel node */\n"); + result.append(" "); + result.append(toplevel_name); + result.append(" [shape=box];\n"); + + result.append("}\n"); + return result; +} diff --git a/components/browser_context_keyed_service/dependency_graph.h b/components/browser_context_keyed_service/dependency_graph.h new file mode 100644 index 0000000..0f5d66a --- /dev/null +++ b/components/browser_context_keyed_service/dependency_graph.h @@ -0,0 +1,67 @@ +// Copyright 2013 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_GRAPH_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_GRAPH_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" + +class DependencyNode; + +// Dynamic graph of dependencies between nodes. +class DependencyGraph { + public: + DependencyGraph(); + ~DependencyGraph(); + + // Adds/Removes a node from our list of live nodes. Removing will + // also remove live dependency links. + void AddNode(DependencyNode* node); + void RemoveNode(DependencyNode* node); + + // Adds a dependency between two nodes. + void AddEdge(DependencyNode* depended, DependencyNode* dependee); + + // Topologically sorts nodes to produce a safe construction order + // (all nodes after their dependees). + bool GetConstructionOrder( + std::vector<DependencyNode*>* order) WARN_UNUSED_RESULT; + + // Topologically sorts nodes to produce a safe destruction order + // (all nodes before their dependees). + bool GetDestructionOrder( + std::vector<DependencyNode*>* order) WARN_UNUSED_RESULT; + + // Returns representation of the dependency graph in graphviz format. + std::string DumpAsGraphviz( + const std::string& toplevel_name, + const base::Callback<std::string(DependencyNode*)>& + node_name_callback) const; + + private: + typedef std::multimap<DependencyNode*, DependencyNode*> EdgeMap; + + // Populates |construction_order_| with computed construction order. + // Returns true on success. + bool BuildConstructionOrder() WARN_UNUSED_RESULT; + + // Keeps track of all live nodes (see AddNode, RemoveNode). + std::vector<DependencyNode*> all_nodes_; + + // Keeps track of edges of the dependency graph. + EdgeMap edges_; + + // Cached construction order (needs rebuild with BuildConstructionOrder + // when empty). + std::vector<DependencyNode*> construction_order_; + + DISALLOW_COPY_AND_ASSIGN(DependencyGraph); +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_GRAPH_H_ diff --git a/components/browser_context_keyed_service/dependency_graph_unittest.cc b/components/browser_context_keyed_service/dependency_graph_unittest.cc new file mode 100644 index 0000000..541ada7 --- /dev/null +++ b/components/browser_context_keyed_service/dependency_graph_unittest.cc @@ -0,0 +1,161 @@ +// Copyright 2013 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 "components/browser_context_keyed_service/dependency_graph.h" +#include "components/browser_context_keyed_service/dependency_node.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class DependencyGraphTest : public testing::Test { +}; + +class DummyNode : public DependencyNode { + public: + explicit DummyNode(DependencyGraph* graph) : dependency_graph_(graph) { + dependency_graph_->AddNode(this); + } + + ~DummyNode() { + dependency_graph_->RemoveNode(this); + } + + private: + DependencyGraph* dependency_graph_; + + DISALLOW_COPY_AND_ASSIGN(DummyNode); +}; + +// Tests that we can deal with a single component. +TEST_F(DependencyGraphTest, SingleCase) { + DependencyGraph graph; + DummyNode node(&graph); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(1U, construction_order.size()); + EXPECT_EQ(&node, construction_order[0]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(1U, destruction_order.size()); + EXPECT_EQ(&node, destruction_order[0]); +} + +// Tests that we get a simple one component depends on the other case. +TEST_F(DependencyGraphTest, SimpleDependency) { + DependencyGraph graph; + DummyNode parent(&graph); + DummyNode child(&graph); + + graph.AddEdge(&parent, &child); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(2U, construction_order.size()); + EXPECT_EQ(&parent, construction_order[0]); + EXPECT_EQ(&child, construction_order[1]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(2U, destruction_order.size()); + EXPECT_EQ(&child, destruction_order[0]); + EXPECT_EQ(&parent, destruction_order[1]); +} + +// Tests two children, one parent. +TEST_F(DependencyGraphTest, TwoChildrenOneParent) { + DependencyGraph graph; + DummyNode parent(&graph); + DummyNode child1(&graph); + DummyNode child2(&graph); + + graph.AddEdge(&parent, &child1); + graph.AddEdge(&parent, &child2); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(3U, construction_order.size()); + EXPECT_EQ(&parent, construction_order[0]); + EXPECT_EQ(&child1, construction_order[1]); + EXPECT_EQ(&child2, construction_order[2]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(3U, destruction_order.size()); + EXPECT_EQ(&child2, destruction_order[0]); + EXPECT_EQ(&child1, destruction_order[1]); + EXPECT_EQ(&parent, destruction_order[2]); +} + +// Tests an M configuration. +TEST_F(DependencyGraphTest, MConfiguration) { + DependencyGraph graph; + + DummyNode parent1(&graph); + DummyNode parent2(&graph); + + DummyNode child_of_1(&graph); + graph.AddEdge(&parent1, &child_of_1); + + DummyNode child_of_12(&graph); + graph.AddEdge(&parent1, &child_of_12); + graph.AddEdge(&parent2, &child_of_12); + + DummyNode child_of_2(&graph); + graph.AddEdge(&parent2, &child_of_2); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(5U, construction_order.size()); + EXPECT_EQ(&parent1, construction_order[0]); + EXPECT_EQ(&parent2, construction_order[1]); + EXPECT_EQ(&child_of_1, construction_order[2]); + EXPECT_EQ(&child_of_12, construction_order[3]); + EXPECT_EQ(&child_of_2, construction_order[4]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(5U, destruction_order.size()); + EXPECT_EQ(&child_of_2, destruction_order[0]); + EXPECT_EQ(&child_of_12, destruction_order[1]); + EXPECT_EQ(&child_of_1, destruction_order[2]); + EXPECT_EQ(&parent2, destruction_order[3]); + EXPECT_EQ(&parent1, destruction_order[4]); +} + +// Tests that it can deal with a simple diamond. +TEST_F(DependencyGraphTest, DiamondConfiguration) { + DependencyGraph graph; + + DummyNode parent(&graph); + + DummyNode middle1(&graph); + graph.AddEdge(&parent, &middle1); + + DummyNode middle2(&graph); + graph.AddEdge(&parent, &middle2); + + DummyNode bottom(&graph); + graph.AddEdge(&middle1, &bottom); + graph.AddEdge(&middle2, &bottom); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(4U, construction_order.size()); + EXPECT_EQ(&parent, construction_order[0]); + EXPECT_EQ(&middle1, construction_order[1]); + EXPECT_EQ(&middle2, construction_order[2]); + EXPECT_EQ(&bottom, construction_order[3]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(4U, destruction_order.size()); + EXPECT_EQ(&bottom, destruction_order[0]); + EXPECT_EQ(&middle2, destruction_order[1]); + EXPECT_EQ(&middle1, destruction_order[2]); + EXPECT_EQ(&parent, destruction_order[3]); +} + +} // namespace diff --git a/components/browser_context_keyed_service/dependency_node.h b/components/browser_context_keyed_service/dependency_node.h new file mode 100644 index 0000000..d870b67 --- /dev/null +++ b/components/browser_context_keyed_service/dependency_node.h @@ -0,0 +1,16 @@ +// Copyright 2013 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_NODE_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_NODE_H_ + +// Base class representing a node in a DependencyGraph. +class DependencyNode { + protected: + // This is intended to be used by the subclasses, not directly. + DependencyNode() {} + ~DependencyNode() {} +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_NODE_H_ diff --git a/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.cc b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.cc new file mode 100644 index 0000000..da92a6d --- /dev/null +++ b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2012 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 "components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h" + +namespace impl { + +// static +void RefcountedProfileKeyedServiceTraits::Destruct( + const RefcountedProfileKeyedService* obj) { + if (obj->requires_destruction_on_thread_ && + !content::BrowserThread::CurrentlyOn(obj->thread_id_)) { + content::BrowserThread::DeleteSoon(obj->thread_id_, FROM_HERE, obj); + } else { + delete obj; + } +} + +} // namespace impl + +RefcountedProfileKeyedService::RefcountedProfileKeyedService() + : requires_destruction_on_thread_(false), + thread_id_(content::BrowserThread::UI) { +} + +RefcountedProfileKeyedService::RefcountedProfileKeyedService( + const content::BrowserThread::ID thread_id) + : requires_destruction_on_thread_(true), + thread_id_(thread_id) { +} + +RefcountedProfileKeyedService::~RefcountedProfileKeyedService() {} + diff --git a/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h new file mode 100644 index 0000000..1d19f72 --- /dev/null +++ b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_H_ + +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner_helpers.h" +#include "content/public/browser/browser_thread.h" + +class RefcountedProfileKeyedService; + +namespace impl { + +struct RefcountedProfileKeyedServiceTraits { + static void Destruct(const RefcountedProfileKeyedService* obj); +}; + +} // namespace impl + +// Base class for refcounted objects that hang off the Profile. +// +// The two pass shutdown described in ProfileKeyedService works a bit +// differently because there could be outstanding references on other +// threads. ShutdownOnUIThread() will be called on the UI thread, and then the +// destructor will run when the last reference is dropped, which may or may not +// be after the corresponding Profile has been destroyed. +// +// Optionally, if you initialize your service with the constructor that takes a +// thread ID, your service will be deleted on that thread. We can't use +// content::DeleteOnThread<> directly because RefcountedProfileKeyedService +// must be one type that RefcountedProfileKeyedServiceFactory can use. +class RefcountedProfileKeyedService + : public base::RefCountedThreadSafe< + RefcountedProfileKeyedService, + impl::RefcountedProfileKeyedServiceTraits> { + public: + // Unlike ProfileKeyedService, ShutdownOnUI is not optional. You must do + // something to drop references during the first pass Shutdown() because this + // is the only point where you are guaranteed that something is running on + // the UI thread. The PKSF framework will ensure that this is only called on + // the UI thread; you do not need to check for that yourself. + virtual void ShutdownOnUIThread() = 0; + + protected: + // If your service does not need to be deleted on a specific thread, use the + // default constructor. + RefcountedProfileKeyedService(); + + // If you need your service to be deleted on a specific thread (for example, + // you're converting a service that used content::DeleteOnThread<IO>), then + // use this constructor with the ID of the thread. + explicit RefcountedProfileKeyedService( + const content::BrowserThread::ID thread_id); + + // The second pass destruction can happen anywhere unless you specify which + // thread this service must be destroyed on by using the second constructor. + virtual ~RefcountedProfileKeyedService(); + + private: + friend struct impl::RefcountedProfileKeyedServiceTraits; + friend class base::DeleteHelper<RefcountedProfileKeyedService>; + + // Do we have to delete this object on a specific thread? + bool requires_destruction_on_thread_; + content::BrowserThread::ID thread_id_; +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_H_ diff --git a/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc new file mode 100644 index 0000000..560180d --- /dev/null +++ b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2012 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 "components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "content/public/browser/browser_context.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service.h" +#include "components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h" + +void RefcountedProfileKeyedServiceFactory::SetTestingFactory( + content::BrowserContext* profile, + FactoryFunction factory) { + // Destroying the profile may cause us to lose data about whether |profile| + // has our preferences registered on it (since the profile object itself + // isn't dead). See if we need to readd it once we've gone through normal + // destruction. + bool add_profile = ArePreferencesSetOn(profile); + + // We have to go through the shutdown and destroy mechanisms because there + // are unit tests that create a service on a profile and then change the + // testing service mid-test. + ProfileShutdown(profile); + ProfileDestroyed(profile); + + if (add_profile) + MarkPreferencesSetOn(profile); + + factories_[profile] = factory; +} + +scoped_refptr<RefcountedProfileKeyedService> +RefcountedProfileKeyedServiceFactory::SetTestingFactoryAndUse( + content::BrowserContext* profile, + FactoryFunction factory) { + DCHECK(factory); + SetTestingFactory(profile, factory); + return GetServiceForProfile(profile, true); +} + +RefcountedProfileKeyedServiceFactory::RefcountedProfileKeyedServiceFactory( + const char* name, + ProfileDependencyManager* manager) + : ProfileKeyedBaseFactory(name, manager) { +} + +RefcountedProfileKeyedServiceFactory::~RefcountedProfileKeyedServiceFactory() { + DCHECK(mapping_.empty()); +} + +scoped_refptr<RefcountedProfileKeyedService> +RefcountedProfileKeyedServiceFactory::GetServiceForProfile( + content::BrowserContext* profile, + bool create) { + profile = GetBrowserContextToUse(profile); + if (!profile) + return NULL; + + // NOTE: If you modify any of the logic below, make sure to update the + // non-refcounted version in profile_keyed_service_factory.cc! + RefCountedStorage::const_iterator it = mapping_.find(profile); + if (it != mapping_.end()) + return it->second; + + // Object not found. + if (!create) + return NULL; // And we're forbidden from creating one. + + // Create new object. + // Check to see if we have a per-Profile testing factory that we should use + // instead of default behavior. + scoped_refptr<RefcountedProfileKeyedService> service; + ProfileOverriddenFunctions::const_iterator jt = factories_.find(profile); + if (jt != factories_.end()) { + if (jt->second) { + if (!profile->IsOffTheRecord()) + RegisterUserPrefsOnProfile(profile); + service = jt->second(profile); + } + } else { + service = BuildServiceInstanceFor(profile); + } + + Associate(profile, service); + return service; +} + +void RefcountedProfileKeyedServiceFactory::Associate( + content::BrowserContext* profile, + const scoped_refptr<RefcountedProfileKeyedService>& service) { + DCHECK(!ContainsKey(mapping_, profile)); + mapping_.insert(std::make_pair(profile, service)); +} + +void RefcountedProfileKeyedServiceFactory::ProfileShutdown( + content::BrowserContext* profile) { + RefCountedStorage::iterator it = mapping_.find(profile); + if (it != mapping_.end() && it->second) + it->second->ShutdownOnUIThread(); +} + +void RefcountedProfileKeyedServiceFactory::ProfileDestroyed( + content::BrowserContext* profile) { + // We "merely" drop our reference to the service. Hopefully this will cause + // the service to be destroyed. If not, oh well. + mapping_.erase(profile); + + // For unit tests, we also remove the factory function both so we don't + // maintain a big map of dead pointers, but also since we may have a second + // object that lives at the same address (see other comments about unit tests + // in this file). + factories_.erase(profile); + + ProfileKeyedBaseFactory::ProfileDestroyed(profile); +} + +void RefcountedProfileKeyedServiceFactory::SetEmptyTestingFactory( + content::BrowserContext* profile) { + SetTestingFactory(profile, NULL); +} + +void RefcountedProfileKeyedServiceFactory::CreateServiceNow( + content::BrowserContext* profile) { + GetServiceForProfile(profile, true); +} diff --git a/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h new file mode 100644 index 0000000..195c657 --- /dev/null +++ b/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h @@ -0,0 +1,88 @@ +// Copyright (c) 2012 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 COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h" +#include "components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h" + +class Profile; +class RefcountedProfileKeyedService; + +// A specialized ProfileKeyedServiceFactory that manages a +// RefcountedThreadSafe<>. +// +// While the factory returns RefcountedThreadSafe<>s, the factory itself is a +// base::NotThreadSafe. Only call methods on this object on the UI thread. +// +// Implementers of RefcountedProfileKeyedService should note that we guarantee +// that ShutdownOnUIThread() is called on the UI thread, but actual object +// destruction can happen anywhere. +class RefcountedProfileKeyedServiceFactory : public ProfileKeyedBaseFactory { + public: + // A function that supplies the instance of a ProfileKeyedService for a given + // Profile. This is used primarily for testing, where we want to feed a + // specific mock into the PKSF system. + typedef scoped_refptr<RefcountedProfileKeyedService> + (*FactoryFunction)(content::BrowserContext* profile); + + // Associates |factory| with |profile| so that |factory| is used to create + // the ProfileKeyedService when requested. |factory| can be NULL to signal + // that ProfileKeyedService should be NULL. Multiple calls to + // SetTestingFactory() are allowed; previous services will be shut down. + void SetTestingFactory(content::BrowserContext* profile, + FactoryFunction factory); + + // Associates |factory| with |profile| and immediately returns the created + // ProfileKeyedService. Since the factory will be used immediately, it may + // not be NULL. + scoped_refptr<RefcountedProfileKeyedService> SetTestingFactoryAndUse( + content::BrowserContext* profile, + FactoryFunction factory); + + protected: + RefcountedProfileKeyedServiceFactory(const char* name, + ProfileDependencyManager* manager); + virtual ~RefcountedProfileKeyedServiceFactory(); + + scoped_refptr<RefcountedProfileKeyedService> GetServiceForProfile( + content::BrowserContext* profile, + bool create); + + // Maps |profile| to |service| with debug checks to prevent duplication. + void Associate(content::BrowserContext* profile, + const scoped_refptr<RefcountedProfileKeyedService>& service); + + // All subclasses of RefcountedProfileKeyedServiceFactory must return a + // RefcountedProfileKeyedService instead of just a ProfileKeyedBase. + virtual scoped_refptr<RefcountedProfileKeyedService> BuildServiceInstanceFor( + content::BrowserContext* profile) const = 0; + + virtual void ProfileShutdown(content::BrowserContext* profile) OVERRIDE; + virtual void ProfileDestroyed(content::BrowserContext* profile) OVERRIDE; + virtual void SetEmptyTestingFactory( + content::BrowserContext* profile) OVERRIDE; + virtual void CreateServiceNow(content::BrowserContext* profile) OVERRIDE; + + private: + typedef std::map<content::BrowserContext*, + scoped_refptr<RefcountedProfileKeyedService> > + RefCountedStorage; + typedef std::map<content::BrowserContext*, + FactoryFunction> ProfileOverriddenFunctions; + + // The mapping between a Profile and its refcounted service. + RefCountedStorage mapping_; + + // The mapping between a Profile and its overridden FactoryFunction. + ProfileOverriddenFunctions factories_; + + DISALLOW_COPY_AND_ASSIGN(RefcountedProfileKeyedServiceFactory); +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ diff --git a/components/components.gyp b/components/components.gyp index 7243700..c361739 100644 --- a/components/components.gyp +++ b/components/components.gyp @@ -12,6 +12,7 @@ 'includes': [ 'autofill.gypi', 'auto_login_parser.gypi', + 'browser_context_keyed_service.gypi', 'components_tests.gypi', 'navigation_interception.gypi', 'sessions.gypi', diff --git a/components/components_tests.gypi b/components/components_tests.gypi index 8fa321b..2de146b 100644 --- a/components/components_tests.gypi +++ b/components/components_tests.gypi @@ -11,6 +11,8 @@ 'type': '<(gtest_target_type)', 'sources': [ 'auto_login_parser/auto_login_parser_unittest.cc', + 'browser_context_keyed_service/browser_context_dependency_manager_unittest.cc', + 'browser_context_keyed_service/dependency_graph_unittest.cc', 'navigation_interception/intercept_navigation_resource_throttle_unittest.cc', 'sessions/serialized_navigation_entry_unittest.cc', 'test/run_all_unittests.cc', @@ -30,6 +32,9 @@ # Dependencies of auto_login_parser 'auto_login_parser', + # Dependencies of browser_context_keyed_service + 'browser_context_keyed_service', + # Dependencies of encryptor 'encryptor', |