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/containers/hash_tables.h" 11 #include "base/lazy_instance.h" 12 #include "base/memory/linked_ptr.h" 13 #include "base/memory/ref_counted.h" 14 #include "base/threading/non_thread_safe.h" 15 #include "chrome/browser/chrome_notification_types.h" 16 #include "chrome/browser/extensions/api/profile_keyed_api_factory.h" 17 #include "chrome/browser/extensions/extension_host.h" 18 #include "components/browser_context_keyed_service/browser_context_keyed_service.h" 19 #include "content/public/browser/browser_thread.h" 20 #include "content/public/browser/notification_observer.h" 21 #include "content/public/browser/notification_registrar.h" 22 #include "content/public/browser/notification_service.h" 23 #include "extensions/common/extension.h" 24 25 namespace extensions { 26 namespace api { 27 class SerialEventDispatcher; 28 class TCPServerSocketEventDispatcher; 29 class TCPSocketEventDispatcher; 30 class UDPSocketEventDispatcher; 31 } 32 } 33 34 namespace extensions { 35 36 // An ApiResourceManager manages the lifetime of a set of resources that 37 // ApiFunctions use. Examples are sockets or USB connections. 38 // 39 // Users of this class should define kThreadId to be the thread that 40 // ApiResourceManager to works on. The default is defined in ApiResource. 41 // The user must also define a static const char* service_name() that returns 42 // the name of the service, and in order for ApiResourceManager to use 43 // service_name() friend this class. 44 // 45 // In the cc file the user must define a GetFactoryInstance() and manage their 46 // own instances (typically using LazyInstance or Singleton). 47 // 48 // E.g.: 49 // 50 // class Resource { 51 // public: 52 // static const BrowserThread::ID kThreadId = BrowserThread::FILE; 53 // private: 54 // friend class ApiResourceManager<Resource>; 55 // static const char* service_name() { 56 // return "ResourceManager"; 57 // } 58 // }; 59 // 60 // In the cc file: 61 // 62 // static base::LazyInstance<ProfileKeyedAPIFactory< 63 // ApiResourceManager<Resource> > > 64 // g_factory = LAZY_INSTANCE_INITIALIZER; 65 // 66 // 67 // template <> 68 // ProfileKeyedAPIFactory<ApiResourceManager<Resource> >* 69 // ApiResourceManager<Resource>::GetFactoryInstance() { 70 // return &g_factory.Get(); 71 // } 72 template <class T> 73 class ApiResourceManager : public ProfileKeyedAPI, 74 public base::NonThreadSafe, 75 public content::NotificationObserver { 76 public: 77 explicit ApiResourceManager(Profile* profile) 78 : thread_id_(T::kThreadId), 79 data_(new ApiResourceData(thread_id_)) { 80 registrar_.Add( 81 this, 82 chrome::NOTIFICATION_EXTENSION_UNLOADED, 83 content::NotificationService::AllSources()); 84 registrar_.Add( 85 this, 86 chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, 87 content::NotificationService::AllSources()); 88 } 89 90 // For Testing. 91 static ApiResourceManager<T>* CreateApiResourceManagerForTest( 92 Profile* profile, 93 content::BrowserThread::ID thread_id) { 94 ApiResourceManager* manager = new ApiResourceManager<T>(profile); 95 manager->thread_id_ = thread_id; 96 manager->data_ = new ApiResourceData(thread_id); 97 return manager; 98 } 99 100 virtual ~ApiResourceManager() { 101 DCHECK(CalledOnValidThread()); 102 DCHECK(content::BrowserThread::IsMessageLoopValid(thread_id_)) << 103 "A unit test is using an ApiResourceManager but didn't provide " 104 "the thread message loop needed for that kind of resource. " 105 "Please ensure that the appropriate message loop is operational."; 106 107 data_->InititateCleanup(); 108 } 109 110 // ProfileKeyedAPI implementation. 111 static ProfileKeyedAPIFactory<ApiResourceManager<T> >* GetFactoryInstance(); 112 113 // Convenience method to get the ApiResourceManager for a profile. 114 static ApiResourceManager<T>* Get(Profile* profile) { 115 return ProfileKeyedAPIFactory<ApiResourceManager<T> >::GetForProfile( 116 profile); 117 } 118 119 // Takes ownership. 120 int Add(T* api_resource) { 121 return data_->Add(api_resource); 122 } 123 124 void Remove(const std::string& extension_id, int api_resource_id) { 125 data_->Remove(extension_id, api_resource_id); 126 } 127 128 T* Get(const std::string& extension_id, int api_resource_id) { 129 return data_->Get(extension_id, api_resource_id); 130 } 131 132 base::hash_set<int>* GetResourceIds(const std::string& extension_id) { 133 return data_->GetResourceIds(extension_id); 134 } 135 136 protected: 137 // content::NotificationObserver: 138 virtual void Observe(int type, 139 const content::NotificationSource& source, 140 const content::NotificationDetails& details) OVERRIDE { 141 switch (type) { 142 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 143 std::string id = 144 content::Details<extensions::UnloadedExtensionInfo>(details)-> 145 extension->id(); 146 data_->InitiateExtensionUnloadedCleanup(id); 147 break; 148 } 149 case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { 150 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); 151 data_->InitiateExtensionSuspendedCleanup(host->extension_id()); 152 break; 153 } 154 } 155 } 156 157 private: 158 friend class api::SerialEventDispatcher; 159 friend class api::TCPServerSocketEventDispatcher; 160 friend class api::TCPSocketEventDispatcher; 161 friend class api::UDPSocketEventDispatcher; 162 friend class ProfileKeyedAPIFactory<ApiResourceManager<T> >; 163 // ProfileKeyedAPI implementation. 164 static const char* service_name() { 165 return T::service_name(); 166 } 167 static const bool kServiceHasOwnInstanceInIncognito = true; 168 static const bool kServiceIsNULLWhileTesting = true; 169 170 // ApiResourceData class handles resource bookkeeping on a thread 171 // where resource lifetime is handled. 172 class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> { 173 public: 174 typedef std::map<int, linked_ptr<T> > ApiResourceMap; 175 // Lookup map from extension id's to allocated resource id's. 176 typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap; 177 178 explicit ApiResourceData(const content::BrowserThread::ID thread_id) 179 : next_id_(1), 180 thread_id_(thread_id) { 181 } 182 183 int Add(T* api_resource) { 184 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 185 int id = GenerateId(); 186 if (id > 0) { 187 linked_ptr<T> resource_ptr(api_resource); 188 api_resource_map_[id] = resource_ptr; 189 190 const std::string& extension_id = api_resource->owner_extension_id(); 191 if (extension_resource_map_.find(extension_id) == 192 extension_resource_map_.end()) { 193 extension_resource_map_[extension_id] = base::hash_set<int>(); 194 } 195 extension_resource_map_[extension_id].insert(id); 196 197 return id; 198 } 199 return 0; 200 } 201 202 void Remove(const std::string& extension_id, int api_resource_id) { 203 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 204 if (GetOwnedResource(extension_id, api_resource_id) != NULL) { 205 DCHECK(extension_resource_map_.find(extension_id) != 206 extension_resource_map_.end()); 207 extension_resource_map_[extension_id].erase(api_resource_id); 208 api_resource_map_.erase(api_resource_id); 209 } 210 } 211 212 T* Get(const std::string& extension_id, int api_resource_id) { 213 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 214 return GetOwnedResource(extension_id, api_resource_id); 215 } 216 217 base::hash_set<int>* GetResourceIds(const std::string& extension_id) { 218 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 219 return GetOwnedResourceIds(extension_id); 220 } 221 222 void InitiateExtensionUnloadedCleanup(const std::string& extension_id) { 223 content::BrowserThread::PostTask(thread_id_, FROM_HERE, 224 base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension, 225 this, extension_id)); 226 } 227 228 void InitiateExtensionSuspendedCleanup(const std::string& extension_id) { 229 content::BrowserThread::PostTask(thread_id_, FROM_HERE, 230 base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension, 231 this, extension_id)); 232 } 233 234 void InititateCleanup() { 235 content::BrowserThread::PostTask(thread_id_, FROM_HERE, 236 base::Bind(&ApiResourceData::Cleanup, this)); 237 } 238 239 private: 240 friend class base::RefCountedThreadSafe<ApiResourceData>; 241 242 virtual ~ApiResourceData() {} 243 244 T* GetOwnedResource(const std::string& extension_id, 245 int api_resource_id) { 246 linked_ptr<T> ptr = api_resource_map_[api_resource_id]; 247 T* resource = ptr.get(); 248 if (resource && extension_id == resource->owner_extension_id()) 249 return resource; 250 return NULL; 251 } 252 253 base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) { 254 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 255 if (extension_resource_map_.find(extension_id) == 256 extension_resource_map_.end()) 257 return NULL; 258 259 return &extension_resource_map_[extension_id]; 260 } 261 262 void CleanupResourcesFromUnloadedExtension( 263 const std::string& extension_id) { 264 CleanupResourcesFromExtension(extension_id, true); 265 } 266 267 void CleanupResourcesFromSuspendedExtension( 268 const std::string& extension_id) { 269 CleanupResourcesFromExtension(extension_id, false); 270 } 271 272 void CleanupResourcesFromExtension(const std::string& extension_id, 273 bool remove_all) { 274 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 275 276 if (extension_resource_map_.find(extension_id) == 277 extension_resource_map_.end()) { 278 return; 279 } 280 281 // Remove all resources, or the non persistent ones only if |remove_all| 282 // is false. 283 base::hash_set<int>& resource_ids = 284 extension_resource_map_[extension_id]; 285 for (base::hash_set<int>::iterator it = resource_ids.begin(); 286 it != resource_ids.end(); ) { 287 bool erase = false; 288 if (remove_all) { 289 erase = true; 290 } else { 291 linked_ptr<T> ptr = api_resource_map_[*it]; 292 T* resource = ptr.get(); 293 erase = (resource && !resource->IsPersistent()); 294 } 295 296 if (erase) { 297 api_resource_map_.erase(*it); 298 resource_ids.erase(it++); 299 } else { 300 ++it; 301 } 302 } // end for 303 304 // Remove extension entry if we removed all its resources. 305 if (resource_ids.size() == 0) { 306 extension_resource_map_.erase(extension_id); 307 } 308 } 309 310 void Cleanup() { 311 DCHECK(content::BrowserThread::CurrentlyOn(thread_id_)); 312 313 api_resource_map_.clear(); 314 extension_resource_map_.clear(); 315 } 316 317 int GenerateId() { 318 return next_id_++; 319 } 320 321 int next_id_; 322 const content::BrowserThread::ID thread_id_; 323 ApiResourceMap api_resource_map_; 324 ExtensionToResourceMap extension_resource_map_; 325 }; 326 327 content::BrowserThread::ID thread_id_; 328 content::NotificationRegistrar registrar_; 329 scoped_refptr<ApiResourceData> data_; 330 }; 331 332 } // namespace extensions 333 334 #endif // CHROME_BROWSER_EXTENSIONS_API_API_RESOURCE_MANAGER_H_ 335