| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/renderer/api/automation/automation_internal_custom_bindings.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/values.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "extensions/common/api/automation.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_messages.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_handlers/automation.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/renderer/api/automation/automation_api_converters.h" |
| #include "extensions/renderer/native_extension_bindings_system.h" |
| #include "extensions/renderer/object_backed_native_handler.h" |
| #include "extensions/renderer/script_context.h" |
| #include "ipc/message_filter.h" |
| #include "third_party/blink/public/strings/grit/blink_accessibility_strings.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "ui/accessibility/ax_event.h" |
| #include "ui/accessibility/ax_event_generator.h" |
| #include "ui/accessibility/platform/automation/automation_api_util.h" |
| #include "ui/accessibility/platform/automation/automation_ax_tree_wrapper.h" |
| #include "ui/accessibility/platform/automation/automation_tree_manager_owner.h" |
| #include "ui/accessibility/platform/automation/automation_v8_bindings.h" |
| #include "ui/accessibility/platform/automation/automation_v8_router.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace extensions { |
| |
| class AutomationMessageFilter : public IPC::MessageFilter { |
| public: |
| AutomationMessageFilter( |
| AutomationInternalCustomBindings* owner, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) |
| : owner_(owner), removed_(false), task_runner_(std::move(task_runner)) { |
| DCHECK(owner); |
| content::RenderThread::Get()->AddFilter(this); |
| } |
| |
| AutomationMessageFilter(const AutomationMessageFilter&) = delete; |
| AutomationMessageFilter& operator=(const AutomationMessageFilter&) = delete; |
| |
| void Detach() { |
| owner_ = nullptr; |
| Remove(); |
| } |
| |
| // IPC::MessageFilter |
| bool OnMessageReceived(const IPC::Message& message) override { |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &AutomationMessageFilter::OnMessageReceivedOnRenderThread, this, |
| message)); |
| |
| // Always return false in case there are multiple |
| // AutomationInternalCustomBindings instances attached to the same thread. |
| return false; |
| } |
| |
| void OnFilterRemoved() override { removed_ = true; } |
| |
| private: |
| void OnMessageReceivedOnRenderThread(const IPC::Message& message) { |
| if (owner_) |
| owner_->OnMessageReceived(message); |
| } |
| |
| ~AutomationMessageFilter() override { Remove(); } |
| |
| void Remove() { |
| if (!removed_) { |
| removed_ = true; |
| content::RenderThread::Get()->RemoveFilter(this); |
| } |
| } |
| |
| AutomationInternalCustomBindings* owner_; |
| bool removed_; |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| }; |
| |
| AutomationInternalCustomBindings::AutomationInternalCustomBindings( |
| ScriptContext* context, |
| NativeExtensionBindingsSystem* bindings_system) |
| : ObjectBackedNativeHandler(context), |
| bindings_system_(bindings_system), |
| should_ignore_context_(false), |
| automation_v8_bindings_( |
| std::make_unique<ui::AutomationV8Bindings>(this, this)) { |
| // We will ignore this instance if the extension has a background page and |
| // this context is not that background page. In all other cases, we will have |
| // multiple instances floating around in the same process. |
| if (context && context->extension()) { |
| const GURL background_page_url = |
| extensions::BackgroundInfo::GetBackgroundURL(context->extension()); |
| should_ignore_context_ = |
| background_page_url != "" && background_page_url != context->url(); |
| } |
| } |
| |
| AutomationInternalCustomBindings::~AutomationInternalCustomBindings() {} |
| |
| void AutomationInternalCustomBindings::OnMessageReceived( |
| const IPC::Message& message) { |
| IPC_BEGIN_MESSAGE_MAP(AutomationInternalCustomBindings, message) |
| IPC_MESSAGE_HANDLER(ExtensionMsg_AccessibilityEventBundle, |
| HandleAccessibilityEvents) |
| IPC_MESSAGE_HANDLER(ExtensionMsg_AccessibilityLocationChange, |
| HandleAccessibilityLocationChange) |
| IPC_END_MESSAGE_MAP() |
| } |
| |
| void AutomationInternalCustomBindings::AddRoutes() { |
| automation_v8_bindings_->AddV8Routes(); |
| } |
| |
| void AutomationInternalCustomBindings::Invalidate() { |
| ObjectBackedNativeHandler::Invalidate(); |
| |
| if (message_filter_) |
| message_filter_->Detach(); |
| |
| AutomationTreeManagerOwner::Invalidate(); |
| } |
| |
| ui::AutomationV8Bindings* |
| AutomationInternalCustomBindings::GetAutomationV8Bindings() const { |
| DCHECK(automation_v8_bindings_); |
| return automation_v8_bindings_.get(); |
| } |
| |
| bool AutomationInternalCustomBindings::IsInteractPermitted() const { |
| const Extension* extension = context()->extension(); |
| CHECK(extension); |
| const AutomationInfo* automation_info = AutomationInfo::Get(extension); |
| CHECK(automation_info); |
| return automation_info->interact; |
| } |
| |
| void AutomationInternalCustomBindings::StartCachingAccessibilityTrees() { |
| if (should_ignore_context_) |
| return; |
| |
| if (!message_filter_) { |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| context()->web_frame()->GetTaskRunner( |
| blink::TaskType::kInternalDefault); |
| message_filter_ = base::MakeRefCounted<AutomationMessageFilter>( |
| this, std::move(task_runner)); |
| } |
| } |
| |
| void AutomationInternalCustomBindings::StopCachingAccessibilityTrees() { |
| message_filter_->Detach(); |
| message_filter_.reset(); |
| } |
| |
| // |
| // Handle accessibility events from the browser process. |
| // |
| |
| void AutomationInternalCustomBindings::HandleAccessibilityEvents( |
| const ExtensionMsg_AccessibilityEventBundleParams& event_bundle, |
| bool is_active_profile) { |
| OnAccessibilityEvents(event_bundle.tree_id, event_bundle.events, |
| event_bundle.updates, event_bundle.mouse_location, |
| is_active_profile); |
| } |
| |
| void AutomationInternalCustomBindings::HandleAccessibilityLocationChange( |
| const ExtensionMsg_AccessibilityLocationChangeParams& params) { |
| OnAccessibilityLocationChange(params.tree_id, params.id, params.new_location); |
| } |
| |
| void AutomationInternalCustomBindings::ThrowInvalidArgumentsException( |
| bool is_fatal) const { |
| GetIsolate()->ThrowException(v8::String::NewFromUtf8Literal( |
| GetIsolate(), |
| "Invalid arguments to AutomationInternalCustomBindings function")); |
| |
| if (!is_fatal) |
| return; |
| |
| LOG(FATAL) << "Invalid arguments to AutomationInternalCustomBindings function" |
| << context()->GetStackTraceAsString(); |
| } |
| |
| v8::Isolate* AutomationInternalCustomBindings::GetIsolate() const { |
| return ObjectBackedNativeHandler::GetIsolate(); |
| } |
| |
| v8::Local<v8::Context> AutomationInternalCustomBindings::GetContext() const { |
| return context()->v8_context(); |
| } |
| |
| void AutomationInternalCustomBindings::RouteHandlerFunction( |
| const std::string& name, |
| AutomationV8Router::HandlerFunction handler_function) { |
| ObjectBackedNativeHandler::RouteHandlerFunction(name, handler_function); |
| } |
| |
| void AutomationInternalCustomBindings::RouteHandlerFunction( |
| const std::string& name, |
| const std::string& api_name, |
| AutomationV8Router::HandlerFunction handler_function) { |
| ObjectBackedNativeHandler::RouteHandlerFunction(name, api_name, |
| handler_function); |
| } |
| |
| ui::TreeChangeObserverFilter |
| AutomationInternalCustomBindings::ParseTreeChangeObserverFilter( |
| const std::string& filter) const { |
| return ConvertAutomationTreeChangeObserverFilter( |
| api::automation::ParseTreeChangeObserverFilter(filter)); |
| } |
| |
| std::string AutomationInternalCustomBindings::GetMarkerTypeString( |
| ax::mojom::MarkerType type) const { |
| return api::automation::ToString(ConvertMarkerTypeFromAXToAutomation(type)); |
| } |
| |
| std::string AutomationInternalCustomBindings::GetFocusedStateString() const { |
| return api::automation::ToString(api::automation::STATE_TYPE_FOCUSED); |
| } |
| |
| std::string AutomationInternalCustomBindings::GetOffscreenStateString() const { |
| return api::automation::ToString(api::automation::STATE_TYPE_OFFSCREEN); |
| } |
| |
| void AutomationInternalCustomBindings::DispatchEvent( |
| const std::string& event_name, |
| const base::Value::List& event_args) const { |
| bindings_system_->DispatchEventInContext(event_name, event_args, nullptr, |
| context()); |
| |
| if (notify_event_for_testing_.is_null() || |
| event_name != "automationInternal.onAccessibilityEvent") { |
| return; |
| } |
| // Find the event type within the event_params for the test. |
| const base::Value::Dict* dict = event_args[0].GetIfDict(); |
| DCHECK(dict); |
| const std::string* event_type_string = dict->FindString("eventType"); |
| DCHECK(event_type_string); |
| api::automation::EventType event_type = |
| api::automation::ParseEventType(*event_type_string); |
| notify_event_for_testing_.Run(event_type); |
| } |
| |
| std::string |
| AutomationInternalCustomBindings::GetLocalizedStringForImageAnnotationStatus( |
| ax::mojom::ImageAnnotationStatus status) const { |
| int message_id = 0; |
| switch (status) { |
| case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation: |
| message_id = IDS_AX_IMAGE_ELIGIBLE_FOR_ANNOTATION; |
| break; |
| case ax::mojom::ImageAnnotationStatus::kAnnotationPending: |
| message_id = IDS_AX_IMAGE_ANNOTATION_PENDING; |
| break; |
| case ax::mojom::ImageAnnotationStatus::kAnnotationAdult: |
| message_id = IDS_AX_IMAGE_ANNOTATION_ADULT; |
| break; |
| case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty: |
| case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed: |
| message_id = IDS_AX_IMAGE_ANNOTATION_NO_DESCRIPTION; |
| break; |
| case ax::mojom::ImageAnnotationStatus::kNone: |
| case ax::mojom::ImageAnnotationStatus::kWillNotAnnotateDueToScheme: |
| case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation: |
| case ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation: |
| case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded: |
| return std::string(); |
| } |
| |
| DCHECK(message_id); |
| |
| return l10n_util::GetStringUTF8(message_id); |
| } |
| |
| std::string AutomationInternalCustomBindings::GetTreeChangeTypeString( |
| ax::mojom::Mutation change_type) const { |
| return ToString(ConvertToAutomationTreeChangeType(change_type)); |
| } |
| |
| std::string AutomationInternalCustomBindings::GetEventTypeString( |
| const std::tuple<ax::mojom::Event, ui::AXEventGenerator::Event>& event_type) |
| const { |
| ui::AXEventGenerator::Event generated_event = std::get<1>(event_type); |
| // Resolve the proper event based on generated or non-generated event sources. |
| api::automation::EventType automation_event_type = |
| generated_event != ui::AXEventGenerator::Event::NONE |
| ? AXGeneratedEventToAutomationEventType(generated_event) |
| : AXEventToAutomationEventType(std::get<0>(event_type)); |
| return api::automation::ToString(automation_event_type); |
| } |
| |
| void AutomationInternalCustomBindings::NotifyTreeEventListenersChanged() { |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| context()->web_frame()->GetTaskRunner(blink::TaskType::kInternalDefault); |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AutomationInternalCustomBindings:: |
| MaybeSendOnAllAutomationEventListenersRemoved, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| } // namespace extensions |