Home | History | Annotate | Download | only in api
      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