1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/automation_internal/automation_internal_api.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h"
11 #include "chrome/browser/extensions/api/automation_internal/automation_util.h"
12 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
13 #include "chrome/browser/extensions/extension_tab_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "chrome/common/extensions/api/automation_internal.h"
18 #include "chrome/common/extensions/manifest_handlers/automation.h"
19 #include "content/public/browser/ax_event_notification_details.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/render_widget_host.h"
23 #include "content/public/browser/render_widget_host_view.h"
24 #include "content/public/browser/web_contents.h"
25 #include "extensions/common/permissions/permissions_data.h"
27 #if defined(OS_CHROMEOS)
28 #include "chrome/browser/ui/ash/accessibility/automation_manager_ash.h"
31 namespace extensions {
32 class AutomationWebContentsObserver;
33 } // namespace extensions
35 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
38 const int kDesktopProcessID = 0;
39 const int kDesktopRoutingID = 0;
41 const char kCannotRequestAutomationOnPage[] =
42 "Cannot request automation tree on url \"*\". "
43 "Extension manifest must request permission to access this host.";
46 namespace extensions {
48 bool CanRequestAutomation(const Extension* extension,
49 const AutomationInfo* automation_info,
50 const content::WebContents* contents) {
51 if (automation_info->desktop)
54 const GURL& url = contents->GetURL();
55 // TODO(aboxhall): check for webstore URL
56 if (automation_info->matches.MatchesURL(url))
59 int tab_id = ExtensionTabUtil::GetTabId(contents);
60 content::RenderProcessHost* process = contents->GetRenderProcessHost();
61 int process_id = process ? process->GetID() : -1;
62 std::string unused_error;
63 return extension->permissions_data()->CanAccessPage(
64 extension, url, url, tab_id, process_id, &unused_error);
67 // Helper class that receives accessibility data from |WebContents|.
68 class AutomationWebContentsObserver
69 : public content::WebContentsObserver,
70 public content::WebContentsUserData<AutomationWebContentsObserver> {
72 virtual ~AutomationWebContentsObserver() {}
74 // content::WebContentsObserver overrides.
75 virtual void AccessibilityEventReceived(
76 const std::vector<content::AXEventNotificationDetails>& details)
78 automation_util::DispatchAccessibilityEventsToAutomation(
79 details, browser_context_,
80 web_contents()->GetContainerBounds().OffsetFromOrigin());
83 virtual void RenderFrameDeleted(
84 content::RenderFrameHost* render_frame_host) OVERRIDE {
85 automation_util::DispatchTreeDestroyedEventToAutomation(
86 render_frame_host->GetProcess()->GetID(),
87 render_frame_host->GetRoutingID(),
92 friend class content::WebContentsUserData<AutomationWebContentsObserver>;
94 AutomationWebContentsObserver(
95 content::WebContents* web_contents)
96 : content::WebContentsObserver(web_contents),
97 browser_context_(web_contents->GetBrowserContext()) {}
99 content::BrowserContext* browser_context_;
101 DISALLOW_COPY_AND_ASSIGN(AutomationWebContentsObserver);
104 // Helper class that implements an action adapter for a |RenderFrameHost|.
105 class RenderFrameHostActionAdapter : public AutomationActionAdapter {
107 explicit RenderFrameHostActionAdapter(content::RenderFrameHost* rfh)
110 virtual ~RenderFrameHostActionAdapter() {}
112 // AutomationActionAdapter implementation.
113 virtual void DoDefault(int32 id) OVERRIDE {
114 rfh_->AccessibilityDoDefaultAction(id);
117 virtual void Focus(int32 id) OVERRIDE {
118 rfh_->AccessibilitySetFocus(id);
121 virtual void MakeVisible(int32 id) OVERRIDE {
122 rfh_->AccessibilityScrollToMakeVisible(id, gfx::Rect());
125 virtual void SetSelection(int32 id, int32 start, int32 end) OVERRIDE {
126 rfh_->AccessibilitySetTextSelection(id, start, end);
130 content::RenderFrameHost* rfh_;
132 DISALLOW_COPY_AND_ASSIGN(RenderFrameHostActionAdapter);
135 ExtensionFunction::ResponseAction
136 AutomationInternalEnableTabFunction::Run() {
137 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
138 EXTENSION_FUNCTION_VALIDATE(automation_info);
140 using api::automation_internal::EnableTab::Params;
141 scoped_ptr<Params> params(Params::Create(*args_));
142 EXTENSION_FUNCTION_VALIDATE(params.get());
143 content::WebContents* contents = NULL;
144 if (params->tab_id.get()) {
145 int tab_id = *params->tab_id;
146 if (!ExtensionTabUtil::GetTabById(tab_id,
149 NULL, /* browser out param*/
150 NULL, /* tab_strip out param */
152 NULL /* tab_index out param */)) {
154 Error(tabs_constants::kTabNotFoundError, base::IntToString(tab_id)));
157 contents = GetCurrentBrowser()->tab_strip_model()->GetActiveWebContents();
159 return RespondNow(Error("No active tab"));
161 content::RenderFrameHost* rfh = contents->GetMainFrame();
163 return RespondNow(Error("Could not enable accessibility for active tab"));
165 if (!CanRequestAutomation(extension(), automation_info, contents)) {
167 Error(kCannotRequestAutomationOnPage, contents->GetURL().spec()));
169 AutomationWebContentsObserver::CreateForWebContents(contents);
170 contents->EnableTreeOnlyAccessibilityMode();
172 ArgumentList(api::automation_internal::EnableTab::Results::Create(
173 rfh->GetProcess()->GetID(), rfh->GetRoutingID())));
176 ExtensionFunction::ResponseAction
177 AutomationInternalPerformActionFunction::Run() {
178 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
179 EXTENSION_FUNCTION_VALIDATE(automation_info && automation_info->interact);
181 using api::automation_internal::PerformAction::Params;
182 scoped_ptr<Params> params(Params::Create(*args_));
183 EXTENSION_FUNCTION_VALIDATE(params.get());
185 if (params->args.process_id == kDesktopProcessID &&
186 params->args.routing_id == kDesktopRoutingID) {
187 #if defined(OS_CHROMEOS)
188 return RouteActionToAdapter(
189 params.get(), AutomationManagerAsh::GetInstance());
192 return RespondNow(Error("Unexpected action on desktop automation tree;"
193 " platform does not support desktop automation"));
194 #endif // defined(OS_CHROMEOS)
196 content::RenderFrameHost* rfh =
197 content::RenderFrameHost::FromID(params->args.process_id,
198 params->args.routing_id);
200 return RespondNow(Error("Ignoring action on destroyed node"));
202 const content::WebContents* contents =
203 content::WebContents::FromRenderFrameHost(rfh);
204 if (!CanRequestAutomation(extension(), automation_info, contents)) {
206 Error(kCannotRequestAutomationOnPage, contents->GetURL().spec()));
209 RenderFrameHostActionAdapter adapter(rfh);
210 return RouteActionToAdapter(params.get(), &adapter);
213 ExtensionFunction::ResponseAction
214 AutomationInternalPerformActionFunction::RouteActionToAdapter(
215 api::automation_internal::PerformAction::Params* params,
216 AutomationActionAdapter* adapter) {
217 int32 automation_id = params->args.automation_node_id;
218 switch (params->args.action_type) {
219 case api::automation_internal::ACTION_TYPE_DODEFAULT:
220 adapter->DoDefault(automation_id);
222 case api::automation_internal::ACTION_TYPE_FOCUS:
223 adapter->Focus(automation_id);
225 case api::automation_internal::ACTION_TYPE_MAKEVISIBLE:
226 adapter->MakeVisible(automation_id);
228 case api::automation_internal::ACTION_TYPE_SETSELECTION: {
229 api::automation_internal::SetSelectionParams selection_params;
230 EXTENSION_FUNCTION_VALIDATE(
231 api::automation_internal::SetSelectionParams::Populate(
232 params->opt_args.additional_properties, &selection_params));
233 adapter->SetSelection(automation_id,
234 selection_params.start_index,
235 selection_params.end_index);
241 return RespondNow(NoArguments());
244 ExtensionFunction::ResponseAction
245 AutomationInternalEnableDesktopFunction::Run() {
246 #if defined(OS_CHROMEOS)
247 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
248 if (!automation_info || !automation_info->desktop)
249 return RespondNow(Error("desktop permission must be requested"));
251 AutomationManagerAsh::GetInstance()->Enable(browser_context());
252 return RespondNow(NoArguments());
254 return RespondNow(Error("getDesktop is unsupported by this platform"));
255 #endif // defined(OS_CHROMEOS)
258 } // namespace extensions