Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 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 "content/browser/renderer_host/text_input_manager.h" |
| 6 | |
Andrew Xu | a178f3a | 2021-04-13 00:54:15 | [diff] [blame] | 7 | #include <algorithm> |
| 8 | |
Andrew Xu | e80a7191 | 2021-04-22 23:56:19 | [diff] [blame] | 9 | #include "base/numerics/clamped_math.h" |
David Sanders | d4bf5eb | 2022-03-17 07:12:05 | [diff] [blame] | 10 | #include "base/observer_list.h" |
Helmut Januschka | a965cc1 | 2024-05-07 07:11:09 | [diff] [blame] | 11 | #include "base/strings/string_number_conversions.h" |
David Sanders | c51d3b0 | 2025-06-13 22:22:17 | [diff] [blame] | 12 | #include "base/trace_event/trace_event.h" |
Peter Kasting | c634073 | 2021-07-05 06:01:00 | [diff] [blame] | 13 | #include "build/build_config.h" |
ekaramad | 8cba7886 | 2016-06-24 19:42:31 | [diff] [blame] | 14 | #include "content/browser/renderer_host/render_widget_host_impl.h" |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 15 | #include "content/browser/renderer_host/render_widget_host_view_base.h" |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 16 | #include "ui/gfx/geometry/rect.h" |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 17 | #include "ui/gfx/range/range.h" |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 18 | |
| 19 | namespace content { |
| 20 | |
| 21 | namespace { |
| 22 | |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 23 | bool ShouldUpdateTextInputState(const ui::mojom::TextInputState& old_state, |
| 24 | const ui::mojom::TextInputState& new_state) { |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 25 | #if defined(USE_AURA) |
Darren Shen | 87552fe5 | 2021-04-14 18:31:04 | [diff] [blame] | 26 | return old_state.node_id != new_state.node_id || |
| 27 | old_state.type != new_state.type || old_state.mode != new_state.mode || |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 28 | old_state.flags != new_state.flags || |
| 29 | old_state.can_compose_inline != new_state.can_compose_inline; |
Dave Tapuska | a1cc370 | 2023-02-03 20:43:24 | [diff] [blame] | 30 | #elif BUILDFLAG(IS_APPLE) |
ekaramad | c3ef49d | 2016-07-27 18:38:25 | [diff] [blame] | 31 | return old_state.type != new_state.type || |
Sidney San Martín | d743cc22 | 2019-07-20 23:11:16 | [diff] [blame] | 32 | old_state.flags != new_state.flags || |
ekaramad | c3ef49d | 2016-07-27 18:38:25 | [diff] [blame] | 33 | old_state.can_compose_inline != new_state.can_compose_inline; |
Xiaohan Wang | 7f8052e0 | 2022-01-14 18:44:28 | [diff] [blame] | 34 | #elif BUILDFLAG(IS_ANDROID) |
ekaramad | 06b2771 | 2016-11-28 17:55:10 | [diff] [blame] | 35 | // On Android, TextInputState update is sent only if there is some change in |
| 36 | // the state. So the new state is always different. |
| 37 | return true; |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 38 | #else |
Peter Boström | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 39 | NOTREACHED(); |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 40 | #endif |
| 41 | } |
| 42 | |
| 43 | } // namespace |
| 44 | |
Anupam Snigdha | 915824a | 2024-03-05 17:55:30 | [diff] [blame] | 45 | TextInputManager::TextInputManager() : active_view_(nullptr) {} |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 46 | |
| 47 | TextInputManager::~TextInputManager() { |
| 48 | // If there is an active view, we should unregister it first so that the |
| 49 | // the tab's top-level RWHV will be notified about |TextInputState.type| |
| 50 | // resetting to none (i.e., we do not have an active RWHV anymore). |
| 51 | if (active_view_) |
| 52 | Unregister(active_view_); |
| 53 | |
| 54 | // Unregister all the remaining views. |
| 55 | std::vector<RenderWidgetHostViewBase*> views; |
My Nguyen | ca19c6d | 2020-08-05 11:32:42 | [diff] [blame] | 56 | for (auto const& pair : text_input_state_map_) |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 57 | views.push_back(pair.first); |
| 58 | |
vmpstr | 10e0d5f | 2016-07-21 23:46:09 | [diff] [blame] | 59 | for (auto* view : views) |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 60 | Unregister(view); |
| 61 | } |
| 62 | |
ekaramad | 8cba7886 | 2016-06-24 19:42:31 | [diff] [blame] | 63 | RenderWidgetHostImpl* TextInputManager::GetActiveWidget() const { |
| 64 | return !!active_view_ ? static_cast<RenderWidgetHostImpl*>( |
| 65 | active_view_->GetRenderWidgetHost()) |
| 66 | : nullptr; |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 67 | } |
| 68 | |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 69 | const ui::mojom::TextInputState* TextInputManager::GetTextInputState() const { |
My Nguyen | ca19c6d | 2020-08-05 11:32:42 | [diff] [blame] | 70 | if (!active_view_) { |
| 71 | return nullptr; |
| 72 | } |
| 73 | |
| 74 | return text_input_state_map_.at(active_view_).get(); |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 75 | } |
| 76 | |
John Palmer | 75ce6c7 | 2021-01-21 05:15:00 | [diff] [blame] | 77 | gfx::Range TextInputManager::GetAutocorrectRange() const { |
| 78 | if (!active_view_) |
| 79 | return gfx::Range(); |
| 80 | |
| 81 | for (auto const& pair : text_input_state_map_) { |
| 82 | for (const auto& ime_text_span_info : pair.second->ime_text_spans_info) { |
| 83 | if (ime_text_span_info->span.type == |
| 84 | ui::ImeTextSpan::Type::kAutocorrect) { |
| 85 | return gfx::Range(ime_text_span_info->span.start_offset, |
| 86 | ime_text_span_info->span.end_offset); |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | return gfx::Range(); |
| 91 | } |
| 92 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 93 | std::optional<ui::GrammarFragment> TextInputManager::GetGrammarFragment( |
Jing Wang | 6554c150 | 2021-05-05 02:04:00 | [diff] [blame] | 94 | gfx::Range range) const { |
| 95 | if (!active_view_) |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 96 | return std::nullopt; |
Jing Wang | 6554c150 | 2021-05-05 02:04:00 | [diff] [blame] | 97 | |
| 98 | for (const auto& ime_text_span_info : |
| 99 | text_input_state_map_.at(active_view_)->ime_text_spans_info) { |
| 100 | if (ime_text_span_info->span.type == |
| 101 | ui::ImeTextSpan::Type::kGrammarSuggestion && |
| 102 | ime_text_span_info->span.suggestions.size() > 0) { |
| 103 | auto span_range = gfx::Range(ime_text_span_info->span.start_offset, |
| 104 | ime_text_span_info->span.end_offset); |
| 105 | if (span_range.Contains(range)) { |
| 106 | return ui::GrammarFragment(span_range, |
| 107 | ime_text_span_info->span.suggestions[0]); |
| 108 | } |
| 109 | } |
| 110 | } |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 111 | return std::nullopt; |
Jing Wang | 6554c150 | 2021-05-05 02:04:00 | [diff] [blame] | 112 | } |
| 113 | |
ekaramad | 2f52009 | 2016-08-22 23:10:24 | [diff] [blame] | 114 | const TextInputManager::SelectionRegion* TextInputManager::GetSelectionRegion( |
| 115 | RenderWidgetHostViewBase* view) const { |
| 116 | DCHECK(!view || IsRegistered(view)); |
| 117 | if (!view) |
| 118 | view = active_view_; |
| 119 | return view ? &selection_region_map_.at(view) : nullptr; |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 120 | } |
| 121 | |
ekaramad | d773ff4 | 2016-08-12 19:30:40 | [diff] [blame] | 122 | const TextInputManager::CompositionRangeInfo* |
ekaramad | 79819af0 | 2017-04-10 21:10:26 | [diff] [blame] | 123 | TextInputManager::GetCompositionRangeInfo() const { |
ekaramad | d773ff4 | 2016-08-12 19:30:40 | [diff] [blame] | 124 | return active_view_ ? &composition_range_info_map_.at(active_view_) : nullptr; |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 125 | } |
| 126 | |
Adam Ettenberger | 23b345b | 2024-10-16 20:03:39 | [diff] [blame] | 127 | #if BUILDFLAG(IS_WIN) |
| 128 | const blink::mojom::ProximateCharacterRangeBounds* |
| 129 | TextInputManager::GetProximateCharacterBoundsInfo( |
| 130 | const RenderWidgetHostViewBase& view) const { |
| 131 | // TODO(crbug.com/355578906): Remove const_cast<RenderWidgetHostViewBase*>, |
| 132 | // which is needed because TextInputManager::ViewMap has mutable |
| 133 | // `RenderWidgetHostViewBase*` keys and the two RenderWidgetHostViewAura |
| 134 | // callers are const methods passing (*this). |
| 135 | // - RenderWidgetHostViewAura::GetProximateCharacterBounds |
| 136 | // - RenderWidgetHostViewAura::GetProximateCharacterIndexFromPoint |
| 137 | const auto found = proximate_character_bounds_map_.find( |
| 138 | const_cast<RenderWidgetHostViewBase*>(&view)); |
| 139 | return found != proximate_character_bounds_map_.end() ? found->second.get() |
| 140 | : nullptr; |
| 141 | } |
| 142 | #endif // BUILDFLAG(IS_WIN) |
| 143 | |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 144 | const TextInputManager::TextSelection* TextInputManager::GetTextSelection( |
| 145 | RenderWidgetHostViewBase* view) const { |
| 146 | DCHECK(!view || IsRegistered(view)); |
| 147 | if (!view) |
| 148 | view = active_view_; |
xiaoyinh | eac75cf | 2017-06-26 23:48:45 | [diff] [blame] | 149 | // A crash occurs when we end up here with an unregistered view. |
| 150 | // See crbug.com/735980 |
| 151 | // TODO(ekaramad): Take a deeper look why this is happening. |
| 152 | return (view && IsRegistered(view)) ? &text_selection_map_.at(view) : nullptr; |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 153 | } |
| 154 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 155 | const std::optional<gfx::Rect> TextInputManager::GetTextControlBounds() const { |
John Palmer | 2200b9a9 | 2021-10-27 04:39:02 | [diff] [blame] | 156 | const ui::mojom::TextInputState* state = GetTextInputState(); |
| 157 | if (!active_view_ || !state || !state->edit_context_control_bounds) |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 158 | return std::nullopt; |
John Palmer | 2200b9a9 | 2021-10-27 04:39:02 | [diff] [blame] | 159 | |
| 160 | auto control_bounds = state->edit_context_control_bounds.value(); |
| 161 | auto new_top_left = |
| 162 | active_view_->TransformPointToRootCoordSpace(control_bounds.origin()); |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 163 | return std::optional<gfx::Rect>( |
John Palmer | 2200b9a9 | 2021-10-27 04:39:02 | [diff] [blame] | 164 | gfx::Rect(new_top_left, control_bounds.size())); |
| 165 | } |
| 166 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 167 | const std::optional<gfx::Rect> TextInputManager::GetTextSelectionBounds() |
John Palmer | 2200b9a9 | 2021-10-27 04:39:02 | [diff] [blame] | 168 | const { |
| 169 | const ui::mojom::TextInputState* state = GetTextInputState(); |
| 170 | if (!active_view_ || !state || !state->edit_context_selection_bounds) |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 171 | return std::nullopt; |
John Palmer | 2200b9a9 | 2021-10-27 04:39:02 | [diff] [blame] | 172 | |
| 173 | auto selection_bounds = state->edit_context_selection_bounds.value(); |
| 174 | auto new_top_left = |
| 175 | active_view_->TransformPointToRootCoordSpace(selection_bounds.origin()); |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 176 | return std::optional<gfx::Rect>( |
John Palmer | 2200b9a9 | 2021-10-27 04:39:02 | [diff] [blame] | 177 | gfx::Rect(new_top_left, selection_bounds.size())); |
| 178 | } |
| 179 | |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 180 | void TextInputManager::UpdateTextInputState( |
| 181 | RenderWidgetHostViewBase* view, |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 182 | const ui::mojom::TextInputState& text_input_state) { |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 183 | DCHECK(IsRegistered(view)); |
| 184 | |
ekaramad | 44c24298 | 2016-07-27 21:17:29 | [diff] [blame] | 185 | if (text_input_state.type == ui::TEXT_INPUT_TYPE_NONE && |
| 186 | active_view_ != view) { |
| 187 | // We reached here because an IPC is received to reset the TextInputState |
| 188 | // for |view|. But |view| != |active_view_|, which suggests that at least |
| 189 | // one other view has become active and we have received the corresponding |
| 190 | // IPC from their RenderWidget sooner than this one. That also means we have |
| 191 | // already synthesized the loss of TextInputState for the |view| before (see |
| 192 | // below). So we can forget about this method ever being called (no observer |
| 193 | // calls necessary). |
changwan | 38c1f94 | 2017-03-10 18:47:38 | [diff] [blame] | 194 | // NOTE: Android requires state to be returned even when the current state |
| 195 | // is/becomes NONE. Otherwise IME may become irresponsive. |
Xiaohan Wang | 7f8052e0 | 2022-01-14 18:44:28 | [diff] [blame] | 196 | #if !BUILDFLAG(IS_ANDROID) |
ekaramad | 44c24298 | 2016-07-27 21:17:29 | [diff] [blame] | 197 | return; |
changwan | 38c1f94 | 2017-03-10 18:47:38 | [diff] [blame] | 198 | #endif |
ekaramad | 44c24298 | 2016-07-27 21:17:29 | [diff] [blame] | 199 | } |
| 200 | |
| 201 | // Since |view| is registered, we already have a previous value for its |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 202 | // TextInputState. |
My Nguyen | ca19c6d | 2020-08-05 11:32:42 | [diff] [blame] | 203 | bool changed = ShouldUpdateTextInputState(*text_input_state_map_[view], |
ekaramad | 06b2771 | 2016-11-28 17:55:10 | [diff] [blame] | 204 | text_input_state); |
Anupam Snigdha | f44f5e7 | 2020-12-07 23:15:01 | [diff] [blame] | 205 | TRACE_EVENT2( |
| 206 | "ime", "TextInputManager::UpdateTextInputState", "changed", changed, |
| 207 | "text_input_state - type, selection, composition, " |
| 208 | "show_ime_if_needed, control_bounds", |
Helmut Januschka | a965cc1 | 2024-05-07 07:11:09 | [diff] [blame] | 209 | base::NumberToString(text_input_state.type) + ", " + |
Anupam Snigdha | f44f5e7 | 2020-12-07 23:15:01 | [diff] [blame] | 210 | text_input_state.selection.ToString() + ", " + |
| 211 | (text_input_state.composition.has_value() |
| 212 | ? text_input_state.composition->ToString() |
| 213 | : "") + |
Helmut Januschka | a965cc1 | 2024-05-07 07:11:09 | [diff] [blame] | 214 | ", " + base::NumberToString(text_input_state.show_ime_if_needed) + |
| 215 | ", " + |
Anupam Snigdha | f44f5e7 | 2020-12-07 23:15:01 | [diff] [blame] | 216 | (text_input_state.edit_context_control_bounds.has_value() |
| 217 | ? text_input_state.edit_context_control_bounds->ToString() |
| 218 | : "")); |
My Nguyen | ca19c6d | 2020-08-05 11:32:42 | [diff] [blame] | 219 | text_input_state_map_[view] = text_input_state.Clone(); |
| 220 | for (const auto& ime_text_span_info : |
| 221 | text_input_state_map_[view]->ime_text_spans_info) { |
| 222 | const gfx::Rect& bounds = ime_text_span_info->bounds; |
| 223 | ime_text_span_info->bounds = gfx::Rect( |
| 224 | view->TransformPointToRootCoordSpace(bounds.origin()), bounds.size()); |
| 225 | } |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 226 | |
ekaramad | 44c24298 | 2016-07-27 21:17:29 | [diff] [blame] | 227 | // If |view| is different from |active_view| and its |TextInputState.type| is |
| 228 | // not NONE, |active_view_| should change to |view|. |
| 229 | if (text_input_state.type != ui::TEXT_INPUT_TYPE_NONE && |
| 230 | active_view_ != view) { |
| 231 | if (active_view_) { |
| 232 | // Ideally, we should always receive an IPC from |active_view_|'s |
| 233 | // RenderWidget to reset its |TextInputState.type| to NONE, before any |
| 234 | // other RenderWidget updates its TextInputState. But there is no |
| 235 | // guarantee in the order of IPCs from different RenderWidgets and another |
| 236 | // RenderWidget's IPC might arrive sooner and we reach here. To make the |
| 237 | // IME behavior identical to the non-OOPIF case, we have to manually reset |
| 238 | // the state for |active_view_|. |
My Nguyen | ca19c6d | 2020-08-05 11:32:42 | [diff] [blame] | 239 | text_input_state_map_[active_view_]->type = ui::TEXT_INPUT_TYPE_NONE; |
ekaramad | 44c24298 | 2016-07-27 21:17:29 | [diff] [blame] | 240 | RenderWidgetHostViewBase* active_view = active_view_; |
| 241 | active_view_ = nullptr; |
| 242 | NotifyObserversAboutInputStateUpdate(active_view, true); |
| 243 | } |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 244 | active_view_ = view; |
ekaramad | 44c24298 | 2016-07-27 21:17:29 | [diff] [blame] | 245 | } |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 246 | |
| 247 | // If the state for |active_view_| is none, then we no longer have an |
| 248 | // |active_view_|. |
| 249 | if (active_view_ == view && text_input_state.type == ui::TEXT_INPUT_TYPE_NONE) |
| 250 | active_view_ = nullptr; |
| 251 | |
| 252 | NotifyObserversAboutInputStateUpdate(view, changed); |
| 253 | } |
| 254 | |
Adam Ettenberger | 23b345b | 2024-10-16 20:03:39 | [diff] [blame] | 255 | #if BUILDFLAG(IS_WIN) |
| 256 | void TextInputManager::UpdateProximateCharacterBounds( |
| 257 | RenderWidgetHostViewBase& view, |
| 258 | blink::mojom::ProximateCharacterRangeBoundsPtr proximate_bounds) { |
| 259 | if (!proximate_bounds) { |
| 260 | proximate_character_bounds_map_.erase(&view); |
| 261 | return; |
| 262 | } |
| 263 | proximate_character_bounds_map_[&view] = std::move(proximate_bounds); |
| 264 | } |
| 265 | #endif // BUILDFLAG(IS_WIN) |
| 266 | |
ekaramad | 50ee203 | 2016-06-29 02:18:25 | [diff] [blame] | 267 | void TextInputManager::ImeCancelComposition(RenderWidgetHostViewBase* view) { |
| 268 | DCHECK(IsRegistered(view)); |
ericwilligers | de38634 | 2016-10-19 02:35:09 | [diff] [blame] | 269 | for (auto& observer : observer_list_) |
| 270 | observer.OnImeCancelComposition(this, view); |
ekaramad | 50ee203 | 2016-06-29 02:18:25 | [diff] [blame] | 271 | } |
| 272 | |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 273 | void TextInputManager::SelectionBoundsChanged( |
| 274 | RenderWidgetHostViewBase* view, |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 275 | const gfx::Rect& anchor_rect, |
| 276 | base::i18n::TextDirection anchor_dir, |
| 277 | const gfx::Rect& focus_rect, |
| 278 | base::i18n::TextDirection focus_dir, |
Andrew Xu | a178f3a | 2021-04-13 00:54:15 | [diff] [blame] | 279 | const gfx::Rect& bounding_box, |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 280 | bool is_anchor_first) { |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 281 | DCHECK(IsRegistered(view)); |
ekaramad | 2f52009 | 2016-08-22 23:10:24 | [diff] [blame] | 282 | // Converting the anchor point to root's coordinate space (for child frame |
| 283 | // views). |
| 284 | gfx::Point anchor_origin_transformed = |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 285 | view->TransformPointToRootCoordSpace(anchor_rect.origin()); |
dmazzoni | d61b8fc | 2016-09-22 21:06:33 | [diff] [blame] | 286 | |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 287 | gfx::SelectionBound anchor_bound, focus_bound; |
ekaramad | 2f52009 | 2016-08-22 23:10:24 | [diff] [blame] | 288 | |
| 289 | anchor_bound.SetEdge(gfx::PointF(anchor_origin_transformed), |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 290 | gfx::PointF(view->TransformPointToRootCoordSpace( |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 291 | anchor_rect.bottom_left()))); |
| 292 | focus_bound.SetEdge( |
| 293 | gfx::PointF(view->TransformPointToRootCoordSpace(focus_rect.origin())), |
| 294 | gfx::PointF( |
| 295 | view->TransformPointToRootCoordSpace(focus_rect.bottom_left()))); |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 296 | |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 297 | if (anchor_rect == focus_rect) { |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 298 | anchor_bound.set_type(gfx::SelectionBound::CENTER); |
| 299 | focus_bound.set_type(gfx::SelectionBound::CENTER); |
| 300 | } else { |
| 301 | // Whether text is LTR at the anchor handle. |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 302 | bool anchor_LTR = anchor_dir == base::i18n::LEFT_TO_RIGHT; |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 303 | // Whether text is LTR at the focus handle. |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 304 | bool focus_LTR = focus_dir == base::i18n::LEFT_TO_RIGHT; |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 305 | |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 306 | if ((is_anchor_first && anchor_LTR) || (!is_anchor_first && !anchor_LTR)) { |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 307 | anchor_bound.set_type(gfx::SelectionBound::LEFT); |
| 308 | } else { |
| 309 | anchor_bound.set_type(gfx::SelectionBound::RIGHT); |
| 310 | } |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 311 | if ((is_anchor_first && focus_LTR) || (!is_anchor_first && !focus_LTR)) { |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 312 | focus_bound.set_type(gfx::SelectionBound::RIGHT); |
| 313 | } else { |
| 314 | focus_bound.set_type(gfx::SelectionBound::LEFT); |
| 315 | } |
| 316 | } |
| 317 | |
Andrew Xu | a178f3a | 2021-04-13 00:54:15 | [diff] [blame] | 318 | // Transform `bounding_box` to the top-level frame's coordinate space. |
| 319 | std::vector<gfx::Point> bounding_box_vertice = { |
| 320 | bounding_box.origin(), bounding_box.top_right(), |
| 321 | bounding_box.bottom_left(), bounding_box.bottom_right()}; |
| 322 | std::vector<int> x_after_transform; |
| 323 | std::vector<int> y_after_transform; |
| 324 | for (const auto& vertex : bounding_box_vertice) { |
| 325 | const gfx::Point vertex_after_transform = |
| 326 | view->TransformPointToRootCoordSpace(vertex); |
| 327 | x_after_transform.push_back(vertex_after_transform.x()); |
| 328 | y_after_transform.push_back(vertex_after_transform.y()); |
| 329 | } |
| 330 | |
| 331 | std::sort(x_after_transform.begin(), x_after_transform.end()); |
| 332 | std::sort(y_after_transform.begin(), y_after_transform.end()); |
| 333 | |
| 334 | const gfx::Point bounding_box_origin_after_transform(x_after_transform[0], |
| 335 | y_after_transform[0]); |
| 336 | const gfx::Point bounding_box_bottom_right_after_transform( |
| 337 | x_after_transform.back(), y_after_transform.back()); |
| 338 | const gfx::Rect bounding_box_transformed( |
| 339 | bounding_box_origin_after_transform, |
Andrew Xu | e80a7191 | 2021-04-22 23:56:19 | [diff] [blame] | 340 | gfx::Size(base::ClampSub(bounding_box_bottom_right_after_transform.x(), |
| 341 | bounding_box_origin_after_transform.x()), |
| 342 | base::ClampSub(bounding_box_bottom_right_after_transform.y(), |
| 343 | bounding_box_origin_after_transform.y()))); |
Andrew Xu | a178f3a | 2021-04-13 00:54:15 | [diff] [blame] | 344 | |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 345 | if (anchor_bound == selection_region_map_[view].anchor && |
Andrew Xu | a178f3a | 2021-04-13 00:54:15 | [diff] [blame] | 346 | focus_bound == selection_region_map_[view].focus && |
| 347 | bounding_box_transformed == selection_region_map_[view].bounding_box) { |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 348 | return; |
Andrew Xu | a178f3a | 2021-04-13 00:54:15 | [diff] [blame] | 349 | } |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 350 | |
| 351 | selection_region_map_[view].anchor = anchor_bound; |
| 352 | selection_region_map_[view].focus = focus_bound; |
Andrew Xu | a178f3a | 2021-04-13 00:54:15 | [diff] [blame] | 353 | selection_region_map_[view].bounding_box = bounding_box_transformed; |
dmazzoni | d61b8fc | 2016-09-22 21:06:33 | [diff] [blame] | 354 | |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 355 | if (anchor_rect == focus_rect) { |
ekaramad | 2f52009 | 2016-08-22 23:10:24 | [diff] [blame] | 356 | selection_region_map_[view].caret_rect.set_origin( |
| 357 | anchor_origin_transformed); |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 358 | selection_region_map_[view].caret_rect.set_size(anchor_rect.size()); |
ekaramad | 2f52009 | 2016-08-22 23:10:24 | [diff] [blame] | 359 | } |
| 360 | selection_region_map_[view].first_selection_rect.set_origin( |
| 361 | anchor_origin_transformed); |
Dave Tapuska | c135bf1 | 2020-06-19 17:37:53 | [diff] [blame] | 362 | selection_region_map_[view].first_selection_rect.set_size(anchor_rect.size()); |
dmazzoni | d61b8fc | 2016-09-22 21:06:33 | [diff] [blame] | 363 | |
Aaron Leventhal | fe0c7ef | 2018-07-25 14:05:53 | [diff] [blame] | 364 | NotifySelectionBoundsChanged(view); |
| 365 | } |
| 366 | |
| 367 | void TextInputManager::NotifySelectionBoundsChanged( |
| 368 | RenderWidgetHostViewBase* view) { |
ericwilligers | de38634 | 2016-10-19 02:35:09 | [diff] [blame] | 369 | for (auto& observer : observer_list_) |
| 370 | observer.OnSelectionBoundsChanged(this, view); |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 371 | } |
| 372 | |
ekaramad | d773ff4 | 2016-08-12 19:30:40 | [diff] [blame] | 373 | // TODO(ekaramad): We use |range| only on Mac OS; but we still track its value |
| 374 | // here for other platforms. See if there is a nice way around this with minimal |
| 375 | // #ifdefs for platform specific code (https://crbug.com/602427). |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 376 | void TextInputManager::ImeCompositionRangeChanged( |
| 377 | RenderWidgetHostViewBase* view, |
| 378 | const gfx::Range& range, |
Alex Mitra | 9b83bea9 | 2025-02-28 14:14:04 | [diff] [blame] | 379 | const std::optional<std::vector<gfx::Rect>>& character_bounds) { |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 380 | DCHECK(IsRegistered(view)); |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 381 | |
Alex Mitra | 08747df | 2023-08-08 17:04:10 | [diff] [blame] | 382 | if (character_bounds.has_value()) { |
| 383 | composition_range_info_map_[view].character_bounds.clear(); |
| 384 | |
| 385 | // The values for the bounds should be converted to root view's coordinates |
| 386 | // before being stored. |
| 387 | for (auto& rect : character_bounds.value()) { |
| 388 | composition_range_info_map_[view].character_bounds.emplace_back( |
| 389 | view->TransformPointToRootCoordSpace(rect.origin()), rect.size()); |
| 390 | } |
| 391 | |
| 392 | composition_range_info_map_[view].range.set_start(range.start()); |
| 393 | composition_range_info_map_[view].range.set_end(range.end()); |
| 394 | } |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 395 | |
Alex Mitra | 08747df | 2023-08-08 17:04:10 | [diff] [blame] | 396 | for (auto& observer : observer_list_) { |
Alex Mitra | 9b83bea9 | 2025-02-28 14:14:04 | [diff] [blame] | 397 | observer.OnImeCompositionRangeChanged(this, view, |
| 398 | character_bounds.has_value()); |
Alex Mitra | 08747df | 2023-08-08 17:04:10 | [diff] [blame] | 399 | } |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 400 | } |
| 401 | |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 402 | void TextInputManager::SelectionChanged(RenderWidgetHostViewBase* view, |
Jan Wilken Dörrie | aace0cfef | 2021-03-11 22:01:58 | [diff] [blame] | 403 | const std::u16string& text, |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 404 | size_t offset, |
changwan | 44664cd | 2017-05-23 19:14:34 | [diff] [blame] | 405 | const gfx::Range& range) { |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 406 | DCHECK(IsRegistered(view)); |
changwan | 44664cd | 2017-05-23 19:14:34 | [diff] [blame] | 407 | text_selection_map_[view].SetSelection(text, offset, range); |
ericwilligers | de38634 | 2016-10-19 02:35:09 | [diff] [blame] | 408 | for (auto& observer : observer_list_) |
| 409 | observer.OnTextSelectionChanged(this, view); |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 410 | } |
| 411 | |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 412 | void TextInputManager::Register(RenderWidgetHostViewBase* view) { |
| 413 | DCHECK(!IsRegistered(view)); |
My Nguyen | ca19c6d | 2020-08-05 11:32:42 | [diff] [blame] | 414 | text_input_state_map_[view] = ui::mojom::TextInputState::New(); |
ekaramad | 4cfc03e | 2016-07-21 17:34:21 | [diff] [blame] | 415 | selection_region_map_[view] = SelectionRegion(); |
| 416 | composition_range_info_map_[view] = CompositionRangeInfo(); |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 417 | text_selection_map_[view] = TextSelection(); |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 418 | } |
| 419 | |
| 420 | void TextInputManager::Unregister(RenderWidgetHostViewBase* view) { |
| 421 | DCHECK(IsRegistered(view)); |
| 422 | |
| 423 | text_input_state_map_.erase(view); |
ekaramad | 4cfc03e | 2016-07-21 17:34:21 | [diff] [blame] | 424 | selection_region_map_.erase(view); |
| 425 | composition_range_info_map_.erase(view); |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 426 | text_selection_map_.erase(view); |
Adam Ettenberger | 23b345b | 2024-10-16 20:03:39 | [diff] [blame] | 427 | #if BUILDFLAG(IS_WIN) |
| 428 | proximate_character_bounds_map_.erase(view); |
| 429 | #endif // BUILDFLAG(IS_WIN) |
ekaramad | 4cfc03e | 2016-07-21 17:34:21 | [diff] [blame] | 430 | |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 431 | if (active_view_ == view) { |
| 432 | active_view_ = nullptr; |
| 433 | NotifyObserversAboutInputStateUpdate(view, true); |
| 434 | } |
| 435 | view->DidUnregisterFromTextInputManager(this); |
| 436 | } |
| 437 | |
| 438 | bool TextInputManager::IsRegistered(RenderWidgetHostViewBase* view) const { |
| 439 | return text_input_state_map_.count(view) == 1; |
| 440 | } |
| 441 | |
| 442 | void TextInputManager::AddObserver(Observer* observer) { |
| 443 | observer_list_.AddObserver(observer); |
| 444 | } |
| 445 | |
| 446 | void TextInputManager::RemoveObserver(Observer* observer) { |
| 447 | observer_list_.RemoveObserver(observer); |
| 448 | } |
| 449 | |
ekaramad | fd5f5a89 | 2016-08-12 04:33:10 | [diff] [blame] | 450 | bool TextInputManager::HasObserver(Observer* observer) const { |
| 451 | return observer_list_.HasObserver(observer); |
| 452 | } |
| 453 | |
ekaramad | 936536d | 2016-06-29 05:44:44 | [diff] [blame] | 454 | size_t TextInputManager::GetRegisteredViewsCountForTesting() { |
| 455 | return text_input_state_map_.size(); |
| 456 | } |
| 457 | |
| 458 | ui::TextInputType TextInputManager::GetTextInputTypeForViewForTesting( |
| 459 | RenderWidgetHostViewBase* view) { |
| 460 | DCHECK(IsRegistered(view)); |
My Nguyen | ca19c6d | 2020-08-05 11:32:42 | [diff] [blame] | 461 | return text_input_state_map_[view]->type; |
ekaramad | 936536d | 2016-06-29 05:44:44 | [diff] [blame] | 462 | } |
| 463 | |
ekaramad | 2c3c487 | 2017-06-15 14:58:06 | [diff] [blame] | 464 | const gfx::Range* TextInputManager::GetCompositionRangeForTesting() const { |
| 465 | if (auto* info = GetCompositionRangeInfo()) |
| 466 | return &info->range; |
| 467 | return nullptr; |
| 468 | } |
| 469 | |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 470 | void TextInputManager::NotifyObserversAboutInputStateUpdate( |
| 471 | RenderWidgetHostViewBase* updated_view, |
| 472 | bool did_update_state) { |
ericwilligers | de38634 | 2016-10-19 02:35:09 | [diff] [blame] | 473 | for (auto& observer : observer_list_) |
| 474 | observer.OnUpdateTextInputStateCalled(this, updated_view, did_update_state); |
ekaramad | add88229 | 2016-06-08 15:22:56 | [diff] [blame] | 475 | } |
| 476 | |
Peter Kasting | c634073 | 2021-07-05 06:01:00 | [diff] [blame] | 477 | TextInputManager::SelectionRegion::SelectionRegion() = default; |
ekaramad | fcce088 | 2016-07-07 02:44:51 | [diff] [blame] | 478 | |
| 479 | TextInputManager::SelectionRegion::SelectionRegion( |
| 480 | const SelectionRegion& other) = default; |
| 481 | |
Peter Kasting | c634073 | 2021-07-05 06:01:00 | [diff] [blame] | 482 | TextInputManager::SelectionRegion& TextInputManager::SelectionRegion::operator=( |
| 483 | const SelectionRegion& other) = default; |
| 484 | |
| 485 | TextInputManager::CompositionRangeInfo::CompositionRangeInfo() = default; |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 486 | |
| 487 | TextInputManager::CompositionRangeInfo::CompositionRangeInfo( |
| 488 | const CompositionRangeInfo& other) = default; |
| 489 | |
Peter Kasting | c634073 | 2021-07-05 06:01:00 | [diff] [blame] | 490 | TextInputManager::CompositionRangeInfo::~CompositionRangeInfo() = default; |
ekaramad | b8e23a96c | 2016-07-13 01:21:15 | [diff] [blame] | 491 | |
changwan | 44664cd | 2017-05-23 19:14:34 | [diff] [blame] | 492 | TextInputManager::TextSelection::TextSelection() |
| 493 | : offset_(0), range_(gfx::Range::InvalidRange()) {} |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 494 | |
| 495 | TextInputManager::TextSelection::TextSelection(const TextSelection& other) = |
| 496 | default; |
| 497 | |
Peter Kasting | c634073 | 2021-07-05 06:01:00 | [diff] [blame] | 498 | TextInputManager::TextSelection::~TextSelection() = default; |
ekaramad | 6f1786b | 2016-07-21 21:10:58 | [diff] [blame] | 499 | |
Jan Wilken Dörrie | aace0cfef | 2021-03-11 22:01:58 | [diff] [blame] | 500 | void TextInputManager::TextSelection::SetSelection(const std::u16string& text, |
ekaramad | 374b266 | 2017-03-02 20:44:52 | [diff] [blame] | 501 | size_t offset, |
changwan | 44664cd | 2017-05-23 19:14:34 | [diff] [blame] | 502 | const gfx::Range& range) { |
ekaramad | 374b266 | 2017-03-02 20:44:52 | [diff] [blame] | 503 | text_ = text; |
| 504 | range_.set_start(range.start()); |
| 505 | range_.set_end(range.end()); |
| 506 | offset_ = offset; |
ekaramad | 65cf559 | 2016-08-18 21:44:53 | [diff] [blame] | 507 | |
ekaramad | 374b266 | 2017-03-02 20:44:52 | [diff] [blame] | 508 | // Update the selected text. |
| 509 | selected_text_.clear(); |
| 510 | if (!text.empty() && !range.is_empty()) { |
| 511 | size_t pos = range.GetMin() - offset; |
| 512 | size_t n = range.length(); |
| 513 | if (pos + n > text.length()) { |
| 514 | LOG(WARNING) |
| 515 | << "The text cannot fully cover range (selection's end point " |
| 516 | "exceeds text length)."; |
| 517 | } |
ekaramad | 65cf559 | 2016-08-18 21:44:53 | [diff] [blame] | 518 | |
ekaramad | 374b266 | 2017-03-02 20:44:52 | [diff] [blame] | 519 | if (pos >= text.length()) { |
| 520 | LOG(WARNING) << "The text cannot cover range (selection range's starting " |
| 521 | "point exceeds text length)."; |
| 522 | } else { |
| 523 | selected_text_.append(text.substr(pos, n)); |
| 524 | } |
ekaramad | 65cf559 | 2016-08-18 21:44:53 | [diff] [blame] | 525 | } |
ekaramad | 65cf559 | 2016-08-18 21:44:53 | [diff] [blame] | 526 | } |
| 527 | |
ekaramad | 4cfc03e | 2016-07-21 17:34:21 | [diff] [blame] | 528 | } // namespace content |