blob: 7b38af0f287fe2036ac202ed888f224b7e4133fe [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
#define EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
#include <map>
#include <memory>
#include <string>
#include <unordered_set>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/notreached.h"
#include "base/scoped_observation.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_factory.h"
#include "extensions/browser/process_manager_observer.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
namespace extensions {
namespace api {
class BluetoothSocketApiFunction;
class BluetoothSocketEventDispatcher;
class SerialConnectFunction;
class SerialPortManager;
class TCPServerSocketEventDispatcher;
class TCPSocketEventDispatcher;
class UDPSocketEventDispatcher;
} // namespace api
template <typename T>
struct NamedThreadTraits {
static_assert(T::kThreadId == content::BrowserThread::IO ||
T::kThreadId == content::BrowserThread::UI,
"ApiResources can only belong to the IO or UI thread.");
static bool IsThreadInitialized() {
return content::BrowserThread::IsThreadInitialized(T::kThreadId);
}
static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() {
return content::BrowserThread::GetTaskRunnerForThread(T::kThreadId);
}
};
// An ApiResourceManager manages the lifetime of a set of resources that
// that live on named threads (i.e. BrowserThread::IO) which ApiFunctions use.
// Examples of such resources are sockets or USB connections. Note: The only
// named threads that are allowed are the IO and UI threads, since all others
// are deprecated. If we ever need a resource on a different background thread,
// we can modify NamedThreadTraits to be more generic and just return a task
// runner.
//
// Users of this class should define kThreadId to be the thread that
// ApiResourceManager to works on. The default is defined in ApiResource.
// The user must also define a static const char* service_name() that returns
// the name of the service, and in order for ApiResourceManager to use
// service_name() friend this class.
//
// In the cc file the user must define a GetFactoryInstance() and manage their
// own instances (typically using LazyInstance or Singleton).
//
// E.g.:
//
// class Resource {
// public:
// static const BrowserThread::ID kThreadId = BrowserThread::IO;
// private:
// friend class ApiResourceManager<Resource>;
// static const char* service_name() {
// return "ResourceManager";
// }
// };
//
// In the cc file:
//
// static base::LazyInstance<BrowserContextKeyedAPIFactory<
// ApiResourceManager<Resource> > >
// g_factory = LAZY_INSTANCE_INITIALIZER;
//
//
// template <>
// BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >*
// ApiResourceManager<Resource>::GetFactoryInstance() {
// return g_factory.Pointer();
// }
template <class T, typename ThreadingTraits = NamedThreadTraits<T>>
class ApiResourceManager : public BrowserContextKeyedAPI,
public ExtensionRegistryObserver,
public ProcessManagerObserver {
public:
explicit ApiResourceManager(content::BrowserContext* context)
: data_(base::MakeRefCounted<ApiResourceData>()) {
extension_registry_observation_.Observe(ExtensionRegistry::Get(context));
process_manager_observation_.Observe(ProcessManager::Get(context));
}
virtual ~ApiResourceManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(ThreadingTraits::IsThreadInitialized())
<< "A unit test is using an ApiResourceManager but didn't provide "
"the thread message loop needed for that kind of resource. "
"Please ensure that the appropriate message loop is operational.";
data_->InititateCleanup();
}
// TODO(lazyboy): Pass unique_ptr<T> instead of T*.
// Takes ownership.
int Add(T* api_resource) { return data_->Add(api_resource); }
void Remove(const ExtensionId& extension_id, int api_resource_id) {
data_->Remove(extension_id, api_resource_id);
}
T* Get(const ExtensionId& extension_id, int api_resource_id) {
return data_->Get(extension_id, api_resource_id);
}
std::unordered_set<int>* GetResourceIds(const ExtensionId& extension_id) {
return data_->GetResourceIds(extension_id);
}
// BrowserContextKeyedAPI implementation.
static BrowserContextKeyedAPIFactory<ApiResourceManager<T>>*
GetFactoryInstance();
// Convenience method to get the ApiResourceManager for a profile.
static ApiResourceManager<T>* Get(content::BrowserContext* context) {
return BrowserContextKeyedAPIFactory<ApiResourceManager<T>>::Get(context);
}
// BrowserContextKeyedAPI implementation.
static const char* service_name() { return T::service_name(); }
// Change the resource mapped to this |extension_id| at this
// |api_resource_id| to |resource|. Returns true and succeeds unless
// |api_resource_id| does not already identify a resource held by
// |extension_id|.
bool Replace(const ExtensionId& extension_id,
int api_resource_id,
T* resource) {
return data_->Replace(extension_id, api_resource_id, resource);
}
protected:
// ProcessManagerObserver:
void OnBackgroundHostClose(const ExtensionId& extension_id) override {
data_->InitiateExtensionSuspendedCleanup(extension_id);
}
// ExtensionRegistryObserver:
void OnExtensionUnloaded(content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) override {
data_->InitiateExtensionUnloadedCleanup(extension->id());
}
private:
// TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and
// we could avoid maintaining a friends list here.
friend class BluetoothAPI;
friend class api::BluetoothSocketApiFunction;
friend class api::BluetoothSocketEventDispatcher;
friend class api::SerialConnectFunction;
friend class api::SerialPortManager;
friend class api::TCPServerSocketEventDispatcher;
friend class api::TCPSocketEventDispatcher;
friend class api::UDPSocketEventDispatcher;
friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T>>;
static const bool kServiceHasOwnInstanceInIncognito = true;
static const bool kServiceIsNULLWhileTesting = true;
// ApiResourceData class handles resource bookkeeping on a thread
// where resource lifetime is handled.
class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> {
public:
typedef std::map<int, std::unique_ptr<T>> ApiResourceMap;
// Lookup map from extension id's to allocated resource id's.
typedef std::map<std::string, std::unordered_set<int>>
ExtensionToResourceMap;
ApiResourceData() : next_id_(1) { DETACH_FROM_SEQUENCE(sequence_checker_); }
// TODO(lazyboy): Pass unique_ptr<T> instead of T*.
int Add(T* api_resource) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int id = GenerateId();
if (id > 0) {
api_resource_map_[id] = base::WrapUnique<T>(api_resource);
const ExtensionId& extension_id = api_resource->owner_extension_id();
ExtensionToResourceMap::iterator it =
extension_resource_map_.find(extension_id);
if (it == extension_resource_map_.end()) {
it = extension_resource_map_
.insert(
std::make_pair(extension_id, std::unordered_set<int>()))
.first;
}
it->second.insert(id);
return id;
}
return 0;
}
void Remove(const ExtensionId& extension_id, int api_resource_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (GetOwnedResource(extension_id, api_resource_id)) {
ExtensionToResourceMap::iterator it =
extension_resource_map_.find(extension_id);
it->second.erase(api_resource_id);
api_resource_map_.erase(api_resource_id);
}
}
T* Get(const ExtensionId& extension_id, int api_resource_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return GetOwnedResource(extension_id, api_resource_id);
}
// Change the resource mapped to this |extension_id| at this
// |api_resource_id| to |resource|. Returns true and succeeds unless
// |api_resource_id| does not already identify a resource held by
// |extension_id|.
bool Replace(const ExtensionId& extension_id,
int api_resource_id,
T* api_resource) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
T* old_resource = api_resource_map_[api_resource_id].get();
if (old_resource && extension_id == old_resource->owner_extension_id()) {
api_resource_map_[api_resource_id] = base::WrapUnique<T>(api_resource);
return true;
}
return false;
}
std::unordered_set<int>* GetResourceIds(const ExtensionId& extension_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return GetOwnedResourceIds(extension_id);
}
void InitiateExtensionUnloadedCleanup(const ExtensionId& extension_id) {
ThreadingTraits::GetSequencedTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&ApiResourceData::CleanupResourcesFromUnloadedExtension, this,
extension_id));
}
void InitiateExtensionSuspendedCleanup(const ExtensionId& extension_id) {
ThreadingTraits::GetSequencedTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&ApiResourceData::CleanupResourcesFromSuspendedExtension, this,
extension_id));
}
void InititateCleanup() {
ThreadingTraits::GetSequencedTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&ApiResourceData::Cleanup, this));
}
private:
friend class base::RefCountedThreadSafe<ApiResourceData>;
virtual ~ApiResourceData() {}
T* GetOwnedResource(const ExtensionId& extension_id, int api_resource_id) {
const std::unique_ptr<T>& ptr = api_resource_map_[api_resource_id];
T* resource = ptr.get();
if (resource && extension_id == resource->owner_extension_id())
return resource;
return NULL;
}
std::unordered_set<int>* GetOwnedResourceIds(
const ExtensionId& extension_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ExtensionToResourceMap::iterator it =
extension_resource_map_.find(extension_id);
if (it == extension_resource_map_.end())
return NULL;
return &(it->second);
}
void CleanupResourcesFromUnloadedExtension(
const ExtensionId& extension_id) {
CleanupResourcesFromExtension(extension_id, true);
}
void CleanupResourcesFromSuspendedExtension(
const ExtensionId& extension_id) {
CleanupResourcesFromExtension(extension_id, false);
}
void CleanupResourcesFromExtension(const ExtensionId& extension_id,
bool remove_all) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ExtensionToResourceMap::iterator extension_it =
extension_resource_map_.find(extension_id);
if (extension_it == extension_resource_map_.end())
return;
// Remove all resources, or the non persistent ones only if |remove_all|
// is false.
std::unordered_set<int>& resource_ids = extension_it->second;
for (std::unordered_set<int>::iterator it = resource_ids.begin();
it != resource_ids.end();) {
bool erase = false;
if (remove_all) {
erase = true;
} else {
std::unique_ptr<T>& ptr = api_resource_map_[*it];
T* resource = ptr.get();
erase = (resource && !resource->IsPersistent());
}
if (erase) {
api_resource_map_.erase(*it);
resource_ids.erase(it++);
} else {
++it;
}
} // end for
// Remove extension entry if we removed all its resources.
if (resource_ids.size() == 0) {
extension_resource_map_.erase(extension_id);
}
}
void Cleanup() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Subtle: Move |api_resource_map_| to a temporary and clear that.
// |api_resource_map_| will become empty and any destructors called
// transitively from clearing |local_api_resource_map| will see empty
// |api_resource_map_| instead of trying to access being-destroyed map.
ApiResourceMap local_api_resource_map;
api_resource_map_.swap(local_api_resource_map);
local_api_resource_map.clear();
// Do the same as above for |extension_resource_map_|.
ExtensionToResourceMap local_extension_resource_map;
extension_resource_map_.swap(local_extension_resource_map);
local_extension_resource_map.clear();
}
int GenerateId() { return next_id_++; }
int next_id_;
ApiResourceMap api_resource_map_;
ExtensionToResourceMap extension_resource_map_;
SEQUENCE_CHECKER(sequence_checker_);
};
scoped_refptr<ApiResourceData> data_;
base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
extension_registry_observation_{this};
base::ScopedObservation<ProcessManager, ProcessManagerObserver>
process_manager_observation_{this};
SEQUENCE_CHECKER(sequence_checker_);
};
template <class T>
struct BrowserContextFactoryDependencies<ApiResourceManager<T>> {
static void DeclareFactoryDependencies(
BrowserContextKeyedAPIFactory<ApiResourceManager<T>>* factory) {
factory->DependsOn(
ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
factory->DependsOn(ExtensionRegistryFactory::GetInstance());
factory->DependsOn(ProcessManagerFactory::GetInstance());
}
};
} // namespace extensions
#endif // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_