• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
IsCalledOnValidThreadNamedThreadTraits40   static bool IsCalledOnValidThread() {
41     return content::BrowserThread::CurrentlyOn(T::kThreadId);
42   }
43 
IsMessageLoopValidNamedThreadTraits44   static bool IsMessageLoopValid() {
45     return content::BrowserThread::IsMessageLoopValid(T::kThreadId);
46   }
47 
GetSequencedTaskRunnerNamedThreadTraits48   static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() {
49     return content::BrowserThread::GetMessageLoopProxyForThread(T::kThreadId);
50   }
51 };
52 
53 template <typename T>
54 struct TestThreadTraits {
IsCalledOnValidThreadTestThreadTraits55   static bool IsCalledOnValidThread() {
56     return content::BrowserThread::CurrentlyOn(thread_id_);
57   }
58 
IsMessageLoopValidTestThreadTraits59   static bool IsMessageLoopValid() {
60     return content::BrowserThread::IsMessageLoopValid(thread_id_);
61   }
62 
GetSequencedTaskRunnerTestThreadTraits63   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:
ApiResourceManager(content::BrowserContext * context)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> >*
CreateApiResourceManagerForTest(content::BrowserContext * context,content::BrowserThread::ID thread_id)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 
~ApiResourceManager()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.
Add(T * api_resource)145   int Add(T* api_resource) { return data_->Add(api_resource); }
146 
Remove(const std::string & extension_id,int api_resource_id)147   void Remove(const std::string& extension_id, int api_resource_id) {
148     data_->Remove(extension_id, api_resource_id);
149   }
150 
Get(const std::string & extension_id,int api_resource_id)151   T* Get(const std::string& extension_id, int api_resource_id) {
152     return data_->Get(extension_id, api_resource_id);
153   }
154 
GetResourceIds(const std::string & extension_id)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.
Get(content::BrowserContext * context)164   static ApiResourceManager<T>* Get(content::BrowserContext* context) {
165     return BrowserContextKeyedAPIFactory<ApiResourceManager<T> >::Get(context);
166   }
167 
168   // BrowserContextKeyedAPI implementation.
service_name()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|.
Replace(const std::string & extension_id,int api_resource_id,T * resource)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:
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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:
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)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 
ApiResourceData()222     ApiResourceData() : next_id_(1) {}
223 
Add(T * api_resource)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 
Remove(const std::string & extension_id,int api_resource_id)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 
Get(const std::string & extension_id,int api_resource_id)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|.
Replace(const std::string & extension_id,int api_resource_id,T * api_resource)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 
GetResourceIds(const std::string & extension_id)275     base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
276       DCHECK(ThreadingTraits::IsCalledOnValidThread());
277       return GetOwnedResourceIds(extension_id);
278     }
279 
InitiateExtensionUnloadedCleanup(const std::string & extension_id)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 
InitiateExtensionSuspendedCleanup(const std::string & extension_id)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 
InititateCleanup()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 
~ApiResourceData()316     virtual ~ApiResourceData() {}
317 
GetOwnedResource(const std::string & extension_id,int api_resource_id)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 
GetOwnedResourceIds(const std::string & extension_id)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 
CleanupResourcesFromUnloadedExtension(const std::string & extension_id)335     void CleanupResourcesFromUnloadedExtension(
336         const std::string& extension_id) {
337       CleanupResourcesFromExtension(extension_id, true);
338     }
339 
CleanupResourcesFromSuspendedExtension(const std::string & extension_id)340     void CleanupResourcesFromSuspendedExtension(
341         const std::string& extension_id) {
342       CleanupResourcesFromExtension(extension_id, false);
343     }
344 
CleanupResourcesFromExtension(const std::string & extension_id,bool remove_all)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 
Cleanup()382     void Cleanup() {
383       DCHECK(ThreadingTraits::IsCalledOnValidThread());
384 
385       api_resource_map_.clear();
386       extension_resource_map_.clear();
387     }
388 
GenerateId()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 {
IsCalledOnValidThreadWorkerPoolThreadTraits449   static bool IsCalledOnValidThread() {
450     return content::BrowserThread::GetBlockingPool()
451         ->IsRunningSequenceOnCurrentThread(
452             content::BrowserThread::GetBlockingPool()->GetNamedSequenceToken(
453                 T::kSequenceToken));
454   }
455 
IsMessageLoopValidWorkerPoolThreadTraits456   static bool IsMessageLoopValid() {
457     return content::BrowserThread::GetBlockingPool() != NULL;
458   }
459 
GetSequencedTaskRunnerWorkerPoolThreadTraits460   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