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.
4
5 #include "chrome/browser/extensions/api/automation_internal/automation_internal_api.h"
6
7 #include <vector>
8
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/ui/browser.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/common/extensions/api/automation_internal.h"
17 #include "chrome/common/extensions/manifest_handlers/automation.h"
18 #include "content/public/browser/ax_event_notification_details.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/render_view_host.h"
21 #include "content/public/browser/render_widget_host.h"
22 #include "content/public/browser/render_widget_host_view.h"
23 #include "content/public/browser/web_contents.h"
24 #include "extensions/common/permissions/permissions_data.h"
25
26 #if defined(OS_CHROMEOS)
27 #include "chrome/browser/ui/ash/accessibility/automation_manager_ash.h"
28 #endif
29
30 namespace extensions {
31 class AutomationWebContentsObserver;
32 } // namespace extensions
33
34 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
35
36 namespace {
37 const int kDesktopProcessID = 0;
38 const int kDesktopRoutingID = 0;
39
40 const char kCannotRequestAutomationOnPage[] =
41 "Cannot request automation tree on url \"*\". "
42 "Extension manifest must request permission to access this host.";
43 } // namespace
44
45 namespace extensions {
46
CanRequestAutomation(const Extension * extension,const AutomationInfo * automation_info,const content::WebContents * contents)47 bool CanRequestAutomation(const Extension* extension,
48 const AutomationInfo* automation_info,
49 const content::WebContents* contents) {
50 if (automation_info->desktop)
51 return true;
52
53 const GURL& url = contents->GetURL();
54 // TODO(aboxhall): check for webstore URL
55 if (automation_info->matches.MatchesURL(url))
56 return true;
57
58 int tab_id = ExtensionTabUtil::GetTabId(contents);
59 content::RenderProcessHost* process = contents->GetRenderProcessHost();
60 int process_id = process ? process->GetID() : -1;
61 std::string unused_error;
62 return extension->permissions_data()->CanAccessPage(
63 extension, url, url, tab_id, process_id, &unused_error);
64 }
65
66 // Helper class that receives accessibility data from |WebContents|.
67 class AutomationWebContentsObserver
68 : public content::WebContentsObserver,
69 public content::WebContentsUserData<AutomationWebContentsObserver> {
70 public:
~AutomationWebContentsObserver()71 virtual ~AutomationWebContentsObserver() {}
72
73 // content::WebContentsObserver overrides.
AccessibilityEventReceived(const std::vector<content::AXEventNotificationDetails> & details)74 virtual void AccessibilityEventReceived(
75 const std::vector<content::AXEventNotificationDetails>& details)
76 OVERRIDE {
77 automation_util::DispatchAccessibilityEventsToAutomation(
78 details, browser_context_);
79 }
80
81 private:
82 friend class content::WebContentsUserData<AutomationWebContentsObserver>;
83
AutomationWebContentsObserver(content::WebContents * web_contents)84 AutomationWebContentsObserver(
85 content::WebContents* web_contents)
86 : content::WebContentsObserver(web_contents),
87 browser_context_(web_contents->GetBrowserContext()) {}
88
89 content::BrowserContext* browser_context_;
90
91 DISALLOW_COPY_AND_ASSIGN(AutomationWebContentsObserver);
92 };
93
94 // Helper class that implements an action adapter for a |RenderWidgetHost|.
95 class RenderWidgetHostActionAdapter : public AutomationActionAdapter {
96 public:
RenderWidgetHostActionAdapter(content::RenderWidgetHost * rwh)97 explicit RenderWidgetHostActionAdapter(content::RenderWidgetHost* rwh)
98 : rwh_(rwh) {}
99
~RenderWidgetHostActionAdapter()100 virtual ~RenderWidgetHostActionAdapter() {}
101
102 // AutomationActionAdapter implementation.
DoDefault(int32 id)103 virtual void DoDefault(int32 id) OVERRIDE {
104 rwh_->AccessibilityDoDefaultAction(id);
105 }
106
Focus(int32 id)107 virtual void Focus(int32 id) OVERRIDE {
108 rwh_->AccessibilitySetFocus(id);
109 }
110
MakeVisible(int32 id)111 virtual void MakeVisible(int32 id) OVERRIDE {
112 rwh_->AccessibilityScrollToMakeVisible(id, gfx::Rect());
113 }
114
SetSelection(int32 id,int32 start,int32 end)115 virtual void SetSelection(int32 id, int32 start, int32 end) OVERRIDE {
116 rwh_->AccessibilitySetTextSelection(id, start, end);
117 }
118
119 private:
120 content::RenderWidgetHost* rwh_;
121
122 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostActionAdapter);
123 };
124
125 ExtensionFunction::ResponseAction
Run()126 AutomationInternalEnableTabFunction::Run() {
127 const AutomationInfo* automation_info = AutomationInfo::Get(GetExtension());
128 EXTENSION_FUNCTION_VALIDATE(automation_info);
129
130 using api::automation_internal::EnableTab::Params;
131 scoped_ptr<Params> params(Params::Create(*args_));
132 EXTENSION_FUNCTION_VALIDATE(params.get());
133 content::WebContents* contents = NULL;
134 if (params->tab_id.get()) {
135 int tab_id = *params->tab_id;
136 if (!ExtensionTabUtil::GetTabById(tab_id,
137 GetProfile(),
138 include_incognito(),
139 NULL, /* browser out param*/
140 NULL, /* tab_strip out param */
141 &contents,
142 NULL /* tab_index out param */)) {
143 return RespondNow(
144 Error(tabs_constants::kTabNotFoundError, base::IntToString(tab_id)));
145 }
146 } else {
147 contents = GetCurrentBrowser()->tab_strip_model()->GetActiveWebContents();
148 if (!contents)
149 return RespondNow(Error("No active tab"));
150 }
151 content::RenderWidgetHost* rwh =
152 contents->GetRenderWidgetHostView()->GetRenderWidgetHost();
153 if (!rwh)
154 return RespondNow(Error("Could not enable accessibility for active tab"));
155
156 if (!CanRequestAutomation(GetExtension(), automation_info, contents)) {
157 return RespondNow(
158 Error(kCannotRequestAutomationOnPage, contents->GetURL().spec()));
159 }
160 AutomationWebContentsObserver::CreateForWebContents(contents);
161 rwh->EnableTreeOnlyAccessibilityMode();
162 return RespondNow(
163 ArgumentList(api::automation_internal::EnableTab::Results::Create(
164 rwh->GetProcess()->GetID(), rwh->GetRoutingID())));
165 }
166
167 ExtensionFunction::ResponseAction
Run()168 AutomationInternalPerformActionFunction::Run() {
169 const AutomationInfo* automation_info = AutomationInfo::Get(GetExtension());
170 EXTENSION_FUNCTION_VALIDATE(automation_info && automation_info->interact);
171
172 using api::automation_internal::PerformAction::Params;
173 scoped_ptr<Params> params(Params::Create(*args_));
174 EXTENSION_FUNCTION_VALIDATE(params.get());
175
176 if (params->args.process_id == kDesktopProcessID &&
177 params->args.routing_id == kDesktopRoutingID) {
178 #if defined(OS_CHROMEOS)
179 return RouteActionToAdapter(
180 params.get(), AutomationManagerAsh::GetInstance());
181 #else
182 NOTREACHED();
183 return RespondNow(Error("Unexpected action on desktop automation tree;"
184 " platform does not support desktop automation"));
185 #endif // defined(OS_CHROMEOS)
186 }
187 content::RenderWidgetHost* rwh = content::RenderWidgetHost::FromID(
188 params->args.process_id, params->args.routing_id);
189
190 if (!rwh)
191 return RespondNow(Error("Ignoring action on destroyed node"));
192 if (rwh->IsRenderView()) {
193 const content::RenderViewHost* rvh = content::RenderViewHost::From(rwh);
194 const content::WebContents* contents =
195 content::WebContents::FromRenderViewHost(rvh);
196 if (!CanRequestAutomation(GetExtension(), automation_info, contents)) {
197 return RespondNow(
198 Error(kCannotRequestAutomationOnPage, contents->GetURL().spec()));
199 }
200 }
201 RenderWidgetHostActionAdapter adapter(rwh);
202 return RouteActionToAdapter(params.get(), &adapter);
203 }
204
205 ExtensionFunction::ResponseAction
RouteActionToAdapter(api::automation_internal::PerformAction::Params * params,AutomationActionAdapter * adapter)206 AutomationInternalPerformActionFunction::RouteActionToAdapter(
207 api::automation_internal::PerformAction::Params* params,
208 AutomationActionAdapter* adapter) {
209 int32 automation_id = params->args.automation_node_id;
210 switch (params->args.action_type) {
211 case api::automation_internal::ACTION_TYPE_DODEFAULT:
212 adapter->DoDefault(automation_id);
213 break;
214 case api::automation_internal::ACTION_TYPE_FOCUS:
215 adapter->Focus(automation_id);
216 break;
217 case api::automation_internal::ACTION_TYPE_MAKEVISIBLE:
218 adapter->MakeVisible(automation_id);
219 break;
220 case api::automation_internal::ACTION_TYPE_SETSELECTION: {
221 api::automation_internal::SetSelectionParams selection_params;
222 EXTENSION_FUNCTION_VALIDATE(
223 api::automation_internal::SetSelectionParams::Populate(
224 params->opt_args.additional_properties, &selection_params));
225 adapter->SetSelection(automation_id,
226 selection_params.start_index,
227 selection_params.end_index);
228 break;
229 }
230 default:
231 NOTREACHED();
232 }
233 return RespondNow(NoArguments());
234 }
235
236 ExtensionFunction::ResponseAction
Run()237 AutomationInternalEnableDesktopFunction::Run() {
238 #if defined(OS_CHROMEOS)
239 const AutomationInfo* automation_info = AutomationInfo::Get(GetExtension());
240 if (!automation_info || !automation_info->desktop)
241 return RespondNow(Error("desktop permission must be requested"));
242
243 AutomationManagerAsh::GetInstance()->Enable(browser_context());
244 return RespondNow(NoArguments());
245 #else
246 return RespondNow(Error("getDesktop is unsupported by this platform"));
247 #endif // defined(OS_CHROMEOS)
248 }
249
250 } // namespace extensions
251