1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#ifndef CHROME_BROWSER_EXTENSIONS_API_API_RESOURCE_MANAGER_H_ 6#define CHROME_BROWSER_EXTENSIONS_API_API_RESOURCE_MANAGER_H_ 7 8#include <map> 9 10#include "base/lazy_instance.h" 11#include "base/memory/linked_ptr.h" 12#include "base/threading/non_thread_safe.h" 13#include "chrome/browser/chrome_notification_types.h" 14#include "chrome/browser/extensions/api/profile_keyed_api_factory.h" 15#include "chrome/common/extensions/extension.h" 16#include "components/browser_context_keyed_service/browser_context_keyed_service.h" 17#include "content/public/browser/browser_thread.h" 18#include "content/public/browser/notification_observer.h" 19#include "content/public/browser/notification_registrar.h" 20#include "content/public/browser/notification_service.h" 21 22namespace extensions { 23 24// An ApiResourceManager manages the lifetime of a set of resources that 25// ApiFunctions use. Examples are sockets or USB connections. 26// 27// Users of this class should define kThreadId to be the thread that 28// ApiResourceManager to works on. The default is defined in ApiResource. 29// The user must also define a static const char* service_name() that returns 30// the name of the service, and in order for ApiResourceManager to use 31// service_name() friend this class. 32// 33// In the cc file the user must define a GetFactoryInstance() and manage their 34// own instances (typically using LazyInstance or Singleton). 35// 36// E.g.: 37// 38// class Resource { 39// public: 40// static const BrowserThread::ID kThreadId = BrowserThread::FILE; 41// private: 42// friend class ApiResourceManager<Resource>; 43// static const char* service_name() { 44// return "ResourceManager"; 45// } 46// }; 47// 48// In the cc file: 49// 50// static base::LazyInstance<ProfileKeyedAPIFactory< 51// ApiResourceManager<Resource> > > 52// g_factory = LAZY_INSTANCE_INITIALIZER; 53// 54// 55// template <> 56// ProfileKeyedAPIFactory<ApiResourceManager<Resource> >* 57// ApiResourceManager<Resource>::GetFactoryInstance() { 58// return &g_factory.Get(); 59// } 60template <class T> 61class ApiResourceManager : public ProfileKeyedAPI, 62 public base::NonThreadSafe, 63 public content::NotificationObserver { 64 public: 65 explicit ApiResourceManager(Profile* profile) 66 : thread_id_(T::kThreadId), 67 data_(new ApiResourceData(thread_id_)) { 68 registrar_.Add( 69 this, 70 chrome::NOTIFICATION_EXTENSION_UNLOADED, 71 content::NotificationService::AllSources()); 72 } 73 74 // For Testing. 75 static ApiResourceManager<T>* CreateApiResourceManagerForTest( 76 Profile* profile, 77 content::BrowserThread::ID thread_id) { 78 ApiResourceManager* manager = new ApiResourceManager<T>(profile); 79 manager->thread_id_ = thread_id; 80 manager->data_.reset(new ApiResourceData(thread_id)); 81 return manager; 82 } 83 84 virtual ~ApiResourceManager() { 85 DCHECK(CalledOnValidThread()); 86 DCHECK(content::BrowserThread::IsMessageLoopValid(thread_id_)) << 87 "A unit test is using an ApiResourceManager but didn't provide " 88 "the thread message loop needed for that kind of resource. " 89 "Please ensure that the appropriate message loop is operational."; 90 91 content::BrowserThread::DeleteSoon(thread_id_, FROM_HERE, data_.release()); 92 } 93 94 // ProfileKeyedAPI implementation. 95 static ProfileKeyedAPIFactory<ApiResourceManager<T> >* GetFactoryInstance(); 96 97 // Convenience method to get the ApiResourceManager for a profile. 98 static ApiResourceManager<T>* Get(Profile* profile) { 99 return ProfileKeyedAPIFactory<ApiResourceManager<T> >::GetForProfile( 100 profile); 101 } 102 103 // Takes ownership. 104 int Add(T* api_resource) { 105 return data_->Add(api_resource); 106 } 107 108 void Remove(const std::string& extension_id, int api_resource_id) { 109 data_->Remove(extension_id, api_resource_id); 110 } 111 112 T* Get(const std::string& extension_id, int api_resource_id) { 113 return data_->Get(extension_id, api_resource_id); 114 } 115 116 protected: 117 // content::NotificationObserver: 118 virtual void Observe(int type, 119 const content::NotificationSource& source, 120 const content::NotificationDetails& details) OVERRIDE { 121 switch (type) { 122 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 123 std::string id = 124 content::Details<extensions::UnloadedExtensionInfo>(details)-> 125 extension->id(); 126 data_->InitiateCleanup(id); 127 break; 128 } 129 } 130 } 131 132 private: 133 friend class ProfileKeyedAPIFactory<ApiResourceManager<T> >; 134 // ProfileKeyedAPI implementation. 135 static const char* service_name() { 136 return T::service_name(); 137 } 138 static const bool kServiceHasOwnInstanceInIncognito = true; 139 static const bool kServiceIsNULLWhileTesting = true; 140 141 // ApiResourceData class handles resource bookkeeping on a thread 142 // where resource lifetime is handled. 143 class ApiResourceData { 144 public: 145 typedef std::map<int, linked_ptr<T> > ApiResourceMap; 146 // Lookup map from extension id's to allocated resource id's. 147 typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap; 148 149 explicit ApiResourceData(const content::BrowserThread::ID thread_id) 150 : next_id_(1), 151 thread_id_(thread_id) { 152 } 153 154 int Add(T* api_resource) { 155 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 156 int id = GenerateId(); 157 if (id > 0) { 158 linked_ptr<T> resource_ptr(api_resource); 159 api_resource_map_[id] = resource_ptr; 160 161 const std::string& extension_id = api_resource->owner_extension_id(); 162 if (extension_resource_map_.find(extension_id) == 163 extension_resource_map_.end()) { 164 extension_resource_map_[extension_id] = base::hash_set<int>(); 165 } 166 extension_resource_map_[extension_id].insert(id); 167 168 return id; 169 } 170 return 0; 171 } 172 173 void Remove(const std::string& extension_id, int api_resource_id) { 174 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 175 if (GetOwnedResource(extension_id, api_resource_id) != NULL) { 176 DCHECK(extension_resource_map_.find(extension_id) != 177 extension_resource_map_.end()); 178 extension_resource_map_[extension_id].erase(api_resource_id); 179 api_resource_map_.erase(api_resource_id); 180 } 181 } 182 183 T* Get(const std::string& extension_id, int api_resource_id) { 184 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 185 return GetOwnedResource(extension_id, api_resource_id); 186 } 187 188 void InitiateCleanup(const std::string& extension_id) { 189 content::BrowserThread::PostTask(thread_id_, FROM_HERE, 190 base::Bind(&ApiResourceData::CleanupResourcesFromExtension, 191 base::Unretained(this), extension_id)); 192 } 193 194 private: 195 T* GetOwnedResource(const std::string& extension_id, 196 int api_resource_id) { 197 linked_ptr<T> ptr = api_resource_map_[api_resource_id]; 198 T* resource = ptr.get(); 199 if (resource && extension_id == resource->owner_extension_id()) 200 return resource; 201 return NULL; 202 } 203 204 void CleanupResourcesFromExtension(const std::string& extension_id) { 205 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 206 if (extension_resource_map_.find(extension_id) != 207 extension_resource_map_.end()) { 208 base::hash_set<int>& resource_ids = 209 extension_resource_map_[extension_id]; 210 for (base::hash_set<int>::iterator it = resource_ids.begin(); 211 it != resource_ids.end(); ++it) { 212 api_resource_map_.erase(*it); 213 } 214 extension_resource_map_.erase(extension_id); 215 } 216 } 217 218 int GenerateId() { 219 return next_id_++; 220 } 221 222 int next_id_; 223 const content::BrowserThread::ID thread_id_; 224 ApiResourceMap api_resource_map_; 225 ExtensionToResourceMap extension_resource_map_; 226 }; 227 228 content::BrowserThread::ID thread_id_; 229 content::NotificationRegistrar registrar_; 230 scoped_ptr<ApiResourceData> data_; 231}; 232 233} // namespace extensions 234 235#endif // CHROME_BROWSER_EXTENSIONS_API_API_RESOURCE_MANAGER_H_ 236