| // Copyright (c) 2012 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/accessibility/accessibility_extension_api.h" |
| |
| #include <stddef.h> |
| #include <memory> |
| #include <set> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/extensions/api/tabs/tabs_constants.h" |
| #include "chrome/browser/extensions/chrome_extension_function_details.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/infobars/infobar_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/extensions/api/accessibility_private.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "components/infobars/core/confirm_infobar_delegate.h" |
| #include "components/infobars/core/infobar.h" |
| #include "content/public/browser/browser_accessibility_state.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/common/error_utils.h" |
| #include "extensions/common/image_util.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "ash/public/interfaces/accessibility_focus_ring_controller.mojom.h" |
| #include "ash/public/interfaces/constants.mojom.h" |
| #include "ash/public/interfaces/event_rewriter_controller.mojom.h" |
| #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" |
| #include "chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h" |
| #include "services/ws/public/mojom/constants.mojom.h" |
| #include "services/ws/public/mojom/event_injector.mojom.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #endif |
| |
| namespace accessibility_private = extensions::api::accessibility_private; |
| |
| namespace { |
| |
| const char kErrorNotSupported[] = "This API is not supported on this platform."; |
| |
| #if defined(OS_CHROMEOS) |
| ash::mojom::AccessibilityControllerPtr GetAccessibilityController() { |
| // Connect to the accessibility mojo interface in ash. |
| ash::mojom::AccessibilityControllerPtr accessibility_controller; |
| content::ServiceManagerConnection::GetForProcess() |
| ->GetConnector() |
| ->BindInterface(ash::mojom::kServiceName, &accessibility_controller); |
| return accessibility_controller; |
| } |
| #endif |
| |
| } // namespace |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetNativeAccessibilityEnabledFunction::Run() { |
| bool enabled = false; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enabled)); |
| if (enabled) { |
| content::BrowserAccessibilityState::GetInstance()-> |
| EnableAccessibility(); |
| } else { |
| content::BrowserAccessibilityState::GetInstance()-> |
| DisableAccessibility(); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetFocusRingsFunction::Run() { |
| #if defined(OS_CHROMEOS) |
| std::unique_ptr<accessibility_private::SetFocusRings::Params> params( |
| accessibility_private::SetFocusRings::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| auto* accessibility_manager = chromeos::AccessibilityManager::Get(); |
| |
| for (const accessibility_private::FocusRingInfo& focus_ring_info : |
| params->focus_rings) { |
| ash::mojom::FocusRingPtr focus_ring_ptr = ash::mojom::FocusRing::New(); |
| focus_ring_ptr->behavior = |
| ash::mojom::FocusRingBehavior::PERSIST_FOCUS_RING; |
| |
| // Convert the given rects into gfx::Rect objects. |
| for (const accessibility_private::ScreenRect& rect : |
| focus_ring_info.rects) { |
| focus_ring_ptr->rects_in_screen.push_back( |
| gfx::Rect(rect.left, rect.top, rect.width, rect.height)); |
| } |
| |
| const std::string id = accessibility_manager->GetFocusRingId( |
| extension_id(), focus_ring_info.id ? *(focus_ring_info.id) : ""); |
| |
| if (!extensions::image_util::ParseHexColorString( |
| focus_ring_info.color, &(focus_ring_ptr->color))) { |
| return RespondNow(Error("Could not parse hex color")); |
| } |
| |
| if (focus_ring_info.secondary_color) { |
| if (!extensions::image_util::ParseHexColorString( |
| *(focus_ring_info.secondary_color), |
| &(focus_ring_ptr->secondary_color))) { |
| return RespondNow(Error("Could not parse secondary hex color")); |
| } |
| } |
| |
| switch (focus_ring_info.type) { |
| case accessibility_private::FOCUS_TYPE_SOLID: |
| focus_ring_ptr->type = ash::mojom::FocusRingType::SOLID; |
| break; |
| case accessibility_private::FOCUS_TYPE_DASHED: |
| focus_ring_ptr->type = ash::mojom::FocusRingType::DASHED; |
| break; |
| case accessibility_private::FOCUS_TYPE_GLOW: |
| focus_ring_ptr->type = ash::mojom::FocusRingType::GLOW; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // Update the touch exploration controller so that synthesized touch events |
| // are anchored within the focused object. |
| // NOTE: The final anchor point will be determined by the first rect of the |
| // final focus ring. |
| if (!focus_ring_ptr->rects_in_screen.empty()) { |
| accessibility_manager->SetTouchAccessibilityAnchorPoint( |
| focus_ring_ptr->rects_in_screen[0].CenterPoint()); |
| } |
| |
| // Set the focus ring. |
| accessibility_manager->SetFocusRing(id, std::move(focus_ring_ptr)); |
| } |
| |
| return RespondNow(NoArguments()); |
| #else |
| return RespondNow(Error(kErrorNotSupported)); |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetHighlightsFunction::Run() { |
| #if defined(OS_CHROMEOS) |
| std::unique_ptr<accessibility_private::SetHighlights::Params> params( |
| accessibility_private::SetHighlights::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| std::vector<gfx::Rect> rects; |
| for (const accessibility_private::ScreenRect& rect : params->rects) { |
| rects.push_back(gfx::Rect(rect.left, rect.top, rect.width, rect.height)); |
| } |
| |
| SkColor color; |
| if (!extensions::image_util::ParseHexColorString(params->color, &color)) |
| return RespondNow(Error("Could not parse hex color")); |
| |
| // Set the highlights to cover all of these rects. |
| chromeos::AccessibilityManager::Get()->SetHighlights(rects, color); |
| |
| return RespondNow(NoArguments()); |
| #endif // defined(OS_CHROMEOS) |
| |
| return RespondNow(Error(kErrorNotSupported)); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetKeyboardListenerFunction::Run() { |
| ChromeExtensionFunctionDetails details(this); |
| CHECK(extension()); |
| |
| #if defined(OS_CHROMEOS) |
| bool enabled; |
| bool capture; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enabled)); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &capture)); |
| |
| chromeos::AccessibilityManager* manager = |
| chromeos::AccessibilityManager::Get(); |
| |
| const std::string current_id = manager->keyboard_listener_extension_id(); |
| if (!current_id.empty() && extension()->id() != current_id) |
| return RespondNow(Error("Existing keyboard listener registered.")); |
| |
| manager->SetKeyboardListenerExtensionId( |
| enabled ? extension()->id() : std::string(), details.GetProfile()); |
| |
| ash::mojom::EventRewriterControllerPtr event_rewriter_controller_ptr; |
| content::ServiceManagerConnection* connection = |
| content::ServiceManagerConnection::GetForProcess(); |
| connection->GetConnector()->BindInterface(ash::mojom::kServiceName, |
| &event_rewriter_controller_ptr); |
| event_rewriter_controller_ptr->CaptureAllKeysForSpokenFeedback(enabled && |
| capture); |
| return RespondNow(NoArguments()); |
| #endif // defined OS_CHROMEOS |
| |
| return RespondNow(Error(kErrorNotSupported)); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateDarkenScreenFunction::Run() { |
| #if defined(OS_CHROMEOS) |
| bool darken = false; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &darken)); |
| chromeos::AccessibilityManager::Get()->SetDarkenScreen(darken); |
| return RespondNow(NoArguments()); |
| #else |
| return RespondNow(Error(kErrorNotSupported)); |
| #endif |
| } |
| |
| #if defined(OS_CHROMEOS) |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetSwitchAccessKeysFunction::Run() { |
| std::unique_ptr<accessibility_private::SetSwitchAccessKeys::Params> params = |
| accessibility_private::SetSwitchAccessKeys::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| GetAccessibilityController()->SetSwitchAccessKeysToCapture(params->key_codes); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetNativeChromeVoxArcSupportForCurrentAppFunction::Run() { |
| std::unique_ptr< |
| accessibility_private::SetNativeChromeVoxArcSupportForCurrentApp::Params> |
| params = accessibility_private:: |
| SetNativeChromeVoxArcSupportForCurrentApp::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| ChromeExtensionFunctionDetails details(this); |
| arc::ArcAccessibilityHelperBridge* bridge = |
| arc::ArcAccessibilityHelperBridge::GetForBrowserContext( |
| details.GetProfile()); |
| if (bridge) { |
| bool enabled; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enabled)); |
| bridge->SetNativeChromeVoxArcSupport(enabled); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSendSyntheticKeyEventFunction::Run() { |
| std::unique_ptr<accessibility_private::SendSyntheticKeyEvent::Params> params = |
| accessibility_private::SendSyntheticKeyEvent::Params::Create(*args_); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| accessibility_private::SyntheticKeyboardEvent* key_data = ¶ms->key_event; |
| |
| int modifiers = 0; |
| if (key_data->modifiers.get()) { |
| if (key_data->modifiers->ctrl && *key_data->modifiers->ctrl) |
| modifiers |= ui::EF_CONTROL_DOWN; |
| if (key_data->modifiers->alt && *key_data->modifiers->alt) |
| modifiers |= ui::EF_ALT_DOWN; |
| if (key_data->modifiers->search && *key_data->modifiers->search) |
| modifiers |= ui::EF_COMMAND_DOWN; |
| if (key_data->modifiers->shift && *key_data->modifiers->shift) |
| modifiers |= ui::EF_SHIFT_DOWN; |
| } |
| |
| std::unique_ptr<ui::KeyEvent> synthetic_key_event = |
| std::make_unique<ui::KeyEvent>( |
| key_data->type == |
| accessibility_private::SYNTHETIC_KEYBOARD_EVENT_TYPE_KEYUP |
| ? ui::ET_KEY_RELEASED |
| : ui::ET_KEY_PRESSED, |
| static_cast<ui::KeyboardCode>(key_data->key_code), |
| static_cast<ui::DomCode>(0), modifiers); |
| |
| ws::mojom::EventInjectorPtr event_injector_ptr; |
| content::ServiceManagerConnection* connection = |
| content::ServiceManagerConnection::GetForProcess(); |
| connection->GetConnector()->BindInterface(ws::mojom::kServiceName, |
| &event_injector_ptr); |
| event_injector_ptr->InjectEventNoAckNoRewriters( |
| display::Screen::GetScreen()->GetPrimaryDisplay().id(), |
| std::move(synthetic_key_event)); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateEnableChromeVoxMouseEventsFunction::Run() { |
| bool enabled = false; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(0, &enabled)); |
| ash::mojom::EventRewriterControllerPtr event_rewriter_controller_ptr; |
| content::ServiceManagerConnection* connection = |
| content::ServiceManagerConnection::GetForProcess(); |
| connection->GetConnector()->BindInterface(ash::mojom::kServiceName, |
| &event_rewriter_controller_ptr); |
| event_rewriter_controller_ptr->SetSendMouseEventsToDelegate(enabled); |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSendSyntheticMouseEventFunction::Run() { |
| std::unique_ptr<accessibility_private::SendSyntheticMouseEvent::Params> |
| params = accessibility_private::SendSyntheticMouseEvent::Params::Create( |
| *args_); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| accessibility_private::SyntheticMouseEvent* mouse_data = ¶ms->mouse_event; |
| |
| ui::EventType type; |
| switch (mouse_data->type) { |
| case accessibility_private::SYNTHETIC_MOUSE_EVENT_TYPE_PRESS: |
| type = ui::ET_MOUSE_PRESSED; |
| break; |
| case accessibility_private::SYNTHETIC_MOUSE_EVENT_TYPE_RELEASE: |
| type = ui::ET_MOUSE_RELEASED; |
| break; |
| case accessibility_private::SYNTHETIC_MOUSE_EVENT_TYPE_DRAG: |
| type = ui::ET_MOUSE_DRAGGED; |
| break; |
| case accessibility_private::SYNTHETIC_MOUSE_EVENT_TYPE_MOVE: |
| type = ui::ET_MOUSE_MOVED; |
| break; |
| case accessibility_private::SYNTHETIC_MOUSE_EVENT_TYPE_ENTER: |
| type = ui::ET_MOUSE_ENTERED; |
| break; |
| case accessibility_private::SYNTHETIC_MOUSE_EVENT_TYPE_EXIT: |
| type = ui::ET_MOUSE_EXITED; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| int flags = ui::EF_LEFT_MOUSE_BUTTON; |
| |
| // Locations are assumed to be display relative (and in DIPs). |
| // TODO(crbug/893752) Choose correct display |
| display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay(); |
| gfx::Point location(mouse_data->x, mouse_data->y); |
| std::unique_ptr<ui::MouseEvent> synthetic_mouse_event = |
| std::make_unique<ui::MouseEvent>(type, location, location, |
| ui::EventTimeForNow(), flags, |
| flags /* changed_button_flags */); |
| |
| ws::mojom::EventInjectorPtr event_injector_ptr; |
| content::ServiceManagerConnection* connection = |
| content::ServiceManagerConnection::GetForProcess(); |
| connection->GetConnector()->BindInterface(ws::mojom::kServiceName, |
| &event_injector_ptr); |
| event_injector_ptr->InjectEventNoAckNoRewriters( |
| display.id(), std::move(synthetic_mouse_event)); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateOnSelectToSpeakStateChangedFunction::Run() { |
| std::unique_ptr<accessibility_private::OnSelectToSpeakStateChanged::Params> |
| params = |
| accessibility_private::OnSelectToSpeakStateChanged::Params::Create( |
| *args_); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| accessibility_private::SelectToSpeakState params_state = params->state; |
| ash::mojom::SelectToSpeakState state; |
| switch (params_state) { |
| case accessibility_private::SelectToSpeakState:: |
| SELECT_TO_SPEAK_STATE_SELECTING: |
| state = ash::mojom::SelectToSpeakState::kSelectToSpeakStateSelecting; |
| break; |
| case accessibility_private::SelectToSpeakState:: |
| SELECT_TO_SPEAK_STATE_SPEAKING: |
| state = ash::mojom::SelectToSpeakState::kSelectToSpeakStateSpeaking; |
| break; |
| case accessibility_private::SelectToSpeakState:: |
| SELECT_TO_SPEAK_STATE_INACTIVE: |
| case accessibility_private::SelectToSpeakState::SELECT_TO_SPEAK_STATE_NONE: |
| state = ash::mojom::SelectToSpeakState::kSelectToSpeakStateInactive; |
| } |
| |
| auto* accessibility_manager = chromeos::AccessibilityManager::Get(); |
| accessibility_manager->OnSelectToSpeakStateChanged(state); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateToggleDictationFunction::Run() { |
| ash::mojom::DictationToggleSource source = |
| ash::mojom::DictationToggleSource::kChromevox; |
| if (extension()->id() == extension_misc::kSwitchAccessExtensionId) |
| source = ash::mojom::DictationToggleSource::kSwitchAccess; |
| else if (extension()->id() == extension_misc::kChromeVoxExtensionId) |
| source = ash::mojom::DictationToggleSource::kChromevox; |
| else |
| NOTREACHED(); |
| |
| GetAccessibilityController()->ToggleDictationFromSource(source); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetSwitchAccessMenuStateFunction::Run() { |
| std::unique_ptr<accessibility_private::SetSwitchAccessMenuState::Params> |
| params = accessibility_private::SetSwitchAccessMenuState::Params::Create( |
| *args_); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| bool show_menu = params->show; |
| |
| int item_count = params->item_count; |
| |
| int padding = 40; |
| int item_width = 88; |
| int item_height = 60; |
| // TODO(anastasi): This should be a preference that the user can change. |
| int max_cols = 3; |
| |
| // The number of rows is the number of items divided by the max columns, |
| // rounded down. |
| int rows = 1 + (item_count - 1) / max_cols; |
| int cols = rows == 1 ? item_count : max_cols; |
| int width = padding + (item_width * cols); |
| int height = padding + (item_height * rows); |
| |
| accessibility_private::ScreenRect elem = std::move(params->element_bounds); |
| |
| gfx::Rect element_bounds(elem.left, elem.top, elem.width, elem.height); |
| |
| if (show_menu) { |
| chromeos::AccessibilityManager::Get()->ShowSwitchAccessMenu(element_bounds, |
| width, height); |
| } else { |
| chromeos::AccessibilityManager::Get()->HideSwitchAccessMenu(); |
| } |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateForwardKeyEventsToSwitchAccessFunction::Run() { |
| std::unique_ptr<accessibility_private::ForwardKeyEventsToSwitchAccess::Params> |
| params = |
| accessibility_private::ForwardKeyEventsToSwitchAccess::Params::Create( |
| *args_); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| GetAccessibilityController()->ForwardKeyEventsToSwitchAccess( |
| params->should_forward); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| AccessibilityPrivateGetBatteryDescriptionFunction:: |
| AccessibilityPrivateGetBatteryDescriptionFunction() {} |
| |
| AccessibilityPrivateGetBatteryDescriptionFunction:: |
| ~AccessibilityPrivateGetBatteryDescriptionFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateGetBatteryDescriptionFunction::Run() { |
| // Get AccessibilityControllerPtr; needs to exist for lifetime of this |
| // function and its callback. |
| controller_ = GetAccessibilityController(); |
| |
| // Get battery description from ash and return it via callback. |
| controller_->GetBatteryDescription( |
| base::BindOnce(&AccessibilityPrivateGetBatteryDescriptionFunction:: |
| OnGotBatteryDescription, |
| this)); |
| |
| return RespondLater(); |
| } |
| |
| void AccessibilityPrivateGetBatteryDescriptionFunction::OnGotBatteryDescription( |
| const base::string16& battery_description) { |
| // Send battery description to extension. |
| Respond(OneArgument(std::make_unique<base::Value>(battery_description))); |
| controller_.reset(); |
| } |
| |
| #endif // defined (OS_CHROMEOS) |