Home | History | Annotate | Download | only in automation_internal
      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