Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Side by Side Diff: third_party/WebKit/Source/core/editing/suggestion/TextSuggestionController.cpp

Issue 2931443003: Add support for Android spellcheck menu in Chrome/WebViews (Closed)
Patch Set: Fix dependency Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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 "core/editing/suggestion/TextSuggestionController.h"
6
7 #include "core/editing/EditingUtilities.h"
8 #include "core/editing/Editor.h"
9 #include "core/editing/FrameSelection.h"
10 #include "core/editing/PlainTextRange.h"
11 #include "core/editing/markers/DocumentMarkerController.h"
12 #include "core/editing/markers/SpellCheckMarker.h"
13 #include "core/editing/spellcheck/SpellChecker.h"
14 #include "core/frame/FrameView.h"
15 #include "core/frame/LocalFrame.h"
16 #include "public/platform/InterfaceProvider.h"
17
18 namespace blink {
19
20 namespace {
21
22 bool ShouldDeleteNextCharacter(const Node* marker_text_node,
23 const DocumentMarker* marker) {
24 // If the character immediately following the range to be deleted is a space,
25 // delete it if either of these conditions holds:
26 // - We're deleting at the beginning of the editable text (to avoid ending up
27 // with a space at the beginning)
28 // - The character immediately before the range being deleted is also a space
29 // (to avoid ending up with two adjacent spaces)
30 const EphemeralRange next_character_range =
31 PlainTextRange(marker->EndOffset(), marker->EndOffset() + 1)
32 .CreateRange(*marker_text_node->parentNode());
33 // No character immediately following the range (so it can't be a space)
34 if (next_character_range.IsNull())
35 return false;
36
37 const String next_character_str =
38 PlainText(next_character_range, TextIteratorBehavior::Builder().Build());
39 UChar next_character = next_character_str[0];
40 // Character immediately following the range is not a space
41 if (next_character != kSpaceCharacter &&
42 next_character != kNoBreakSpaceCharacter)
43 return false;
44
45 // First case: we're deleting at the beginning of the editable text
46 if (marker->StartOffset() == 0)
47 return true;
48
49 const EphemeralRange prev_character_range =
50 PlainTextRange(marker->StartOffset() - 1, marker->StartOffset())
51 .CreateRange(*marker_text_node->parentNode());
52 // Not at beginning, but there's no character immediately before the range
53 // being deleted (so it can't be a space)
54 if (prev_character_range.IsNull())
55 return false;
56
57 const String prev_character_str =
58 PlainText(prev_character_range, TextIteratorBehavior::Builder().Build());
59 // Return true if the character immediately before the range is a space, false
60 // otherwise
61 UChar prev_character = prev_character_str[0];
62 return prev_character == kSpaceCharacter ||
63 prev_character == kNoBreakSpaceCharacter;
64 }
65
66 } // namespace
67
68 TextSuggestionController::TextSuggestionController(LocalFrame& frame)
69 : suggestion_menu_is_open_(false), frame_(&frame) {}
70
71 void TextSuggestionController::DocumentAttached(Document* document) {
72 DCHECK(document);
73 SetContext(document);
74 }
75
76 bool TextSuggestionController::IsMenuOpen() const {
77 return suggestion_menu_is_open_;
78 }
79
80 void TextSuggestionController::HandlePotentialMisspelledWordTap() {
81 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
82 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
83 if (!node_and_marker)
84 return;
85
86 if (!text_suggestion_host_) {
87 GetFrame().GetInterfaceProvider()->GetInterface(
88 mojo::MakeRequest(&text_suggestion_host_));
89 }
90
91 text_suggestion_host_->StartSpellCheckMenuTimer();
92 }
93
94 DEFINE_TRACE(TextSuggestionController) {
95 visitor->Trace(frame_);
96 DocumentShutdownObserver::Trace(visitor);
97 }
98
99 void TextSuggestionController::ApplySpellCheckSuggestion(
100 const String& suggestion) {
101 GetFrame().GetSpellChecker().ReplaceMisspelledRange(suggestion);
102 SuggestionMenuClosed();
103 }
104
105 void TextSuggestionController::DeleteActiveSuggestionRange() {
106 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
107 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
108 if (!node_and_marker)
109 return;
110
111 Node* const marker_text_node = node_and_marker.value().first;
112 const DocumentMarker* const marker = node_and_marker.value().second;
113
114 const bool delete_next_char =
115 ShouldDeleteNextCharacter(marker_text_node, marker);
116
117 const EphemeralRange range_to_delete = EphemeralRange(
118 Position(marker_text_node, marker->StartOffset()),
119 Position(marker_text_node, marker->EndOffset() + delete_next_char));
120
121 GetFrame().Selection().SetSelection(
122 SelectionInDOMTree::Builder().SetBaseAndExtent(range_to_delete).Build());
123
124 // FrameSelection::SetSelection() may destroy the frame.
125 if (!IsAvailable())
126 return;
127
128 // Dispatch 'beforeinput'.
129 Element* const target = GetFrame().GetEditor().FindEventTargetFromSelection();
130 DataTransfer* const data_transfer = DataTransfer::Create(
131 DataTransfer::DataTransferType::kInsertReplacementText,
132 DataTransferAccessPolicy::kDataTransferReadable,
133 DataObject::CreateFromString(""));
134
135 const bool cancel = DispatchBeforeInputDataTransfer(
136 target, InputEvent::InputType::kInsertReplacementText,
137 data_transfer) != DispatchEventResult::kNotCanceled;
138
139 // 'beforeinput' event handler may destroy target frame.
140 if (!IsAvailable())
141 return;
142
143 // TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
144 // needs to be audited. See http://crbug.com/590369 for more details.
145 GetFrame().GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
146
147 if (cancel)
148 return;
149 GetFrame().GetEditor().ReplaceSelectionWithText(
150 "", false, false, InputEvent::InputType::kInsertReplacementText);
151
152 SuggestionMenuClosed();
153 }
154
155 void TextSuggestionController::NewWordAddedToDictionary(const String& word) {
156 // Android pops up a dialog to let the user confirm they actually want to add
157 // the word to the dictionary; this method gets called as soon as the dialog
158 // is shown. So the word isn't actually in the dictionary here, even if the
159 // user will end up confirming the dialog, and we shouldn't try to re-run
160 // spellcheck here.
161
162 // Note: this actually matches the behavior in native Android text boxes
163 GetDocument().Markers().RemoveSpellingMarkersUnderWords(
164 Vector<String>({word}));
165 SuggestionMenuClosed();
166 }
167
168 void TextSuggestionController::SpellCheckMenuTimeoutCallback() {
169 Optional<std::pair<Node*, SpellCheckMarker*>> const node_and_marker =
170 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
171 if (!node_and_marker)
172 return;
173
174 Node* const container_node = node_and_marker.value().first;
175 SpellCheckMarker* const marker = node_and_marker.value().second;
176
177 const EphemeralRange marker_range =
178 EphemeralRange(Position(container_node, marker->StartOffset()),
179 Position(container_node, marker->EndOffset()));
180 const String& misspelled_word = PlainText(marker_range);
181 const String& description = marker->Description();
182
183 suggestion_menu_is_open_ = true;
184 GetFrame().Selection().SetCaretVisible(false);
185 GetDocument().Markers().AddActiveSuggestionMarker(
186 marker_range, SK_ColorTRANSPARENT, StyleableMarker::Thickness::kThin,
187 LayoutTheme::GetTheme().PlatformActiveSpellingMarkerHighlightColor());
188
189 Vector<String> suggestions;
190 description.Split('\n', suggestions);
191
192 Vector<mojom::blink::SpellCheckSuggestionPtr> suggestion_ptrs;
193 for (const String& suggestion : suggestions) {
194 mojom::blink::SpellCheckSuggestionPtr info_ptr(
195 mojom::blink::SpellCheckSuggestion::New());
196 info_ptr->suggestion = suggestion;
197 suggestion_ptrs.push_back(std::move(info_ptr));
198 }
199
200 const IntRect& absolute_bounds = GetFrame().Selection().AbsoluteCaretBounds();
201 const IntRect& viewport_bounds =
202 GetFrame().View()->ContentsToViewport(absolute_bounds);
203
204 text_suggestion_host_->ShowSpellCheckSuggestionMenu(
205 viewport_bounds.X(), viewport_bounds.MaxY(), std::move(misspelled_word),
206 std::move(suggestion_ptrs));
207 }
208
209 void TextSuggestionController::SuggestionMenuClosed() {
210 if (!IsAvailable())
211 return;
212
213 GetDocument().Markers().RemoveMarkersOfTypes(
214 DocumentMarker::kActiveSuggestion);
215 GetFrame().Selection().SetCaretVisible(true);
216 suggestion_menu_is_open_ = false;
217 }
218
219 Document& TextSuggestionController::GetDocument() const {
220 DCHECK(IsAvailable());
221 return *LifecycleContext();
222 }
223
224 bool TextSuggestionController::IsAvailable() const {
225 return LifecycleContext();
226 }
227
228 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698