api_resource_manager.h revision 116680a4aac90f2aa7413d9095a592090648e557
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 BluetoothSocketApiFunction;
29class BluetoothSocketEventDispatcher;
30}
31
32namespace core_api {
33class SerialEventDispatcher;
34class TCPServerSocketEventDispatcher;
35class TCPSocketEventDispatcher;
36class UDPSocketEventDispatcher;
37}
38
39// An ApiResourceManager manages the lifetime of a set of resources that
40// ApiFunctions use. Examples are sockets or USB connections.
41//
42// Users of this class should define kThreadId to be the thread that
43// ApiResourceManager to works on. The default is defined in ApiResource.
44// The user must also define a static const char* service_name() that returns
45// the name of the service, and in order for ApiResourceManager to use
46// service_name() friend this class.
47//
48// In the cc file the user must define a GetFactoryInstance() and manage their
49// own instances (typically using LazyInstance or Singleton).
50//
51// E.g.:
52//
53// class Resource {
54//  public:
55//   static const BrowserThread::ID kThreadId = BrowserThread::FILE;
56//  private:
57//   friend class ApiResourceManager<Resource>;
58//   static const char* service_name() {
59//     return "ResourceManager";
60//    }
61// };
62//
63// In the cc file:
64//
65// static base::LazyInstance<BrowserContextKeyedAPIFactory<
66//     ApiResourceManager<Resource> > >
67//         g_factory = LAZY_INSTANCE_INITIALIZER;
68//
69//
70// template <>
71// BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >*
72// ApiResourceManager<Resource>::GetFactoryInstance() {
73//   return g_factory.Pointer();
74// }
75template <class T>
76class ApiResourceManager : public BrowserContextKeyedAPI,
77                           public base::NonThreadSafe,
78                           public content::NotificationObserver {
79 public:
80  explicit ApiResourceManager(content::BrowserContext* context)
81      : thread_id_(T::kThreadId), data_(new ApiResourceData(thread_id_)) {
82    registrar_.Add(this,
83                   chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
84                   content::NotificationService::AllSources());
85    registrar_.Add(this,
86                   chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
87                   content::NotificationService::AllSources());
88  }
89
90  // For Testing.
91  static ApiResourceManager<T>* CreateApiResourceManagerForTest(
92      content::BrowserContext* context,
93      content::BrowserThread::ID thread_id) {
94    ApiResourceManager* manager = new ApiResourceManager<T>(context);
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  // BrowserContextKeyedAPI implementation.
111  static BrowserContextKeyedAPIFactory<ApiResourceManager<T> >*
112      GetFactoryInstance();
113
114  // Convenience method to get the ApiResourceManager for a profile.
115  static ApiResourceManager<T>* Get(content::BrowserContext* context) {
116    return BrowserContextKeyedAPIFactory<ApiResourceManager<T> >::Get(context);
117  }
118
119  // Takes ownership.
120  int Add(T* api_resource) { return data_->Add(api_resource); }
121
122  void Remove(const std::string& extension_id, int api_resource_id) {
123    data_->Remove(extension_id, api_resource_id);
124  }
125
126  T* Get(const std::string& extension_id, int api_resource_id) {
127    return data_->Get(extension_id, api_resource_id);
128  }
129
130  base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
131    return data_->GetResourceIds(extension_id);
132  }
133
134 protected:
135  // content::NotificationObserver:
136  virtual void Observe(int type,
137                       const content::NotificationSource& source,
138                       const content::NotificationDetails& details) OVERRIDE {
139    switch (type) {
140      case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
141        std::string id = content::Details<extensions::UnloadedExtensionInfo>(
142                             details)->extension->id();
143        data_->InitiateExtensionUnloadedCleanup(id);
144        break;
145      }
146      case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
147        ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
148        data_->InitiateExtensionSuspendedCleanup(host->extension_id());
149        break;
150      }
151    }
152  }
153
154 private:
155  // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and
156  // we could avoid maintaining a friends list here.
157  friend class BluetoothAPI;
158  friend class api::BluetoothSocketApiFunction;
159  friend class api::BluetoothSocketEventDispatcher;
160  friend class core_api::SerialEventDispatcher;
161  friend class core_api::TCPServerSocketEventDispatcher;
162  friend class core_api::TCPSocketEventDispatcher;
163  friend class core_api::UDPSocketEventDispatcher;
164  friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T> >;
165
166  // BrowserContextKeyedAPI implementation.
167  static const char* service_name() { return T::service_name(); }
168
169  static const bool kServiceHasOwnInstanceInIncognito = true;
170  static const bool kServiceIsNULLWhileTesting = true;
171
172  // ApiResourceData class handles resource bookkeeping on a thread
173  // where resource lifetime is handled.
174  class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> {
175   public:
176    typedef std::map<int, linked_ptr<T> > ApiResourceMap;
177    // Lookup map from extension id's to allocated resource id's.
178    typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap;
179
180    explicit ApiResourceData(const content::BrowserThread::ID thread_id)
181        : next_id_(1), thread_id_(thread_id) {}
182
183    int Add(T* api_resource) {
184      DCHECK_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(thread_id_);
219      return GetOwnedResourceIds(extension_id);
220    }
221
222    void InitiateExtensionUnloadedCleanup(const std::string& extension_id) {
223      if (content::BrowserThread::CurrentlyOn(thread_id_)) {
224        CleanupResourcesFromUnloadedExtension(extension_id);
225      } else {
226        content::BrowserThread::PostTask(
227            thread_id_,
228            FROM_HERE,
229            base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension,
230                       this,
231                       extension_id));
232      }
233    }
234
235    void InitiateExtensionSuspendedCleanup(const std::string& extension_id) {
236      if (content::BrowserThread::CurrentlyOn(thread_id_)) {
237        CleanupResourcesFromSuspendedExtension(extension_id);
238      } else {
239        content::BrowserThread::PostTask(
240            thread_id_,
241            FROM_HERE,
242            base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension,
243                       this,
244                       extension_id));
245      }
246    }
247
248    void InititateCleanup() {
249      if (content::BrowserThread::CurrentlyOn(thread_id_)) {
250        Cleanup();
251      } else {
252        content::BrowserThread::PostTask(
253            thread_id_, FROM_HERE, base::Bind(&ApiResourceData::Cleanup, this));
254      }
255    }
256
257   private:
258    friend class base::RefCountedThreadSafe<ApiResourceData>;
259
260    virtual ~ApiResourceData() {}
261
262    T* GetOwnedResource(const std::string& extension_id, int api_resource_id) {
263      linked_ptr<T> ptr = api_resource_map_[api_resource_id];
264      T* resource = ptr.get();
265      if (resource && extension_id == resource->owner_extension_id())
266        return resource;
267      return NULL;
268    }
269
270    base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) {
271      DCHECK_CURRENTLY_ON(thread_id_);
272      if (extension_resource_map_.find(extension_id) ==
273          extension_resource_map_.end())
274        return NULL;
275
276      return &extension_resource_map_[extension_id];
277    }
278
279    void CleanupResourcesFromUnloadedExtension(
280        const std::string& extension_id) {
281      CleanupResourcesFromExtension(extension_id, true);
282    }
283
284    void CleanupResourcesFromSuspendedExtension(
285        const std::string& extension_id) {
286      CleanupResourcesFromExtension(extension_id, false);
287    }
288
289    void CleanupResourcesFromExtension(const std::string& extension_id,
290                                       bool remove_all) {
291      DCHECK_CURRENTLY_ON(thread_id_);
292
293      if (extension_resource_map_.find(extension_id) ==
294          extension_resource_map_.end()) {
295        return;
296      }
297
298      // Remove all resources, or the non persistent ones only if |remove_all|
299      // is false.
300      base::hash_set<int>& resource_ids = extension_resource_map_[extension_id];
301      for (base::hash_set<int>::iterator it = resource_ids.begin();
302           it != resource_ids.end();) {
303        bool erase = false;
304        if (remove_all) {
305          erase = true;
306        } else {
307          linked_ptr<T> ptr = api_resource_map_[*it];
308          T* resource = ptr.get();
309          erase = (resource && !resource->IsPersistent());
310        }
311
312        if (erase) {
313          api_resource_map_.erase(*it);
314          resource_ids.erase(it++);
315        } else {
316          ++it;
317        }
318      }  // end for
319
320      // Remove extension entry if we removed all its resources.
321      if (resource_ids.size() == 0) {
322        extension_resource_map_.erase(extension_id);
323      }
324    }
325
326    void Cleanup() {
327      DCHECK_CURRENTLY_ON(thread_id_);
328
329      api_resource_map_.clear();
330      extension_resource_map_.clear();
331    }
332
333    int GenerateId() { return next_id_++; }
334
335    int next_id_;
336    const content::BrowserThread::ID thread_id_;
337    ApiResourceMap api_resource_map_;
338    ExtensionToResourceMap extension_resource_map_;
339  };
340
341  content::BrowserThread::ID thread_id_;
342  content::NotificationRegistrar registrar_;
343  scoped_refptr<ApiResourceData> data_;
344};
345
346}  // namespace extensions
347
348#endif  // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
349