automation_internal_api.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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
30namespace extensions {
31class AutomationWebContentsObserver;
32}  // namespace extensions
33
34DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
35
36namespace {
37const int kDesktopProcessID = 0;
38const int kDesktopRoutingID = 0;
39
40const char kCannotRequestAutomationOnPage[] =
41    "Cannot request automation tree on url \"*\". "
42    "Extension manifest must request permission to access this host.";
43}  // namespace
44
45namespace extensions {
46
47bool 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|.
67class AutomationWebContentsObserver
68    : public content::WebContentsObserver,
69      public content::WebContentsUserData<AutomationWebContentsObserver> {
70 public:
71  virtual ~AutomationWebContentsObserver() {}
72
73  // content::WebContentsObserver overrides.
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
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|.
95class RenderWidgetHostActionAdapter : public AutomationActionAdapter {
96 public:
97  explicit RenderWidgetHostActionAdapter(content::RenderWidgetHost* rwh)
98      : rwh_(rwh) {}
99
100  virtual ~RenderWidgetHostActionAdapter() {}
101
102  // AutomationActionAdapter implementation.
103  virtual void DoDefault(int32 id) OVERRIDE {
104    rwh_->AccessibilityDoDefaultAction(id);
105  }
106
107  virtual void Focus(int32 id) OVERRIDE {
108    rwh_->AccessibilitySetFocus(id);
109  }
110
111  virtual void MakeVisible(int32 id) OVERRIDE {
112    rwh_->AccessibilityScrollToMakeVisible(id, gfx::Rect());
113  }
114
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
125ExtensionFunction::ResponseAction
126AutomationInternalEnableTabFunction::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
167ExtensionFunction::ResponseAction
168AutomationInternalPerformActionFunction::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
205ExtensionFunction::ResponseAction
206AutomationInternalPerformActionFunction::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
236ExtensionFunction::ResponseAction
237AutomationInternalEnableDesktopFunction::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