| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/api/automation_internal/automation_event_router.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/observer_list.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "extensions/browser/api/automation_internal/automation_internal_api_delegate.h" |
| #include "extensions/browser/api/extensions_api_client.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/common/api/automation_internal.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_id.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "mojo/public/cpp/bindings/pending_associated_remote.h" |
| #include "ui/accessibility/ax_action_data.h" |
| #include "ui/accessibility/ax_node_data.h" |
| |
| namespace extensions { |
| |
| // static |
| AutomationEventRouter* AutomationEventRouter::GetInstance() { |
| return base::Singleton< |
| AutomationEventRouter, |
| base::LeakySingletonTraits<AutomationEventRouter>>::get(); |
| } |
| |
| AutomationEventRouter::AutomationEventRouter() { |
| #if defined(USE_AURA) |
| // Not reset because |this| is leaked. |
| ExtensionsAPIClient::Get() |
| ->GetAutomationInternalApiDelegate() |
| ->SetAutomationEventRouterInterface(this); |
| #endif |
| |
| ui::AXActionHandlerRegistry::GetInstance()->AddObserver(this); |
| } |
| |
| AutomationEventRouter::~AutomationEventRouter() { |
| CHECK(!remote_router_); |
| } |
| |
| void AutomationEventRouter::RegisterListenerForOneTree( |
| const ExtensionId& extension_id, |
| const RenderProcessHostId& listener_rph_id, |
| content::WebContents* web_contents, |
| ui::AXTreeID source_ax_tree_id) { |
| Register(extension_id, listener_rph_id, web_contents, source_ax_tree_id, |
| /*desktop=*/false); |
| } |
| |
| void AutomationEventRouter::RegisterListenerWithDesktopPermission( |
| const ExtensionId& extension_id, |
| const RenderProcessHostId& listener_rph_id, |
| content::WebContents* web_contents) { |
| Register(extension_id, listener_rph_id, web_contents, ui::AXTreeIDUnknown(), |
| /*desktop=*/true); |
| } |
| |
| void AutomationEventRouter::UnregisterListenerWithDesktopPermission( |
| const RenderProcessHostId& listener_rph_id) { |
| content::RenderProcessHost* host = |
| content::RenderProcessHost::FromID(listener_rph_id); |
| if (host) |
| RemoveAutomationListener(host); |
| } |
| |
| void AutomationEventRouter::UnregisterAllListenersWithDesktopPermission() { |
| for (const auto& request_pair : keepalive_request_uuid_for_worker_) { |
| const WorkerId& worker_id = request_pair.first; |
| const base::Uuid& request_uuid = request_pair.second; |
| content::RenderProcessHost* host = |
| content::RenderProcessHost::FromID(worker_id.render_process_id); |
| |
| if (rph_observers_.IsObservingSource(host)) |
| rph_observers_.RemoveObservation(host); |
| |
| ProcessManager* process_manager = |
| ProcessManager::Get(host->GetBrowserContext()); |
| DCHECK(process_manager); |
| |
| process_manager->DecrementServiceWorkerKeepaliveCount( |
| worker_id, request_uuid, Activity::ACCESSIBILITY, std::string()); |
| } |
| keepalive_request_uuid_for_worker_.clear(); |
| } |
| |
| void AutomationEventRouter::DispatchAccessibilityLocationChange( |
| const ui::AXTreeID& tree_id, |
| const ui::AXLocationChange& details) { |
| if (remote_router_) { |
| remote_router_->DispatchAccessibilityLocationChange(tree_id, details); |
| return; |
| } |
| |
| for (const auto& remote : automation_remote_set_) { |
| remote->DispatchAccessibilityLocationChange(tree_id, details.id, |
| details.new_location); |
| } |
| } |
| |
| void AutomationEventRouter::DispatchAccessibilityScrollChange( |
| const ui::AXTreeID& tree_id, |
| const ui::AXScrollChange& details) { |
| for (const auto& remote : automation_remote_set_) { |
| remote->DispatchAccessibilityScrollChange( |
| tree_id, details.id, details.scroll_x, details.scroll_y); |
| } |
| } |
| |
| void AutomationEventRouter::DispatchTreeDestroyedEvent(ui::AXTreeID tree_id) { |
| if (remote_router_) { |
| remote_router_->DispatchTreeDestroyedEvent(tree_id); |
| return; |
| } |
| |
| for (const auto& remote : automation_remote_set_) { |
| remote->DispatchTreeDestroyedEvent(tree_id); |
| } |
| } |
| |
| void AutomationEventRouter::DispatchActionResult( |
| const ui::AXActionData& data, |
| bool result, |
| content::BrowserContext* browser_context) { |
| CHECK(!data.source_extension_id.empty()); |
| |
| for (const auto& remote : automation_remote_set_) { |
| remote->DispatchActionResult(data, result); |
| } |
| } |
| |
| void AutomationEventRouter::DispatchGetTextLocationDataResult( |
| const ui::AXActionData& data, |
| const std::optional<gfx::Rect>& rect) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| CHECK(!data.source_extension_id.empty()); |
| |
| for (const auto& remote : automation_remote_set_) { |
| remote->DispatchGetTextLocationResult(data, rect); |
| } |
| #else |
| NOTREACHED(); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| void AutomationEventRouter::NotifyAllAutomationExtensionsGone() { |
| for (AutomationEventRouterObserver& observer : observers_) |
| observer.AllAutomationExtensionsGone(); |
| } |
| |
| void AutomationEventRouter::NotifyExtensionListenerAdded() { |
| for (AutomationEventRouterObserver& observer : observers_) |
| observer.ExtensionListenerAdded(); |
| } |
| |
| void AutomationEventRouter::AddObserver( |
| AutomationEventRouterObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void AutomationEventRouter::RemoveObserver( |
| AutomationEventRouterObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| bool AutomationEventRouter::HasObserver( |
| AutomationEventRouterObserver* observer) { |
| return observers_.HasObserver(observer); |
| } |
| |
| void AutomationEventRouter::RegisterRemoteRouter( |
| AutomationEventRouterInterface* router) { |
| // There can be at most 1 remote router. So either this method is setting the |
| // remote router, or it's unsetting the remote router. |
| CHECK(!router || !remote_router_); |
| remote_router_ = router; |
| } |
| |
| AutomationEventRouter::AutomationListener::AutomationListener( |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents) {} |
| |
| AutomationEventRouter::AutomationListener::~AutomationListener() = default; |
| |
| void AutomationEventRouter::AutomationListener::PrimaryPageChanged( |
| content::Page& page) { |
| router->RemoveAutomationListener( |
| content::RenderProcessHost::FromID(render_process_host_id)); |
| } |
| |
| void AutomationEventRouter::Register(const ExtensionId& extension_id, |
| const RenderProcessHostId& listener_rph_id, |
| content::WebContents* web_contents, |
| ui::AXTreeID ax_tree_id, |
| bool desktop) { |
| DCHECK(desktop || ax_tree_id != ui::AXTreeIDUnknown()); |
| |
| AutomationListener* listener = GetListenerByRenderProcessID(listener_rph_id); |
| |
| // We have an entry with that process so update the set of tree ids it wants |
| // to listen to, and update its desktop permission. |
| if (listener) { |
| if (desktop) { |
| listener->desktop = true; |
| } else { |
| listener->tree_ids.insert(ax_tree_id); |
| } |
| |
| return; |
| } |
| |
| // Add a new entry if we don't have one with that process. |
| auto new_listener = std::make_unique<AutomationListener>(web_contents); |
| new_listener->router = this; |
| new_listener->extension_id = extension_id; |
| new_listener->render_process_host_id = listener_rph_id; |
| new_listener->desktop = desktop; |
| if (!desktop) { |
| new_listener->tree_ids.insert(ax_tree_id); |
| } |
| |
| listeners_.emplace_back(std::move(new_listener)); |
| |
| content::RenderProcessHost* host = |
| content::RenderProcessHost::FromID(listener_rph_id); |
| rph_observers_.AddObservation(host); |
| for (AutomationEventRouterObserver& observer : observers_) |
| observer.ExtensionListenerAdded(); |
| |
| if (!desktop) |
| return; |
| |
| ProcessManager* process_manager = |
| ProcessManager::Get(host->GetBrowserContext()); |
| DCHECK(process_manager); |
| |
| std::vector<WorkerId> all_worker_ids = |
| process_manager->GetServiceWorkersForExtension(extension_id); |
| for (const WorkerId& worker_id : all_worker_ids) { |
| if (worker_id.render_process_id != listener_rph_id) { |
| continue; |
| } |
| |
| keepalive_request_uuid_for_worker_[worker_id] = |
| process_manager->IncrementServiceWorkerKeepaliveCount( |
| worker_id, |
| content::ServiceWorkerExternalRequestTimeoutType::kDoesNotTimeout, |
| Activity::ACCESSIBILITY, std::string()); |
| } |
| } |
| |
| void AutomationEventRouter::DispatchAccessibilityEvents( |
| const ui::AXTreeID& tree_id, |
| const std::vector<ui::AXTreeUpdate>& updates, |
| const gfx::Point& mouse_location, |
| const std::vector<ui::AXEvent>& events) { |
| if (remote_router_) { |
| remote_router_->DispatchAccessibilityEvents( |
| tree_id, std::move(updates), mouse_location, std::move(events)); |
| return; |
| } |
| |
| for (const auto& remote : automation_remote_set_) { |
| remote->DispatchAccessibilityEvents(tree_id, updates, mouse_location, |
| events); |
| } |
| } |
| |
| void AutomationEventRouter::RenderProcessExited( |
| content::RenderProcessHost* host, |
| const content::ChildProcessTerminationInfo& info) { |
| RemoveAutomationListener(host); |
| } |
| |
| void AutomationEventRouter::RenderProcessHostDestroyed( |
| content::RenderProcessHost* host) { |
| RemoveAutomationListener(host); |
| } |
| |
| void AutomationEventRouter::RemoveAutomationListener( |
| content::RenderProcessHost* host) { |
| RenderProcessHostId rph_id = host->GetDeprecatedID(); |
| ExtensionId extension_id; |
| for (auto listener = listeners_.begin(); listener != listeners_.end();) { |
| if ((*listener)->render_process_host_id == rph_id) { |
| // Copy the extension ID, as we're about to erase the source. |
| extension_id = (*listener)->extension_id; |
| listener = listeners_.erase(listener); |
| } else { |
| listener++; |
| } |
| } |
| |
| if (rph_observers_.IsObservingSource(host)) |
| rph_observers_.RemoveObservation(host); |
| |
| if (!rph_observers_.IsObservingAnySource()) { |
| for (AutomationEventRouterObserver& observer : observers_) |
| observer.AllAutomationExtensionsGone(); |
| } |
| |
| auto* process_manager = ProcessManager::Get(host->GetBrowserContext()); |
| DCHECK(process_manager); |
| |
| std::vector<WorkerId> all_worker_ids = |
| process_manager->GetServiceWorkersForExtension(extension_id); |
| |
| for (const WorkerId& worker_id : all_worker_ids) { |
| if (worker_id.render_process_id != rph_id) { |
| continue; |
| } |
| const auto& request_uuid_iter = |
| keepalive_request_uuid_for_worker_.find(worker_id); |
| if (request_uuid_iter == keepalive_request_uuid_for_worker_.end()) |
| continue; |
| |
| base::Uuid request_uuid = std::move(request_uuid_iter->second); |
| keepalive_request_uuid_for_worker_.erase(worker_id); |
| |
| process_manager->DecrementServiceWorkerKeepaliveCount( |
| worker_id, request_uuid, Activity::ACCESSIBILITY, std::string()); |
| } |
| } |
| |
| void AutomationEventRouter::TreeRemoved(ui::AXTreeID ax_tree_id) { |
| DispatchTreeDestroyedEvent(ax_tree_id); |
| } |
| |
| AutomationEventRouter::AutomationListener* |
| AutomationEventRouter::GetListenerByRenderProcessID( |
| const RenderProcessHostId& listener_rph_id) const { |
| const auto iter = std::ranges::find( |
| listeners_, listener_rph_id, &AutomationListener::render_process_host_id); |
| |
| if (iter != listeners_.end()) { |
| return iter->get(); |
| } |
| return nullptr; |
| } |
| |
| void AutomationEventRouter::BindAutomation( |
| mojo::PendingAssociatedRemote<ax::mojom::Automation> automation) { |
| automation_remote_set_.Add(std::move(automation)); |
| } |
| |
| // static |
| void AutomationEventRouter::BindForRenderer( |
| RenderProcessHostId render_process_id, |
| mojo::PendingAssociatedReceiver< |
| extensions::mojom::RendererAutomationRegistry> receiver) { |
| AutomationEventRouter* router = AutomationEventRouter::GetInstance(); |
| CHECK(router); |
| |
| router->receivers_.Add(router, std::move(receiver), render_process_id); |
| } |
| |
| } // namespace extensions |