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/string16.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h"
13 #include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
14 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
15 #include "chrome/browser/extensions/extension_tab_util.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/common/extensions/api/automation_internal.h"
20 #include "chrome/common/extensions/chrome_extension_messages.h"
21 #include "chrome/common/extensions/manifest_handlers/automation.h"
22 #include "content/public/browser/ax_event_notification_details.h"
23 #include "content/public/browser/browser_accessibility_state.h"
24 #include "content/public/browser/browser_context.h"
25 #include "content/public/browser/browser_plugin_guest_manager.h"
26 #include "content/public/browser/render_frame_host.h"
27 #include "content/public/browser/render_process_host.h"
28 #include "content/public/browser/render_view_host.h"
29 #include "content/public/browser/render_widget_host.h"
30 #include "content/public/browser/render_widget_host_view.h"
31 #include "content/public/browser/web_contents.h"
32 #include "content/public/browser/web_contents_observer.h"
33 #include "content/public/browser/web_contents_user_data.h"
34 #include "extensions/common/extension_messages.h"
35 #include "extensions/common/permissions/permissions_data.h"
38 #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
41 namespace extensions
{
42 class AutomationWebContentsObserver
;
43 } // namespace extensions
45 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver
);
47 namespace extensions
{
51 const int kDesktopTreeID
= 0;
52 const char kCannotRequestAutomationOnPage
[] =
53 "Cannot request automation tree on url \"*\". "
54 "Extension manifest must request permission to access this host.";
55 const char kRendererDestroyed
[] = "The tab was closed.";
56 const char kNoMainFrame
[] = "No main frame.";
57 const char kNoDocument
[] = "No document.";
58 const char kNodeDestroyed
[] =
59 "domQuerySelector sent on node which is no longer in the tree.";
61 // Handles sending and receiving IPCs for a single querySelector request. On
62 // creation, sends the request IPC, and is destroyed either when the response is
63 // received or the renderer is destroyed.
64 class QuerySelectorHandler
: public content::WebContentsObserver
{
67 content::WebContents
* web_contents
,
70 const base::string16
& query
,
71 const extensions::AutomationInternalQuerySelectorFunction::Callback
&
73 : content::WebContentsObserver(web_contents
),
74 request_id_(request_id
),
76 content::RenderViewHost
* rvh
= web_contents
->GetRenderViewHost();
78 rvh
->Send(new ExtensionMsg_AutomationQuerySelector(
79 rvh
->GetRoutingID(), request_id
, acc_obj_id
, query
));
82 ~QuerySelectorHandler() override
{}
84 bool OnMessageReceived(const IPC::Message
& message
) override
{
85 if (message
.type() != ExtensionHostMsg_AutomationQuerySelector_Result::ID
)
88 // There may be several requests in flight; check this response matches.
89 int message_request_id
= 0;
90 base::PickleIterator
iter(message
);
91 if (!iter
.ReadInt(&message_request_id
))
94 if (message_request_id
!= request_id_
)
97 IPC_BEGIN_MESSAGE_MAP(QuerySelectorHandler
, message
)
98 IPC_MESSAGE_HANDLER(ExtensionHostMsg_AutomationQuerySelector_Result
,
100 IPC_END_MESSAGE_MAP()
104 void WebContentsDestroyed() override
{
105 callback_
.Run(kRendererDestroyed
, 0);
110 void OnQueryResponse(int request_id
,
111 ExtensionHostMsg_AutomationQuerySelector_Error error
,
112 int result_acc_obj_id
) {
113 std::string error_string
;
114 switch (error
.value
) {
115 case ExtensionHostMsg_AutomationQuerySelector_Error::kNone
:
118 case ExtensionHostMsg_AutomationQuerySelector_Error::kNoMainFrame
:
119 error_string
= kNoMainFrame
;
121 case ExtensionHostMsg_AutomationQuerySelector_Error::kNoDocument
:
122 error_string
= kNoDocument
;
124 case ExtensionHostMsg_AutomationQuerySelector_Error::kNodeDestroyed
:
125 error_string
= kNodeDestroyed
;
128 callback_
.Run(error_string
, result_acc_obj_id
);
133 const extensions::AutomationInternalQuerySelectorFunction::Callback callback_
;
136 bool CanRequestAutomation(const Extension
* extension
,
137 const AutomationInfo
* automation_info
,
138 const content::WebContents
* contents
) {
139 if (automation_info
->desktop
)
142 const GURL
& url
= contents
->GetURL();
143 // TODO(aboxhall): check for webstore URL
144 if (automation_info
->matches
.MatchesURL(url
))
147 int tab_id
= ExtensionTabUtil::GetTabId(contents
);
148 content::RenderProcessHost
* process
= contents
->GetRenderProcessHost();
149 int process_id
= process
? process
->GetID() : -1;
150 std::string unused_error
;
151 return extension
->permissions_data()->CanAccessPage(
152 extension
, url
, tab_id
, process_id
, &unused_error
);
155 // Helper class that implements an action adapter for a |RenderFrameHost|.
156 class RenderFrameHostActionAdapter
: public AutomationActionAdapter
{
158 explicit RenderFrameHostActionAdapter(content::RenderFrameHost
* rfh
)
161 virtual ~RenderFrameHostActionAdapter() {}
163 // AutomationActionAdapter implementation.
164 void DoDefault(int32 id
) override
{ rfh_
->AccessibilityDoDefaultAction(id
); }
166 void Focus(int32 id
) override
{ rfh_
->AccessibilitySetFocus(id
); }
168 void MakeVisible(int32 id
) override
{
169 rfh_
->AccessibilityScrollToMakeVisible(id
, gfx::Rect());
172 void SetSelection(int32 id
, int32 start
, int32 end
) override
{
173 rfh_
->AccessibilitySetTextSelection(id
, start
, end
);
176 void ShowContextMenu(int32 id
) override
{
177 rfh_
->AccessibilityShowContextMenu(id
);
181 content::RenderFrameHost
* rfh_
;
183 DISALLOW_COPY_AND_ASSIGN(RenderFrameHostActionAdapter
);
188 // Helper class that receives accessibility data from |WebContents|.
189 class AutomationWebContentsObserver
190 : public content::WebContentsObserver
,
191 public content::WebContentsUserData
<AutomationWebContentsObserver
> {
193 ~AutomationWebContentsObserver() override
{}
195 // content::WebContentsObserver overrides.
196 void AccessibilityEventReceived(
197 const std::vector
<content::AXEventNotificationDetails
>& details
)
199 std::vector
<content::AXEventNotificationDetails
>::const_iterator iter
=
201 for (; iter
!= details
.end(); ++iter
) {
202 const content::AXEventNotificationDetails
& event
= *iter
;
203 ExtensionMsg_AccessibilityEventParams params
;
204 params
.tree_id
= event
.ax_tree_id
;
205 params
.id
= event
.id
;
206 params
.event_type
= event
.event_type
;
207 params
.update
= event
.update
;
208 params
.location_offset
=
209 web_contents()->GetContainerBounds().OffsetFromOrigin();
211 AutomationEventRouter
* router
= AutomationEventRouter::GetInstance();
212 router
->DispatchAccessibilityEvent(params
);
216 void RenderFrameDeleted(
217 content::RenderFrameHost
* render_frame_host
) override
{
218 int tree_id
= render_frame_host
->GetAXTreeID();
219 AutomationEventRouter::GetInstance()->DispatchTreeDestroyedEvent(
225 friend class content::WebContentsUserData
<AutomationWebContentsObserver
>;
227 explicit AutomationWebContentsObserver(content::WebContents
* web_contents
)
228 : content::WebContentsObserver(web_contents
),
229 browser_context_(web_contents
->GetBrowserContext()) {}
231 content::BrowserContext
* browser_context_
;
233 DISALLOW_COPY_AND_ASSIGN(AutomationWebContentsObserver
);
236 ExtensionFunction::ResponseAction
237 AutomationInternalEnableTabFunction::Run() {
238 const AutomationInfo
* automation_info
= AutomationInfo::Get(extension());
239 EXTENSION_FUNCTION_VALIDATE(automation_info
);
241 using api::automation_internal::EnableTab::Params
;
242 scoped_ptr
<Params
> params(Params::Create(*args_
));
243 EXTENSION_FUNCTION_VALIDATE(params
.get());
244 content::WebContents
* contents
= NULL
;
245 if (params
->args
.tab_id
.get()) {
246 int tab_id
= *params
->args
.tab_id
;
247 if (!ExtensionTabUtil::GetTabById(tab_id
,
250 NULL
, /* browser out param*/
251 NULL
, /* tab_strip out param */
253 NULL
/* tab_index out param */)) {
255 Error(tabs_constants::kTabNotFoundError
, base::IntToString(tab_id
)));
258 contents
= GetCurrentBrowser()->tab_strip_model()->GetActiveWebContents();
260 return RespondNow(Error("No active tab"));
263 content::RenderFrameHost
* rfh
= contents
->GetMainFrame();
265 return RespondNow(Error("Could not enable accessibility for active tab"));
267 if (!CanRequestAutomation(extension(), automation_info
, contents
)) {
269 Error(kCannotRequestAutomationOnPage
, contents
->GetURL().spec()));
272 AutomationWebContentsObserver::CreateForWebContents(contents
);
273 contents
->EnableTreeOnlyAccessibilityMode();
275 int ax_tree_id
= rfh
->GetAXTreeID();
277 // This gets removed when the extension process dies.
278 AutomationEventRouter::GetInstance()->RegisterListenerForOneTree(
280 params
->args
.routing_id
,
283 return RespondNow(ArgumentList(
284 api::automation_internal::EnableTab::Results::Create(ax_tree_id
)));
287 ExtensionFunction::ResponseAction
AutomationInternalEnableFrameFunction::Run() {
288 // TODO(dtseng): Limited to desktop tree for now pending out of proc iframes.
289 using api::automation_internal::EnableFrame::Params
;
291 scoped_ptr
<Params
> params(Params::Create(*args_
));
292 EXTENSION_FUNCTION_VALIDATE(params
.get());
294 content::RenderFrameHost
* rfh
=
295 content::RenderFrameHost::FromAXTreeID(params
->tree_id
);
297 return RespondNow(Error("unable to load tab"));
299 content::WebContents
* contents
=
300 content::WebContents::FromRenderFrameHost(rfh
);
301 AutomationWebContentsObserver::CreateForWebContents(contents
);
302 contents
->EnableTreeOnlyAccessibilityMode();
304 return RespondNow(NoArguments());
307 ExtensionFunction::ResponseAction
308 AutomationInternalPerformActionFunction::Run() {
309 const AutomationInfo
* automation_info
= AutomationInfo::Get(extension());
310 EXTENSION_FUNCTION_VALIDATE(automation_info
&& automation_info
->interact
);
312 using api::automation_internal::PerformAction::Params
;
313 scoped_ptr
<Params
> params(Params::Create(*args_
));
314 EXTENSION_FUNCTION_VALIDATE(params
.get());
316 if (params
->args
.tree_id
== kDesktopTreeID
) {
317 #if defined(USE_AURA)
318 return RouteActionToAdapter(params
.get(),
319 AutomationManagerAura::GetInstance());
322 return RespondNow(Error("Unexpected action on desktop automation tree;"
323 " platform does not support desktop automation"));
324 #endif // defined(USE_AURA)
326 content::RenderFrameHost
* rfh
=
327 content::RenderFrameHost::FromAXTreeID(params
->args
.tree_id
);
329 return RespondNow(Error("Ignoring action on destroyed node"));
331 const content::WebContents
* contents
=
332 content::WebContents::FromRenderFrameHost(rfh
);
333 if (!CanRequestAutomation(extension(), automation_info
, contents
)) {
335 Error(kCannotRequestAutomationOnPage
, contents
->GetURL().spec()));
338 RenderFrameHostActionAdapter
adapter(rfh
);
339 return RouteActionToAdapter(params
.get(), &adapter
);
342 ExtensionFunction::ResponseAction
343 AutomationInternalPerformActionFunction::RouteActionToAdapter(
344 api::automation_internal::PerformAction::Params
* params
,
345 AutomationActionAdapter
* adapter
) {
346 int32 automation_id
= params
->args
.automation_node_id
;
347 switch (params
->args
.action_type
) {
348 case api::automation_internal::ACTION_TYPE_DODEFAULT
:
349 adapter
->DoDefault(automation_id
);
351 case api::automation_internal::ACTION_TYPE_FOCUS
:
352 adapter
->Focus(automation_id
);
354 case api::automation_internal::ACTION_TYPE_MAKEVISIBLE
:
355 adapter
->MakeVisible(automation_id
);
357 case api::automation_internal::ACTION_TYPE_SETSELECTION
: {
358 api::automation_internal::SetSelectionParams selection_params
;
359 EXTENSION_FUNCTION_VALIDATE(
360 api::automation_internal::SetSelectionParams::Populate(
361 params
->opt_args
.additional_properties
, &selection_params
));
362 adapter
->SetSelection(automation_id
,
363 selection_params
.start_index
,
364 selection_params
.end_index
);
367 case api::automation_internal::ACTION_TYPE_SHOWCONTEXTMENU
: {
368 adapter
->ShowContextMenu(automation_id
);
374 return RespondNow(NoArguments());
377 ExtensionFunction::ResponseAction
378 AutomationInternalEnableDesktopFunction::Run() {
379 #if defined(USE_AURA)
380 const AutomationInfo
* automation_info
= AutomationInfo::Get(extension());
381 if (!automation_info
|| !automation_info
->desktop
)
382 return RespondNow(Error("desktop permission must be requested"));
384 using api::automation_internal::EnableDesktop::Params
;
385 scoped_ptr
<Params
> params(Params::Create(*args_
));
386 EXTENSION_FUNCTION_VALIDATE(params
.get());
388 // This gets removed when the extension process dies.
389 AutomationEventRouter::GetInstance()->RegisterListenerWithDesktopPermission(
393 AutomationManagerAura::GetInstance()->Enable(browser_context());
394 return RespondNow(NoArguments());
396 return RespondNow(Error("getDesktop is unsupported by this platform"));
397 #endif // defined(USE_AURA)
401 int AutomationInternalQuerySelectorFunction::query_request_id_counter_
= 0;
403 ExtensionFunction::ResponseAction
404 AutomationInternalQuerySelectorFunction::Run() {
405 const AutomationInfo
* automation_info
= AutomationInfo::Get(extension());
406 EXTENSION_FUNCTION_VALIDATE(automation_info
);
408 using api::automation_internal::QuerySelector::Params
;
409 scoped_ptr
<Params
> params(Params::Create(*args_
));
410 EXTENSION_FUNCTION_VALIDATE(params
.get());
412 if (params
->args
.tree_id
== kDesktopTreeID
) {
414 Error("domQuerySelector queries may not be used on the desktop."));
416 content::RenderFrameHost
* rfh
=
417 content::RenderFrameHost::FromAXTreeID(params
->args
.tree_id
);
419 return RespondNow(Error("domQuerySelector query sent on destroyed tree."));
421 content::WebContents
* contents
=
422 content::WebContents::FromRenderFrameHost(rfh
);
424 int request_id
= query_request_id_counter_
++;
425 base::string16 selector
= base::UTF8ToUTF16(params
->args
.selector
);
427 // QuerySelectorHandler handles IPCs and deletes itself on completion.
428 new QuerySelectorHandler(
429 contents
, request_id
, params
->args
.automation_node_id
, selector
,
430 base::Bind(&AutomationInternalQuerySelectorFunction::OnResponse
, this));
432 return RespondLater();
435 void AutomationInternalQuerySelectorFunction::OnResponse(
436 const std::string
& error
,
437 int result_acc_obj_id
) {
438 if (!error
.empty()) {
439 Respond(Error(error
));
443 Respond(OneArgument(new base::FundamentalValue(result_acc_obj_id
)));
446 } // namespace extensions