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