• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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