blob: 4c4b8f6980c991e1742a884cd45eb03a1ba91c0a [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_event_router.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_devtools_manager.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/extension_processes_api.h"
#include "chrome/browser/extensions/extension_processes_api_constants.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/extensions/api/webrequest/webrequest_api.h"
#include "chrome/browser/extensions/process_map.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_view_type.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/api/extension_api.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
using content::BrowserThread;
using extensions::ExtensionAPI;
namespace {
const char kDispatchEvent[] = "Event.dispatchJSON";
const char kOnInstalledEvent[] = "experimental.extension.onInstalled";
void NotifyEventListenerRemovedOnIOThread(
void* profile,
const std::string& extension_id,
const std::string& sub_event_name) {
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
profile, extension_id, sub_event_name);
}
} // namespace
struct ExtensionEventRouter::ListenerProcess {
content::RenderProcessHost* process;
std::string extension_id;
ListenerProcess(content::RenderProcessHost* process,
const std::string& extension_id)
: process(process), extension_id(extension_id) {}
bool operator<(const ListenerProcess& that) const {
if (process < that.process)
return true;
if (process == that.process && extension_id < that.extension_id)
return true;
return false;
}
};
struct ExtensionEventRouter::ExtensionEvent {
std::string event_name;
std::string event_args;
GURL event_url;
Profile* restrict_to_profile;
std::string cross_incognito_args;
ExtensionEvent(const std::string& event_name,
const std::string& event_args,
const GURL& event_url,
Profile* restrict_to_profile,
const std::string& cross_incognito_args)
: event_name(event_name),
event_args(event_args),
event_url(event_url),
restrict_to_profile(restrict_to_profile),
cross_incognito_args(cross_incognito_args) {}
};
// static
void ExtensionEventRouter::DispatchEvent(IPC::Message::Sender* ipc_sender,
const std::string& extension_id,
const std::string& event_name,
const std::string& event_args,
const GURL& event_url) {
ListValue args;
args.Set(0, Value::CreateStringValue(event_name));
args.Set(1, Value::CreateStringValue(event_args));
ipc_sender->Send(new ExtensionMsg_MessageInvoke(MSG_ROUTING_CONTROL,
extension_id, kDispatchEvent, args, event_url));
}
ExtensionEventRouter::ExtensionEventRouter(Profile* profile)
: profile_(profile),
extension_devtools_manager_(profile->GetExtensionDevToolsManager()) {
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::NotificationService::AllSources());
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
content::Source<Profile>(profile_));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
content::Source<Profile>(profile_));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
content::Source<Profile>(profile_));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
content::Source<Profile>(profile_));
// TODO(tessamac): also get notified for background page crash/failure.
}
ExtensionEventRouter::~ExtensionEventRouter() {}
void ExtensionEventRouter::AddEventListener(
const std::string& event_name,
content::RenderProcessHost* process,
const std::string& extension_id) {
ListenerProcess listener(process, extension_id);
DCHECK_EQ(listeners_[event_name].count(listener), 0u) << event_name;
listeners_[event_name].insert(listener);
if (extension_devtools_manager_.get())
extension_devtools_manager_->AddEventListener(event_name,
process->GetID());
// We lazily tell the TaskManager to start updating when listeners to the
// processes.onUpdated event arrive.
if (event_name.compare(extension_processes_api_constants::kOnUpdated) == 0)
ExtensionProcessesEventRouter::GetInstance()->ListenerAdded();
}
void ExtensionEventRouter::RemoveEventListener(
const std::string& event_name,
content::RenderProcessHost* process,
const std::string& extension_id) {
ListenerProcess listener(process, extension_id);
DCHECK_EQ(listeners_[event_name].count(listener), 1u) <<
" PID=" << process->GetID() << " extension=" << extension_id <<
" event=" << event_name;
listeners_[event_name].erase(listener);
// Note: extension_id may point to data in the now-deleted listeners_ object.
// Do not use.
if (extension_devtools_manager_.get())
extension_devtools_manager_->RemoveEventListener(event_name,
process->GetID());
// If a processes.onUpdated event listener is removed (or a process with one
// exits), then we let the TaskManager know that it has one fewer listener.
if (event_name.compare(extension_processes_api_constants::kOnUpdated) == 0)
ExtensionProcessesEventRouter::GetInstance()->ListenerRemoved();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(
&NotifyEventListenerRemovedOnIOThread,
profile_, listener.extension_id, event_name));
}
void ExtensionEventRouter::AddLazyEventListener(
const std::string& event_name,
const std::string& extension_id) {
ListenerProcess lazy_listener(NULL, extension_id);
bool is_new = lazy_listeners_[event_name].insert(lazy_listener).second;
if (is_new) {
ExtensionPrefs* prefs = profile_->GetExtensionService()->extension_prefs();
std::set<std::string> events = prefs->GetRegisteredEvents(extension_id);
bool prefs_is_new = events.insert(event_name).second;
if (prefs_is_new)
prefs->SetRegisteredEvents(extension_id, events);
}
}
void ExtensionEventRouter::RemoveLazyEventListener(
const std::string& event_name,
const std::string& extension_id) {
ListenerProcess lazy_listener(NULL, extension_id);
bool did_exist = lazy_listeners_[event_name].erase(lazy_listener) > 0;
if (did_exist) {
ExtensionPrefs* prefs = profile_->GetExtensionService()->extension_prefs();
std::set<std::string> events = prefs->GetRegisteredEvents(extension_id);
bool prefs_did_exist = events.erase(event_name) > 0;
DCHECK(prefs_did_exist);
prefs->SetRegisteredEvents(extension_id, events);
}
}
bool ExtensionEventRouter::HasEventListener(const std::string& event_name) {
return (HasEventListenerImpl(listeners_, "", event_name) ||
HasEventListenerImpl(lazy_listeners_, "", event_name));
}
bool ExtensionEventRouter::ExtensionHasEventListener(
const std::string& extension_id, const std::string& event_name) {
return (HasEventListenerImpl(listeners_, extension_id, event_name) ||
HasEventListenerImpl(lazy_listeners_, extension_id, event_name));
}
bool ExtensionEventRouter::HasEventListenerImpl(
const ListenerMap& listener_map,
const std::string& extension_id,
const std::string& event_name) {
ListenerMap::const_iterator it = listener_map.find(event_name);
if (it == listener_map.end())
return false;
const std::set<ListenerProcess>& listeners = it->second;
if (extension_id.empty())
return !listeners.empty();
for (std::set<ListenerProcess>::const_iterator listener = listeners.begin();
listener != listeners.end(); ++listener) {
if (listener->extension_id == extension_id)
return true;
}
return false;
}
void ExtensionEventRouter::DispatchEventToRenderers(
const std::string& event_name,
const std::string& event_args,
Profile* restrict_to_profile,
const GURL& event_url) {
linked_ptr<ExtensionEvent> event(
new ExtensionEvent(event_name, event_args, event_url,
restrict_to_profile, ""));
DispatchEventImpl("", event, false);
}
void ExtensionEventRouter::DispatchEventToExtension(
const std::string& extension_id,
const std::string& event_name,
const std::string& event_args,
Profile* restrict_to_profile,
const GURL& event_url) {
DCHECK(!extension_id.empty());
linked_ptr<ExtensionEvent> event(
new ExtensionEvent(event_name, event_args, event_url,
restrict_to_profile, ""));
DispatchEventImpl(extension_id, event, false);
}
void ExtensionEventRouter::DispatchEventsToRenderersAcrossIncognito(
const std::string& event_name,
const std::string& event_args,
Profile* restrict_to_profile,
const std::string& cross_incognito_args,
const GURL& event_url) {
linked_ptr<ExtensionEvent> event(
new ExtensionEvent(event_name, event_args, event_url,
restrict_to_profile, cross_incognito_args));
DispatchEventImpl("", event, false);
}
bool ExtensionEventRouter::CanDispatchEventNow(const Extension* extension) {
DCHECK(extension);
if (extension->has_background_page() &&
!extension->background_page_persists()) {
ExtensionProcessManager* pm = profile_->GetExtensionProcessManager();
// TODO(mpcomplete): this is incorrect. We need to check whether the page
// has finished loading. If not, we can't dispatch the event (because the
// listener hasn't been set up yet).
if (!pm->GetBackgroundHostForExtension(extension->id()))
return false;
}
return true;
}
void ExtensionEventRouter::DispatchEventImpl(
const std::string& extension_id,
const linked_ptr<ExtensionEvent>& event,
bool was_pending) {
// Ensure we are only dispatching pending events to a particular extension.
if (was_pending)
CHECK(!extension_id.empty());
if (!profile_)
return;
// We don't expect to get events from a completely different profile.
DCHECK(!event->restrict_to_profile ||
profile_->IsSameProfile(event->restrict_to_profile));
if (!was_pending)
LoadLazyBackgroundPagesForEvent(extension_id, event);
ListenerMap::iterator it = listeners_.find(event->event_name);
if (it == listeners_.end())
return;
std::set<ListenerProcess>& listeners = it->second;
ExtensionService* service = profile_->GetExtensionService();
// Send the event only to renderers that are listening for it.
for (std::set<ListenerProcess>::iterator listener = listeners.begin();
listener != listeners.end(); ++listener) {
if (!extension_id.empty() && extension_id != listener->extension_id)
continue;
const Extension* extension = service->extensions()->GetByID(
listener->extension_id);
// The extension could have been removed, but we do not unregister it until
// the extension process is unloaded.
if (!extension)
continue;
Profile* listener_profile = Profile::FromBrowserContext(
listener->process->GetBrowserContext());
extensions::ProcessMap* process_map =
listener_profile->GetExtensionService()->process_map();
// If the event is privileged, only send to extension processes. Otherwise,
// it's OK to send to normal renderers (e.g., for content scripts).
if (ExtensionAPI::GetInstance()->IsPrivileged(event->event_name) &&
!process_map->Contains(extension->id(), listener->process->GetID())) {
continue;
}
// Is this event from a different profile than the renderer (ie, an
// incognito tab event sent to a normal process, or vice versa).
bool cross_incognito = event->restrict_to_profile &&
listener_profile != event->restrict_to_profile;
// Send the event with different arguments to extensions that can't
// cross incognito, if necessary.
if (cross_incognito && !service->CanCrossIncognito(extension)) {
if (!event->cross_incognito_args.empty()) {
DispatchEvent(listener->process, listener->extension_id,
event->event_name, event->cross_incognito_args,
event->event_url);
IncrementInFlightEvents(extension);
}
continue;
}
DispatchEvent(listener->process, listener->extension_id,
event->event_name, event->event_args, event->event_url);
IncrementInFlightEvents(extension);
}
}
void ExtensionEventRouter::LoadLazyBackgroundPagesForEvent(
const std::string& extension_id,
const linked_ptr<ExtensionEvent>& event) {
ExtensionService* service = profile_->GetExtensionService();
ExtensionProcessManager* pm = profile_->GetExtensionProcessManager();
ListenerMap::iterator it = lazy_listeners_.find(event->event_name);
if (it == lazy_listeners_.end())
return;
std::set<ListenerProcess>& listeners = it->second;
for (std::set<ListenerProcess>::iterator listener = listeners.begin();
listener != listeners.end(); ++listener) {
if (!extension_id.empty() && extension_id != listener->extension_id)
continue;
const Extension* extension = service->extensions()->GetByID(
listener->extension_id);
if (extension && !CanDispatchEventNow(extension)) {
AppendEvent(extension->id(), event);
pm->CreateBackgroundHost(extension, extension->GetBackgroundURL());
}
}
}
void ExtensionEventRouter::IncrementInFlightEvents(const Extension* extension) {
if (!extension->background_page_persists())
in_flight_events_[extension->id()]++;
}
void ExtensionEventRouter::OnExtensionEventAck(
const std::string& extension_id) {
CHECK(in_flight_events_[extension_id] > 0);
in_flight_events_[extension_id]--;
}
bool ExtensionEventRouter::HasInFlightEvents(const std::string& extension_id) {
return in_flight_events_[extension_id] > 0;
}
void ExtensionEventRouter::AppendEvent(
const std::string& extension_id,
const linked_ptr<ExtensionEvent>& event) {
PendingEventsList* events_list = NULL;
PendingEventsPerExtMap::iterator it = pending_events_.find(extension_id);
if (it == pending_events_.end()) {
events_list = new PendingEventsList();
pending_events_[extension_id] = linked_ptr<PendingEventsList>(events_list);
} else {
events_list = it->second.get();
}
events_list->push_back(event);
}
void ExtensionEventRouter::DispatchPendingEvents(
const std::string& extension_id) {
CHECK(!extension_id.empty());
PendingEventsPerExtMap::const_iterator map_it =
pending_events_.find(extension_id);
if (map_it == pending_events_.end())
return;
PendingEventsList* events_list = map_it->second.get();
for (PendingEventsList::const_iterator it = events_list->begin();
it != events_list->end(); ++it)
DispatchEventImpl(extension_id, *it, true);
events_list->clear();
pending_events_.erase(extension_id);
// Check if the extension is idle, which may be the case if no events were
// successfully dispatched.
profile_->GetExtensionProcessManager()->OnExtensionIdle(extension_id);
}
void ExtensionEventRouter::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED:
case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: {
content::RenderProcessHost* renderer =
content::Source<content::RenderProcessHost>(source).ptr();
// Remove all event listeners associated with this renderer
for (ListenerMap::iterator it = listeners_.begin();
it != listeners_.end(); ) {
ListenerMap::iterator current_it = it++;
for (std::set<ListenerProcess>::iterator jt =
current_it->second.begin();
jt != current_it->second.end(); ) {
std::set<ListenerProcess>::iterator current_jt = jt++;
if (current_jt->process == renderer) {
RemoveEventListener(current_it->first,
current_jt->process,
current_jt->extension_id);
}
}
}
break;
}
case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING: {
// If an on-demand background page finished loading, dispatch queued up
// events for it.
ExtensionHost* eh = content::Details<ExtensionHost>(details).ptr();
if (eh->extension_host_type() ==
chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE &&
!eh->extension()->background_page_persists()) {
DispatchPendingEvents(eh->extension_id());
}
break;
}
case chrome::NOTIFICATION_EXTENSION_LOADED: {
// Add all registered lazy listeners to our cache.
const Extension* extension =
content::Details<const Extension>(details).ptr();
std::set<std::string> registered_events =
profile_->GetExtensionService()->extension_prefs()->
GetRegisteredEvents(extension->id());
ListenerProcess lazy_listener(NULL, extension->id());
for (std::set<std::string>::iterator it = registered_events.begin();
it != registered_events.end(); ++it) {
lazy_listeners_[*it].insert(lazy_listener);
}
break;
}
case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
// Remove all registered lazy listeners from our cache.
UnloadedExtensionInfo* unloaded =
content::Details<UnloadedExtensionInfo>(details).ptr();
ListenerProcess lazy_listener(NULL, unloaded->extension->id());
for (ListenerMap::iterator it = lazy_listeners_.begin();
it != lazy_listeners_.end(); ++it) {
it->second.erase(lazy_listener);
}
break;
}
case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
// Dispatch the onInstalled event.
const Extension* extension =
content::Details<const Extension>(details).ptr();
AddLazyEventListener(kOnInstalledEvent, extension->id());
DispatchEventToExtension(
extension->id(), kOnInstalledEvent, "[]", NULL, GURL());
break;
}
// TODO(tessamac): if background page crashed/failed clear queue.
default:
NOTREACHED();
return;
}
}