blob: d86ceaf30eef87e352d452fcd6e05997de880946 [file] [log] [blame]
// Copyright 2015 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/api/automation_internal/automation_event_router.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/stl_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/extensions/api/automation_api_constants.h"
#include "chrome/common/extensions/api/automation_internal.h"
#include "chrome/common/extensions/chrome_extension_messages.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/event_router.h"
#include "extensions/common/extension.h"
#include "ui/accessibility/ax_enums.h"
#include "ui/accessibility/ax_node_data.h"
namespace extensions {
// static
AutomationEventRouter* AutomationEventRouter::GetInstance() {
return base::Singleton<
AutomationEventRouter,
base::LeakySingletonTraits<AutomationEventRouter>>::get();
}
AutomationEventRouter::AutomationEventRouter()
: active_profile_(ProfileManager::GetActiveUserProfile()) {
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::NotificationService::AllBrowserContextsAndSources());
}
AutomationEventRouter::~AutomationEventRouter() {
}
void AutomationEventRouter::RegisterListenerForOneTree(
const ExtensionId& extension_id,
int listener_process_id,
int listener_routing_id,
int source_ax_tree_id) {
Register(extension_id,
listener_process_id,
listener_routing_id,
source_ax_tree_id,
false);
}
void AutomationEventRouter::RegisterListenerWithDesktopPermission(
const ExtensionId& extension_id,
int listener_process_id,
int listener_routing_id) {
Register(extension_id,
listener_process_id,
listener_routing_id,
api::automation::kDesktopTreeID,
true);
}
void AutomationEventRouter::DispatchAccessibilityEvent(
const ExtensionMsg_AccessibilityEventParams& params) {
if (active_profile_ != ProfileManager::GetActiveUserProfile()) {
active_profile_ = ProfileManager::GetActiveUserProfile();
UpdateActiveProfile();
}
for (const auto& listener : listeners_) {
// Skip listeners that don't want to listen to this tree.
if (!listener.desktop &&
listener.tree_ids.find(params.tree_id) == listener.tree_ids.end()) {
continue;
}
content::RenderProcessHost* rph =
content::RenderProcessHost::FromID(listener.process_id);
rph->Send(new ExtensionMsg_AccessibilityEvent(listener.routing_id,
params,
listener.is_active_profile));
}
}
void AutomationEventRouter::DispatchAccessibilityLocationChange(
const ExtensionMsg_AccessibilityLocationChangeParams& params) {
for (const auto& listener : listeners_) {
// Skip listeners that don't want to listen to this tree.
if (!listener.desktop &&
listener.tree_ids.find(params.tree_id) == listener.tree_ids.end()) {
continue;
}
content::RenderProcessHost* rph =
content::RenderProcessHost::FromID(listener.process_id);
rph->Send(new ExtensionMsg_AccessibilityLocationChange(
listener.routing_id,
params));
}
}
void AutomationEventRouter::DispatchTreeDestroyedEvent(
int tree_id,
content::BrowserContext* browser_context) {
std::unique_ptr<base::ListValue> args(
api::automation_internal::OnAccessibilityTreeDestroyed::Create(tree_id));
std::unique_ptr<Event> event(new Event(
events::AUTOMATION_INTERNAL_ON_ACCESSIBILITY_TREE_DESTROYED,
api::automation_internal::OnAccessibilityTreeDestroyed::kEventName,
std::move(args)));
event->restrict_to_browser_context = browser_context;
EventRouter::Get(browser_context)->BroadcastEvent(std::move(event));
}
AutomationEventRouter::AutomationListener::AutomationListener() {
}
AutomationEventRouter::AutomationListener::AutomationListener(
const AutomationListener& other) = default;
AutomationEventRouter::AutomationListener::~AutomationListener() {
}
void AutomationEventRouter::Register(
const ExtensionId& extension_id,
int listener_process_id,
int listener_routing_id,
int ax_tree_id,
bool desktop) {
auto iter = std::find_if(
listeners_.begin(),
listeners_.end(),
[listener_process_id, listener_routing_id](
const AutomationListener& item) {
return (item.process_id == listener_process_id &&
item.routing_id == listener_routing_id);
});
// Add a new entry if we don't have one with that process and routing id.
if (iter == listeners_.end()) {
AutomationListener listener;
listener.extension_id = extension_id;
listener.routing_id = listener_routing_id;
listener.process_id = listener_process_id;
listener.desktop = desktop;
listener.tree_ids.insert(ax_tree_id);
listeners_.push_back(listener);
UpdateActiveProfile();
return;
}
// We have an entry with that process and routing id, so update the set of
// tree ids it wants to listen to, and update its desktop permission.
iter->tree_ids.insert(ax_tree_id);
if (desktop)
iter->desktop = true;
}
void AutomationEventRouter::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type != content::NOTIFICATION_RENDERER_PROCESS_TERMINATED &&
type != content::NOTIFICATION_RENDERER_PROCESS_CLOSED) {
NOTREACHED();
return;
}
content::RenderProcessHost* rph =
content::Source<content::RenderProcessHost>(source).ptr();
int process_id = rph->GetID();
listeners_.erase(
std::remove_if(
listeners_.begin(),
listeners_.end(),
[process_id](const AutomationListener& item) {
return item.process_id == process_id;
}),
listeners_.end());
UpdateActiveProfile();
}
void AutomationEventRouter::UpdateActiveProfile() {
for (auto& listener : listeners_) {
#if defined(OS_CHROMEOS)
int extension_id_count = 0;
for (const auto& listener2 : listeners_) {
if (listener2.extension_id == listener.extension_id)
extension_id_count++;
}
content::RenderProcessHost* rph =
content::RenderProcessHost::FromID(listener.process_id);
// The purpose of is_active_profile is to ensure different instances of
// the same extension running in different profiles don't interfere with
// one another. If an automation extension is only running in one profile,
// always mark it as active. If it's running in two or more profiles,
// only mark one as active.
listener.is_active_profile = (extension_id_count == 1 ||
rph->GetBrowserContext() == active_profile_);
#else
listener.is_active_profile = true;
#endif
}
}
} // namespace extensions