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