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 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: 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|. 95 class 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 125 ExtensionFunction::ResponseAction 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 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 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 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