| // 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; |
| } |
| } |