Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / api / automation_internal / automation_internal_api.cc
blobafd510a2e2bcd2f0ad2e5e17fcfde827028bc1b4
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"
7 #include <vector>
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"
37 #if defined(USE_AURA)
38 #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
39 #endif
41 namespace extensions {
42 class AutomationWebContentsObserver;
43 } // namespace extensions
45 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver);
47 namespace extensions {
49 namespace {
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 {
65 public:
66 QuerySelectorHandler(
67 content::WebContents* web_contents,
68 int request_id,
69 int acc_obj_id,
70 const base::string16& query,
71 const extensions::AutomationInternalQuerySelectorFunction::Callback&
72 callback)
73 : content::WebContentsObserver(web_contents),
74 request_id_(request_id),
75 callback_(callback) {
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)
86 return false;
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))
92 return false;
94 if (message_request_id != request_id_)
95 return false;
97 IPC_BEGIN_MESSAGE_MAP(QuerySelectorHandler, message)
98 IPC_MESSAGE_HANDLER(ExtensionHostMsg_AutomationQuerySelector_Result,
99 OnQueryResponse)
100 IPC_END_MESSAGE_MAP()
101 return true;
104 void WebContentsDestroyed() override {
105 callback_.Run(kRendererDestroyed, 0);
106 delete this;
109 private:
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:
116 error_string = "";
117 break;
118 case ExtensionHostMsg_AutomationQuerySelector_Error::kNoMainFrame:
119 error_string = kNoMainFrame;
120 break;
121 case ExtensionHostMsg_AutomationQuerySelector_Error::kNoDocument:
122 error_string = kNoDocument;
123 break;
124 case ExtensionHostMsg_AutomationQuerySelector_Error::kNodeDestroyed:
125 error_string = kNodeDestroyed;
126 break;
128 callback_.Run(error_string, result_acc_obj_id);
129 delete this;
132 int request_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)
140 return true;
142 const GURL& url = contents->GetURL();
143 // TODO(aboxhall): check for webstore URL
144 if (automation_info->matches.MatchesURL(url))
145 return true;
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 {
157 public:
158 explicit RenderFrameHostActionAdapter(content::RenderFrameHost* rfh)
159 : rfh_(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);
180 private:
181 content::RenderFrameHost* rfh_;
183 DISALLOW_COPY_AND_ASSIGN(RenderFrameHostActionAdapter);
186 } // namespace
188 // Helper class that receives accessibility data from |WebContents|.
189 class AutomationWebContentsObserver
190 : public content::WebContentsObserver,
191 public content::WebContentsUserData<AutomationWebContentsObserver> {
192 public:
193 ~AutomationWebContentsObserver() override {}
195 // content::WebContentsObserver overrides.
196 void AccessibilityEventReceived(
197 const std::vector<content::AXEventNotificationDetails>& details)
198 override {
199 std::vector<content::AXEventNotificationDetails>::const_iterator iter =
200 details.begin();
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(
220 tree_id,
221 browser_context_);
224 private:
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,
248 GetProfile(),
249 include_incognito(),
250 NULL, /* browser out param*/
251 NULL, /* tab_strip out param */
252 &contents,
253 NULL /* tab_index out param */)) {
254 return RespondNow(
255 Error(tabs_constants::kTabNotFoundError, base::IntToString(tab_id)));
257 } else {
258 contents = GetCurrentBrowser()->tab_strip_model()->GetActiveWebContents();
259 if (!contents)
260 return RespondNow(Error("No active tab"));
263 content::RenderFrameHost* rfh = contents->GetMainFrame();
264 if (!rfh)
265 return RespondNow(Error("Could not enable accessibility for active tab"));
267 if (!CanRequestAutomation(extension(), automation_info, contents)) {
268 return RespondNow(
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(
279 extension_id(),
280 source_process_id(),
281 params->args.routing_id,
282 ax_tree_id);
284 return RespondNow(ArgumentList(
285 api::automation_internal::EnableTab::Results::Create(ax_tree_id)));
288 ExtensionFunction::ResponseAction AutomationInternalEnableFrameFunction::Run() {
289 // TODO(dtseng): Limited to desktop tree for now pending out of proc iframes.
290 using api::automation_internal::EnableFrame::Params;
292 scoped_ptr<Params> params(Params::Create(*args_));
293 EXTENSION_FUNCTION_VALIDATE(params.get());
295 content::RenderFrameHost* rfh =
296 content::RenderFrameHost::FromAXTreeID(params->tree_id);
297 if (!rfh)
298 return RespondNow(Error("unable to load tab"));
300 content::WebContents* contents =
301 content::WebContents::FromRenderFrameHost(rfh);
302 AutomationWebContentsObserver::CreateForWebContents(contents);
303 contents->EnableTreeOnlyAccessibilityMode();
305 return RespondNow(NoArguments());
308 ExtensionFunction::ResponseAction
309 AutomationInternalPerformActionFunction::Run() {
310 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
311 EXTENSION_FUNCTION_VALIDATE(automation_info && automation_info->interact);
313 using api::automation_internal::PerformAction::Params;
314 scoped_ptr<Params> params(Params::Create(*args_));
315 EXTENSION_FUNCTION_VALIDATE(params.get());
317 if (params->args.tree_id == kDesktopTreeID) {
318 #if defined(USE_AURA)
319 return RouteActionToAdapter(params.get(),
320 AutomationManagerAura::GetInstance());
321 #else
322 NOTREACHED();
323 return RespondNow(Error("Unexpected action on desktop automation tree;"
324 " platform does not support desktop automation"));
325 #endif // defined(USE_AURA)
327 content::RenderFrameHost* rfh =
328 content::RenderFrameHost::FromAXTreeID(params->args.tree_id);
329 if (!rfh)
330 return RespondNow(Error("Ignoring action on destroyed node"));
332 const content::WebContents* contents =
333 content::WebContents::FromRenderFrameHost(rfh);
334 if (!CanRequestAutomation(extension(), automation_info, contents)) {
335 return RespondNow(
336 Error(kCannotRequestAutomationOnPage, contents->GetURL().spec()));
339 RenderFrameHostActionAdapter adapter(rfh);
340 return RouteActionToAdapter(params.get(), &adapter);
343 ExtensionFunction::ResponseAction
344 AutomationInternalPerformActionFunction::RouteActionToAdapter(
345 api::automation_internal::PerformAction::Params* params,
346 AutomationActionAdapter* adapter) {
347 int32 automation_id = params->args.automation_node_id;
348 switch (params->args.action_type) {
349 case api::automation_internal::ACTION_TYPE_DODEFAULT:
350 adapter->DoDefault(automation_id);
351 break;
352 case api::automation_internal::ACTION_TYPE_FOCUS:
353 adapter->Focus(automation_id);
354 break;
355 case api::automation_internal::ACTION_TYPE_MAKEVISIBLE:
356 adapter->MakeVisible(automation_id);
357 break;
358 case api::automation_internal::ACTION_TYPE_SETSELECTION: {
359 api::automation_internal::SetSelectionParams selection_params;
360 EXTENSION_FUNCTION_VALIDATE(
361 api::automation_internal::SetSelectionParams::Populate(
362 params->opt_args.additional_properties, &selection_params));
363 adapter->SetSelection(automation_id,
364 selection_params.start_index,
365 selection_params.end_index);
366 break;
368 case api::automation_internal::ACTION_TYPE_SHOWCONTEXTMENU: {
369 adapter->ShowContextMenu(automation_id);
370 break;
372 default:
373 NOTREACHED();
375 return RespondNow(NoArguments());
378 ExtensionFunction::ResponseAction
379 AutomationInternalEnableDesktopFunction::Run() {
380 #if defined(USE_AURA)
381 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
382 if (!automation_info || !automation_info->desktop)
383 return RespondNow(Error("desktop permission must be requested"));
385 using api::automation_internal::EnableDesktop::Params;
386 scoped_ptr<Params> params(Params::Create(*args_));
387 EXTENSION_FUNCTION_VALIDATE(params.get());
389 // This gets removed when the extension process dies.
390 AutomationEventRouter::GetInstance()->RegisterListenerWithDesktopPermission(
391 extension_id(),
392 source_process_id(),
393 params->routing_id);
395 AutomationManagerAura::GetInstance()->Enable(browser_context());
396 return RespondNow(NoArguments());
397 #else
398 return RespondNow(Error("getDesktop is unsupported by this platform"));
399 #endif // defined(USE_AURA)
402 // static
403 int AutomationInternalQuerySelectorFunction::query_request_id_counter_ = 0;
405 ExtensionFunction::ResponseAction
406 AutomationInternalQuerySelectorFunction::Run() {
407 const AutomationInfo* automation_info = AutomationInfo::Get(extension());
408 EXTENSION_FUNCTION_VALIDATE(automation_info);
410 using api::automation_internal::QuerySelector::Params;
411 scoped_ptr<Params> params(Params::Create(*args_));
412 EXTENSION_FUNCTION_VALIDATE(params.get());
414 if (params->args.tree_id == kDesktopTreeID) {
415 return RespondNow(
416 Error("domQuerySelector queries may not be used on the desktop."));
418 content::RenderFrameHost* rfh =
419 content::RenderFrameHost::FromAXTreeID(params->args.tree_id);
420 if (!rfh)
421 return RespondNow(Error("domQuerySelector query sent on destroyed tree."));
423 content::WebContents* contents =
424 content::WebContents::FromRenderFrameHost(rfh);
426 int request_id = query_request_id_counter_++;
427 base::string16 selector = base::UTF8ToUTF16(params->args.selector);
429 // QuerySelectorHandler handles IPCs and deletes itself on completion.
430 new QuerySelectorHandler(
431 contents, request_id, params->args.automation_node_id, selector,
432 base::Bind(&AutomationInternalQuerySelectorFunction::OnResponse, this));
434 return RespondLater();
437 void AutomationInternalQuerySelectorFunction::OnResponse(
438 const std::string& error,
439 int result_acc_obj_id) {
440 if (!error.empty()) {
441 Respond(Error(error));
442 return;
445 Respond(OneArgument(new base::FundamentalValue(result_acc_obj_id)));
448 } // namespace extensions