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 "third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.h"
6 
7 #include "build/build_config.h"
8 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
9 #include "third_party/blink/renderer/core/editing/frame_selection.h"
10 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
11 #include "third_party/blink/renderer/core/editing/markers/suggestion_marker_properties.h"
12 #include "third_party/blink/renderer/core/editing/selection_template.h"
13 #include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h"
14 #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
15 #include "third_party/blink/renderer/core/editing/visible_selection.h"
16 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
17 
18 using ui::mojom::ImeTextSpanThickness;
19 using ui::mojom::ImeTextSpanUnderlineStyle;
20 
21 namespace blink {
22 
23 class TextSuggestionControllerTest : public EditingTestBase {
24  public:
IsTextSuggestionHostAvailable()25   bool IsTextSuggestionHostAvailable() {
26     return bool(GetDocument()
27                     .GetFrame()
28                     ->GetTextSuggestionController()
29                     .text_suggestion_host_.is_bound());
30   }
31 
ShowSuggestionMenu(const HeapVector<std::pair<Member<const Text>,Member<DocumentMarker>>> & node_suggestion_marker_pairs,size_t max_number_of_suggestions)32   void ShowSuggestionMenu(
33       const HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>&
34           node_suggestion_marker_pairs,
35       size_t max_number_of_suggestions) {
36     GetDocument().GetFrame()->GetTextSuggestionController().ShowSuggestionMenu(
37         node_suggestion_marker_pairs, max_number_of_suggestions);
38   }
39 
ComputeRangeSurroundingCaret(const PositionInFlatTree & caret_position)40   EphemeralRangeInFlatTree ComputeRangeSurroundingCaret(
41       const PositionInFlatTree& caret_position) {
42     const Node* const position_node = caret_position.ComputeContainerNode();
43     const unsigned position_offset_in_node =
44         caret_position.ComputeOffsetInContainerNode();
45     // See ComputeRangeSurroundingCaret() in TextSuggestionController.
46     return EphemeralRangeInFlatTree(
47         PositionInFlatTree(position_node, position_offset_in_node - 1),
48         PositionInFlatTree(position_node, position_offset_in_node + 1));
49   }
50 };
51 
TEST_F(TextSuggestionControllerTest,ApplySpellCheckSuggestion)52 TEST_F(TextSuggestionControllerTest, ApplySpellCheckSuggestion) {
53   SetBodyContent(
54       "<div contenteditable>"
55       "spllchck"
56       "</div>");
57   Element* div = GetDocument().QuerySelector("div");
58   Node* text = div->firstChild();
59 
60   GetDocument().Markers().AddActiveSuggestionMarker(
61       EphemeralRange(Position(text, 0), Position(text, 8)), Color::kBlack,
62       ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
63       Color::kBlack, Color::kBlack);
64   // Select immediately before misspelling
65   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
66       SelectionInDOMTree::Builder()
67           .SetBaseAndExtent(Position(text, 0), Position(text, 0))
68           .Build());
69   GetDocument()
70       .GetFrame()
71       ->GetTextSuggestionController()
72       .ApplySpellCheckSuggestion("spellcheck");
73 
74   EXPECT_EQ("spellcheck", text->textContent());
75 
76   // Cursor should be at end of replaced text
77   const VisibleSelectionInFlatTree& selection =
78       GetFrame().Selection().ComputeVisibleSelectionInFlatTree();
79   EXPECT_EQ(text, selection.Start().ComputeContainerNode());
80   EXPECT_EQ(10, selection.Start().ComputeOffsetInContainerNode());
81   EXPECT_EQ(text, selection.End().ComputeContainerNode());
82   EXPECT_EQ(10, selection.End().ComputeOffsetInContainerNode());
83 }
84 
85 // Flaky on Android: http://crbug.com/1104700
86 #if defined(OS_ANDROID)
87 #define MAYBE_ApplyTextSuggestion DISABLED_ApplyTextSuggestion
88 #else
89 #define MAYBE_ApplyTextSuggestion ApplyTextSuggestion
90 #endif
TEST_F(TextSuggestionControllerTest,ApplyTextSuggestion)91 TEST_F(TextSuggestionControllerTest, ApplyTextSuggestion) {
92   SetBodyContent(
93       "<div contenteditable>"
94       "word1 word2 word3 word4"
95       "</div>");
96   Element* div = GetDocument().QuerySelector("div");
97   auto* text = To<Text>(div->firstChild());
98 
99   // Add marker on "word1". This marker should *not* be cleared by the
100   // replace operation.
101   GetDocument().Markers().AddSuggestionMarker(
102       EphemeralRange(Position(text, 0), Position(text, 5)),
103       SuggestionMarkerProperties::Builder()
104           .SetSuggestions(Vector<String>({"marker1"}))
105           .Build());
106 
107   // Add marker on "word1 word2 word3 word4". This marker should *not* be
108   // cleared by the replace operation.
109   GetDocument().Markers().AddSuggestionMarker(
110       EphemeralRange(Position(text, 0), Position(text, 23)),
111       SuggestionMarkerProperties::Builder()
112           .SetSuggestions(Vector<String>({"marker2"}))
113           .Build());
114 
115   // Add marker on "word2 word3". This marker should *not* be cleared by the
116   // replace operation.
117   GetDocument().Markers().AddSuggestionMarker(
118       EphemeralRange(Position(text, 6), Position(text, 17)),
119       SuggestionMarkerProperties::Builder()
120           .SetSuggestions(Vector<String>({"marker3"}))
121           .Build());
122 
123   // Add marker on "word4". This marker should *not* be cleared by the
124   // replace operation.
125   GetDocument().Markers().AddSuggestionMarker(
126       EphemeralRange(Position(text, 18), Position(text, 23)),
127       SuggestionMarkerProperties::Builder()
128           .SetSuggestions(Vector<String>({"marker4"}))
129           .Build());
130 
131   // Add marker on "word1 word2". This marker should be cleared by the
132   // replace operation.
133   GetDocument().Markers().AddSuggestionMarker(
134       EphemeralRange(Position(text, 0), Position(text, 11)),
135       SuggestionMarkerProperties::Builder()
136           .SetSuggestions(Vector<String>({"marker5"}))
137           .Build());
138 
139   // Add marker on "word3 word4". This marker should be cleared by the
140   // replace operation.
141   GetDocument().Markers().AddSuggestionMarker(
142       EphemeralRange(Position(text, 12), Position(text, 23)),
143       SuggestionMarkerProperties::Builder()
144           .SetSuggestions(Vector<String>({"marker6"}))
145           .Build());
146 
147   // Select immediately before word2.
148   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
149       SelectionInDOMTree::Builder()
150           .SetBaseAndExtent(Position(text, 6), Position(text, 6))
151           .Build());
152 
153   // Replace "word2 word3" with "marker3" (marker should have tag 3; tags start
154   // from 1, not 0).
155   GetDocument().GetFrame()->GetTextSuggestionController().ApplyTextSuggestion(
156       3, 0);
157 
158   // This returns the markers sorted by start offset; we need them sorted by
159   // start *and* end offset, since we have multiple markers starting at 0.
160   DocumentMarkerVector markers = GetDocument().Markers().MarkersFor(*text);
161   std::sort(markers.begin(), markers.end(),
162             [](const DocumentMarker* marker1, const DocumentMarker* marker2) {
163               if (marker1->StartOffset() != marker2->StartOffset())
164                 return marker1->StartOffset() < marker2->StartOffset();
165               return marker1->EndOffset() < marker2->EndOffset();
166             });
167 
168   EXPECT_EQ(4u, markers.size());
169 
170   // marker1
171   EXPECT_EQ(0u, markers[0]->StartOffset());
172   EXPECT_EQ(5u, markers[0]->EndOffset());
173 
174   // marker2
175   EXPECT_EQ(0u, markers[1]->StartOffset());
176   EXPECT_EQ(19u, markers[1]->EndOffset());
177 
178   // marker3
179   EXPECT_EQ(6u, markers[2]->StartOffset());
180   EXPECT_EQ(13u, markers[2]->EndOffset());
181 
182   const auto* const suggestion_marker = To<SuggestionMarker>(markers[2].Get());
183   EXPECT_EQ(1u, suggestion_marker->Suggestions().size());
184   EXPECT_EQ(String("word2 word3"), suggestion_marker->Suggestions()[0]);
185 
186   // marker4
187   EXPECT_EQ(14u, markers[3]->StartOffset());
188   EXPECT_EQ(19u, markers[3]->EndOffset());
189 
190   // marker5 and marker6 should've been cleared
191 
192   // Cursor should be at end of replaced text
193   const VisibleSelectionInFlatTree& selection =
194       GetFrame().Selection().ComputeVisibleSelectionInFlatTree();
195   EXPECT_EQ(text, selection.Start().ComputeContainerNode());
196   EXPECT_EQ(13, selection.Start().ComputeOffsetInContainerNode());
197   EXPECT_EQ(text, selection.End().ComputeContainerNode());
198   EXPECT_EQ(13, selection.End().ComputeOffsetInContainerNode());
199 }
200 
TEST_F(TextSuggestionControllerTest,ApplyingMisspellingTextSuggestionClearsMarker)201 TEST_F(TextSuggestionControllerTest,
202        ApplyingMisspellingTextSuggestionClearsMarker) {
203   SetBodyContent(
204       "<div contenteditable>"
205       "mispelled"
206       "</div>");
207   Element* div = GetDocument().QuerySelector("div");
208   auto* text = To<Text>(div->firstChild());
209 
210   // Add marker on "mispelled". This marker should be cleared by the replace
211   // operation.
212   GetDocument().Markers().AddSuggestionMarker(
213       EphemeralRange(Position(text, 0), Position(text, 9)),
214       SuggestionMarkerProperties::Builder()
215           .SetType(SuggestionMarker::SuggestionType::kMisspelling)
216           .SetSuggestions(Vector<String>({"misspelled"}))
217           .Build());
218 
219   // Check the tag for the marker that was just added (the current tag value is
220   // not reset between test cases).
221   int32_t marker_tag =
222       To<SuggestionMarker>(GetDocument().Markers().MarkersFor(*text)[0].Get())
223           ->Tag();
224 
225   // Select immediately before "mispelled".
226   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
227       SelectionInDOMTree::Builder()
228           .SetBaseAndExtent(Position(text, 0), Position(text, 0))
229           .Build());
230 
231   // Replace "mispelled" with "misspelled".
232   GetDocument().GetFrame()->GetTextSuggestionController().ApplyTextSuggestion(
233       marker_tag, 0);
234 
235   EXPECT_EQ(0u, GetDocument().Markers().MarkersFor(*text).size());
236   EXPECT_EQ("misspelled", text->textContent());
237 }
238 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteAtEnd)239 TEST_F(TextSuggestionControllerTest, DeleteActiveSuggestionRange_DeleteAtEnd) {
240   SetBodyContent(
241       "<div contenteditable>"
242       "word1 word2"
243       "</div>");
244   Element* div = GetDocument().QuerySelector("div");
245   Node* text = div->firstChild();
246 
247   // Mark "word2" as the active suggestion range
248   GetDocument().Markers().AddActiveSuggestionMarker(
249       EphemeralRange(Position(text, 6), Position(text, 11)),
250       Color::kTransparent, ImeTextSpanThickness::kThin,
251       ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
252   // Select immediately before word2
253   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
254       SelectionInDOMTree::Builder()
255           .SetBaseAndExtent(Position(text, 6), Position(text, 6))
256           .Build());
257   GetDocument()
258       .GetFrame()
259       ->GetTextSuggestionController()
260       .DeleteActiveSuggestionRange();
261 
262   EXPECT_EQ("word1\xA0", text->textContent());
263 }
264 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteInMiddle)265 TEST_F(TextSuggestionControllerTest,
266        DeleteActiveSuggestionRange_DeleteInMiddle) {
267   SetBodyContent(
268       "<div contenteditable>"
269       "word1 word2 word3"
270       "</div>");
271   Element* div = GetDocument().QuerySelector("div");
272   Node* text = div->firstChild();
273 
274   // Mark "word2" as the active suggestion range
275   GetDocument().Markers().AddActiveSuggestionMarker(
276       EphemeralRange(Position(text, 6), Position(text, 11)),
277       Color::kTransparent, ImeTextSpanThickness::kThin,
278       ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
279   // Select immediately before word2
280   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
281       SelectionInDOMTree::Builder()
282           .SetBaseAndExtent(Position(text, 6), Position(text, 6))
283           .Build());
284   GetDocument()
285       .GetFrame()
286       ->GetTextSuggestionController()
287       .DeleteActiveSuggestionRange();
288 
289   // One of the extra spaces around "word2" should have been removed
290   EXPECT_EQ("word1\xA0word3", text->textContent());
291 }
292 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteAtBeginningWithSpaceAfter)293 TEST_F(TextSuggestionControllerTest,
294        DeleteActiveSuggestionRange_DeleteAtBeginningWithSpaceAfter) {
295   SetBodyContent(
296       "<div contenteditable>"
297       "word1 word2"
298       "</div>");
299   Element* div = GetDocument().QuerySelector("div");
300   Node* text = div->firstChild();
301 
302   // Mark "word1" as the active suggestion range
303   GetDocument().Markers().AddActiveSuggestionMarker(
304       EphemeralRange(Position(text, 0), Position(text, 5)), Color::kTransparent,
305       ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
306       Color::kBlack, Color::kBlack);
307   // Select immediately before word1
308   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
309       SelectionInDOMTree::Builder()
310           .SetBaseAndExtent(Position(text, 0), Position(text, 0))
311           .Build());
312   GetDocument()
313       .GetFrame()
314       ->GetTextSuggestionController()
315       .DeleteActiveSuggestionRange();
316 
317   // The space after "word1" should have been removed (to avoid leaving an
318   // empty space at the beginning of the composition)
319   EXPECT_EQ("word2", text->textContent());
320 }
321 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteEntireRange)322 TEST_F(TextSuggestionControllerTest,
323        DeleteActiveSuggestionRange_DeleteEntireRange) {
324   SetBodyContent(
325       "<div contenteditable>"
326       "word1"
327       "</div>");
328   Element* div = GetDocument().QuerySelector("div");
329   Node* text = div->firstChild();
330 
331   // Mark "word1" as the active suggestion range
332   GetDocument().Markers().AddActiveSuggestionMarker(
333       EphemeralRange(Position(text, 0), Position(text, 5)), Color::kTransparent,
334       ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
335       Color::kBlack, Color::kBlack);
336   // Select immediately before word1
337   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
338       SelectionInDOMTree::Builder()
339           .SetBaseAndExtent(Position(text, 0), Position(text, 0))
340           .Build());
341   GetDocument()
342       .GetFrame()
343       ->GetTextSuggestionController()
344       .DeleteActiveSuggestionRange();
345 
346   EXPECT_EQ("", text->textContent());
347 }
348 
349 // The following two cases test situations that probably shouldn't occur in
350 // normal use (spell check/suggestoin markers not spanning a whole word), but
351 // are included anyway to verify that DeleteActiveSuggestionRange() is
352 // well-behaved in these cases
353 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteRangeWithTextBeforeAndSpaceAfter)354 TEST_F(TextSuggestionControllerTest,
355        DeleteActiveSuggestionRange_DeleteRangeWithTextBeforeAndSpaceAfter) {
356   SetBodyContent(
357       "<div contenteditable>"
358       "word1word2 word3"
359       "</div>");
360   Element* div = GetDocument().QuerySelector("div");
361   Node* text = div->firstChild();
362 
363   // Mark "word2" as the active suggestion range
364   GetDocument().Markers().AddActiveSuggestionMarker(
365       EphemeralRange(Position(text, 5), Position(text, 10)),
366       Color::kTransparent, ImeTextSpanThickness::kThin,
367       ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
368   // Select immediately before word2
369   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
370       SelectionInDOMTree::Builder()
371           .SetBaseAndExtent(Position(text, 5), Position(text, 5))
372           .Build());
373   GetDocument()
374       .GetFrame()
375       ->GetTextSuggestionController()
376       .DeleteActiveSuggestionRange();
377 
378   EXPECT_EQ("word1\xA0word3", text->textContent());
379 }
380 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteRangeWithSpaceBeforeAndTextAfter)381 TEST_F(TextSuggestionControllerTest,
382        DeleteActiveSuggestionRange_DeleteRangeWithSpaceBeforeAndTextAfter) {
383   SetBodyContent(
384       "<div contenteditable>"
385       "word1 word2word3"
386       "</div>");
387   Element* div = GetDocument().QuerySelector("div");
388   Node* text = div->firstChild();
389 
390   // Mark "word2" as the active suggestion range
391   GetDocument().Markers().AddActiveSuggestionMarker(
392       EphemeralRange(Position(text, 6), Position(text, 11)),
393       Color::kTransparent, ImeTextSpanThickness::kThin,
394       ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
395   // Select immediately before word2
396   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
397       SelectionInDOMTree::Builder()
398           .SetBaseAndExtent(Position(text, 6), Position(text, 6))
399           .Build());
400   GetDocument()
401       .GetFrame()
402       ->GetTextSuggestionController()
403       .DeleteActiveSuggestionRange();
404 
405   EXPECT_EQ("word1\xA0word3", text->textContent());
406 }
407 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteAtBeginningWithTextAfter)408 TEST_F(TextSuggestionControllerTest,
409        DeleteActiveSuggestionRange_DeleteAtBeginningWithTextAfter) {
410   SetBodyContent(
411       "<div contenteditable>"
412       "word1word2"
413       "</div>");
414   Element* div = GetDocument().QuerySelector("div");
415   Node* text = div->firstChild();
416 
417   // Mark "word1" as the active suggestion range
418   GetDocument().Markers().AddActiveSuggestionMarker(
419       EphemeralRange(Position(text, 0), Position(text, 5)), Color::kTransparent,
420       ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
421       Color::kBlack, Color::kBlack);
422   // Select immediately before word1
423   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
424       SelectionInDOMTree::Builder()
425           .SetBaseAndExtent(Position(text, 0), Position(text, 0))
426           .Build());
427   GetDocument()
428       .GetFrame()
429       ->GetTextSuggestionController()
430       .DeleteActiveSuggestionRange();
431 
432   EXPECT_EQ("word2", text->textContent());
433 }
434 
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_OnNewWordAddedToDictionary)435 TEST_F(TextSuggestionControllerTest,
436        DeleteActiveSuggestionRange_OnNewWordAddedToDictionary) {
437   SetBodyContent(
438       "<div contenteditable>"
439       "embiggen"
440       "</div>");
441   Element* div = GetDocument().QuerySelector("div");
442   Node* text = div->firstChild();
443 
444   // Mark "embiggen" as misspelled
445   GetDocument().Markers().AddSpellingMarker(
446       EphemeralRange(Position(text, 0), Position(text, 8)));
447   // Select inside before "embiggen"
448   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
449       SelectionInDOMTree::Builder()
450           .SetBaseAndExtent(Position(text, 1), Position(text, 1))
451           .Build());
452 
453   // Add some other word to the dictionary
454   GetDocument()
455       .GetFrame()
456       ->GetTextSuggestionController()
457       .OnNewWordAddedToDictionary("cromulent");
458   // Verify the spelling marker is still present
459   EXPECT_NE(nullptr, GetDocument()
460                          .GetFrame()
461                          ->GetSpellChecker()
462                          .GetSpellCheckMarkerUnderSelection()
463                          .first);
464 
465   // Add "embiggen" to the dictionary
466   GetDocument()
467       .GetFrame()
468       ->GetTextSuggestionController()
469       .OnNewWordAddedToDictionary("embiggen");
470   // Verify the spelling marker is gone
471   EXPECT_EQ(nullptr, GetDocument()
472                          .GetFrame()
473                          ->GetSpellChecker()
474                          .GetSpellCheckMarkerUnderSelection()
475                          .first);
476 }
477 
TEST_F(TextSuggestionControllerTest,CallbackHappensAfterDocumentDestroyed)478 TEST_F(TextSuggestionControllerTest, CallbackHappensAfterDocumentDestroyed) {
479   LocalFrame& frame = *GetDocument().GetFrame();
480   frame.DomWindow()->FrameDestroyed();
481 
482   // Shouldn't crash
483   frame.GetTextSuggestionController().SuggestionMenuTimeoutCallback(0);
484 }
485 
TEST_F(TextSuggestionControllerTest,SuggestionMarkerWithEmptySuggestion)486 TEST_F(TextSuggestionControllerTest, SuggestionMarkerWithEmptySuggestion) {
487   SetBodyContent(
488       "<div contenteditable>"
489       "hello"
490       "</div>");
491   Element* div = GetDocument().QuerySelector("div");
492   auto* text = To<Text>(div->firstChild());
493 
494   // Set suggestion marker with empty suggestion list.
495   GetDocument().Markers().AddSuggestionMarker(
496       EphemeralRange(Position(text, 0), Position(text, 5)),
497       SuggestionMarkerProperties::Builder()
498           .SetSuggestions(Vector<String>())
499           .Build());
500 
501   // Set the caret inside the word.
502   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
503       SelectionInDOMTree::Builder()
504           .SetBaseAndExtent(Position(text, 3), Position(text, 3))
505           .Build());
506 
507   // Handle potential suggestion tap on the caret position.
508   GetDocument()
509       .GetFrame()
510       ->GetTextSuggestionController()
511       .HandlePotentialSuggestionTap(PositionInFlatTree(text, 3));
512 
513   // We don't trigger menu in this case so there shouldn't be any mojom
514   // connection available.
515   EXPECT_FALSE(IsTextSuggestionHostAvailable());
516 
517   const VisibleSelectionInFlatTree& selection =
518       GetFrame().Selection().ComputeVisibleSelectionInFlatTree();
519   EXPECT_FALSE(selection.IsNone());
520 
521   const EphemeralRangeInFlatTree& range_to_check =
522       ComputeRangeSurroundingCaret(selection.Start());
523 
524   const HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>&
525       node_suggestion_marker_pairs =
526           GetFrame().GetDocument()->Markers().MarkersIntersectingRange(
527               range_to_check, DocumentMarker::MarkerTypes::Suggestion());
528   EXPECT_FALSE(node_suggestion_marker_pairs.IsEmpty());
529 
530   // Calling ShowSuggestionMenu() shouldn't crash. See crbug.com/901135.
531   // ShowSuggestionMenu() may still get called because of race condition.
532   ShowSuggestionMenu(node_suggestion_marker_pairs, 3);
533 }
534 
TEST_F(TextSuggestionControllerTest,SuggestionMarkerWithSuggestion)535 TEST_F(TextSuggestionControllerTest, SuggestionMarkerWithSuggestion) {
536   SetBodyContent(
537       "<div contenteditable>"
538       "hello"
539       "</div>");
540   Element* div = GetDocument().QuerySelector("div");
541   auto* text = To<Text>(div->firstChild());
542 
543   // Set suggestion marker with two suggestions.
544   GetDocument().Markers().AddSuggestionMarker(
545       EphemeralRange(Position(text, 0), Position(text, 5)),
546       SuggestionMarkerProperties::Builder()
547           .SetSuggestions(Vector<String>({"marker1", "marker2"}))
548           .Build());
549 
550   // Set the caret inside the word.
551   GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
552       SelectionInDOMTree::Builder()
553           .SetBaseAndExtent(Position(text, 3), Position(text, 3))
554           .Build());
555 
556   // Handle potential suggestion tap on the caret position.
557   GetDocument()
558       .GetFrame()
559       ->GetTextSuggestionController()
560       .HandlePotentialSuggestionTap(PositionInFlatTree(text, 3));
561 
562   EXPECT_TRUE(IsTextSuggestionHostAvailable());
563 }
564 
565 }  // namespace blink
566