| // 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/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/browser/lazy_background_task_queue.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/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 "ash/shell.h" |
| #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" |
| #include "chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h" |
| #include "services/ui/public/interfaces/accessibility_manager.mojom.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/events/event_sink.h" |
| #endif |
| |
| namespace accessibility_private = extensions::api::accessibility_private; |
| |
| namespace { |
| |
| const char kErrorNotSupported[] = "This API is not supported on this platform."; |
| |
| } // 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 |
| AccessibilityPrivateSetFocusRingFunction::Run() { |
| #if defined(OS_CHROMEOS) |
| |
| std::unique_ptr<extensions::api::accessibility_private::SetFocusRing::Params> |
| params( |
| extensions::api::accessibility_private::SetFocusRing::Params::Create( |
| *args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| std::vector<gfx::Rect> rects; |
| for (const extensions::api::accessibility_private::ScreenRect& rect : |
| params->rects) { |
| rects.push_back(gfx::Rect(rect.left, rect.top, rect.width, rect.height)); |
| } |
| |
| auto* accessibility_manager = chromeos::AccessibilityManager::Get(); |
| if (params->color) { |
| SkColor color; |
| if (!extensions::image_util::ParseHexColorString(*(params->color), &color)) |
| return RespondNow(Error("Could not parse hex color")); |
| accessibility_manager->SetFocusRingColor(color, extension_id()); |
| } else { |
| accessibility_manager->ResetFocusRingColor(extension_id()); |
| } |
| |
| // Move the visible focus ring to cover all of these rects. |
| accessibility_manager->SetFocusRing( |
| rects, ash::mojom::FocusRingBehavior::PERSIST_FOCUS_RING, extension_id()); |
| |
| // Also update the touch exploration controller so that synthesized |
| // touch events are anchored within the focused object. |
| if (!rects.empty()) { |
| chromeos::AccessibilityManager* manager = |
| chromeos::AccessibilityManager::Get(); |
| manager->SetTouchAccessibilityAnchorPoint(rects[0].CenterPoint()); |
| } |
| |
| return RespondNow(NoArguments()); |
| #endif // defined(OS_CHROMEOS) |
| |
| return RespondNow(Error(kErrorNotSupported)); |
| } |
| |
| ExtensionFunction::ResponseAction |
| AccessibilityPrivateSetHighlightsFunction::Run() { |
| #if defined(OS_CHROMEOS) |
| std::unique_ptr<extensions::api::accessibility_private::SetHighlights::Params> |
| params( |
| extensions::api::accessibility_private::SetHighlights::Params::Create( |
| *args_)); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| std::vector<gfx::Rect> rects; |
| for (const extensions::api::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); |
| |
| // For now, only accept key code if it represents an alphanumeric character. |
| std::set<int> key_codes; |
| for (auto key_code : params->key_codes) { |
| EXTENSION_FUNCTION_VALIDATE(key_code >= ui::VKEY_0 && |
| key_code <= ui::VKEY_Z); |
| key_codes.insert(key_code); |
| } |
| |
| chromeos::AccessibilityManager* manager = |
| chromeos::AccessibilityManager::Get(); |
| |
| // AccessibilityManager can be null during system shut down, but no need to |
| // return error in this case, so just check that manager is not null. |
| if (manager) |
| manager->SetSwitchAccessKeys(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; |
| } |
| |
| ui::KeyEvent synthetic_key_event( |
| 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); |
| |
| // Only keyboard events, so dispatching to primary window suffices. |
| ui::EventSink* sink = |
| ash::Shell::GetPrimaryRootWindow()->GetHost()->event_sink(); |
| if (sink->OnEventFromSource(&synthetic_key_event).dispatcher_destroyed) |
| return RespondNow(Error("Unable to dispatch key ")); |
| |
| 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()); |
| } |
| |
| #endif // defined (OS_CHROMEOS) |