| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/automation_internal/automation_internal_api.h" |
| |
| #include <vector> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "chrome/browser/extensions/api/automation_internal/automation_action_adapter.h" |
| #include "chrome/browser/extensions/api/automation_internal/automation_util.h" |
| #include "chrome/browser/extensions/api/tabs/tabs_constants.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/extensions/api/automation_internal.h" |
| #include "chrome/common/extensions/manifest_handlers/automation.h" |
| #include "content/public/browser/ax_event_notification_details.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chrome/browser/ui/ash/accessibility/automation_manager_ash.h" |
| #endif |
| |
| namespace extensions { |
| class AutomationWebContentsObserver; |
| } // namespace extensions |
| |
| DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::AutomationWebContentsObserver); |
| |
| namespace { |
| const int kDesktopProcessID = 0; |
| const int kDesktopRoutingID = 0; |
| |
| const char kCannotRequestAutomationOnPage[] = |
| "Cannot request automation tree on url \"*\". " |
| "Extension manifest must request permission to access this host."; |
| } // namespace |
| |
| namespace extensions { |
| |
| bool CanRequestAutomation(const Extension* extension, |
| const AutomationInfo* automation_info, |
| const content::WebContents* contents) { |
| if (automation_info->desktop) |
| return true; |
| |
| const GURL& url = contents->GetURL(); |
| // TODO(aboxhall): check for webstore URL |
| if (automation_info->matches.MatchesURL(url)) |
| return true; |
| |
| int tab_id = ExtensionTabUtil::GetTabId(contents); |
| content::RenderProcessHost* process = contents->GetRenderProcessHost(); |
| int process_id = process ? process->GetID() : -1; |
| std::string unused_error; |
| return extension->permissions_data()->CanAccessPage( |
| extension, url, url, tab_id, process_id, &unused_error); |
| } |
| |
| // Helper class that receives accessibility data from |WebContents|. |
| class AutomationWebContentsObserver |
| : public content::WebContentsObserver, |
| public content::WebContentsUserData<AutomationWebContentsObserver> { |
| public: |
| virtual ~AutomationWebContentsObserver() {} |
| |
| // content::WebContentsObserver overrides. |
| virtual void AccessibilityEventReceived( |
| const std::vector<content::AXEventNotificationDetails>& details) |
| OVERRIDE { |
| automation_util::DispatchAccessibilityEventsToAutomation( |
| details, browser_context_); |
| } |
| |
| virtual void RenderFrameDeleted( |
| content::RenderFrameHost* render_frame_host) OVERRIDE { |
| automation_util::DispatchTreeDestroyedEventToAutomation( |
| render_frame_host->GetProcess()->GetID(), |
| render_frame_host->GetRoutingID(), |
| browser_context_); |
| } |
| |
| private: |
| friend class content::WebContentsUserData<AutomationWebContentsObserver>; |
| |
| AutomationWebContentsObserver( |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| browser_context_(web_contents->GetBrowserContext()) {} |
| |
| content::BrowserContext* browser_context_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AutomationWebContentsObserver); |
| }; |
| |
| // Helper class that implements an action adapter for a |RenderFrameHost|. |
| class RenderFrameHostActionAdapter : public AutomationActionAdapter { |
| public: |
| explicit RenderFrameHostActionAdapter(content::RenderFrameHost* rfh) |
| : rfh_(rfh) {} |
| |
| virtual ~RenderFrameHostActionAdapter() {} |
| |
| // AutomationActionAdapter implementation. |
| virtual void DoDefault(int32 id) OVERRIDE { |
| rfh_->AccessibilityDoDefaultAction(id); |
| } |
| |
| virtual void Focus(int32 id) OVERRIDE { |
| rfh_->AccessibilitySetFocus(id); |
| } |
| |
| virtual void MakeVisible(int32 id) OVERRIDE { |
| rfh_->AccessibilityScrollToMakeVisible(id, gfx::Rect()); |
| } |
| |
| virtual void SetSelection(int32 id, int32 start, int32 end) OVERRIDE { |
| rfh_->AccessibilitySetTextSelection(id, start, end); |
| } |
| |
| private: |
| content::RenderFrameHost* rfh_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderFrameHostActionAdapter); |
| }; |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalEnableTabFunction::Run() { |
| const AutomationInfo* automation_info = AutomationInfo::Get(extension()); |
| EXTENSION_FUNCTION_VALIDATE(automation_info); |
| |
| using api::automation_internal::EnableTab::Params; |
| scoped_ptr<Params> params(Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| content::WebContents* contents = NULL; |
| if (params->tab_id.get()) { |
| int tab_id = *params->tab_id; |
| if (!ExtensionTabUtil::GetTabById(tab_id, |
| GetProfile(), |
| include_incognito(), |
| NULL, /* browser out param*/ |
| NULL, /* tab_strip out param */ |
| &contents, |
| NULL /* tab_index out param */)) { |
| return RespondNow( |
| Error(tabs_constants::kTabNotFoundError, base::IntToString(tab_id))); |
| } |
| } else { |
| contents = GetCurrentBrowser()->tab_strip_model()->GetActiveWebContents(); |
| if (!contents) |
| return RespondNow(Error("No active tab")); |
| } |
| content::RenderFrameHost* rfh = contents->GetMainFrame(); |
| if (!rfh) |
| return RespondNow(Error("Could not enable accessibility for active tab")); |
| |
| if (!CanRequestAutomation(extension(), automation_info, contents)) { |
| return RespondNow( |
| Error(kCannotRequestAutomationOnPage, contents->GetURL().spec())); |
| } |
| AutomationWebContentsObserver::CreateForWebContents(contents); |
| contents->EnableTreeOnlyAccessibilityMode(); |
| return RespondNow( |
| ArgumentList(api::automation_internal::EnableTab::Results::Create( |
| rfh->GetProcess()->GetID(), rfh->GetRoutingID()))); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalPerformActionFunction::Run() { |
| const AutomationInfo* automation_info = AutomationInfo::Get(extension()); |
| EXTENSION_FUNCTION_VALIDATE(automation_info && automation_info->interact); |
| |
| using api::automation_internal::PerformAction::Params; |
| scoped_ptr<Params> params(Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| |
| if (params->args.process_id == kDesktopProcessID && |
| params->args.routing_id == kDesktopRoutingID) { |
| #if defined(OS_CHROMEOS) |
| return RouteActionToAdapter( |
| params.get(), AutomationManagerAsh::GetInstance()); |
| #else |
| NOTREACHED(); |
| return RespondNow(Error("Unexpected action on desktop automation tree;" |
| " platform does not support desktop automation")); |
| #endif // defined(OS_CHROMEOS) |
| } |
| content::RenderFrameHost* rfh = |
| content::RenderFrameHost::FromID(params->args.process_id, |
| params->args.routing_id); |
| if (!rfh) |
| return RespondNow(Error("Ignoring action on destroyed node")); |
| |
| const content::WebContents* contents = |
| content::WebContents::FromRenderFrameHost(rfh); |
| if (!CanRequestAutomation(extension(), automation_info, contents)) { |
| return RespondNow( |
| Error(kCannotRequestAutomationOnPage, contents->GetURL().spec())); |
| } |
| |
| RenderFrameHostActionAdapter adapter(rfh); |
| return RouteActionToAdapter(params.get(), &adapter); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalPerformActionFunction::RouteActionToAdapter( |
| api::automation_internal::PerformAction::Params* params, |
| AutomationActionAdapter* adapter) { |
| int32 automation_id = params->args.automation_node_id; |
| switch (params->args.action_type) { |
| case api::automation_internal::ACTION_TYPE_DODEFAULT: |
| adapter->DoDefault(automation_id); |
| break; |
| case api::automation_internal::ACTION_TYPE_FOCUS: |
| adapter->Focus(automation_id); |
| break; |
| case api::automation_internal::ACTION_TYPE_MAKEVISIBLE: |
| adapter->MakeVisible(automation_id); |
| break; |
| case api::automation_internal::ACTION_TYPE_SETSELECTION: { |
| api::automation_internal::SetSelectionParams selection_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::SetSelectionParams::Populate( |
| params->opt_args.additional_properties, &selection_params)); |
| adapter->SetSelection(automation_id, |
| selection_params.start_index, |
| selection_params.end_index); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalEnableDesktopFunction::Run() { |
| #if defined(OS_CHROMEOS) |
| const AutomationInfo* automation_info = AutomationInfo::Get(extension()); |
| if (!automation_info || !automation_info->desktop) |
| return RespondNow(Error("desktop permission must be requested")); |
| |
| AutomationManagerAsh::GetInstance()->Enable(browser_context()); |
| return RespondNow(NoArguments()); |
| #else |
| return RespondNow(Error("getDesktop is unsupported by this platform")); |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| } // namespace extensions |