1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ash/accessibility/accessibility_highlight_controller.h"
6 
7 #include <vector>
8 
9 #include "ash/accessibility/accessibility_focus_ring_controller_impl.h"
10 #include "ash/display/window_tree_host_manager.h"
11 #include "ash/public/cpp/accessibility_focus_ring_info.h"
12 #include "ash/shell.h"
13 #include "ui/base/ime/input_method.h"
14 #include "ui/base/ime/text_input_client.h"
15 #include "ui/events/event.h"
16 #include "ui/wm/core/coordinate_conversion.h"
17 #include "ui/wm/core/cursor_manager.h"
18 #include "ui/wm/public/activation_client.h"
19 
20 namespace ash {
21 
22 namespace {
23 
24 constexpr char kHighlightCallerId[] = "HighlightController";
25 // The color for the keyboard focus ring. (The same orange color as ChromeVox.)
26 const SkColor kFocusColor = SkColorSetRGB(247, 152, 58);
27 
28 // Returns the input method shared between ash and the browser for in-process
29 // ash. Returns null for out-of-process ash.
GetSharedInputMethod()30 ui::InputMethod* GetSharedInputMethod() {
31   return Shell::Get()->window_tree_host_manager()->input_method();
32 }
33 
SetFocusRing(AccessibilityFocusRingController * controller,std::vector<gfx::Rect> rects)34 void SetFocusRing(AccessibilityFocusRingController* controller,
35                   std::vector<gfx::Rect> rects) {
36   auto focus_ring = std::make_unique<AccessibilityFocusRingInfo>();
37   focus_ring->rects_in_screen = rects;
38   focus_ring->behavior = FocusRingBehavior::FADE_OUT;
39   focus_ring->type = FocusRingType::GLOW;
40   focus_ring->color = kFocusColor;
41 
42   controller->SetFocusRing(kHighlightCallerId, std::move(focus_ring));
43 }
44 
45 }  // namespace
46 
AccessibilityHighlightController()47 AccessibilityHighlightController::AccessibilityHighlightController() {
48   Shell::Get()->AddPreTargetHandler(this);
49   Shell::Get()->cursor_manager()->AddObserver(this);
50 
51   GetSharedInputMethod()->AddObserver(this);
52 }
53 
~AccessibilityHighlightController()54 AccessibilityHighlightController::~AccessibilityHighlightController() {
55   AccessibilityFocusRingControllerImpl* controller =
56       Shell::Get()->accessibility_focus_ring_controller();
57   SetFocusRing(controller, std::vector<gfx::Rect>());
58   controller->HideCaretRing();
59   controller->HideCursorRing();
60 
61   GetSharedInputMethod()->RemoveObserver(this);
62   Shell::Get()->cursor_manager()->RemoveObserver(this);
63   Shell::Get()->RemovePreTargetHandler(this);
64 }
65 
HighlightFocus(bool focus)66 void AccessibilityHighlightController::HighlightFocus(bool focus) {
67   focus_ = focus;
68   UpdateFocusAndCaretHighlights();
69 }
70 
HighlightCursor(bool cursor)71 void AccessibilityHighlightController::HighlightCursor(bool cursor) {
72   cursor_ = cursor;
73   UpdateCursorHighlight();
74 }
75 
HighlightCaret(bool caret)76 void AccessibilityHighlightController::HighlightCaret(bool caret) {
77   caret_ = caret;
78   UpdateFocusAndCaretHighlights();
79 }
80 
SetFocusHighlightRect(const gfx::Rect & bounds_in_screen)81 void AccessibilityHighlightController::SetFocusHighlightRect(
82     const gfx::Rect& bounds_in_screen) {
83   focus_rect_ = bounds_in_screen;
84   UpdateFocusAndCaretHighlights();
85 }
86 
SetCaretBounds(const gfx::Rect & caret_bounds_in_screen)87 void AccessibilityHighlightController::SetCaretBounds(
88     const gfx::Rect& caret_bounds_in_screen) {
89   gfx::Point new_caret_point = caret_bounds_in_screen.CenterPoint();
90   ::wm::ConvertPointFromScreen(Shell::GetPrimaryRootWindow(), &new_caret_point);
91   bool new_caret_visible = IsCaretVisible(caret_bounds_in_screen);
92   if (new_caret_point == caret_point_ && new_caret_visible == caret_visible_)
93     return;
94   caret_point_ = new_caret_point;
95   caret_visible_ = IsCaretVisible(caret_bounds_in_screen);
96   UpdateFocusAndCaretHighlights();
97 }
98 
OnMouseEvent(ui::MouseEvent * event)99 void AccessibilityHighlightController::OnMouseEvent(ui::MouseEvent* event) {
100   if (event->type() == ui::ET_MOUSE_MOVED ||
101       event->type() == ui::ET_MOUSE_DRAGGED) {
102     cursor_point_ = event->location();
103     if (event->target()) {
104       ::wm::ConvertPointToScreen(static_cast<aura::Window*>(event->target()),
105                                  &cursor_point_);
106     }
107     UpdateCursorHighlight();
108   }
109 }
110 
OnKeyEvent(ui::KeyEvent * event)111 void AccessibilityHighlightController::OnKeyEvent(ui::KeyEvent* event) {
112   if (event->type() == ui::ET_KEY_PRESSED)
113     UpdateFocusAndCaretHighlights();
114 }
115 
OnTextInputStateChanged(const ui::TextInputClient * client)116 void AccessibilityHighlightController::OnTextInputStateChanged(
117     const ui::TextInputClient* client) {
118   if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) {
119     caret_visible_ = false;
120     UpdateFocusAndCaretHighlights();
121   }
122 }
123 
OnCaretBoundsChanged(const ui::TextInputClient * client)124 void AccessibilityHighlightController::OnCaretBoundsChanged(
125     const ui::TextInputClient* client) {
126   if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) {
127     caret_visible_ = false;
128     return;
129   }
130   SetCaretBounds(client->GetCaretBounds());
131 }
132 
OnCursorVisibilityChanged(bool is_visible)133 void AccessibilityHighlightController::OnCursorVisibilityChanged(
134     bool is_visible) {
135   UpdateCursorHighlight();
136 }
137 
IsCursorVisible()138 bool AccessibilityHighlightController::IsCursorVisible() {
139   return Shell::Get()->cursor_manager()->IsCursorVisible();
140 }
141 
IsCaretVisible(const gfx::Rect & caret_bounds_in_screen)142 bool AccessibilityHighlightController::IsCaretVisible(
143     const gfx::Rect& caret_bounds_in_screen) {
144   // Empty bounds are not visible. Don't use IsEmpty() because web contents
145   // carets can have positive height but zero width.
146   if (caret_bounds_in_screen.width() == 0 &&
147       caret_bounds_in_screen.height() == 0) {
148     return false;
149   }
150 
151   aura::Window* root_window = Shell::GetPrimaryRootWindow();
152   aura::Window* active_window =
153       ::wm::GetActivationClient(root_window)->GetActiveWindow();
154   if (!active_window)
155     active_window = root_window;
156   return active_window->GetBoundsInScreen().Contains(caret_point_);
157 }
158 
UpdateFocusAndCaretHighlights()159 void AccessibilityHighlightController::UpdateFocusAndCaretHighlights() {
160   AccessibilityFocusRingControllerImpl* controller =
161       Shell::Get()->accessibility_focus_ring_controller();
162 
163   // The caret highlight takes precedence over the focus highlight if
164   // both are visible.
165   if (caret_ && caret_visible_) {
166     controller->SetCaretRing(caret_point_);
167     SetFocusRing(controller, std::vector<gfx::Rect>());
168   } else if (focus_) {
169     controller->HideCaretRing();
170     std::vector<gfx::Rect> rects;
171     if (!focus_rect_.IsEmpty())
172       rects.push_back(focus_rect_);
173     SetFocusRing(controller, rects);
174   } else {
175     controller->HideCaretRing();
176     SetFocusRing(controller, std::vector<gfx::Rect>());
177   }
178 }
179 
UpdateCursorHighlight()180 void AccessibilityHighlightController::UpdateCursorHighlight() {
181   AccessibilityFocusRingControllerImpl* controller =
182       Shell::Get()->accessibility_focus_ring_controller();
183   if (cursor_ && IsCursorVisible())
184     controller->SetCursorRing(cursor_point_);
185   else
186     controller->HideCursorRing();
187 }
188 
189 }  // namespace ash
190