api_resource_manager.h revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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/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 "components/keyed_service/core/keyed_service.h"
17#include "content/public/browser/browser_thread.h"
18#include "content/public/browser/notification_observer.h"
19#include "content/public/browser/notification_registrar.h"
20#include "content/public/browser/notification_service.h"
21#include "extensions/browser/browser_context_keyed_api_factory.h"
22#include "extensions/browser/extension_host.h"
23#include "extensions/common/extension.h"
24
25namespace extensions {
26
27namespace api {
28class SerialEventDispatcher;
29}
30
31namespace core_api {
32class TCPServerSocketEventDispatcher;
33class TCPSocketEventDispatcher;
34class UDPSocketEventDispatcher;
35}
36
37// An ApiResourceManager manages the lifetime of a set of resources that
38// ApiFunctions use. Examples are sockets or USB connections.
39//
40// Users of this class should define kThreadId to be the thread that
41// ApiResourceManager to works on. The default is defined in ApiResource.
42// The user must also define a static const char* service_name() that returns
43// the name of the service, and in order for ApiResourceManager to use
44// service_name() friend this class.
45//
46// In the cc file the user must define a GetFactoryInstance() and manage their
47// own instances (typically using LazyInstance or Singleton).
48//
49// E.g.:
50//
51// class Resource {
52//  public:
53//   static const BrowserThread::ID kThreadId = BrowserThread::FILE;
54//  private:
55//   friend class ApiResourceManager<Resource>;
56//   static const char* service_name() {
57//     return "ResourceManager";
58//    }
59// };
60//
61// In the cc file:
62//
63// static base::LazyInstance<BrowserContextKeyedAPIFactory<
64//     ApiResourceManager<Resource> > >
65//         g_factory = LAZY_INSTANCE_INITIALIZER;
66//
67//
68// template <>
69// BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >*
70// ApiResourceManager<Resource>::GetFactoryInstance() {
71//   return g_factory.Pointer();
72// }
73template <class T>
74class ApiResourceManager : public BrowserContextKeyedAPI,
75                           public base::NonThreadSafe,
76                           public content::NotificationObserver {
77 public:
78  explicit ApiResourceManager(content::BrowserContext* context)
79      : thread_id_(T::kThreadId), data_(new ApiResourceData(thread_id_)) {
80    registrar_.Add(this,
81                   chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
82                   content::NotificationService::AllSources());
83    registrar_.Add(this,
84                   chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
85                   content::NotificationService::AllSources());
86  }
87
88  // For Testing.
89  static ApiResourceManager<T>* CreateApiResourceManagerForTest(
90      content::BrowserContext* context,
91      content::BrowserThread::ID thread_id) {
92    ApiResourceManager* manager = new ApiResourceManager<T>(context);
93    manager->thread_id_ = thread_id;
94    manager->data_ = new ApiResourceData(thread_id);
95    return manager;
96  }
97
98  virtual ~ApiResourceManager() {
99    DCHECK(CalledOnValidThread());
100    DCHECK(content::BrowserThread::IsMessageLoopValid(thread_id_))
101        << "A unit test is using an ApiResourceManager but didn't provide "
102           "the thread message loop needed for that kind of resource. "
103           "Please ensure that the appropriate message loop is operational.";
104
105    data_->InititateCleanup();
106  }
107
108  // BrowserContextKeyedAPI implementation.
109  static BrowserContextKeyedAPIFactory<ApiResourceManager<T> >*
110      GetFactoryInstance();
111
112  // Convenience method to get the ApiResourceManager for a profile.
113  static ApiResourceManager<T>* Get(content::BrowserContext* context) {
114    return BrowserContextKeyedAPIFactory<ApiResourceManager<T> >::Get(context);
115  }
116
117  // Takes ownership.
118  int Add(T* api_resource) { return data_->Add(api_resource); }
119
120  void Remove(const std::string& extension_id, int api_resource_id) {
121    data_->Remove(extension_id, api_resource_id);
122  }
123
124  T* Get(const std::string& extension_id, int api_resource_id) {
125    return data_->Get(extension_id, api_resource_id);
126  }
127
128  base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
129    return data_->GetResourceIds(extension_id);
130  }
131
132 protected:
133  // content::NotificationObserver:
134  virtual void Observe(int type,
135                       const content::NotificationSource& source,
136                       const content::NotificationDetails& details) OVERRIDE {
137    switch (type) {
138      case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
139        std::string id = content::Details<extensions::UnloadedExtensionInfo>(
140                             details)->extension->id();
141        data_->InitiateExtensionUnloadedCleanup(id);
142        break;
143      }
144      case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
145        ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
146        data_->InitiateExtensionSuspendedCleanup(host->extension_id());
147        break;
148      }
149    }
150  }
151
152 private:
153  // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and
154  // we could avoid maintaining a friends list here.
155  friend class api::SerialEventDispatcher;
156  friend class core_api::TCPServerSocketEventDispatcher;
157  friend class core_api::TCPSocketEventDispatcher;
158  friend class core_api::UDPSocketEventDispatcher;
159  friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T> >;
160
161  // BrowserContextKeyedAPI implementation.
162  static const char* service_name() { return T::service_name(); }
163
164  static const bool kServiceHasOwnInstanceInIncognito = true;
165  static const bool kServiceIsNULLWhileTesting = true;
166
167  // ApiResourceData class handles resource bookkeeping on a thread
168  // where resource lifetime is handled.
169  class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> {
170   public:
171    typedef std::map<int, linked_ptr<T> > ApiResourceMap;
172    // Lookup map from extension id's to allocated resource id's.
173    typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap;
174
175    explicit ApiResourceData(const content::BrowserThread::ID thread_id)
176        : next_id_(1), thread_id_(thread_id) {}
177
178    int Add(T* api_resource) {
179      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
180      int id = GenerateId();
181      if (id > 0) {
182        linked_ptr<T> resource_ptr(api_resource);
183        api_resource_map_[id] = resource_ptr;
184
185        const std::string& extension_id = api_resource->owner_extension_id();
186        if (extension_resource_map_.find(extension_id) ==
187            extension_resource_map_.end()) {
188          extension_resource_map_[extension_id] = base::hash_set<int>();
189        }
190        extension_resource_map_[extension_id].insert(id);
191
192        return id;
193      }
194      return 0;
195    }
196
197    void Remove(const std::string& extension_id, int api_resource_id) {
198      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
199      if (GetOwnedResource(extension_id, api_resource_id) != NULL) {
200        DCHECK(extension_resource_map_.find(extension_id) !=
201               extension_resource_map_.end());
202        extension_resource_map_[extension_id].erase(api_resource_id);
203        api_resource_map_.erase(api_resource_id);
204      }
205    }
206
207    T* Get(const std::string& extension_id, int api_resource_id) {
208      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
209      return GetOwnedResource(extension_id, api_resource_id);
210    }
211
212    base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
213      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
214      return GetOwnedResourceIds(extension_id);
215    }
216
217    void InitiateExtensionUnloadedCleanup(const std::string& extension_id) {
218      content::BrowserThread::PostTask(
219          thread_id_,
220          FROM_HERE,
221          base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension,
222                     this,
223                     extension_id));
224    }
225
226    void InitiateExtensionSuspendedCleanup(const std::string& extension_id) {
227      content::BrowserThread::PostTask(
228          thread_id_,
229          FROM_HERE,
230          base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension,
231                     this,
232                     extension_id));
233    }
234
235    void InititateCleanup() {
236      content::BrowserThread::PostTask(
237          thread_id_, FROM_HERE, base::Bind(&ApiResourceData::Cleanup, this));
238    }
239
240   private:
241    friend class base::RefCountedThreadSafe<ApiResourceData>;
242
243    virtual ~ApiResourceData() {}
244
245    T* GetOwnedResource(const std::string& extension_id, 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 = extension_resource_map_[extension_id];
284      for (base::hash_set<int>::iterator it = resource_ids.begin();
285           it != resource_ids.end();) {
286        bool erase = false;
287        if (remove_all) {
288          erase = true;
289        } else {
290          linked_ptr<T> ptr = api_resource_map_[*it];
291          T* resource = ptr.get();
292          erase = (resource && !resource->IsPersistent());
293        }
294
295        if (erase) {
296          api_resource_map_.erase(*it);
297          resource_ids.erase(it++);
298        } else {
299          ++it;
300        }
301      }  // end for
302
303      // Remove extension entry if we removed all its resources.
304      if (resource_ids.size() == 0) {
305        extension_resource_map_.erase(extension_id);
306      }
307    }
308
309    void Cleanup() {
310      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
311
312      api_resource_map_.clear();
313      extension_resource_map_.clear();
314    }
315
316    int GenerateId() { return next_id_++; }
317
318    int next_id_;
319    const content::BrowserThread::ID thread_id_;
320    ApiResourceMap api_resource_map_;
321    ExtensionToResourceMap extension_resource_map_;
322  };
323
324  content::BrowserThread::ID thread_id_;
325  content::NotificationRegistrar registrar_;
326  scoped_refptr<ApiResourceData> data_;
327};
328
329}  // namespace extensions
330
331#endif  // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
332