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