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/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_frame_host.h"
20 #include "content/public/browser/render_process_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"
26 #if defined(OS_CHROMEOS)
27 #include "chrome/browser/ui/ash/accessibility/automation_manager_ash.h"
30 namespace extensions {
31 class AutomationWebContentsObserver;
32 } // namespace extensions
34 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
37 const int kDesktopProcessID = 0;
38 const int kDesktopRoutingID = 0;
40 const char kCannotRequestAutomationOnPage[] =
41 "Cannot request automation tree on url \"*\". "
42 "Extension manifest must request permission to access this host.";
45 namespace extensions {
47 bool CanRequestAutomation(const Extension* extension,
48 const AutomationInfo* automation_info,
49 const content::WebContents* contents) {
50 if (automation_info->desktop)
53 const GURL& url = contents->GetURL();
54 // TODO(aboxhall): check for webstore URL
55 if (automation_info->matches.MatchesURL(url))
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);
66 // Helper class that receives accessibility data from |WebContents|.
67 class AutomationWebContentsObserver
68 : public content::WebContentsObserver,
69 public content::WebContentsUserData<AutomationWebContentsObserver> {
71 virtual ~AutomationWebContentsObserver() {}
73 // content::WebContentsObserver overrides.
74 virtual void AccessibilityEventReceived(
75 const std::vector<content::AXEventNotificationDetails>& details)
77 automation_util::DispatchAccessibilityEventsToAutomation(
78 details, browser_context_);
81 virtual void RenderFrameDeleted(
82 content::RenderFrameHost* render_frame_host) OVERRIDE {
83 automation_util::DispatchTreeDestroyedEventToAutomation(
84 render_frame_host->GetProcess()->GetID(),
85 render_frame_host->GetRoutingID(),
90 friend class content::WebContentsUserData<AutomationWebContentsObserver>;
92 AutomationWebContentsObserver(
93 content::WebContents* web_contents)
94 : content::WebContentsObserver(web_contents),
95 browser_context_(web_contents->GetBrowserContext()) {}
97 content::BrowserContext* browser_context_;
99 DISALLOW_COPY_AND_ASSIGN(AutomationWebContentsObserver);
102 // Helper class that implements an action adapter for a |RenderFrameHost|.
103 class RenderFrameHostActionAdapter : public AutomationActionAdapter {
105 explicit RenderFrameHostActionAdapter(content::RenderFrameHost* rfh)
108 virtual ~RenderFrameHostActionAdapter() {}
110 // AutomationActionAdapter implementation.
111 virtual void DoDefault(int32 id) OVERRIDE {
112 rfh_->AccessibilityDoDefaultAction(id);
115 virtual void Focus(int32 id) OVERRIDE {
116 rfh_->AccessibilitySetFocus(id);
119 virtual void MakeVisible(int32 id) OVERRIDE {
120 rfh_->AccessibilityScrollToMakeVisible(id, gfx::Rect());
123 virtual void SetSelection(int32 id, int32 start, int32 end) OVERRIDE {
124 rfh_->AccessibilitySetTextSelection(id, start, end);
128 content::RenderFrameHost* rfh_;
130 DISALLOW_COPY_AND_ASSIGN(RenderFrameHostActionAdapter);
133 ExtensionFunction::ResponseAction
134 AutomationInternalEnableTabFunction::Run() {
135 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
136 EXTENSION_FUNCTION_VALIDATE(automation_info);
138 using api::automation_internal::EnableTab::Params;
139 scoped_ptr<Params> params(Params::Create(*args_));
140 EXTENSION_FUNCTION_VALIDATE(params.get());
141 content::WebContents* contents = NULL;
142 if (params->tab_id.get()) {
143 int tab_id = *params->tab_id;
144 if (!ExtensionTabUtil::GetTabById(tab_id,
147 NULL, /* browser out param*/
148 NULL, /* tab_strip out param */
150 NULL /* tab_index out param */)) {
152 Error(tabs_constants::kTabNotFoundError, base::IntToString(tab_id)));
155 contents = GetCurrentBrowser()->tab_strip_model()->GetActiveWebContents();
157 return RespondNow(Error("No active tab"));
159 content::RenderFrameHost* rfh = contents->GetMainFrame();
161 return RespondNow(Error("Could not enable accessibility for active tab"));
163 if (!CanRequestAutomation(extension(), automation_info, contents)) {
165 Error(kCannotRequestAutomationOnPage, contents->GetURL().spec()));
167 AutomationWebContentsObserver::CreateForWebContents(contents);
168 contents->EnableTreeOnlyAccessibilityMode();
170 ArgumentList(api::automation_internal::EnableTab::Results::Create(
171 rfh->GetProcess()->GetID(), rfh->GetRoutingID())));
174 ExtensionFunction::ResponseAction
175 AutomationInternalPerformActionFunction::Run() {
176 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
177 EXTENSION_FUNCTION_VALIDATE(automation_info && automation_info->interact);
179 using api::automation_internal::PerformAction::Params;
180 scoped_ptr<Params> params(Params::Create(*args_));
181 EXTENSION_FUNCTION_VALIDATE(params.get());
183 if (params->args.process_id == kDesktopProcessID &&
184 params->args.routing_id == kDesktopRoutingID) {
185 #if defined(OS_CHROMEOS)
186 return RouteActionToAdapter(
187 params.get(), AutomationManagerAsh::GetInstance());
190 return RespondNow(Error("Unexpected action on desktop automation tree;"
191 " platform does not support desktop automation"));
192 #endif // defined(OS_CHROMEOS)
194 content::RenderFrameHost* rfh =
195 content::RenderFrameHost::FromID(params->args.process_id,
196 params->args.routing_id);
198 return RespondNow(Error("Ignoring action on destroyed node"));
200 const content::WebContents* contents =
201 content::WebContents::FromRenderFrameHost(rfh);
202 if (!CanRequestAutomation(extension(), automation_info, contents)) {
204 Error(kCannotRequestAutomationOnPage, contents->GetURL().spec()));
207 RenderFrameHostActionAdapter adapter(rfh);
208 return RouteActionToAdapter(params.get(), &adapter);
211 ExtensionFunction::ResponseAction
212 AutomationInternalPerformActionFunction::RouteActionToAdapter(
213 api::automation_internal::PerformAction::Params* params,
214 AutomationActionAdapter* adapter) {
215 int32 automation_id = params->args.automation_node_id;
216 switch (params->args.action_type) {
217 case api::automation_internal::ACTION_TYPE_DODEFAULT:
218 adapter->DoDefault(automation_id);
220 case api::automation_internal::ACTION_TYPE_FOCUS:
221 adapter->Focus(automation_id);
223 case api::automation_internal::ACTION_TYPE_MAKEVISIBLE:
224 adapter->MakeVisible(automation_id);
226 case api::automation_internal::ACTION_TYPE_SETSELECTION: {
227 api::automation_internal::SetSelectionParams selection_params;
228 EXTENSION_FUNCTION_VALIDATE(
229 api::automation_internal::SetSelectionParams::Populate(
230 params->opt_args.additional_properties, &selection_params));
231 adapter->SetSelection(automation_id,
232 selection_params.start_index,
233 selection_params.end_index);
239 return RespondNow(NoArguments());
242 ExtensionFunction::ResponseAction
243 AutomationInternalEnableDesktopFunction::Run() {
244 #if defined(OS_CHROMEOS)
245 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
246 if (!automation_info || !automation_info->desktop)
247 return RespondNow(Error("desktop permission must be requested"));
249 AutomationManagerAsh::GetInstance()->Enable(browser_context());
250 return RespondNow(NoArguments());
252 return RespondNow(Error("getDesktop is unsupported by this platform"));
253 #endif // defined(OS_CHROMEOS)
256 } // namespace extensions