api_resource_manager.h revision 116680a4aac90f2aa7413d9095a592090648e557
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 BluetoothSocketApiFunction; 29class BluetoothSocketEventDispatcher; 30} 31 32namespace core_api { 33class SerialEventDispatcher; 34class TCPServerSocketEventDispatcher; 35class TCPSocketEventDispatcher; 36class UDPSocketEventDispatcher; 37} 38 39// An ApiResourceManager manages the lifetime of a set of resources that 40// ApiFunctions use. Examples are sockets or USB connections. 41// 42// Users of this class should define kThreadId to be the thread that 43// ApiResourceManager to works on. The default is defined in ApiResource. 44// The user must also define a static const char* service_name() that returns 45// the name of the service, and in order for ApiResourceManager to use 46// service_name() friend this class. 47// 48// In the cc file the user must define a GetFactoryInstance() and manage their 49// own instances (typically using LazyInstance or Singleton). 50// 51// E.g.: 52// 53// class Resource { 54// public: 55// static const BrowserThread::ID kThreadId = BrowserThread::FILE; 56// private: 57// friend class ApiResourceManager<Resource>; 58// static const char* service_name() { 59// return "ResourceManager"; 60// } 61// }; 62// 63// In the cc file: 64// 65// static base::LazyInstance<BrowserContextKeyedAPIFactory< 66// ApiResourceManager<Resource> > > 67// g_factory = LAZY_INSTANCE_INITIALIZER; 68// 69// 70// template <> 71// BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >* 72// ApiResourceManager<Resource>::GetFactoryInstance() { 73// return g_factory.Pointer(); 74// } 75template <class T> 76class ApiResourceManager : public BrowserContextKeyedAPI, 77 public base::NonThreadSafe, 78 public content::NotificationObserver { 79 public: 80 explicit ApiResourceManager(content::BrowserContext* context) 81 : thread_id_(T::kThreadId), data_(new ApiResourceData(thread_id_)) { 82 registrar_.Add(this, 83 chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, 84 content::NotificationService::AllSources()); 85 registrar_.Add(this, 86 chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, 87 content::NotificationService::AllSources()); 88 } 89 90 // For Testing. 91 static ApiResourceManager<T>* CreateApiResourceManagerForTest( 92 content::BrowserContext* context, 93 content::BrowserThread::ID thread_id) { 94 ApiResourceManager* manager = new ApiResourceManager<T>(context); 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 // BrowserContextKeyedAPI implementation. 111 static BrowserContextKeyedAPIFactory<ApiResourceManager<T> >* 112 GetFactoryInstance(); 113 114 // Convenience method to get the ApiResourceManager for a profile. 115 static ApiResourceManager<T>* Get(content::BrowserContext* context) { 116 return BrowserContextKeyedAPIFactory<ApiResourceManager<T> >::Get(context); 117 } 118 119 // Takes ownership. 120 int Add(T* api_resource) { return data_->Add(api_resource); } 121 122 void Remove(const std::string& extension_id, int api_resource_id) { 123 data_->Remove(extension_id, api_resource_id); 124 } 125 126 T* Get(const std::string& extension_id, int api_resource_id) { 127 return data_->Get(extension_id, api_resource_id); 128 } 129 130 base::hash_set<int>* GetResourceIds(const std::string& extension_id) { 131 return data_->GetResourceIds(extension_id); 132 } 133 134 protected: 135 // content::NotificationObserver: 136 virtual void Observe(int type, 137 const content::NotificationSource& source, 138 const content::NotificationDetails& details) OVERRIDE { 139 switch (type) { 140 case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { 141 std::string id = content::Details<extensions::UnloadedExtensionInfo>( 142 details)->extension->id(); 143 data_->InitiateExtensionUnloadedCleanup(id); 144 break; 145 } 146 case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { 147 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); 148 data_->InitiateExtensionSuspendedCleanup(host->extension_id()); 149 break; 150 } 151 } 152 } 153 154 private: 155 // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and 156 // we could avoid maintaining a friends list here. 157 friend class BluetoothAPI; 158 friend class api::BluetoothSocketApiFunction; 159 friend class api::BluetoothSocketEventDispatcher; 160 friend class core_api::SerialEventDispatcher; 161 friend class core_api::TCPServerSocketEventDispatcher; 162 friend class core_api::TCPSocketEventDispatcher; 163 friend class core_api::UDPSocketEventDispatcher; 164 friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T> >; 165 166 // BrowserContextKeyedAPI implementation. 167 static const char* service_name() { return T::service_name(); } 168 169 static const bool kServiceHasOwnInstanceInIncognito = true; 170 static const bool kServiceIsNULLWhileTesting = true; 171 172 // ApiResourceData class handles resource bookkeeping on a thread 173 // where resource lifetime is handled. 174 class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> { 175 public: 176 typedef std::map<int, linked_ptr<T> > ApiResourceMap; 177 // Lookup map from extension id's to allocated resource id's. 178 typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap; 179 180 explicit ApiResourceData(const content::BrowserThread::ID thread_id) 181 : next_id_(1), thread_id_(thread_id) {} 182 183 int Add(T* api_resource) { 184 DCHECK_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(thread_id_); 219 return GetOwnedResourceIds(extension_id); 220 } 221 222 void InitiateExtensionUnloadedCleanup(const std::string& extension_id) { 223 if (content::BrowserThread::CurrentlyOn(thread_id_)) { 224 CleanupResourcesFromUnloadedExtension(extension_id); 225 } else { 226 content::BrowserThread::PostTask( 227 thread_id_, 228 FROM_HERE, 229 base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension, 230 this, 231 extension_id)); 232 } 233 } 234 235 void InitiateExtensionSuspendedCleanup(const std::string& extension_id) { 236 if (content::BrowserThread::CurrentlyOn(thread_id_)) { 237 CleanupResourcesFromSuspendedExtension(extension_id); 238 } else { 239 content::BrowserThread::PostTask( 240 thread_id_, 241 FROM_HERE, 242 base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension, 243 this, 244 extension_id)); 245 } 246 } 247 248 void InititateCleanup() { 249 if (content::BrowserThread::CurrentlyOn(thread_id_)) { 250 Cleanup(); 251 } else { 252 content::BrowserThread::PostTask( 253 thread_id_, FROM_HERE, base::Bind(&ApiResourceData::Cleanup, this)); 254 } 255 } 256 257 private: 258 friend class base::RefCountedThreadSafe<ApiResourceData>; 259 260 virtual ~ApiResourceData() {} 261 262 T* GetOwnedResource(const std::string& extension_id, int api_resource_id) { 263 linked_ptr<T> ptr = api_resource_map_[api_resource_id]; 264 T* resource = ptr.get(); 265 if (resource && extension_id == resource->owner_extension_id()) 266 return resource; 267 return NULL; 268 } 269 270 base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) { 271 DCHECK_CURRENTLY_ON(thread_id_); 272 if (extension_resource_map_.find(extension_id) == 273 extension_resource_map_.end()) 274 return NULL; 275 276 return &extension_resource_map_[extension_id]; 277 } 278 279 void CleanupResourcesFromUnloadedExtension( 280 const std::string& extension_id) { 281 CleanupResourcesFromExtension(extension_id, true); 282 } 283 284 void CleanupResourcesFromSuspendedExtension( 285 const std::string& extension_id) { 286 CleanupResourcesFromExtension(extension_id, false); 287 } 288 289 void CleanupResourcesFromExtension(const std::string& extension_id, 290 bool remove_all) { 291 DCHECK_CURRENTLY_ON(thread_id_); 292 293 if (extension_resource_map_.find(extension_id) == 294 extension_resource_map_.end()) { 295 return; 296 } 297 298 // Remove all resources, or the non persistent ones only if |remove_all| 299 // is false. 300 base::hash_set<int>& resource_ids = extension_resource_map_[extension_id]; 301 for (base::hash_set<int>::iterator it = resource_ids.begin(); 302 it != resource_ids.end();) { 303 bool erase = false; 304 if (remove_all) { 305 erase = true; 306 } else { 307 linked_ptr<T> ptr = api_resource_map_[*it]; 308 T* resource = ptr.get(); 309 erase = (resource && !resource->IsPersistent()); 310 } 311 312 if (erase) { 313 api_resource_map_.erase(*it); 314 resource_ids.erase(it++); 315 } else { 316 ++it; 317 } 318 } // end for 319 320 // Remove extension entry if we removed all its resources. 321 if (resource_ids.size() == 0) { 322 extension_resource_map_.erase(extension_id); 323 } 324 } 325 326 void Cleanup() { 327 DCHECK_CURRENTLY_ON(thread_id_); 328 329 api_resource_map_.clear(); 330 extension_resource_map_.clear(); 331 } 332 333 int GenerateId() { return next_id_++; } 334 335 int next_id_; 336 const content::BrowserThread::ID thread_id_; 337 ApiResourceMap api_resource_map_; 338 ExtensionToResourceMap extension_resource_map_; 339 }; 340 341 content::BrowserThread::ID thread_id_; 342 content::NotificationRegistrar registrar_; 343 scoped_refptr<ApiResourceData> data_; 344}; 345 346} // namespace extensions 347 348#endif // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_ 349