// Copyright (c) 2011 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/values.h"
#include "chrome/browser/extensions/extension_devtools_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/extension_webrequest_api.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_messages.h"
#include "content/browser/child_process_security_policy.h"
#include "content/browser/renderer_host/render_process_host.h"
#include "content/common/notification_service.h"
namespace {
const char kDispatchEvent[] = "Event.dispatchJSON";
static void DispatchEvent(RenderProcessHost* renderer,
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));
renderer->Send(new ExtensionMsg_MessageInvoke(MSG_ROUTING_CONTROL,
extension_id, kDispatchEvent, args, event_url));
}
static void NotifyEventListenerRemovedOnIOThread(
ProfileId profile_id,
const std::string& extension_id,
const std::string& sub_event_name) {
ExtensionWebRequestEventRouter::GetInstance()->RemoveEventListener(
profile_id, extension_id, sub_event_name);
}
} // namespace
struct ExtensionEventRouter::EventListener {
RenderProcessHost* process;
std::string extension_id;
explicit EventListener(RenderProcessHost* process,
const std::string& extension_id)
: process(process), extension_id(extension_id) {}
bool operator<(const EventListener& that) const {
if (process < that.process)
return true;
if (process == that.process && extension_id < that.extension_id)
return true;
return false;
}
};
// static
bool ExtensionEventRouter::CanCrossIncognito(Profile* profile,
const std::string& extension_id) {
const Extension* extension =
profile->GetExtensionService()->GetExtensionById(extension_id, false);
return CanCrossIncognito(profile, extension);
}
// static
bool ExtensionEventRouter::CanCrossIncognito(Profile* profile,
const Extension* extension) {
// We allow the extension to see events and data from another profile iff it
// uses "spanning" behavior and it has incognito access. "split" mode
// extensions only see events for a matching profile.
return
(profile->GetExtensionService()->IsIncognitoEnabled(extension->id()) &&
!extension->incognito_split_mode());
}
ExtensionEventRouter::ExtensionEventRouter(Profile* profile)
: profile_(profile),
extension_devtools_manager_(profile->GetExtensionDevToolsManager()) {
registrar_.Add(this, NotificationType::RENDERER_PROCESS_TERMINATED,
NotificationService::AllSources());
registrar_.Add(this, NotificationType::RENDERER_PROCESS_CLOSED,
NotificationService::AllSources());
}
ExtensionEventRouter::~ExtensionEventRouter() {
}
void ExtensionEventRouter::AddEventListener(
const std::string& event_name,
RenderProcessHost* process,
const std::string& extension_id) {
EventListener 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->id());
// 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,
RenderProcessHost* process,
const std::string& extension_id) {
EventListener listener(process, extension_id);
DCHECK_EQ(listeners_[event_name].count(listener), 1u) <<
" PID=" << process->id() << " 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->id());
// 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,
NewRunnableFunction(
&NotifyEventListenerRemovedOnIOThread,
profile_->GetRuntimeId(), listener.extension_id, event_name));
}
bool ExtensionEventRouter::HasEventListener(const std::string& event_name) {
return (listeners_.find(event_name) != listeners_.end() &&
!listeners_[event_name].empty());
}
bool ExtensionEventRouter::ExtensionHasEventListener(
const std::string& extension_id, const std::string& event_name) {
ListenerMap::iterator it = listeners_.find(event_name);
if (it == listeners_.end())
return false;
std::set<EventListener>& listeners = it->second;
for (std::set<EventListener>::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) {
DispatchEventImpl("", event_name, event_args, restrict_to_profile, event_url);
}
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());
DispatchEventImpl(extension_id, event_name, event_args, restrict_to_profile,
event_url);
}
void ExtensionEventRouter::DispatchEventImpl(
const std::string& extension_id,
const std::string& event_name, const std::string& event_args,
Profile* restrict_to_profile, const GURL& event_url) {
if (!profile_)
return;
// We don't expect to get events from a completely different profile.
DCHECK(!restrict_to_profile || profile_->IsSameProfile(restrict_to_profile));
ListenerMap::iterator it = listeners_.find(event_name);
if (it == listeners_.end())
return;
std::set<EventListener>& listeners = it->second;
ExtensionService* service = profile_->GetExtensionService();
// Send the event only to renderers that are listening for it.
for (std::set<EventListener>::iterator listener = listeners.begin();
listener != listeners.end(); ++listener) {
if (!ChildProcessSecurityPolicy::GetInstance()->
HasExtensionBindings(listener->process->id())) {
// Don't send browser-level events to unprivileged processes.
continue;
}
if (!extension_id.empty() && extension_id != listener->extension_id)
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 = restrict_to_profile &&
listener->process->profile() != restrict_to_profile;
const Extension* extension = service->GetExtensionById(
listener->extension_id, false);
if (cross_incognito && !service->CanCrossIncognito(extension))
continue;
DispatchEvent(listener->process, listener->extension_id,
event_name, event_args, event_url);
}
}
void ExtensionEventRouter::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
case NotificationType::RENDERER_PROCESS_TERMINATED:
case NotificationType::RENDERER_PROCESS_CLOSED: {
RenderProcessHost* renderer = Source<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<EventListener>::iterator jt = current_it->second.begin();
jt != current_it->second.end(); ) {
std::set<EventListener>::iterator current_jt = jt++;
if (current_jt->process == renderer) {
RemoveEventListener(current_it->first,
current_jt->process,
current_jt->extension_id);
}
}
}
break;
}
default:
NOTREACHED();
return;
}
}