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/memory/linked_ptr.h" 12 #include "base/memory/ref_counted.h" 13 #include "base/scoped_observer.h" 14 #include "base/threading/non_thread_safe.h" 15 #include "components/keyed_service/core/keyed_service.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/browser/notification_observer.h" 18 #include "content/public/browser/notification_registrar.h" 19 #include "content/public/browser/notification_service.h" 20 #include "extensions/browser/browser_context_keyed_api_factory.h" 21 #include "extensions/browser/extension_host.h" 22 #include "extensions/browser/extension_registry.h" 23 #include "extensions/browser/extension_registry_observer.h" 24 #include "extensions/browser/notification_types.h" 25 #include "extensions/common/extension.h" 26 27 namespace extensions { 28 29 namespace core_api { 30 class BluetoothSocketApiFunction; 31 class BluetoothSocketEventDispatcher; 32 class SerialEventDispatcher; 33 class TCPServerSocketEventDispatcher; 34 class TCPSocketEventDispatcher; 35 class UDPSocketEventDispatcher; 36 } 37 38 template <typename T> 39 struct NamedThreadTraits { 40 static bool IsCalledOnValidThread() { 41 return content::BrowserThread::CurrentlyOn(T::kThreadId); 42 } 43 44 static bool IsMessageLoopValid() { 45 return content::BrowserThread::IsMessageLoopValid(T::kThreadId); 46 } 47 48 static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { 49 return content::BrowserThread::GetMessageLoopProxyForThread(T::kThreadId); 50 } 51 }; 52 53 template <typename T> 54 struct TestThreadTraits { 55 static bool IsCalledOnValidThread() { 56 return content::BrowserThread::CurrentlyOn(thread_id_); 57 } 58 59 static bool IsMessageLoopValid() { 60 return content::BrowserThread::IsMessageLoopValid(thread_id_); 61 } 62 63 static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { 64 return content::BrowserThread::GetMessageLoopProxyForThread(thread_id_); 65 } 66 67 static content::BrowserThread::ID thread_id_; 68 }; 69 70 template <typename T> 71 content::BrowserThread::ID TestThreadTraits<T>::thread_id_ = 72 content::BrowserThread::IO; 73 74 // An ApiResourceManager manages the lifetime of a set of resources that 75 // that live on named threads (i.e. BrowserThread::IO) which ApiFunctions use. 76 // Examples of such resources are sockets or USB connections. 77 // 78 // Users of this class should define kThreadId to be the thread that 79 // ApiResourceManager to works on. The default is defined in ApiResource. 80 // The user must also define a static const char* service_name() that returns 81 // the name of the service, and in order for ApiResourceManager to use 82 // service_name() friend this class. 83 // 84 // In the cc file the user must define a GetFactoryInstance() and manage their 85 // own instances (typically using LazyInstance or Singleton). 86 // 87 // E.g.: 88 // 89 // class Resource { 90 // public: 91 // static const BrowserThread::ID kThreadId = BrowserThread::FILE; 92 // private: 93 // friend class ApiResourceManager<Resource>; 94 // static const char* service_name() { 95 // return "ResourceManager"; 96 // } 97 // }; 98 // 99 // In the cc file: 100 // 101 // static base::LazyInstance<BrowserContextKeyedAPIFactory< 102 // ApiResourceManager<Resource> > > 103 // g_factory = LAZY_INSTANCE_INITIALIZER; 104 // 105 // 106 // template <> 107 // BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >* 108 // ApiResourceManager<Resource>::GetFactoryInstance() { 109 // return g_factory.Pointer(); 110 // } 111 template <class T, typename ThreadingTraits = NamedThreadTraits<T> > 112 class ApiResourceManager : public BrowserContextKeyedAPI, 113 public base::NonThreadSafe, 114 public content::NotificationObserver, 115 public ExtensionRegistryObserver { 116 public: 117 explicit ApiResourceManager(content::BrowserContext* context) 118 : data_(new ApiResourceData()), extension_registry_observer_(this) { 119 extension_registry_observer_.Add(ExtensionRegistry::Get(context)); 120 registrar_.Add(this, 121 extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, 122 content::NotificationService::AllSources()); 123 } 124 // For Testing. 125 static ApiResourceManager<T, TestThreadTraits<T> >* 126 CreateApiResourceManagerForTest(content::BrowserContext* context, 127 content::BrowserThread::ID thread_id) { 128 TestThreadTraits<T>::thread_id_ = thread_id; 129 ApiResourceManager<T, TestThreadTraits<T> >* manager = 130 new ApiResourceManager<T, TestThreadTraits<T> >(context); 131 return manager; 132 } 133 134 virtual ~ApiResourceManager() { 135 DCHECK(CalledOnValidThread()); 136 DCHECK(ThreadingTraits::IsMessageLoopValid()) 137 << "A unit test is using an ApiResourceManager but didn't provide " 138 "the thread message loop needed for that kind of resource. " 139 "Please ensure that the appropriate message loop is operational."; 140 141 data_->InititateCleanup(); 142 } 143 144 // Takes ownership. 145 int Add(T* api_resource) { return data_->Add(api_resource); } 146 147 void Remove(const std::string& extension_id, int api_resource_id) { 148 data_->Remove(extension_id, api_resource_id); 149 } 150 151 T* Get(const std::string& extension_id, int api_resource_id) { 152 return data_->Get(extension_id, api_resource_id); 153 } 154 155 base::hash_set<int>* GetResourceIds(const std::string& extension_id) { 156 return data_->GetResourceIds(extension_id); 157 } 158 159 // BrowserContextKeyedAPI implementation. 160 static BrowserContextKeyedAPIFactory<ApiResourceManager<T> >* 161 GetFactoryInstance(); 162 163 // Convenience method to get the ApiResourceManager for a profile. 164 static ApiResourceManager<T>* Get(content::BrowserContext* context) { 165 return BrowserContextKeyedAPIFactory<ApiResourceManager<T> >::Get(context); 166 } 167 168 // BrowserContextKeyedAPI implementation. 169 static const char* service_name() { return T::service_name(); } 170 171 // Change the resource mapped to this |extension_id| at this 172 // |api_resource_id| to |resource|. Returns true and succeeds unless 173 // |api_resource_id| does not already identify a resource held by 174 // |extension_id|. 175 bool Replace(const std::string& extension_id, 176 int api_resource_id, 177 T* resource) { 178 return data_->Replace(extension_id, api_resource_id, resource); 179 } 180 181 protected: 182 // content::NotificationObserver: 183 virtual void Observe(int type, 184 const content::NotificationSource& source, 185 const content::NotificationDetails& details) OVERRIDE { 186 DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, type); 187 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); 188 data_->InitiateExtensionSuspendedCleanup(host->extension_id()); 189 } 190 191 // ExtensionRegistryObserver: 192 virtual void OnExtensionUnloaded( 193 content::BrowserContext* browser_context, 194 const Extension* extension, 195 UnloadedExtensionInfo::Reason reason) OVERRIDE { 196 data_->InitiateExtensionUnloadedCleanup(extension->id()); 197 } 198 199 private: 200 // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and 201 // we could avoid maintaining a friends list here. 202 friend class BluetoothAPI; 203 friend class core_api::BluetoothSocketApiFunction; 204 friend class core_api::BluetoothSocketEventDispatcher; 205 friend class core_api::SerialEventDispatcher; 206 friend class core_api::TCPServerSocketEventDispatcher; 207 friend class core_api::TCPSocketEventDispatcher; 208 friend class core_api::UDPSocketEventDispatcher; 209 friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T> >; 210 211 static const bool kServiceHasOwnInstanceInIncognito = true; 212 static const bool kServiceIsNULLWhileTesting = true; 213 214 // ApiResourceData class handles resource bookkeeping on a thread 215 // where resource lifetime is handled. 216 class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> { 217 public: 218 typedef std::map<int, linked_ptr<T> > ApiResourceMap; 219 // Lookup map from extension id's to allocated resource id's. 220 typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap; 221 222 ApiResourceData() : next_id_(1) {} 223 224 int Add(T* api_resource) { 225 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 226 int id = GenerateId(); 227 if (id > 0) { 228 linked_ptr<T> resource_ptr(api_resource); 229 api_resource_map_[id] = resource_ptr; 230 231 const std::string& extension_id = api_resource->owner_extension_id(); 232 ExtensionToResourceMap::iterator it = 233 extension_resource_map_.find(extension_id); 234 if (it == extension_resource_map_.end()) { 235 it = extension_resource_map_.insert( 236 std::make_pair(extension_id, base::hash_set<int>())).first; 237 } 238 it->second.insert(id); 239 return id; 240 } 241 return 0; 242 } 243 244 void Remove(const std::string& extension_id, int api_resource_id) { 245 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 246 if (GetOwnedResource(extension_id, api_resource_id)) { 247 ExtensionToResourceMap::iterator it = 248 extension_resource_map_.find(extension_id); 249 it->second.erase(api_resource_id); 250 api_resource_map_.erase(api_resource_id); 251 } 252 } 253 254 T* Get(const std::string& extension_id, int api_resource_id) { 255 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 256 return GetOwnedResource(extension_id, api_resource_id); 257 } 258 259 // Change the resource mapped to this |extension_id| at this 260 // |api_resource_id| to |resource|. Returns true and succeeds unless 261 // |api_resource_id| does not already identify a resource held by 262 // |extension_id|. 263 bool Replace(const std::string& extension_id, 264 int api_resource_id, 265 T* api_resource) { 266 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 267 T* old_resource = api_resource_map_[api_resource_id].get(); 268 if (old_resource && extension_id == old_resource->owner_extension_id()) { 269 api_resource_map_[api_resource_id] = linked_ptr<T>(api_resource); 270 return true; 271 } 272 return false; 273 } 274 275 base::hash_set<int>* GetResourceIds(const std::string& extension_id) { 276 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 277 return GetOwnedResourceIds(extension_id); 278 } 279 280 void InitiateExtensionUnloadedCleanup(const std::string& extension_id) { 281 if (ThreadingTraits::IsCalledOnValidThread()) { 282 CleanupResourcesFromUnloadedExtension(extension_id); 283 } else { 284 ThreadingTraits::GetSequencedTaskRunner()->PostTask( 285 FROM_HERE, 286 base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension, 287 this, 288 extension_id)); 289 } 290 } 291 292 void InitiateExtensionSuspendedCleanup(const std::string& extension_id) { 293 if (ThreadingTraits::IsCalledOnValidThread()) { 294 CleanupResourcesFromSuspendedExtension(extension_id); 295 } else { 296 ThreadingTraits::GetSequencedTaskRunner()->PostTask( 297 FROM_HERE, 298 base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension, 299 this, 300 extension_id)); 301 } 302 } 303 304 void InititateCleanup() { 305 if (ThreadingTraits::IsCalledOnValidThread()) { 306 Cleanup(); 307 } else { 308 ThreadingTraits::GetSequencedTaskRunner()->PostTask( 309 FROM_HERE, base::Bind(&ApiResourceData::Cleanup, this)); 310 } 311 } 312 313 private: 314 friend class base::RefCountedThreadSafe<ApiResourceData>; 315 316 virtual ~ApiResourceData() {} 317 318 T* GetOwnedResource(const std::string& extension_id, int api_resource_id) { 319 linked_ptr<T> ptr = api_resource_map_[api_resource_id]; 320 T* resource = ptr.get(); 321 if (resource && extension_id == resource->owner_extension_id()) 322 return resource; 323 return NULL; 324 } 325 326 base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) { 327 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 328 ExtensionToResourceMap::iterator it = 329 extension_resource_map_.find(extension_id); 330 if (it == extension_resource_map_.end()) 331 return NULL; 332 return &(it->second); 333 } 334 335 void CleanupResourcesFromUnloadedExtension( 336 const std::string& extension_id) { 337 CleanupResourcesFromExtension(extension_id, true); 338 } 339 340 void CleanupResourcesFromSuspendedExtension( 341 const std::string& extension_id) { 342 CleanupResourcesFromExtension(extension_id, false); 343 } 344 345 void CleanupResourcesFromExtension(const std::string& extension_id, 346 bool remove_all) { 347 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 348 349 ExtensionToResourceMap::iterator it = 350 extension_resource_map_.find(extension_id); 351 if (it == extension_resource_map_.end()) 352 return; 353 354 // Remove all resources, or the non persistent ones only if |remove_all| 355 // is false. 356 base::hash_set<int>& resource_ids = it->second; 357 for (base::hash_set<int>::iterator it = resource_ids.begin(); 358 it != resource_ids.end();) { 359 bool erase = false; 360 if (remove_all) { 361 erase = true; 362 } else { 363 linked_ptr<T> ptr = api_resource_map_[*it]; 364 T* resource = ptr.get(); 365 erase = (resource && !resource->IsPersistent()); 366 } 367 368 if (erase) { 369 api_resource_map_.erase(*it); 370 resource_ids.erase(it++); 371 } else { 372 ++it; 373 } 374 } // end for 375 376 // Remove extension entry if we removed all its resources. 377 if (resource_ids.size() == 0) { 378 extension_resource_map_.erase(extension_id); 379 } 380 } 381 382 void Cleanup() { 383 DCHECK(ThreadingTraits::IsCalledOnValidThread()); 384 385 api_resource_map_.clear(); 386 extension_resource_map_.clear(); 387 } 388 389 int GenerateId() { return next_id_++; } 390 391 int next_id_; 392 ApiResourceMap api_resource_map_; 393 ExtensionToResourceMap extension_resource_map_; 394 }; 395 396 content::NotificationRegistrar registrar_; 397 scoped_refptr<ApiResourceData> data_; 398 399 ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> 400 extension_registry_observer_; 401 }; 402 403 // With WorkerPoolThreadTraits, ApiResourceManager can be used to manage the 404 // lifetime of a set of resources that live on sequenced task runner threads 405 // which ApiFunctions use. Examples of such resources are temporary file 406 // resources produced by certain API calls. 407 // 408 // Instead of kThreadId. classes used for tracking such resources should define 409 // kSequenceToken and kShutdownBehavior to identify sequence task runner for 410 // ApiResourceManager to work on and how pending tasks should behave on 411 // shutdown. 412 // The user must also define a static const char* service_name() that returns 413 // the name of the service, and in order for ApiWorkerPoolResourceManager to use 414 // service_name() friend this class. 415 // 416 // In the cc file the user must define a GetFactoryInstance() and manage their 417 // own instances (typically using LazyInstance or Singleton). 418 // 419 // E.g.: 420 // 421 // class PoolResource { 422 // public: 423 // static const char kSequenceToken[] = "temp_files"; 424 // static const base::SequencedWorkerPool::WorkerShutdown kShutdownBehavior = 425 // base::SequencedWorkerPool::BLOCK_SHUTDOWN; 426 // private: 427 // friend class ApiResourceManager<WorkerPoolResource, 428 // WorkerPoolThreadTraits>; 429 // static const char* service_name() { 430 // return "TempFilesResourceManager"; 431 // } 432 // }; 433 // 434 // In the cc file: 435 // 436 // static base::LazyInstance<BrowserContextKeyedAPIFactory< 437 // ApiResourceManager<Resource, WorkerPoolThreadTraits> > > 438 // g_factory = LAZY_INSTANCE_INITIALIZER; 439 // 440 // 441 // template <> 442 // BrowserContextKeyedAPIFactory<ApiResourceManager<WorkerPoolResource> >* 443 // ApiResourceManager<WorkerPoolPoolResource, 444 // WorkerPoolThreadTraits>::GetFactoryInstance() { 445 // return g_factory.Pointer(); 446 // } 447 template <typename T> 448 struct WorkerPoolThreadTraits { 449 static bool IsCalledOnValidThread() { 450 return content::BrowserThread::GetBlockingPool() 451 ->IsRunningSequenceOnCurrentThread( 452 content::BrowserThread::GetBlockingPool()->GetNamedSequenceToken( 453 T::kSequenceToken)); 454 } 455 456 static bool IsMessageLoopValid() { 457 return content::BrowserThread::GetBlockingPool() != NULL; 458 } 459 460 static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { 461 return content::BrowserThread::GetBlockingPool() 462 ->GetSequencedTaskRunnerWithShutdownBehavior( 463 content::BrowserThread::GetBlockingPool()->GetNamedSequenceToken( 464 T::kSequenceToken), 465 T::kShutdownBehavior); 466 } 467 }; 468 469 } // namespace extensions 470 471 #endif // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_ 472