• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
ApiResourceManager(Profile * profile)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.
CreateApiResourceManagerForTest(Profile * profile,content::BrowserThread::ID thread_id)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 
~ApiResourceManager()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.
Get(Profile * profile)114   static ApiResourceManager<T>* Get(Profile* profile) {
115     return ProfileKeyedAPIFactory<ApiResourceManager<T> >::GetForProfile(
116         profile);
117   }
118 
119   // Takes ownership.
Add(T * api_resource)120   int Add(T* api_resource) {
121     return data_->Add(api_resource);
122   }
123 
Remove(const std::string & extension_id,int api_resource_id)124   void Remove(const std::string& extension_id, int api_resource_id) {
125     data_->Remove(extension_id, api_resource_id);
126   }
127 
Get(const std::string & extension_id,int api_resource_id)128   T* Get(const std::string& extension_id, int api_resource_id) {
129     return data_->Get(extension_id, api_resource_id);
130   }
131 
GetResourceIds(const std::string & extension_id)132   base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
133     return data_->GetResourceIds(extension_id);
134   }
135 
136  protected:
137   // content::NotificationObserver:
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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.
service_name()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 
ApiResourceData(const content::BrowserThread::ID thread_id)178     explicit ApiResourceData(const content::BrowserThread::ID thread_id)
179         : next_id_(1),
180           thread_id_(thread_id) {
181     }
182 
Add(T * api_resource)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 
Remove(const std::string & extension_id,int api_resource_id)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 
Get(const std::string & extension_id,int api_resource_id)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 
GetResourceIds(const std::string & extension_id)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 
InitiateExtensionUnloadedCleanup(const std::string & extension_id)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 
InitiateExtensionSuspendedCleanup(const std::string & extension_id)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 
InititateCleanup()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 
~ApiResourceData()242     virtual ~ApiResourceData() {}
243 
GetOwnedResource(const std::string & extension_id,int api_resource_id)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 
GetOwnedResourceIds(const std::string & extension_id)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 
CleanupResourcesFromUnloadedExtension(const std::string & extension_id)262     void CleanupResourcesFromUnloadedExtension(
263         const std::string& extension_id) {
264       CleanupResourcesFromExtension(extension_id, true);
265     }
266 
CleanupResourcesFromSuspendedExtension(const std::string & extension_id)267     void CleanupResourcesFromSuspendedExtension(
268         const std::string& extension_id) {
269       CleanupResourcesFromExtension(extension_id, false);
270     }
271 
CleanupResourcesFromExtension(const std::string & extension_id,bool remove_all)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 
Cleanup()310     void Cleanup() {
311       DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
312 
313       api_resource_map_.clear();
314       extension_resource_map_.clear();
315     }
316 
GenerateId()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