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

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: Re-upload with no changes because I screwed something up trying to cancel the last upload when I sa… 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 const double kSuggestionUnderlineAlphaMultiplier = 0.4;
23
24 } // anonymous namespace
25
26 TextSuggestionController::TextSuggestionController(LocalFrame& frame)
27 : suggestion_menu_is_open_(false), frame_(&frame) {}
28
29 bool TextSuggestionController::IsMenuOpen() const {
30 return suggestion_menu_is_open_;
31 }
32
33 void TextSuggestionController::HandlePotentialMisspelledWordTap() {
34 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
35 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
36 if (!node_and_marker)
37 return;
38
39 if (!text_suggestion_host_) {
40 GetFrame().GetInterfaceProvider()->GetInterface(
41 mojo::MakeRequest(&text_suggestion_host_));
42 }
43
44 text_suggestion_host_->StartSpellCheckMenuTimer();
45 }
46
47 DEFINE_TRACE(TextSuggestionController) {
48 visitor->Trace(frame_);
49 }
50
51 void TextSuggestionController::ApplySpellCheckSuggestion(
52 const String& suggestion) {
53 GetFrame().GetSpellChecker().ReplaceMisspelledRange(suggestion);
54 SuggestionMenuClosed();
55 }
56
57 namespace {
Xiaocheng 2017/06/13 18:17:42 nit: Shouldn't have two anonymous namespaces.
58
59 bool ShouldDeleteNextCharacter(const Node* marker_text_node,
60 const DocumentMarker* marker) {
61 // If the character immediately following the range to be deleted is a space,
62 // delete it if either of these conditions holds:
63 // - We're deleting at the beginning of the editable text (to avoid ending up
64 // with a space at the beginning)
65 // - The character immediately before the range being deleted is also a space
66 // (to avoid ending up with two adjacent spaces)
67 const EphemeralRange next_character_range =
68 PlainTextRange(marker->EndOffset(), marker->EndOffset() + 1)
69 .CreateRange(*marker_text_node->parentNode());
70 // No character immediately following the range (so it can't be a space)
71 if (next_character_range.IsNull())
72 return false;
73
74 const String next_character_str = PlainText(
75 next_character_range,
76 TextIteratorBehavior::Builder().SetEmitsSpaceForNbsp(true).Build());
Xiaocheng 2017/06/13 18:17:42 |EmitsSpaceForNbsp| worsens the performance, as it
77 // Character immediately following the range is not a space
78 if (!WTF::IsASCIISpace(next_character_str[0]))
79 return false;
80
81 // First case: we're deleting at the beginning of the editable text
82 if (marker->StartOffset() == 0)
83 return true;
84
85 const EphemeralRange prev_character_range =
86 PlainTextRange(marker->StartOffset() - 1, marker->StartOffset())
87 .CreateRange(*marker_text_node->parentNode());
88 // Not at beginning, but there's no character immediately before the range
89 // being deleted (so it can't be a space)
90 if (prev_character_range.IsNull())
91 return false;
92
93 const String prev_character_str = PlainText(
94 prev_character_range,
95 TextIteratorBehavior::Builder().SetEmitsSpaceForNbsp(true).Build());
Xiaocheng 2017/06/13 18:17:42 Ditto.
96 // Return true if the character immediately before the range is space, false
97 // otherwise
98 return WTF::IsASCIISpace(prev_character_str[0]);
99 }
100
101 } // namespace
102
103 void TextSuggestionController::DeleteActiveSuggestionRange() {
104 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
105 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
106 if (!node_and_marker)
107 return;
108
109 Node* const marker_text_node = node_and_marker.value().first;
110 const DocumentMarker* const marker = node_and_marker.value().second;
111
112 const bool delete_next_char =
113 ShouldDeleteNextCharacter(marker_text_node, marker);
114
115 const EphemeralRange range_to_delete = EphemeralRange(
116 Position(marker_text_node, marker->StartOffset()),
117 Position(marker_text_node, marker->EndOffset() + delete_next_char));
118
119 GetFrame().Selection().SetSelection(
yosin_UTC9 2017/06/13 06:29:31 FrameSelection::SetSelection() can make current fr
120 SelectionInDOMTree::Builder().SetBaseAndExtent(range_to_delete).Build());
121
122 Document& current_document = *GetFrame().GetDocument();
123
124 // Dispatch 'beforeinput'.
125 Element* const target = GetFrame().GetEditor().FindEventTargetFromSelection();
126 DataTransfer* const data_transfer = DataTransfer::Create(
127 DataTransfer::DataTransferType::kInsertReplacementText,
128 DataTransferAccessPolicy::kDataTransferReadable,
129 DataObject::CreateFromString(""));
130
131 const bool cancel = DispatchBeforeInputDataTransfer(
132 target, InputEvent::InputType::kInsertReplacementText,
133 data_transfer) != DispatchEventResult::kNotCanceled;
134
135 // 'beforeinput' event handler may destroy target frame.
136 if (current_document != GetFrame().GetDocument())
137 return;
138
139 // TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
140 // needs to be audited. See http://crbug.com/590369 for more details.
141 GetFrame().GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
142
143 if (cancel)
144 return;
145 GetFrame().GetEditor().ReplaceSelectionWithText(
146 "", false, false, InputEvent::InputType::kInsertReplacementText);
147
148 SuggestionMenuClosed();
149 }
150
151 void TextSuggestionController::NewWordAddedToDictionary(const String& word) {
152 // Android pops up a dialog to let the user confirm they actually want to add
153 // the word to the dictionary; this method gets called as soon as the dialog
154 // is shown. So the word isn't actually in the dictionary here, even if the
155 // user will end up confirming the dialog, and we shouldn't try to re-run
156 // spellcheck here.
157
158 // Note: this actually matches the behavior in native Android text boxes
159 GetDocument().Markers().RemoveSpellingMarkersUnderWords(
160 Vector<String>({word}));
161 SuggestionMenuClosed();
162 }
163
164 void TextSuggestionController::SpellCheckMenuTimeoutCallback() {
165 Optional<std::pair<Node*, SpellCheckMarker*>> const node_and_marker =
166 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
167 if (!node_and_marker)
168 return;
169
170 Node* const container_node = node_and_marker.value().first;
171 SpellCheckMarker* const marker = node_and_marker.value().second;
172
173 const EphemeralRange marker_range =
174 EphemeralRange(Position(container_node, marker->StartOffset()),
175 Position(container_node, marker->EndOffset()));
176 const String& misspelled_word = PlainText(marker_range);
177 const String& description = marker->Description();
178
179 suggestion_menu_is_open_ = true;
180 GetFrame().Selection().SetCaretVisible(false);
181 GetDocument().Markers().AddActiveSuggestionMarker(
182 marker_range, SK_ColorTRANSPARENT, StyleableMarker::Thickness::kThin,
183 LayoutTheme::GetTheme()
184 .PlatformSpellingMarkerUnderlineColor()
yosin_UTC9 2017/06/13 06:29:31 Could you introduce |PlatformActiveSuggestionUnder
rlanday 2017/06/13 06:59:27 I'm basically porting over the code Android uses t
yosin_UTC9 2017/06/13 07:12:15 Let's provide four colors in LayoutTheme to provid
185 .CombineWithAlpha(kSuggestionUnderlineAlphaMultiplier));
186
187 Vector<String> suggestions;
188 description.Split('\n', suggestions);
189
190 Vector<mojom::blink::SpellCheckSuggestionPtr> suggestion_ptrs;
191 for (const String& suggestion : suggestions) {
192 mojom::blink::SpellCheckSuggestionPtr info_ptr(
193 mojom::blink::SpellCheckSuggestion::New());
194 info_ptr->suggestion = suggestion;
195 suggestion_ptrs.push_back(std::move(info_ptr));
196 }
197
198 const IntRect& absolute_bounds = GetFrame().Selection().AbsoluteCaretBounds();
199 const IntRect& viewport_bounds =
200 GetFrame().View()->ContentsToViewport(absolute_bounds);
201
202 text_suggestion_host_->ShowSpellCheckSuggestionMenu(
203 viewport_bounds.X(), viewport_bounds.MaxY(), std::move(misspelled_word),
204 std::move(suggestion_ptrs));
205 }
206
207 void TextSuggestionController::SuggestionMenuClosed() {
Xiaocheng 2017/06/13 18:17:42 The frame may be already detached when called from
208 GetDocument().Markers().RemoveMarkersOfTypes(
209 DocumentMarker::kActiveSuggestion);
210 GetFrame().Selection().SetCaretVisible(true);
211 suggestion_menu_is_open_ = false;
212 }
213
214 Document& TextSuggestionController::GetDocument() const {
215 DCHECK(IsAvailable());
216 return *GetFrame().GetDocument();
217 }
218
219 bool TextSuggestionController::IsAvailable() const {
220 return GetFrame().GetDocument();
221 }
222
223 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698