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

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: Remove some unused fields in SuggestionsPopupWindow, don't hold reference to mutable array passed in 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 bool TextSuggestionController::IsMenuOpen() const {
72 return suggestion_menu_is_open_;
73 }
74
75 void TextSuggestionController::HandlePotentialMisspelledWordTap() {
76 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
77 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
78 if (!node_and_marker)
79 return;
80
81 if (!text_suggestion_host_) {
82 GetFrame().GetInterfaceProvider()->GetInterface(
83 mojo::MakeRequest(&text_suggestion_host_));
84 }
85
86 text_suggestion_host_->StartSpellCheckMenuTimer();
87 }
88
89 DEFINE_TRACE(TextSuggestionController) {
90 visitor->Trace(frame_);
91 }
92
93 void TextSuggestionController::ApplySpellCheckSuggestion(
94 const String& suggestion) {
95 GetFrame().GetSpellChecker().ReplaceMisspelledRange(suggestion);
96 SuggestionMenuClosed();
97 }
98
99 void TextSuggestionController::DeleteActiveSuggestionRange() {
100 Optional<std::pair<Node*, SpellCheckMarker*>> node_and_marker =
101 GetFrame().GetSpellChecker().GetSpellCheckMarkerTouchingSelection();
102 if (!node_and_marker)
103 return;
104
105 Node* const marker_text_node = node_and_marker.value().first;
106 const DocumentMarker* const marker = node_and_marker.value().second;
107
108 const bool delete_next_char =
109 ShouldDeleteNextCharacter(marker_text_node, marker);
110
111 const EphemeralRange range_to_delete = EphemeralRange(
112 Position(marker_text_node, marker->StartOffset()),
113 Position(marker_text_node, marker->EndOffset() + delete_next_char));
114
115 Document& current_document = *GetFrame().GetDocument();
116
117 GetFrame().Selection().SetSelection(
118 SelectionInDOMTree::Builder().SetBaseAndExtent(range_to_delete).Build());
119
120 // FrameSelection::SetSelection() may destroy the frame.
121 if (current_document != GetFrame().GetDocument())
122 return;
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().PlatformActiveSpellingMarkerHighlightColor());
184
185 Vector<String> suggestions;
186 description.Split('\n', suggestions);
187
188 Vector<mojom::blink::SpellCheckSuggestionPtr> suggestion_ptrs;
189 for (const String& suggestion : suggestions) {
190 mojom::blink::SpellCheckSuggestionPtr info_ptr(
191 mojom::blink::SpellCheckSuggestion::New());
192 info_ptr->suggestion = suggestion;
193 suggestion_ptrs.push_back(std::move(info_ptr));
194 }
195
196 const IntRect& absolute_bounds = GetFrame().Selection().AbsoluteCaretBounds();
197 const IntRect& viewport_bounds =
198 GetFrame().View()->ContentsToViewport(absolute_bounds);
199
200 text_suggestion_host_->ShowSpellCheckSuggestionMenu(
201 viewport_bounds.X(), viewport_bounds.MaxY(), std::move(misspelled_word),
202 std::move(suggestion_ptrs));
203 }
204
205 void TextSuggestionController::SuggestionMenuClosed() {
206 if (!frame_)
Xiaocheng 2017/06/14 00:44:38 This doesn't work because |frame_| won't be set to
yosin_UTC9 2017/06/14 01:17:04 It is good idea to observe Document shutdown as SM
207 return;
208
209 GetDocument().Markers().RemoveMarkersOfTypes(
210 DocumentMarker::kActiveSuggestion);
211 GetFrame().Selection().SetCaretVisible(true);
212 suggestion_menu_is_open_ = false;
213 }
214
215 Document& TextSuggestionController::GetDocument() const {
216 DCHECK(IsAvailable());
217 return *GetFrame().GetDocument();
218 }
219
220 bool TextSuggestionController::IsAvailable() const {
221 return GetFrame().GetDocument();
222 }
223
224 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698