| // 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 <stdint.h> |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/macros.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/extensions/api/automation_internal/automation_event_router.h" |
| #include "chrome/browser/extensions/api/tabs/tabs_constants.h" |
| #include "chrome/browser/extensions/chrome_extension_function_details.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/extensions/api/automation.h" |
| #include "chrome/common/extensions/api/automation_internal.h" |
| #include "chrome/common/extensions/chrome_extension_messages.h" |
| #include "content/public/browser/ax_event_notification_details.h" |
| #include "content/public/browser/browser_accessibility_state.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_plugin_guest_manager.h" |
| #include "content/public/browser/media_session.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 "content/public/browser/web_contents_observer.h" |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "extensions/common/extension_messages.h" |
| #include "extensions/common/manifest_handlers/automation.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "ui/accessibility/ax_action_data.h" |
| #include "ui/accessibility/ax_action_handler.h" |
| #include "ui/accessibility/ax_enum_util.h" |
| #include "ui/accessibility/ax_tree_id_registry.h" |
| |
| #if defined(USE_AURA) |
| #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h" |
| #include "ui/aura/env.h" |
| #endif |
| |
| namespace extensions { |
| class AutomationWebContentsObserver; |
| } // namespace extensions |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const char kCannotRequestAutomationOnPage[] = |
| "Cannot request automation tree on url \"*\". " |
| "Extension manifest must request permission to access this host."; |
| const char kRendererDestroyed[] = "The tab was closed."; |
| const char kNoDocument[] = "No document."; |
| const char kNodeDestroyed[] = |
| "domQuerySelector sent on node which is no longer in the tree."; |
| |
| // Handles sending and receiving IPCs for a single querySelector request. On |
| // creation, sends the request IPC, and is destroyed either when the response is |
| // received or the renderer is destroyed. |
| class QuerySelectorHandler : public content::WebContentsObserver { |
| public: |
| QuerySelectorHandler( |
| content::WebContents* web_contents, |
| int request_id, |
| int acc_obj_id, |
| const base::string16& query, |
| const extensions::AutomationInternalQuerySelectorFunction::Callback& |
| callback) |
| : content::WebContentsObserver(web_contents), |
| request_id_(request_id), |
| callback_(callback) { |
| content::RenderFrameHost* rfh = web_contents->GetMainFrame(); |
| |
| rfh->Send(new ExtensionMsg_AutomationQuerySelector( |
| rfh->GetRoutingID(), request_id, acc_obj_id, query)); |
| } |
| |
| ~QuerySelectorHandler() override {} |
| |
| bool OnMessageReceived(const IPC::Message& message, |
| content::RenderFrameHost* render_frame_host) override { |
| if (message.type() != ExtensionHostMsg_AutomationQuerySelector_Result::ID) |
| return false; |
| |
| // There may be several requests in flight; check this response matches. |
| int message_request_id = 0; |
| base::PickleIterator iter(message); |
| if (!iter.ReadInt(&message_request_id)) |
| return false; |
| |
| if (message_request_id != request_id_) |
| return false; |
| |
| IPC_BEGIN_MESSAGE_MAP(QuerySelectorHandler, message) |
| IPC_MESSAGE_HANDLER(ExtensionHostMsg_AutomationQuerySelector_Result, |
| OnQueryResponse) |
| IPC_END_MESSAGE_MAP() |
| return true; |
| } |
| |
| void WebContentsDestroyed() override { |
| callback_.Run(kRendererDestroyed, 0); |
| delete this; |
| } |
| |
| private: |
| void OnQueryResponse(int request_id, |
| ExtensionHostMsg_AutomationQuerySelector_Error error, |
| int result_acc_obj_id) { |
| std::string error_string; |
| switch (error.value) { |
| case ExtensionHostMsg_AutomationQuerySelector_Error::kNone: |
| break; |
| case ExtensionHostMsg_AutomationQuerySelector_Error::kNoDocument: |
| error_string = kNoDocument; |
| break; |
| case ExtensionHostMsg_AutomationQuerySelector_Error::kNodeDestroyed: |
| error_string = kNodeDestroyed; |
| break; |
| } |
| callback_.Run(error_string, result_acc_obj_id); |
| delete this; |
| } |
| |
| int request_id_; |
| const extensions::AutomationInternalQuerySelectorFunction::Callback callback_; |
| }; |
| |
| bool CanRequestAutomation(const Extension* extension, |
| const AutomationInfo* automation_info, |
| 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); |
| std::string unused_error; |
| return extension->permissions_data()->CanAccessPage(url, tab_id, |
| &unused_error); |
| } |
| |
| } // namespace |
| |
| // Helper class that receives accessibility data from |WebContents|. |
| class AutomationWebContentsObserver |
| : public content::WebContentsObserver, |
| public content::WebContentsUserData<AutomationWebContentsObserver> { |
| public: |
| ~AutomationWebContentsObserver() override {} |
| |
| // content::WebContentsObserver overrides. |
| void AccessibilityEventReceived(const content::AXEventNotificationDetails& |
| content_event_bundle) override { |
| ExtensionMsg_AccessibilityEventBundleParams extension_event_bundle; |
| extension_event_bundle.updates = content_event_bundle.updates; |
| extension_event_bundle.events = content_event_bundle.events; |
| extension_event_bundle.tree_id = content_event_bundle.ax_tree_id; |
| #if defined(USE_AURA) |
| extension_event_bundle.mouse_location = |
| aura::Env::GetInstance()->last_mouse_location(); |
| #endif |
| AutomationEventRouter* router = AutomationEventRouter::GetInstance(); |
| router->DispatchAccessibilityEvents(extension_event_bundle); |
| } |
| |
| void AccessibilityLocationChangesReceived( |
| const std::vector<content::AXLocationChangeNotificationDetails>& details) |
| override { |
| for (const auto& src : details) { |
| ExtensionMsg_AccessibilityLocationChangeParams dst; |
| dst.id = src.id; |
| dst.tree_id = src.ax_tree_id; |
| dst.new_location = src.new_location; |
| AutomationEventRouter* router = AutomationEventRouter::GetInstance(); |
| router->DispatchAccessibilityLocationChange(dst); |
| } |
| } |
| |
| void RenderFrameDeleted( |
| content::RenderFrameHost* render_frame_host) override { |
| ui::AXTreeID tree_id = render_frame_host->GetAXTreeID(); |
| AutomationEventRouter::GetInstance()->DispatchTreeDestroyedEvent( |
| tree_id, |
| browser_context_); |
| } |
| |
| void MediaStartedPlaying(const MediaPlayerInfo& video_type, |
| const MediaPlayerId& id) override { |
| content::AXEventNotificationDetails content_event_bundle; |
| content_event_bundle.ax_tree_id = id.render_frame_host->GetAXTreeID(); |
| content_event_bundle.events.resize(1); |
| content_event_bundle.events[0].event_type = |
| ax::mojom::Event::kMediaStartedPlaying; |
| AccessibilityEventReceived(content_event_bundle); |
| } |
| |
| void MediaStoppedPlaying( |
| const MediaPlayerInfo& video_type, |
| const MediaPlayerId& id, |
| WebContentsObserver::MediaStoppedReason reason) override { |
| content::AXEventNotificationDetails content_event_bundle; |
| content_event_bundle.ax_tree_id = id.render_frame_host->GetAXTreeID(); |
| content_event_bundle.events.resize(1); |
| content_event_bundle.events[0].event_type = |
| ax::mojom::Event::kMediaStoppedPlaying; |
| AccessibilityEventReceived(content_event_bundle); |
| } |
| |
| private: |
| friend class content::WebContentsUserData<AutomationWebContentsObserver>; |
| |
| explicit AutomationWebContentsObserver(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| browser_context_(web_contents->GetBrowserContext()) { |
| if (web_contents->IsCurrentlyAudible()) { |
| content::RenderFrameHost* rfh = web_contents->GetMainFrame(); |
| if (!rfh) |
| return; |
| |
| content::AXEventNotificationDetails content_event_bundle; |
| content_event_bundle.ax_tree_id = rfh->GetAXTreeID(); |
| content_event_bundle.events.resize(1); |
| content_event_bundle.events[0].event_type = |
| ax::mojom::Event::kMediaStartedPlaying; |
| AccessibilityEventReceived(content_event_bundle); |
| } |
| } |
| |
| content::BrowserContext* browser_context_; |
| |
| WEB_CONTENTS_USER_DATA_KEY_DECL(); |
| |
| DISALLOW_COPY_AND_ASSIGN(AutomationWebContentsObserver); |
| }; |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(AutomationWebContentsObserver) |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalEnableTabFunction::Run() { |
| const AutomationInfo* automation_info = AutomationInfo::Get(extension()); |
| EXTENSION_FUNCTION_VALIDATE(automation_info); |
| |
| using api::automation_internal::EnableTab::Params; |
| std::unique_ptr<Params> params(Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| content::WebContents* contents = NULL; |
| if (params->args.tab_id.get()) { |
| int tab_id = *params->args.tab_id; |
| if (!ExtensionTabUtil::GetTabById( |
| tab_id, browser_context(), include_incognito_information(), |
| 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 = ChromeExtensionFunctionDetails(this) |
| .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->EnableWebContentsOnlyAccessibilityMode(); |
| |
| ui::AXTreeID ax_tree_id = rfh->GetAXTreeID(); |
| |
| // This gets removed when the extension process dies. |
| AutomationEventRouter::GetInstance()->RegisterListenerForOneTree( |
| extension_id(), |
| source_process_id(), |
| ax_tree_id); |
| |
| return RespondNow( |
| ArgumentList(api::automation_internal::EnableTab::Results::Create( |
| ax_tree_id.ToString()))); |
| } |
| |
| ExtensionFunction::ResponseAction AutomationInternalEnableFrameFunction::Run() { |
| // TODO(dtseng): Limited to desktop tree for now pending out of proc iframes. |
| using api::automation_internal::EnableFrame::Params; |
| |
| std::unique_ptr<Params> params(Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| |
| content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID( |
| ui::AXTreeID::FromString(params->tree_id)); |
| if (!rfh) |
| return RespondNow(Error("unable to load tab")); |
| |
| content::WebContents* contents = |
| content::WebContents::FromRenderFrameHost(rfh); |
| AutomationWebContentsObserver::CreateForWebContents(contents); |
| |
| // Only call this if this is the root of a frame tree, to avoid resetting |
| // the accessibility state multiple times. |
| if (!rfh->GetParent()) |
| contents->EnableWebContentsOnlyAccessibilityMode(); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalPerformActionFunction::ConvertToAXActionData( |
| api::automation_internal::PerformAction::Params* params, |
| ui::AXActionData* action) { |
| action->target_tree_id = ui::AXTreeID::FromString(params->args.tree_id); |
| action->source_extension_id = extension_id(); |
| action->target_node_id = params->args.automation_node_id; |
| int* request_id = params->args.request_id.get(); |
| action->request_id = request_id ? *request_id : -1; |
| api::automation::ActionType action_type = |
| api::automation::ParseActionType(params->args.action_type); |
| switch (action_type) { |
| case api::automation::ACTION_TYPE_BLUR: |
| action->action = ax::mojom::Action::kBlur; |
| break; |
| case api::automation::ACTION_TYPE_CLEARACCESSIBILITYFOCUS: |
| action->action = ax::mojom::Action::kClearAccessibilityFocus; |
| break; |
| case api::automation::ACTION_TYPE_DECREMENT: |
| action->action = ax::mojom::Action::kDecrement; |
| break; |
| case api::automation::ACTION_TYPE_DODEFAULT: |
| action->action = ax::mojom::Action::kDoDefault; |
| break; |
| case api::automation::ACTION_TYPE_INCREMENT: |
| action->action = ax::mojom::Action::kIncrement; |
| break; |
| case api::automation::ACTION_TYPE_FOCUS: |
| action->action = ax::mojom::Action::kFocus; |
| break; |
| case api::automation::ACTION_TYPE_GETIMAGEDATA: { |
| api::automation_internal::GetImageDataParams get_image_data_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::GetImageDataParams::Populate( |
| params->opt_args.additional_properties, &get_image_data_params)); |
| action->action = ax::mojom::Action::kGetImageData; |
| action->target_rect = gfx::Rect(0, 0, get_image_data_params.max_width, |
| get_image_data_params.max_height); |
| break; |
| } |
| case api::automation::ACTION_TYPE_HITTEST: { |
| api::automation_internal::HitTestParams hit_test_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::HitTestParams::Populate( |
| params->opt_args.additional_properties, &hit_test_params)); |
| action->action = ax::mojom::Action::kHitTest; |
| action->target_point = gfx::Point(hit_test_params.x, hit_test_params.y); |
| action->hit_test_event_to_fire = |
| ui::ParseEvent(hit_test_params.event_to_fire.c_str()); |
| if (action->hit_test_event_to_fire == ax::mojom::Event::kNone) |
| return RespondNow(NoArguments()); |
| break; |
| } |
| case api::automation::ACTION_TYPE_LOADINLINETEXTBOXES: |
| action->action = ax::mojom::Action::kLoadInlineTextBoxes; |
| break; |
| case api::automation::ACTION_TYPE_SETACCESSIBILITYFOCUS: |
| action->action = ax::mojom::Action::kSetAccessibilityFocus; |
| break; |
| case api::automation::ACTION_TYPE_SCROLLTOMAKEVISIBLE: |
| action->action = ax::mojom::Action::kScrollToMakeVisible; |
| break; |
| case api::automation::ACTION_TYPE_SCROLLBACKWARD: |
| action->action = ax::mojom::Action::kScrollBackward; |
| break; |
| case api::automation::ACTION_TYPE_SCROLLFORWARD: |
| action->action = ax::mojom::Action::kScrollForward; |
| break; |
| case api::automation::ACTION_TYPE_SCROLLUP: |
| action->action = ax::mojom::Action::kScrollUp; |
| break; |
| case api::automation::ACTION_TYPE_SCROLLDOWN: |
| action->action = ax::mojom::Action::kScrollDown; |
| break; |
| case api::automation::ACTION_TYPE_SCROLLLEFT: |
| action->action = ax::mojom::Action::kScrollLeft; |
| break; |
| case api::automation::ACTION_TYPE_SCROLLRIGHT: |
| action->action = ax::mojom::Action::kScrollRight; |
| break; |
| case api::automation::ACTION_TYPE_SETSELECTION: { |
| api::automation_internal::SetSelectionParams selection_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::SetSelectionParams::Populate( |
| params->opt_args.additional_properties, &selection_params)); |
| action->anchor_node_id = params->args.automation_node_id; |
| action->anchor_offset = selection_params.anchor_offset; |
| action->focus_node_id = selection_params.focus_node_id; |
| action->focus_offset = selection_params.focus_offset; |
| action->action = ax::mojom::Action::kSetSelection; |
| break; |
| } |
| case api::automation::ACTION_TYPE_SHOWCONTEXTMENU: { |
| action->action = ax::mojom::Action::kShowContextMenu; |
| break; |
| } |
| case api::automation:: |
| ACTION_TYPE_SETSEQUENTIALFOCUSNAVIGATIONSTARTINGPOINT: { |
| action->action = |
| ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint; |
| break; |
| } |
| case api::automation::ACTION_TYPE_CUSTOMACTION: { |
| api::automation_internal::PerformCustomActionParams |
| perform_custom_action_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::PerformCustomActionParams::Populate( |
| params->opt_args.additional_properties, |
| &perform_custom_action_params)); |
| action->action = ax::mojom::Action::kCustomAction; |
| action->custom_action_id = perform_custom_action_params.custom_action_id; |
| break; |
| } |
| case api::automation::ACTION_TYPE_REPLACESELECTEDTEXT: { |
| api::automation_internal::ReplaceSelectedTextParams |
| replace_selected_text_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::ReplaceSelectedTextParams::Populate( |
| params->opt_args.additional_properties, |
| &replace_selected_text_params)); |
| action->action = ax::mojom::Action::kReplaceSelectedText; |
| action->value = replace_selected_text_params.value; |
| break; |
| } |
| case api::automation::ACTION_TYPE_SETVALUE: { |
| api::automation_internal::SetValueParams set_value_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::SetValueParams::Populate( |
| params->opt_args.additional_properties, &set_value_params)); |
| action->action = ax::mojom::Action::kSetValue; |
| action->value = set_value_params.value; |
| break; |
| } |
| // These actions are currently unused by any existing clients of |
| // automation. They also require additional arguments to be plumbed |
| // through (e.g. setValue takes a string value to be set). Future clients |
| // may wish to extend the api to support these actions. |
| case api::automation::ACTION_TYPE_SCROLLTOPOINT: |
| case api::automation::ACTION_TYPE_SETSCROLLOFFSET: |
| return RespondNow( |
| Error("Unsupported action: " + params->args.action_type)); |
| case api::automation::ACTION_TYPE_GETTEXTLOCATION: { |
| api::automation_internal::GetTextLocationDataParams |
| get_text_location_params; |
| EXTENSION_FUNCTION_VALIDATE( |
| api::automation_internal::GetTextLocationDataParams::Populate( |
| params->opt_args.additional_properties, |
| &get_text_location_params)); |
| action->action = ax::mojom::Action::kGetTextLocation; |
| action->start_index = get_text_location_params.start_index; |
| action->end_index = get_text_location_params.end_index; |
| break; |
| } |
| case api::automation::ACTION_TYPE_NONE: |
| break; |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| 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; |
| std::unique_ptr<Params> params(Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance(); |
| ui::AXActionHandler* action_handler = registry->GetActionHandler( |
| ui::AXTreeID::FromString(params->args.tree_id)); |
| if (action_handler) { |
| // Handle an AXActionHandler with a rfh first. Some actions require a rfh -> |
| // web contents and this api requires web contents to perform a permissions |
| // check. |
| content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID( |
| ui::AXTreeID::FromString(params->args.tree_id)); |
| if (rfh) { |
| content::WebContents* contents = |
| content::WebContents::FromRenderFrameHost(rfh); |
| if (!CanRequestAutomation(extension(), automation_info, contents)) { |
| return RespondNow( |
| Error(kCannotRequestAutomationOnPage, contents->GetURL().spec())); |
| } |
| |
| // Handle internal actions. |
| api::automation_internal::ActionTypePrivate internal_action_type = |
| api::automation_internal::ParseActionTypePrivate( |
| params->args.action_type); |
| content::MediaSession* session = content::MediaSession::Get(contents); |
| switch (internal_action_type) { |
| case api::automation_internal::ACTION_TYPE_PRIVATE_STARTDUCKINGMEDIA: |
| session->StartDucking(); |
| return RespondNow(NoArguments()); |
| case api::automation_internal::ACTION_TYPE_PRIVATE_STOPDUCKINGMEDIA: |
| session->StopDucking(); |
| return RespondNow(NoArguments()); |
| case api::automation_internal::ACTION_TYPE_PRIVATE_RESUMEMEDIA: |
| session->Resume(content::MediaSession::SuspendType::kSystem); |
| return RespondNow(NoArguments()); |
| case api::automation_internal::ACTION_TYPE_PRIVATE_SUSPENDMEDIA: |
| session->Suspend(content::MediaSession::SuspendType::kSystem); |
| return RespondNow(NoArguments()); |
| case api::automation_internal::ACTION_TYPE_PRIVATE_NONE: |
| // Not a private action. |
| break; |
| } |
| } |
| |
| ui::AXActionData data; |
| ExtensionFunction::ResponseAction result = |
| ConvertToAXActionData(params.get(), &data); |
| action_handler->PerformAction(data); |
| return result; |
| } |
| return RespondNow(Error("Unable to perform action on unknown tree.")); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalEnableDesktopFunction::Run() { |
| #if defined(USE_AURA) |
| const AutomationInfo* automation_info = AutomationInfo::Get(extension()); |
| if (!automation_info || !automation_info->desktop) |
| return RespondNow(Error("desktop permission must be requested")); |
| |
| // This gets removed when the extension process dies. |
| AutomationEventRouter::GetInstance()->RegisterListenerWithDesktopPermission( |
| extension_id(), source_process_id()); |
| |
| AutomationManagerAura::GetInstance()->Enable(); |
| ui::AXTreeID ax_tree_id = AutomationManagerAura::GetInstance()->ax_tree_id(); |
| return RespondNow( |
| ArgumentList(api::automation_internal::EnableDesktop::Results::Create( |
| ax_tree_id.ToString()))); |
| #else |
| return RespondNow(Error("getDesktop is unsupported by this platform")); |
| #endif // defined(USE_AURA) |
| } |
| |
| // static |
| int AutomationInternalQuerySelectorFunction::query_request_id_counter_ = 0; |
| |
| ExtensionFunction::ResponseAction |
| AutomationInternalQuerySelectorFunction::Run() { |
| const AutomationInfo* automation_info = AutomationInfo::Get(extension()); |
| EXTENSION_FUNCTION_VALIDATE(automation_info); |
| |
| using api::automation_internal::QuerySelector::Params; |
| std::unique_ptr<Params> params(Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| |
| content::RenderFrameHost* rfh = content::RenderFrameHost::FromAXTreeID( |
| ui::AXTreeID::FromString(params->args.tree_id)); |
| if (!rfh) { |
| return RespondNow( |
| Error("domQuerySelector query sent on non-web or destroyed tree.")); |
| } |
| |
| content::WebContents* contents = |
| content::WebContents::FromRenderFrameHost(rfh); |
| |
| int request_id = query_request_id_counter_++; |
| base::string16 selector = base::UTF8ToUTF16(params->args.selector); |
| |
| // QuerySelectorHandler handles IPCs and deletes itself on completion. |
| new QuerySelectorHandler( |
| contents, request_id, params->args.automation_node_id, selector, |
| base::Bind(&AutomationInternalQuerySelectorFunction::OnResponse, this)); |
| |
| return RespondLater(); |
| } |
| |
| void AutomationInternalQuerySelectorFunction::OnResponse( |
| const std::string& error, |
| int result_acc_obj_id) { |
| if (!error.empty()) { |
| Respond(Error(error)); |
| return; |
| } |
| |
| Respond(OneArgument(std::make_unique<base::Value>(result_acc_obj_id))); |
| } |
| |
| } // namespace extensions |