1 // Copyright 2020 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/page/scrolling/text_fragment_selector_generator.h"
6 
7 #include <gtest/gtest.h>
8 
9 #include "base/run_loop.h"
10 #include "base/test/bind.h"
11 #include "base/test/metrics/histogram_tester.h"
12 #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
13 #include "components/ukm/test_ukm_recorder.h"
14 #include "mojo/public/cpp/bindings/receiver_set.h"
15 #include "services/metrics/public/cpp/ukm_builders.h"
16 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
17 #include "third_party/blink/public/mojom/link_to_text/link_to_text.mojom-blink.h"
18 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
19 #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
20 #include "third_party/blink/renderer/core/frame/local_frame.h"
21 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
22 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
23 
24 using LinkGenerationError = shared_highlighting::LinkGenerationError;
25 
26 namespace blink {
27 
28 namespace {
29 const char kSuccessUkmMetric[] = "Success";
30 const char kErrorUkmMetric[] = "Error";
31 }  // namespace
32 
33 class TextFragmentSelectorGeneratorTest : public SimTest {
34  public:
SetUp()35   void SetUp() override {
36     SimTest::SetUp();
37     WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
38   }
39 
VerifySelector(Position selected_start,Position selected_end,String expected_selector)40   void VerifySelector(Position selected_start,
41                       Position selected_end,
42                       String expected_selector) {
43     String generated_selector = GenerateSelector(selected_start, selected_end);
44     EXPECT_EQ(expected_selector, generated_selector);
45 
46     // Should not have logged errors in a success case.
47     histogram_tester_.ExpectTotalCount("SharedHighlights.LinkGenerated.Error",
48                                        0);
49 
50     auto* recorder =
51         static_cast<ukm::TestUkmRecorder*>(GetDocument().UkmRecorder());
52     auto entries = recorder->GetEntriesByName(
53         ukm::builders::SharedHighlights_LinkGenerated::kEntryName);
54     ASSERT_EQ(1u, entries.size());
55     const ukm::mojom::UkmEntry* entry = entries[0];
56     EXPECT_EQ(GetDocument().UkmSourceID(), entry->source_id);
57     recorder->ExpectEntryMetric(entry, kSuccessUkmMetric, true);
58     EXPECT_FALSE(recorder->GetEntryMetric(entry, kErrorUkmMetric));
59   }
60 
VerifySelectorFails(Position selected_start,Position selected_end,LinkGenerationError error)61   void VerifySelectorFails(Position selected_start,
62                            Position selected_end,
63                            LinkGenerationError error) {
64     String generated_selector = GenerateSelector(selected_start, selected_end);
65     EXPECT_EQ("", generated_selector);
66 
67     histogram_tester_.ExpectBucketCount("SharedHighlights.LinkGenerated.Error",
68                                         error, 1);
69 
70     auto* recorder =
71         static_cast<ukm::TestUkmRecorder*>(GetDocument().UkmRecorder());
72     auto entries = recorder->GetEntriesByName(
73         ukm::builders::SharedHighlights_LinkGenerated::kEntryName);
74     ASSERT_EQ(1u, entries.size());
75     const ukm::mojom::UkmEntry* entry = entries[0];
76     EXPECT_EQ(GetDocument().UkmSourceID(), entry->source_id);
77     recorder->ExpectEntryMetric(entry, kSuccessUkmMetric, false);
78     recorder->ExpectEntryMetric(entry, kErrorUkmMetric,
79                                 static_cast<int64_t>(error));
80   }
81 
GenerateSelector(Position selected_start,Position selected_end)82   String GenerateSelector(Position selected_start, Position selected_end) {
83     StubUkmRecorder();
84 
85     GetDocument()
86         .GetFrame()
87         ->GetTextFragmentSelectorGenerator()
88         ->UpdateSelection(GetDocument().GetFrame(),
89                           ToEphemeralRangeInFlatTree(
90                               EphemeralRange(selected_start, selected_end)));
91 
92     bool callback_called = false;
93     String selector;
94     auto lambda = [](bool& callback_called, String& selector,
95                      const String& generated_selector) {
96       selector = generated_selector;
97       callback_called = true;
98     };
99     auto callback =
100         WTF::Bind(lambda, std::ref(callback_called), std::ref(selector));
101     GetDocument()
102         .GetFrame()
103         ->GetTextFragmentSelectorGenerator()
104         ->GenerateSelector(std::move(callback));
105     base::RunLoop().RunUntilIdle();
106 
107     EXPECT_TRUE(callback_called);
108     return selector;
109   }
110 
111  protected:
StubUkmRecorder()112   void StubUkmRecorder() {
113     // Needed to keep old recorders alive, as other instances might depend on
114     // one of them, causing tests to crash during teardown.
115     old_ukm_recorders_.push_back(std::move(GetDocument().ukm_recorder_));
116     GetDocument().ukm_recorder_ = std::make_unique<ukm::TestUkmRecorder>();
117   }
118 
119   base::HistogramTester histogram_tester_;
120 
121   // TODO(crbug.com/1153990): Find a better mocking solution and clean up this
122   // variable.
123   std::vector<std::unique_ptr<ukm::UkmRecorder>> old_ukm_recorders_;
124 };
125 
126 // Basic exact selector case.
TEST_F(TextFragmentSelectorGeneratorTest,EmptySelection)127 TEST_F(TextFragmentSelectorGeneratorTest, EmptySelection) {
128   SimRequest request("https://example.com/test.html", "text/html");
129   LoadURL("https://example.com/test.html");
130   request.Complete(R"HTML(
131     <!DOCTYPE html>
132     <p id='first'>First paragraph</p>
133   )HTML");
134   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
135   const auto& selected_start = Position(first_paragraph, 5);
136   const auto& selected_end = Position(first_paragraph, 6);
137   ASSERT_EQ(" ", PlainText(EphemeralRange(selected_start, selected_end)));
138 
139   VerifySelectorFails(selected_start, selected_end,
140                       LinkGenerationError::kEmptySelection);
141 }
142 
143 // Basic exact selector case.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector)144 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector) {
145   SimRequest request("https://example.com/test.html", "text/html");
146   LoadURL("https://example.com/test.html");
147   request.Complete(R"HTML(
148     <!DOCTYPE html>
149     <div>Test page</div>
150     <p id='first'>First paragraph text that is longer than 20 chars</p>
151     <p id='second'>Second paragraph text</p>
152   )HTML");
153   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
154   const auto& selected_start = Position(first_paragraph, 0);
155   const auto& selected_end = Position(first_paragraph, 28);
156   ASSERT_EQ("First paragraph text that is",
157             PlainText(EphemeralRange(selected_start, selected_end)));
158 
159   VerifySelector(selected_start, selected_end,
160                  "First%20paragraph%20text%20that%20is");
161 }
162 
163 // Exact selector test where selection contains nested <i> node.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextWithNestedTextNodes)164 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextWithNestedTextNodes) {
165   SimRequest request("https://example.com/test.html", "text/html");
166   LoadURL("https://example.com/test.html");
167   request.Complete(R"HTML(
168     <!DOCTYPE html>
169     <div>Test page</div>
170     <p id='first'>First paragraph text that is <i>longer than 20</i> chars</p>
171     <p id='second'>Second paragraph text</p>
172   )HTML");
173   Node* first_paragraph = GetDocument().getElementById("first");
174   const auto& selected_start = Position(first_paragraph->firstChild(), 0);
175   const auto& selected_end =
176       Position(first_paragraph->firstChild()->nextSibling()->firstChild(), 6);
177   ASSERT_EQ("First paragraph text that is longer",
178             PlainText(EphemeralRange(selected_start, selected_end)));
179 
180   VerifySelector(selected_start, selected_end,
181                  "First%20paragraph%20text%20that%20is%20longer");
182 }
183 
184 // Exact selector test where selection contains multiple spaces.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextWithExtraSpace)185 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextWithExtraSpace) {
186   SimRequest request("https://example.com/test.html", "text/html");
187   LoadURL("https://example.com/test.html");
188   request.Complete(R"HTML(
189     <!DOCTYPE html>
190     <div>Test page</div>
191     <p id='first'>First paragraph text that is longer than 20 chars</p>
192     <p id='second'>Second paragraph
193       text</p>
194   )HTML");
195   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
196   const auto& selected_start = Position(second_paragraph, 0);
197   const auto& selected_end = Position(second_paragraph, 27);
198   ASSERT_EQ("Second paragraph text",
199             PlainText(EphemeralRange(selected_start, selected_end)));
200 
201   VerifySelector(selected_start, selected_end, "Second%20paragraph%20text");
202 }
203 
204 // Exact selector where selection is too short, in which case context is
205 // required.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_TooShortNeedsContext)206 TEST_F(TextFragmentSelectorGeneratorTest,
207        ExactTextSelector_TooShortNeedsContext) {
208   SimRequest request("https://example.com/test.html", "text/html");
209   LoadURL("https://example.com/test.html");
210   request.Complete(R"HTML(
211     <!DOCTYPE html>
212     <div>Test page</div>
213     <p id='first'>First paragraph prefix to unique snippet of text.</p>
214     <p id='second'>Second paragraph</p>
215   )HTML");
216   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
217   const auto& selected_start = Position(first_paragraph, 26);
218   const auto& selected_end = Position(first_paragraph, 40);
219   ASSERT_EQ("unique snippet",
220             PlainText(EphemeralRange(selected_start, selected_end)));
221 
222   VerifySelector(selected_start, selected_end, "to-,unique%20snippet,-of");
223 }
224 
225 // Exact selector with context test. Case when only one word for prefix and
226 // suffix is enough to disambiguate the selection.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_WithOneWordContext)227 TEST_F(TextFragmentSelectorGeneratorTest,
228        ExactTextSelector_WithOneWordContext) {
229   SimRequest request("https://example.com/test.html", "text/html");
230   LoadURL("https://example.com/test.html");
231   request.Complete(R"HTML(
232     <!DOCTYPE html>
233     <div>Test page</div>
234     <p id='first'>First paragraph text that is longer than 20 chars</p>
235     <p id='second'>Second paragraph text that is short</p>
236   )HTML");
237   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
238   const auto& selected_start = Position(first_paragraph, 6);
239   const auto& selected_end = Position(first_paragraph, 28);
240   ASSERT_EQ("paragraph text that is",
241             PlainText(EphemeralRange(selected_start, selected_end)));
242 
243   VerifySelector(selected_start, selected_end,
244                  "First-,paragraph%20text%20that%20is,-longer");
245 }
246 
247 // Exact selector with context test. Case when multiple words for prefix and
248 // suffix is necessary to disambiguate the selection.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_MultipleWordContext)249 TEST_F(TextFragmentSelectorGeneratorTest,
250        ExactTextSelector_MultipleWordContext) {
251   SimRequest request("https://example.com/test.html", "text/html");
252   LoadURL("https://example.com/test.html");
253   request.Complete(R"HTML(
254     <!DOCTYPE html>
255     <div>Test page</div>
256     <p id='first'>First prefix to not unique snippet of text followed by suffix</p>
257     <p id='second'>Second prefix to not unique snippet of text followed by suffix</p>
258   )HTML");
259   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
260   const auto& selected_start = Position(first_paragraph, 16);
261   const auto& selected_end = Position(first_paragraph, 42);
262   ASSERT_EQ("not unique snippet of text",
263             PlainText(EphemeralRange(selected_start, selected_end)));
264 
265   VerifySelector(selected_start, selected_end,
266                  "First%20prefix%20to-,not%20unique%20snippet%20of%"
267                  "20text,-followed%20by%20suffix");
268 }
269 
270 // Exact selector with context test. Case when multiple words for prefix and
271 // suffix is necessary to disambiguate the selection and prefix and suffix
272 // contain extra space.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_MultipleWordContext_ExtraSpace)273 TEST_F(TextFragmentSelectorGeneratorTest,
274        ExactTextSelector_MultipleWordContext_ExtraSpace) {
275   SimRequest request("https://example.com/test.html", "text/html");
276   LoadURL("https://example.com/test.html");
277   request.Complete(R"HTML(
278     <!DOCTYPE html>
279     <div>Test page</div>
280     <p id='first'>First prefix      to not unique snippet of text followed       by suffix</p>
281     <p id='second'>Second prefix to not unique snippet of text followed by suffix</p>
282   )HTML");
283   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
284   const auto& selected_start = Position(first_paragraph, 21);
285   const auto& selected_end = Position(first_paragraph, 47);
286   ASSERT_EQ("not unique snippet of text",
287             PlainText(EphemeralRange(selected_start, selected_end)));
288 
289   VerifySelector(selected_start, selected_end,
290                  "First%20prefix%20to-,not%20unique%20snippet%20of%"
291                  "20text,-followed%20by%20suffix");
292 }
293 
294 // Exact selector with context test. Case when available prefix for all the
295 // occurrences of selected text is the same. In this case suffix should be
296 // extended until unique selector is found.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SamePrefix)297 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_SamePrefix) {
298   SimRequest request("https://example.com/test.html", "text/html");
299   LoadURL("https://example.com/test.html");
300   request.Complete(R"HTML(
301     <!DOCTYPE html>
302     <div>Test page</div>
303     <p id='first'>Prefix to not unique snippet of text followed by different suffix</p>
304     <p id='second'>Prefix to not unique snippet of text followed by suffix</p>
305   )HTML");
306   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
307   const auto& selected_start = Position(first_paragraph, 10);
308   const auto& selected_end = Position(first_paragraph, 36);
309   ASSERT_EQ("not unique snippet of text",
310             PlainText(EphemeralRange(selected_start, selected_end)));
311 
312   VerifySelector(selected_start, selected_end,
313                  "Prefix%20to-,not%20unique%20snippet%20of%20text,-"
314                  "followed%20by%20different");
315 }
316 
317 // Exact selector with context test. Case when available suffix for all the
318 // occurrences of selected text is the same. In this case prefix should be
319 // extended until unique selector is found.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SameSuffix)320 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_SameSuffix) {
321   SimRequest request("https://example.com/test.html", "text/html");
322   LoadURL("https://example.com/test.html");
323   request.Complete(R"HTML(
324     <!DOCTYPE html>
325     <div>Test page</div>
326     <p id='first'>First paragraph prefix to not unique snippet of text followed by suffix</p>
327     <p id='second'>Second paragraph prefix to not unique snippet of text followed by suffix</p>
328   )HTML");
329   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
330   const auto& selected_start = Position(first_paragraph, 26);
331   const auto& selected_end = Position(first_paragraph, 52);
332   ASSERT_EQ("not unique snippet of text",
333             PlainText(EphemeralRange(selected_start, selected_end)));
334 
335   VerifySelector(selected_start, selected_end,
336                  "First%20paragraph%20prefix%20to-,not%20unique%"
337                  "20snippet%20of%20text,-followed%20by%20suffix");
338 }
339 
340 // Exact selector with context test. Case when available prefix and suffix for
341 // all the occurrences of selected text are the same. In this case generation
342 // should be unsuccessful.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SamePrefixSuffix)343 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_SamePrefixSuffix) {
344   SimRequest request("https://example.com/test.html", "text/html");
345   LoadURL("https://example.com/test.html");
346   request.Complete(R"HTML(
347     <!DOCTYPE html>
348     <div>Test page</div>
349     <p id='first'>Same paragraph prefix to not unique snippet of text followed by suffix</p>
350     <p id='second'>Same paragraph prefix to not unique snippet of text followed by suffix</p>
351   )HTML");
352   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
353   const auto& selected_start = Position(first_paragraph, 25);
354   const auto& selected_end = Position(first_paragraph, 51);
355   ASSERT_EQ("not unique snippet of text",
356             PlainText(EphemeralRange(selected_start, selected_end)));
357 
358   VerifySelectorFails(selected_start, selected_end,
359                       LinkGenerationError::kContextExhausted);
360 }
361 
362 // Exact selector with context test. Case when available prefix and suffix for
363 // all the occurrences of selected text are the same for the first 10 words. In
364 // this case generation should be unsuccessful.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SimilarLongPreffixSuffix)365 TEST_F(TextFragmentSelectorGeneratorTest,
366        ExactTextSelector_SimilarLongPreffixSuffix) {
367   SimRequest request("https://example.com/test.html", "text/html");
368   LoadURL("https://example.com/test.html");
369   request.Complete(R"HTML(
370     <!DOCTYPE html>
371     <div>Test page</div>
372     <p id='first'>First paragraph prefix one two three four five six seven
373      eight nine ten to not unique snippet of text followed by suffix</p>
374     <p id='second'>Second paragraph prefix one two three four five six seven
375      eight nine ten to not unique snippet of text followed by suffix</p>
376   )HTML");
377   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
378   const auto& selected_start = Position(first_paragraph, 80);
379   const auto& selected_end = Position(first_paragraph, 106);
380   ASSERT_EQ("not unique snippet of text",
381             PlainText(EphemeralRange(selected_start, selected_end)));
382 
383   VerifySelectorFails(selected_start, selected_end,
384                       LinkGenerationError::kContextLimitReached);
385 }
386 
387 // Exact selector with context test. Case when no prefix is available.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NoPrefix)388 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_NoPrefix) {
389   SimRequest request("https://example.com/test.html", "text/html");
390   LoadURL("https://example.com/test.html");
391   request.Complete(R"HTML(
392     <!DOCTYPE html>
393     <p id='first'>Not unique snippet of text followed by first suffix</p>
394     <p id='second'>Not unique snippet of text followed by second suffix</p>
395   )HTML");
396   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
397   const auto& selected_start = Position(first_paragraph, 0);
398   const auto& selected_end = Position(first_paragraph, 26);
399   ASSERT_EQ("Not unique snippet of text",
400             PlainText(EphemeralRange(selected_start, selected_end)));
401 
402   VerifySelector(selected_start, selected_end,
403                  "Not%20unique%20snippet%20of%20text,-followed%20by%20first");
404 }
405 
406 // Exact selector with context test. Case when no suffix is available.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NoSuffix)407 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_NoSuffix) {
408   SimRequest request("https://example.com/test.html", "text/html");
409   LoadURL("https://example.com/test.html");
410   request.Complete(R"HTML(
411     <!DOCTYPE html>
412     <div>Test page</div>
413     <p id='first'>First prefix to not unique snippet of text</p>
414     <p id='second'>Second prefix to not unique snippet of text</p>
415   )HTML");
416   Node* first_paragraph = GetDocument().getElementById("second")->firstChild();
417   const auto& selected_start = Position(first_paragraph, 17);
418   const auto& selected_end = Position(first_paragraph, 43);
419   ASSERT_EQ("not unique snippet of text",
420             PlainText(EphemeralRange(selected_start, selected_end)));
421 
422   VerifySelector(selected_start, selected_end,
423                  "Second%20prefix%20to-,not%20unique%20snippet%20of%"
424                  "20text");
425 }
426 
427 // Exact selector with context test. Case when available prefix is the
428 // preceding block.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_PrevNodePrefix)429 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_PrevNodePrefix) {
430   SimRequest request("https://example.com/test.html", "text/html");
431   LoadURL("https://example.com/test.html");
432   request.Complete(R"HTML(
433     <!DOCTYPE html>
434     <div>Test page</div>
435     <p id='first'>First paragraph with not unique snippet</p>
436     <p id='second'>not unique snippet of text</p>
437   )HTML");
438   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
439   const auto& selected_start = Position(second_paragraph, 0);
440   const auto& selected_end = Position(second_paragraph, 18);
441   ASSERT_EQ("not unique snippet",
442             PlainText(EphemeralRange(selected_start, selected_end)));
443 
444   VerifySelector(selected_start, selected_end,
445                  "snippet-,not%20unique%20snippet,-of");
446 }
447 
448 // Exact selector with context test. Case when available prefix is the
449 // preceding block, which is a text node.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_PrevTextNodePrefix)450 TEST_F(TextFragmentSelectorGeneratorTest,
451        ExactTextSelector_PrevTextNodePrefix) {
452   SimRequest request("https://example.com/test.html", "text/html");
453   LoadURL("https://example.com/test.html");
454   request.Complete(R"HTML(
455     <!DOCTYPE html>
456     <div>Test page</div>
457     <p id='first'>First paragraph with not unique snippet</p>
458     text
459     <p id='second'>not unique snippet of text</p>
460   )HTML");
461   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
462   const auto& selected_start = Position(second_paragraph, 0);
463   const auto& selected_end = Position(second_paragraph, 18);
464   ASSERT_EQ("not unique snippet",
465             PlainText(EphemeralRange(selected_start, selected_end)));
466 
467   VerifySelector(selected_start, selected_end,
468                  "text-,not%20unique%20snippet,-of");
469 }
470 
471 // Exact selector with context test. Case when available suffix is the next
472 // block.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NextNodeSuffix)473 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_NextNodeSuffix) {
474   SimRequest request("https://example.com/test.html", "text/html");
475   LoadURL("https://example.com/test.html");
476   request.Complete(R"HTML(
477     <!DOCTYPE html>
478     <div>Test page</div>
479     <p id='first'>First paragraph with not unique snippet</p>
480     <p id='second'>not unique snippet of text</p>
481   )HTML");
482   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
483   const auto& selected_start = Position(first_paragraph, 21);
484   const auto& selected_end = Position(first_paragraph, 39);
485   ASSERT_EQ("not unique snippet",
486             PlainText(EphemeralRange(selected_start, selected_end)));
487 
488   VerifySelector(selected_start, selected_end,
489                  "with-,not%20unique%20snippet,-not");
490 }
491 
492 // Exact selector with context test. Case when available suffix is the next
493 // block, which is a text node.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NexttextNodeSuffix)494 TEST_F(TextFragmentSelectorGeneratorTest,
495        ExactTextSelector_NexttextNodeSuffix) {
496   SimRequest request("https://example.com/test.html", "text/html");
497   LoadURL("https://example.com/test.html");
498   request.Complete(R"HTML(
499     <!DOCTYPE html>
500     <div>Test page</div>
501     <p id='first'>First paragraph with not unique snippet</p>
502     text
503     <p id='second'>not unique snippet of text</p>
504   )HTML");
505   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
506   const auto& selected_start = Position(first_paragraph, 21);
507   const auto& selected_end = Position(first_paragraph, 39);
508   ASSERT_EQ("not unique snippet",
509             PlainText(EphemeralRange(selected_start, selected_end)));
510 
511   VerifySelector(selected_start, selected_end,
512                  "with-,not%20unique%20snippet,-text");
513 }
514 
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector)515 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector) {
516   SimRequest request("https://example.com/test.html", "text/html");
517   LoadURL("https://example.com/test.html");
518   request.Complete(R"HTML(
519     <!DOCTYPE html>
520     <div>Test page</div>
521     <p id='first'>First paragraph text that is longer than 20 chars</p>
522     <p id='second'>Second paragraph text</p>
523   )HTML");
524   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
525   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
526   const auto& selected_start = Position(first_paragraph, 0);
527   const auto& selected_end = Position(second_paragraph, 6);
528   ASSERT_EQ("First paragraph text that is longer than 20 chars\n\nSecond",
529             PlainText(EphemeralRange(selected_start, selected_end)));
530 
531   VerifySelector(selected_start, selected_end, "First,Second");
532 }
533 
534 // It should be more than 300 characters selected from the same node so that
535 // ranges are used.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_SameNode)536 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_SameNode) {
537   SimRequest request("https://example.com/test.html", "text/html");
538   LoadURL("https://example.com/test.html");
539   request.Complete(R"HTML(
540     <!DOCTYPE html>
541     <div>Test page</div>
542     <p id='first'>First paragraph text text text text text text text
543     text text text text text text text text text text text text text
544     text text text text text text text text text text text text text
545     text text text text text text text text text text text text text
546     text text text text text text text text text and last text</p>
547   )HTML");
548   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
549   const auto& selected_start = Position(first_paragraph, 0);
550   const auto& selected_end = Position(first_paragraph, 320);
551   ASSERT_EQ(
552       "First paragraph text text text text text text text \
553 text text text text text text text text text text text text text \
554 text text text text text text text text text text text text text \
555 text text text text text text text text text text text text text \
556 text text text text text text text text text and last text",
557       PlainText(EphemeralRange(selected_start, selected_end)));
558 
559   VerifySelector(selected_start, selected_end, "First%20paragraph,last%20text");
560 }
561 
562 // It should be more than 300 characters selected from the same node so that
563 // ranges are used.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_SameNode_MultipleSelections)564 TEST_F(TextFragmentSelectorGeneratorTest,
565        RangeSelector_SameNode_MultipleSelections) {
566   SimRequest request("https://example.com/test.html", "text/html");
567   LoadURL("https://example.com/test.html");
568   request.Complete(R"HTML(
569     <!DOCTYPE html>
570     <div>Test page</div>
571     <p id='first'>First paragraph text text text text text text text
572     text text text text text text text text text text text text text
573     text text text text text text text text text text text text text
574     text text text text text text text text text text text text text
575     text text text text text text text text text text and last text</p>
576   )HTML");
577   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
578   const auto& selected_start = Position(first_paragraph, 0);
579   const auto& selected_end = Position(first_paragraph, 325);
580   ASSERT_EQ(
581       "First paragraph text text text text text text text \
582 text text text text text text text text text text text text text \
583 text text text text text text text text text text text text text \
584 text text text text text text text text text text text text text \
585 text text text text text text text text text text and last text",
586       PlainText(EphemeralRange(selected_start, selected_end)));
587   ASSERT_EQ(309u,
588             PlainText(EphemeralRange(selected_start, selected_end)).length());
589 
590   VerifySelector(selected_start, selected_end, "First%20paragraph,last%20text");
591 
592   const auto& second_selected_start = Position(first_paragraph, 6);
593   const auto& second_selected_end = Position(first_paragraph, 325);
594   ASSERT_EQ(
595       "paragraph text text text text text text text \
596 text text text text text text text text text text text text text \
597 text text text text text text text text text text text text text \
598 text text text text text text text text text text text text text \
599 text text text text text text text text text text and last text",
600       PlainText(EphemeralRange(second_selected_start, second_selected_end)));
601   ASSERT_EQ(303u, PlainText(EphemeralRange(second_selected_start,
602                                            second_selected_end))
603                       .length());
604 
605   VerifySelector(second_selected_start, second_selected_end,
606                  "paragraph%20text,last%20text");
607 }
608 
609 // When using all the selected text for the range is not enough for unique
610 // match, context should be added.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_RangeNotUnique)611 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_RangeNotUnique) {
612   SimRequest request("https://example.com/test.html", "text/html");
613   LoadURL("https://example.com/test.html");
614   request.Complete(R"HTML(
615     <!DOCTYPE html>
616     <div>Test page</div>
617     <p id='first'>First paragraph</p><p id='text1'>text</p>
618     <p id='second'>Second paragraph</p><p id='text2'>text</p>
619   )HTML");
620   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
621   Node* first_text = GetDocument().getElementById("text1")->firstChild();
622   const auto& selected_start = Position(first_paragraph, 6);
623   const auto& selected_end = Position(first_text, 4);
624   ASSERT_EQ("paragraph\n\ntext",
625             PlainText(EphemeralRange(selected_start, selected_end)));
626 
627   VerifySelector(selected_start, selected_end, "First-,paragraph,text,-Second");
628 }
629 
630 // When using all the selected text for the range is not enough for unique
631 // match, context should be added, but only prefxi and no suffix is available.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_RangeNotUnique_NoSuffix)632 TEST_F(TextFragmentSelectorGeneratorTest,
633        RangeSelector_RangeNotUnique_NoSuffix) {
634   SimRequest request("https://example.com/test.html", "text/html");
635   LoadURL("https://example.com/test.html");
636   request.Complete(R"HTML(
637     <!DOCTYPE html>
638     <div>Test page</div>
639     <p id='first'>First paragraph</p><p id='text1'>text</p>
640     <p id='second'>Second paragraph</p><p id='text2'>text</p>
641   )HTML");
642   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
643   Node* second_text = GetDocument().getElementById("text2")->firstChild();
644   const auto& selected_start = Position(second_paragraph, 7);
645   const auto& selected_end = Position(second_text, 4);
646   ASSERT_EQ("paragraph\n\ntext",
647             PlainText(EphemeralRange(selected_start, selected_end)));
648 
649   VerifySelector(selected_start, selected_end, "Second-,paragraph,text");
650 }
651 
652 // When no range end is available it should return empty selector.
653 // There is no range end available because there is no word break in the second
654 // half of the selection.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_NoRangeEnd)655 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_NoRangeEnd) {
656   SimRequest request("https://example.com/test.html", "text/html");
657   LoadURL("https://example.com/test.html");
658   request.Complete(R"HTML(
659     <!DOCTYPE html>
660     <div>Test page</div>
661     <p id='first'>First paragraph text text text text text text text
662     text text text text text text text text text text text text text
663     text text text text text text text text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_and_last_text</p>
664   )HTML");
665   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
666   const auto& selected_start = Position(first_paragraph, 0);
667   const auto& selected_end = Position(first_paragraph, 312);
668   ASSERT_EQ(
669       "First paragraph text text text text text text text \
670 text text text text text text text text text text text text text \
671 text text text text text text text text_text_text_text_text_text_\
672 text_text_text_text_text_text_text_text_text_text_text_text_text_\
673 text_text_text_text_text_text_text_text_text_and_last_text",
674       PlainText(EphemeralRange(selected_start, selected_end)));
675 
676   VerifySelectorFails(selected_start, selected_end,
677                       LinkGenerationError::kNoRange);
678 }
679 
680 // Selection should be autocompleted to contain full words.
TEST_F(TextFragmentSelectorGeneratorTest,WordLimit)681 TEST_F(TextFragmentSelectorGeneratorTest, WordLimit) {
682   SimRequest request("https://example.com/test.html", "text/html");
683   LoadURL("https://example.com/test.html");
684   request.Complete(R"HTML(
685     <!DOCTYPE html>
686     <div>Test page</div>
687     <p id='first'>First paragraph text that is longer than 20 chars</p>
688   )HTML");
689   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
690   const auto& selected_start = Position(first_paragraph, 7);
691   const auto& selected_end = Position(first_paragraph, 33);
692   ASSERT_EQ("aragraph text that is long",
693             PlainText(EphemeralRange(selected_start, selected_end)));
694 
695   VerifySelector(selected_start, selected_end,
696                  "paragraph%20text%20that%20is%20longer");
697 }
698 
699 // Selection should be autocompleted to contain full words. The autocompletion
700 // should work with extra spaces.
TEST_F(TextFragmentSelectorGeneratorTest,WordLimit_ExtraSpaces)701 TEST_F(TextFragmentSelectorGeneratorTest, WordLimit_ExtraSpaces) {
702   SimRequest request("https://example.com/test.html", "text/html");
703   LoadURL("https://example.com/test.html");
704   request.Complete(R"HTML(
705     <!DOCTYPE html>
706     <div>Test page</div>
707     <p id='first'>First
708     paragraph text
709     that is longer than 20 chars</p>
710   )HTML");
711   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
712   const auto& selected_start = Position(first_paragraph, 11);
713   const auto& selected_end = Position(first_paragraph, 41);
714   ASSERT_EQ("aragraph text that is long",
715             PlainText(EphemeralRange(selected_start, selected_end)));
716 
717   VerifySelector(selected_start, selected_end,
718                  "paragraph%20text%20that%20is%20longer");
719 }
720 
721 // When selection starts at the end of a word, selection shouldn't be
722 // autocompleted to contain extra words.
TEST_F(TextFragmentSelectorGeneratorTest,WordLimit_SelectionStartsAndEndsAtWordLimit)723 TEST_F(TextFragmentSelectorGeneratorTest,
724        WordLimit_SelectionStartsAndEndsAtWordLimit) {
725   SimRequest request("https://example.com/test.html", "text/html");
726   LoadURL("https://example.com/test.html");
727   request.Complete(R"HTML(
728     <!DOCTYPE html>
729     <div>Test page</div>
730     <p id='first'>First paragraph text that is longer  than 20 chars</p>
731   )HTML");
732   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
733   const auto& selected_start = Position(first_paragraph, 5);
734   const auto& selected_end = Position(first_paragraph, 37);
735   ASSERT_EQ(" paragraph text that is longer ",
736             PlainText(EphemeralRange(selected_start, selected_end)));
737 
738   VerifySelector(selected_start, selected_end,
739                  "paragraph%20text%20that%20is%20longer");
740 }
741 
742 // Check the case when selections starts with an non text node.
TEST_F(TextFragmentSelectorGeneratorTest,StartsWithImage)743 TEST_F(TextFragmentSelectorGeneratorTest, StartsWithImage) {
744   SimRequest request("https://example.com/test.html", "text/html");
745   LoadURL("https://example.com/test.html");
746   request.Complete(R"HTML(
747     <!DOCTYPE html>
748     <div>Test page</div>
749     <img id="img">
750     <p id='first'>First paragraph text that is longer  than 20 chars</p>
751   )HTML");
752   Node* img = GetDocument().getElementById("img");
753   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
754   const auto& start = Position(img, 0);
755   const auto& end = Position(first_paragraph, 5);
756   ASSERT_EQ("\nFirst", PlainText(EphemeralRange(start, end)));
757 
758   VerifySelector(start, end, "page-,First,-paragraph");
759 }
760 
761 // Check the case when selections starts with an non text node.
TEST_F(TextFragmentSelectorGeneratorTest,StartsWithBlockWithImage)762 TEST_F(TextFragmentSelectorGeneratorTest, StartsWithBlockWithImage) {
763   SimRequest request("https://example.com/test.html", "text/html");
764   LoadURL("https://example.com/test.html");
765   request.Complete(R"HTML(
766     <!DOCTYPE html>
767     <div>Test page</div>
768     <div id="img_div">
769       <img id="img">
770     </div>
771     <p id='first'>First paragraph text that is longer  than 20 chars</p>
772   )HTML");
773   Node* img = GetDocument().getElementById("img_div");
774   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
775   const auto& start = Position(img, 0);
776   const auto& end = Position(first_paragraph, 5);
777   ASSERT_EQ("\nFirst", PlainText(EphemeralRange(start, end)));
778 
779   VerifySelector(start, end, "page-,First,-paragraph");
780 }
781 
782 // Check the case when selections starts with a node nested in "inline-block"
783 // node. crbug.com/1151474
TEST_F(TextFragmentSelectorGeneratorTest,StartsWithInlineBlockChild)784 TEST_F(TextFragmentSelectorGeneratorTest, StartsWithInlineBlockChild) {
785   SimRequest request("https://example.com/test.html", "text/html");
786   LoadURL("https://example.com/test.html");
787   request.Complete(R"HTML(
788     <!DOCTYPE html>
789     <style>
790       li {
791         display: inline-block;
792       }
793     </style>
794     <div>Test page</div>
795     <ul>
796       <li>
797         <a id="link1"/>
798       </li>
799       <li>
800         <a id="link2"/>
801       </li>
802       <li>
803         <a id="link3"/>
804       </li>
805     </ul>
806     <p id='first'>First paragraph text that is longer  than 20 chars</p>
807   )HTML");
808 
809   GetDocument().View()->UpdateAllLifecyclePhasesForTest();
810   Node* img = GetDocument().getElementById("link1");
811   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
812   const auto& start = Position(img, PositionAnchorType::kAfterChildren);
813   const auto& end = Position(first_paragraph, 5);
814   ASSERT_EQ("  \nFirst", PlainText(EphemeralRange(start, end)));
815 
816   VerifySelector(start, end, "page-,First,-paragraph");
817 }
818 
819 // Check the case when selections ends with an non text node.
TEST_F(TextFragmentSelectorGeneratorTest,EndswithImage)820 TEST_F(TextFragmentSelectorGeneratorTest, EndswithImage) {
821   SimRequest request("https://example.com/test.html", "text/html");
822   LoadURL("https://example.com/test.html");
823   request.Complete(R"HTML(
824     <!DOCTYPE html>
825     <div>Test page</div>
826     <p id='first'>First paragraph text that is longer than 20 chars</p>
827     <img id="img">
828     </img>
829   )HTML");
830   Node* img = GetDocument().getElementById("img");
831   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
832   const auto& start = Position(first_paragraph, 44);
833   const auto& end = Position(img, 0);
834   ASSERT_EQ("chars\n\n", PlainText(EphemeralRange(start, end)));
835 
836   VerifySelector(start, end, "20-,chars");
837 }
838 
839 // Check the case when selections starts at the end of the previous block.
TEST_F(TextFragmentSelectorGeneratorTest,StartIsEndofPrevBlock)840 TEST_F(TextFragmentSelectorGeneratorTest, StartIsEndofPrevBlock) {
841   SimRequest request("https://example.com/test.html", "text/html");
842   LoadURL("https://example.com/test.html");
843   request.Complete(R"HTML(
844     <!DOCTYPE html>
845     <p id='first'>First paragraph     </p>
846     <p id='second'>Second paragraph</p>
847   )HTML");
848   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
849   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
850   const auto& start = Position(first_paragraph, 18);
851   const auto& end = Position(second_paragraph, 6);
852   ASSERT_EQ("\nSecond", PlainText(EphemeralRange(start, end)));
853 
854   VerifySelector(start, end, "paragraph-,Second,-paragraph");
855 }
856 
857 // Check the case when selections starts at the end of the previous block.
TEST_F(TextFragmentSelectorGeneratorTest,EndIsStartofNextBlock)858 TEST_F(TextFragmentSelectorGeneratorTest, EndIsStartofNextBlock) {
859   SimRequest request("https://example.com/test.html", "text/html");
860   LoadURL("https://example.com/test.html");
861   request.Complete(R"HTML(
862     <!DOCTYPE html>
863     <p id='first'>First paragraph</p>
864     <p id='second'>     Second paragraph</p>
865   )HTML");
866   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
867   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
868   const auto& start = Position(first_paragraph, 0);
869   const auto& end = Position(second_paragraph, 2);
870   ASSERT_EQ("First paragraph\n\n", PlainText(EphemeralRange(start, end)));
871 
872   VerifySelector(start, end, "First%20paragraph,-Second");
873 }
874 
875 // Check the case when parent of selection start is a sibling of a node where
876 // selection ends.
877 //   :root
878 //  /      \
879 // div      p
880 //  |       |
881 //  p      "]Second"
882 //  |
883 // "[First..."
884 // Where [] indicate selection. In this case, when the selection is adjusted, we
885 // want to ensure it correctly traverses the tree back to the previous text node
886 // and not to the <div>(sibling of second <p>).
887 //
888 // crbug.com/1154308 - checks the use of Previous instead of
889 // PreviousSkippingChildren in TextFragmentSelectorGenerator::AdjustSelection
TEST_F(TextFragmentSelectorGeneratorTest,PrevNodeIsSiblingsChild)890 TEST_F(TextFragmentSelectorGeneratorTest, PrevNodeIsSiblingsChild) {
891   SimRequest request("https://example.com/test.html", "text/html");
892   LoadURL("https://example.com/test.html");
893 
894   // HTML is intentionally not formatted. Adding new lines and itendation
895   // creates empty text nodes which changes the dom tree.
896   request.Complete(R"HTML(
897     <!DOCTYPE html>
898   <div><p id='start'>First paragraph</p></div><p id='end'>Second paragraph</p>
899   )HTML");
900   GetDocument().UpdateStyleAndLayoutTree();
901   Node* first_paragraph = GetDocument().getElementById("start")->firstChild();
902   Node* second_paragraph = GetDocument().getElementById("end");
903   const auto& start = Position(first_paragraph, 0);
904   const auto& end = Position(second_paragraph, 0);
905   ASSERT_EQ("First paragraph\n\n", PlainText(EphemeralRange(start, end)));
906 
907   VerifySelector(start, end, "First%20paragraph,-Second");
908 }
909 
910 // Check the case when parent of selection start is a sibling of a node where
911 // selection ends.
912 //    :root
913 //  /    |     \
914 // div  div     p
915 //  |    |       \
916 //  p   "test"   "]Second"
917 //  |
918 //"[First..."
919 //
920 // Where [] indicate selection. In this case, when the selection is adjusted, we
921 // want to ensure it correctly traverses the tree back to the previous text by
922 // correctly skipping the invisible div but not skipping the second <p>.
923 //
924 // crbug.com/1154308 - checks the use of Previous instead of
925 // PreviousSkippingChildren in FindBuffer::BackwardVisibleTextNode
TEST_F(TextFragmentSelectorGeneratorTest,PrevPrevNodeIsSiblingsChild)926 TEST_F(TextFragmentSelectorGeneratorTest, PrevPrevNodeIsSiblingsChild) {
927   SimRequest request("https://example.com/test.html", "text/html");
928   LoadURL("https://example.com/test.html");
929   // HTML is intentionally not formatted. Adding new lines and itendation
930   // creates empty text nodes which changes the dom tree.
931   request.Complete(R"HTML(
932     <!DOCTYPE html>
933   <div><p id='start'>First paragraph</p></div><div style='display:none'>test</div><p id='end'>Second paragraph</p>
934   )HTML");
935   GetDocument().UpdateStyleAndLayoutTree();
936   Node* first_paragraph = GetDocument().getElementById("start")->firstChild();
937   Node* second_paragraph = GetDocument().getElementById("end");
938   const auto& start = Position(first_paragraph, 0);
939   const auto& end = Position(second_paragraph, 0);
940   ASSERT_EQ("First paragraph\n\n", PlainText(EphemeralRange(start, end)));
941 
942   VerifySelector(start, end, "First%20paragraph,-Second");
943 }
944 
945 // Checks that for short selection that have nested block element range selector
946 // is used.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_SameNode_Interrupted)947 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_SameNode_Interrupted) {
948   SimRequest request("https://example.com/test.html", "text/html");
949   LoadURL("https://example.com/test.html");
950   request.Complete(R"HTML(
951     <!DOCTYPE html>
952     <div id='first'>First <div>block text</div> paragraph text</div>
953   )HTML");
954   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
955   const auto& start = Position(first_paragraph, 0);
956   const auto& end = Position(first_paragraph->nextSibling()->nextSibling(), 10);
957   ASSERT_EQ("First\nblock text\nparagraph",
958             PlainText(EphemeralRange(start, end)));
959 
960   VerifySelector(start, end, "First,paragraph");
961 }
962 
963 // Basic test case for |GetNextTextBlock|.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock)964 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock) {
965   SimRequest request("https://example.com/test.html", "text/html");
966   LoadURL("https://example.com/test.html");
967   request.Complete(R"HTML(
968     <!DOCTYPE html>
969     <p id='first'>First paragraph text</p>
970   )HTML");
971   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
972   const auto& start = Position(first_paragraph, 16);
973   const auto& end = Position(first_paragraph, 20);
974   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
975 
976   EXPECT_EQ("First paragraph", GetDocument()
977                                    .GetFrame()
978                                    ->GetTextFragmentSelectorGenerator()
979                                    ->GetPreviousTextBlockForTesting(start));
980 }
981 
982 // Check the case when available prefix contains collapsible space.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_ExtraSpace)983 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_ExtraSpace) {
984   SimRequest request("https://example.com/test.html", "text/html");
985   LoadURL("https://example.com/test.html");
986   request.Complete(R"HTML(
987     <!DOCTYPE html>
988     <p id='first'>First
989 
990          paragraph text</p>
991   )HTML");
992   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
993   const auto& start = Position(first_paragraph, 26);
994   const auto& end = Position(first_paragraph, 30);
995   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
996 
997   EXPECT_EQ("First paragraph", GetDocument()
998                                    .GetFrame()
999                                    ->GetTextFragmentSelectorGenerator()
1000                                    ->GetPreviousTextBlockForTesting(start));
1001 }
1002 
1003 // Check the case when available prefix complete text content of the previous
1004 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_PrevNode)1005 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_PrevNode) {
1006   SimRequest request("https://example.com/test.html", "text/html");
1007   LoadURL("https://example.com/test.html");
1008   request.Complete(R"HTML(
1009     <!DOCTYPE html>
1010     <p id='first'>First paragraph text</p>
1011     <p id='second'>Second paragraph text</p>
1012   )HTML");
1013   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
1014   const auto& start = Position(second_paragraph, 0);
1015   const auto& end = Position(second_paragraph, 6);
1016   ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1017 
1018   EXPECT_EQ("First paragraph text",
1019             GetDocument()
1020                 .GetFrame()
1021                 ->GetTextFragmentSelectorGenerator()
1022                 ->GetPreviousTextBlockForTesting(start));
1023 }
1024 
1025 // Check the case when there is a commented block between selection and the
1026 // available prefix.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_PrevNode_WithComment)1027 TEST_F(TextFragmentSelectorGeneratorTest,
1028        GetPreviousTextBlock_PrevNode_WithComment) {
1029   SimRequest request("https://example.com/test.html", "text/html");
1030   LoadURL("https://example.com/test.html");
1031   request.Complete(R"HTML(
1032     <!DOCTYPE html>
1033     <p id='first'>First paragraph text</p>
1034     <!--
1035       multiline comment that should be ignored.
1036     //-->
1037     <p id='second'>Second paragraph text</p>
1038   )HTML");
1039   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
1040   const auto& start = Position(second_paragraph, 0);
1041   const auto& end = Position(second_paragraph, 6);
1042   ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1043 
1044   EXPECT_EQ("First paragraph text",
1045             GetDocument()
1046                 .GetFrame()
1047                 ->GetTextFragmentSelectorGenerator()
1048                 ->GetPreviousTextBlockForTesting(start));
1049 }
1050 
1051 // Check the case when available prefix is a text node outside of selection
1052 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_PrevTextNode)1053 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_PrevTextNode) {
1054   SimRequest request("https://example.com/test.html", "text/html");
1055   LoadURL("https://example.com/test.html");
1056   request.Complete(R"HTML(
1057     <!DOCTYPE html>
1058     text
1059     <p id='first'>First paragraph text</p>
1060   )HTML");
1061   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1062   const auto& start = Position(first_paragraph, 0);
1063   const auto& end = Position(first_paragraph, 5);
1064   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1065 
1066   EXPECT_EQ("text", GetDocument()
1067                         .GetFrame()
1068                         ->GetTextFragmentSelectorGenerator()
1069                         ->GetPreviousTextBlockForTesting(start));
1070 }
1071 
1072 // Check the case when available prefix is a parent node text content outside of
1073 // selection block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_ParentNode)1074 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_ParentNode) {
1075   SimRequest request("https://example.com/test.html", "text/html");
1076   LoadURL("https://example.com/test.html");
1077   request.Complete(R"HTML(
1078     <!DOCTYPE html>
1079     <div>nested
1080     <p id='first'>First paragraph text</p></div>
1081   )HTML");
1082   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1083   const auto& start = Position(first_paragraph, 0);
1084   const auto& end = Position(first_paragraph, 5);
1085   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1086 
1087   EXPECT_EQ("nested", GetDocument()
1088                           .GetFrame()
1089                           ->GetTextFragmentSelectorGenerator()
1090                           ->GetPreviousTextBlockForTesting(start));
1091 }
1092 
1093 // Check the case when available prefix contains non-block tag(e.g. <b>).
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedTextNode)1094 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_NestedTextNode) {
1095   SimRequest request("https://example.com/test.html", "text/html");
1096   LoadURL("https://example.com/test.html");
1097   request.Complete(R"HTML(
1098     <!DOCTYPE html>
1099     <p id='first'>First <b>bold text</b> paragraph text</p>
1100   )HTML");
1101   Node* first_paragraph = GetDocument().getElementById("first")->lastChild();
1102   const auto& start = Position(first_paragraph, 11);
1103   const auto& end = Position(first_paragraph, 15);
1104   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1105 
1106   EXPECT_EQ("First bold text paragraph",
1107             GetDocument()
1108                 .GetFrame()
1109                 ->GetTextFragmentSelectorGenerator()
1110                 ->GetPreviousTextBlockForTesting(start));
1111 }
1112 
1113 // Check the case when available prefix is collected until nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedBlock)1114 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_NestedBlock) {
1115   SimRequest request("https://example.com/test.html", "text/html");
1116   LoadURL("https://example.com/test.html");
1117   request.Complete(R"HTML(
1118     <!DOCTYPE html>
1119     <div id='first'>First <div id='div'>div</div> paragraph text</div>
1120   )HTML");
1121   Node* first_paragraph = GetDocument().getElementById("div")->nextSibling();
1122   const auto& start = Position(first_paragraph, 11);
1123   const auto& end = Position(first_paragraph, 15);
1124   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1125 
1126   EXPECT_EQ("paragraph", GetDocument()
1127                              .GetFrame()
1128                              ->GetTextFragmentSelectorGenerator()
1129                              ->GetPreviousTextBlockForTesting(start));
1130 }
1131 
1132 // Check the case when available prefix includes non-block element but stops at
1133 // nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedBlockInNestedText)1134 TEST_F(TextFragmentSelectorGeneratorTest,
1135        GetPreviousTextBlock_NestedBlockInNestedText) {
1136   SimRequest request("https://example.com/test.html", "text/html");
1137   LoadURL("https://example.com/test.html");
1138   request.Complete(R"HTML(
1139     <!DOCTYPE html>
1140     <div id='first'>First <b><div id='div'>div</div>bold</b> paragraph text</div>
1141   )HTML");
1142   Node* first_paragraph = GetDocument().getElementById("first")->lastChild();
1143   const auto& start = Position(first_paragraph, 11);
1144   const auto& end = Position(first_paragraph, 15);
1145   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1146 
1147   EXPECT_EQ("bold paragraph", GetDocument()
1148                                   .GetFrame()
1149                                   ->GetTextFragmentSelectorGenerator()
1150                                   ->GetPreviousTextBlockForTesting(start));
1151 }
1152 
1153 // Check the case when available prefix includes invisible block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedInvisibleBlock)1154 TEST_F(TextFragmentSelectorGeneratorTest,
1155        GetPreviousTextBlock_NestedInvisibleBlock) {
1156   SimRequest request("https://example.com/test.html", "text/html");
1157   LoadURL("https://example.com/test.html");
1158   request.Complete(R"HTML(
1159     <!DOCTYPE html>
1160     <div id='first'>First <div id='div' style='display:none'>invisible</div> paragraph text</div>
1161   )HTML");
1162   Node* first_paragraph = GetDocument().getElementById("div")->nextSibling();
1163   const auto& start = Position(first_paragraph, 0);
1164   const auto& end = Position(first_paragraph, 10);
1165   ASSERT_EQ("paragraph", PlainText(EphemeralRange(start, end)));
1166 
1167   EXPECT_EQ("First", GetDocument()
1168                          .GetFrame()
1169                          ->GetTextFragmentSelectorGenerator()
1170                          ->GetPreviousTextBlockForTesting(start));
1171 }
1172 
1173 // Check the case when previous node is used for available prefix when selection
1174 // is not at index=0 but there is only space before it.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_SpacesBeforeSelection)1175 TEST_F(TextFragmentSelectorGeneratorTest,
1176        GetPreviousTextBlock_SpacesBeforeSelection) {
1177   SimRequest request("https://example.com/test.html", "text/html");
1178   LoadURL("https://example.com/test.html");
1179   request.Complete(R"HTML(
1180     <!DOCTYPE html>
1181     <p id='first'>First paragraph text</p>
1182     <p id='second'>
1183       Second paragraph text
1184     </p>
1185   )HTML");
1186   Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
1187   const auto& start = Position(second_paragraph, 6);
1188   const auto& end = Position(second_paragraph, 13);
1189   ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1190 
1191   EXPECT_EQ("First paragraph text",
1192             GetDocument()
1193                 .GetFrame()
1194                 ->GetTextFragmentSelectorGenerator()
1195                 ->GetPreviousTextBlockForTesting(start));
1196 }
1197 
1198 // Check the case when previous node is used for available prefix when selection
1199 // is not at index=0 but there is only invisible block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_InvisibleBeforeSelection)1200 TEST_F(TextFragmentSelectorGeneratorTest,
1201        GetPreviousTextBlock_InvisibleBeforeSelection) {
1202   SimRequest request("https://example.com/test.html", "text/html");
1203   LoadURL("https://example.com/test.html");
1204   request.Complete(R"HTML(
1205     <!DOCTYPE html>
1206     <p id='first'>First paragraph text</p>
1207     <div id='second'>
1208       <p id='invisible' style='display:none'>
1209         invisible text
1210       </p>
1211       Second paragraph text
1212     </div>
1213   )HTML");
1214   Node* second_paragraph =
1215       GetDocument().getElementById("invisible")->nextSibling();
1216   const auto& start = Position(second_paragraph, 6);
1217   const auto& end = Position(second_paragraph, 13);
1218   ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1219 
1220   EXPECT_EQ("First paragraph text",
1221             GetDocument()
1222                 .GetFrame()
1223                 ->GetTextFragmentSelectorGenerator()
1224                 ->GetPreviousTextBlockForTesting(start));
1225 }
1226 
1227 // Similar test for suffix.
1228 
1229 // Basic test case for |GetNextTextBlock|.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock)1230 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock) {
1231   SimRequest request("https://example.com/test.html", "text/html");
1232   LoadURL("https://example.com/test.html");
1233   request.Complete(R"HTML(
1234     <!DOCTYPE html>
1235     <p id='first'>First paragraph text</p>
1236   )HTML");
1237   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1238   const auto& start = Position(first_paragraph, 0);
1239   const auto& end = Position(first_paragraph, 5);
1240   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1241 
1242   EXPECT_EQ("paragraph text", GetDocument()
1243                                   .GetFrame()
1244                                   ->GetTextFragmentSelectorGenerator()
1245                                   ->GetNextTextBlockForTesting(end));
1246 }
1247 
1248 // Check the case when available suffix contains collapsible space.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_ExtraSpace)1249 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_ExtraSpace) {
1250   SimRequest request("https://example.com/test.html", "text/html");
1251   LoadURL("https://example.com/test.html");
1252   request.Complete(R"HTML(
1253     <!DOCTYPE html>
1254     <p id='first'>First paragraph
1255 
1256 
1257      text</p>
1258   )HTML");
1259   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1260   const auto& start = Position(first_paragraph, 0);
1261   const auto& end = Position(first_paragraph, 5);
1262   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1263 
1264   EXPECT_EQ("paragraph text", GetDocument()
1265                                   .GetFrame()
1266                                   ->GetTextFragmentSelectorGenerator()
1267                                   ->GetNextTextBlockForTesting(end));
1268 }
1269 
1270 // Check the case when available suffix is complete text content of the next
1271 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NextNode)1272 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NextNode) {
1273   SimRequest request("https://example.com/test.html", "text/html");
1274   LoadURL("https://example.com/test.html");
1275   request.Complete(R"HTML(
1276     <!DOCTYPE html>
1277     <p id='first'>First paragraph text</p>
1278     <p id='second'>Second paragraph text</p>
1279   )HTML");
1280   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1281   const auto& start = Position(first_paragraph, 0);
1282   const auto& end = Position(first_paragraph, 20);
1283   ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1284 
1285   EXPECT_EQ("Second paragraph text", GetDocument()
1286                                          .GetFrame()
1287                                          ->GetTextFragmentSelectorGenerator()
1288                                          ->GetNextTextBlockForTesting(end));
1289 }
1290 
1291 // Check the case when there is a commented block between selection and the
1292 // available suffix.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NextNode_WithComment)1293 TEST_F(TextFragmentSelectorGeneratorTest,
1294        GetNextTextBlock_NextNode_WithComment) {
1295   SimRequest request("https://example.com/test.html", "text/html");
1296   LoadURL("https://example.com/test.html");
1297   request.Complete(R"HTML(
1298     <!DOCTYPE html>
1299     <p id='first'>First paragraph text</p>
1300     <!--
1301       multiline comment that should be ignored.
1302     //-->
1303     <p id='second'>Second paragraph text</p>
1304   )HTML");
1305   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1306   const auto& start = Position(first_paragraph, 0);
1307   const auto& end = Position(first_paragraph, 20);
1308   ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1309 
1310   EXPECT_EQ("Second paragraph text", GetDocument()
1311                                          .GetFrame()
1312                                          ->GetTextFragmentSelectorGenerator()
1313                                          ->GetNextTextBlockForTesting(end));
1314 }
1315 
1316 // Check the case when available suffix is a text node outside of selection
1317 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NextTextNode)1318 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NextTextNode) {
1319   SimRequest request("https://example.com/test.html", "text/html");
1320   LoadURL("https://example.com/test.html");
1321   request.Complete(R"HTML(
1322     <!DOCTYPE html>
1323     <p id='first'>First paragraph text</p>
1324     text
1325   )HTML");
1326   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1327   const auto& start = Position(first_paragraph, 0);
1328   const auto& end = Position(first_paragraph, 20);
1329   ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1330 
1331   EXPECT_EQ("text", GetDocument()
1332                         .GetFrame()
1333                         ->GetTextFragmentSelectorGenerator()
1334                         ->GetNextTextBlockForTesting(end));
1335 }
1336 
1337 // Check the case when available suffix is a parent node text content outside of
1338 // selection block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_ParentNode)1339 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_ParentNode) {
1340   SimRequest request("https://example.com/test.html", "text/html");
1341   LoadURL("https://example.com/test.html");
1342   request.Complete(R"HTML(
1343     <!DOCTYPE html>
1344     <div><p id='first'>First paragraph text</p> nested</div>
1345   )HTML");
1346   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1347   const auto& start = Position(first_paragraph, 0);
1348   const auto& end = Position(first_paragraph, 20);
1349   ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1350 
1351   EXPECT_EQ("nested", GetDocument()
1352                           .GetFrame()
1353                           ->GetTextFragmentSelectorGenerator()
1354                           ->GetNextTextBlockForTesting(end));
1355 }
1356 
1357 // Check the case when available suffix contains non-block tag(e.g. <b>).
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedTextNode)1358 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NestedTextNode) {
1359   SimRequest request("https://example.com/test.html", "text/html");
1360   LoadURL("https://example.com/test.html");
1361   request.Complete(R"HTML(
1362     <!DOCTYPE html>
1363     <p id='first'>First <b>bold text</b> paragraph text</p>
1364   )HTML");
1365   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1366   const auto& start = Position(first_paragraph, 0);
1367   const auto& end = Position(first_paragraph, 5);
1368   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1369 
1370   EXPECT_EQ("bold text paragraph text", GetDocument()
1371                                             .GetFrame()
1372                                             ->GetTextFragmentSelectorGenerator()
1373                                             ->GetNextTextBlockForTesting(end));
1374 }
1375 
1376 // Check the case when available suffix is collected until nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedBlock)1377 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NestedBlock) {
1378   SimRequest request("https://example.com/test.html", "text/html");
1379   LoadURL("https://example.com/test.html");
1380   request.Complete(R"HTML(
1381     <!DOCTYPE html>
1382     <div id='first'>First paragraph <div id='div'>div</div> text</div>
1383   )HTML");
1384   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1385   const auto& start = Position(first_paragraph, 0);
1386   const auto& end = Position(first_paragraph, 5);
1387   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1388 
1389   EXPECT_EQ("paragraph", GetDocument()
1390                              .GetFrame()
1391                              ->GetTextFragmentSelectorGenerator()
1392                              ->GetNextTextBlockForTesting(end));
1393 }
1394 
1395 // Check the case when available suffix includes non-block element but stops at
1396 // nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedBlockInNestedText)1397 TEST_F(TextFragmentSelectorGeneratorTest,
1398        GetNextTextBlock_NestedBlockInNestedText) {
1399   SimRequest request("https://example.com/test.html", "text/html");
1400   LoadURL("https://example.com/test.html");
1401   request.Complete(R"HTML(
1402     <!DOCTYPE html>
1403     <div id='first'>First <b>bold<div id='div'>div</div></b> paragraph text</div>
1404   )HTML");
1405   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1406   const auto& start = Position(first_paragraph, 0);
1407   const auto& end = Position(first_paragraph, 5);
1408   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1409 
1410   EXPECT_EQ("bold", GetDocument()
1411                         .GetFrame()
1412                         ->GetTextFragmentSelectorGenerator()
1413                         ->GetNextTextBlockForTesting(end));
1414 }
1415 
1416 // Check the case when available suffix includes invisible block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedInvisibleBlock)1417 TEST_F(TextFragmentSelectorGeneratorTest,
1418        GetNextTextBlock_NestedInvisibleBlock) {
1419   SimRequest request("https://example.com/test.html", "text/html");
1420   LoadURL("https://example.com/test.html");
1421   request.Complete(R"HTML(
1422     <!DOCTYPE html>
1423     <div id='first'>First <div id='div' style='display:none'>invisible</div> paragraph text</div>
1424   )HTML");
1425   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1426   const auto& start = Position(first_paragraph, 0);
1427   const auto& end = Position(first_paragraph, 5);
1428   ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1429 
1430   EXPECT_EQ("paragraph text", GetDocument()
1431                                   .GetFrame()
1432                                   ->GetTextFragmentSelectorGenerator()
1433                                   ->GetNextTextBlockForTesting(end));
1434 }
1435 
1436 // Check the case when next node is used for available suffix when selection is
1437 // not at last index but there is only space after it.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_SpacesAfterSelection)1438 TEST_F(TextFragmentSelectorGeneratorTest,
1439        GetNextTextBlock_SpacesAfterSelection) {
1440   SimRequest request("https://example.com/test.html", "text/html");
1441   LoadURL("https://example.com/test.html");
1442   request.Complete(R"HTML(
1443     <!DOCTYPE html>
1444     <p id='first'>
1445       First paragraph text
1446     </p>
1447     <p id='second'>
1448       Second paragraph text
1449     </p>
1450   )HTML");
1451   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1452   const auto& start = Position(first_paragraph, 23);
1453   const auto& end = Position(first_paragraph, 27);
1454   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1455 
1456   EXPECT_EQ("Second paragraph text", GetDocument()
1457                                          .GetFrame()
1458                                          ->GetTextFragmentSelectorGenerator()
1459                                          ->GetNextTextBlockForTesting(end));
1460 }
1461 
1462 // Check the case when next node is used for available suffix when selection is
1463 // not at last index but there is only invisible block after it.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_InvisibleAfterSelection)1464 TEST_F(TextFragmentSelectorGeneratorTest,
1465        GetNextTextBlock_InvisibleAfterSelection) {
1466   SimRequest request("https://example.com/test.html", "text/html");
1467   LoadURL("https://example.com/test.html");
1468   request.Complete(R"HTML(
1469     <!DOCTYPE html>
1470     <div id='first'>
1471       First paragraph text
1472       <div id='invisible' style='display:none'>
1473         invisible text
1474       </div>
1475     </div>
1476     <p id='second'>
1477       Second paragraph text
1478     </p>
1479   )HTML");
1480   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1481   const auto& start = Position(first_paragraph, 23);
1482   const auto& end = Position(first_paragraph, 27);
1483   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1484 
1485   EXPECT_EQ("Second paragraph text", GetDocument()
1486                                          .GetFrame()
1487                                          ->GetTextFragmentSelectorGenerator()
1488                                          ->GetNextTextBlockForTesting(end));
1489 }
1490 
1491 // Check the case when previous node is used for available prefix when selection
1492 // is not at last index but there is only invisible block. Invisible block
1493 // contains another block which also should be invisible.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_InvisibleAfterSelection_WithNestedInvisible)1494 TEST_F(TextFragmentSelectorGeneratorTest,
1495        GetNextTextBlock_InvisibleAfterSelection_WithNestedInvisible) {
1496   SimRequest request("https://example.com/test.html", "text/html");
1497   LoadURL("https://example.com/test.html");
1498   request.Complete(R"HTML(
1499     <!DOCTYPE html>
1500     <div id='first'>
1501       First paragraph text
1502       <div id='invisible' style='display:none'>
1503         invisible text
1504         <div>
1505           nested invisible text
1506         </div
1507       </div>
1508     </div>
1509     <p id='second'>
1510       Second paragraph text
1511       <div id='invisible' style='display:none'>
1512         invisible text
1513         <div>
1514           nested invisible text
1515         </div
1516       </div>
1517     </p>
1518     test
1519   )HTML");
1520   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1521   const auto& start = Position(first_paragraph, 23);
1522   const auto& end = Position(first_paragraph, 27);
1523   ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1524 
1525   EXPECT_EQ("Second paragraph text", GetDocument()
1526                                          .GetFrame()
1527                                          ->GetTextFragmentSelectorGenerator()
1528                                          ->GetNextTextBlockForTesting(end));
1529 }
1530 
1531 // Checks that selection in the same text node is considerered uninterrupted.
TEST_F(TextFragmentSelectorGeneratorTest,IsInSameUninterruptedBlock_OneTextNode)1532 TEST_F(TextFragmentSelectorGeneratorTest,
1533        IsInSameUninterruptedBlock_OneTextNode) {
1534   SimRequest request("https://example.com/test.html", "text/html");
1535   LoadURL("https://example.com/test.html");
1536   request.Complete(R"HTML(
1537     <!DOCTYPE html>
1538     <div id='first'>First paragraph text</div>
1539   )HTML");
1540   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1541   const auto& start = Position(first_paragraph, 0);
1542   const auto& end = Position(first_paragraph, 15);
1543   ASSERT_EQ("First paragraph", PlainText(EphemeralRange(start, end)));
1544 
1545   EXPECT_TRUE(GetDocument()
1546                   .GetFrame()
1547                   ->GetTextFragmentSelectorGenerator()
1548                   ->IsInSameUninterruptedBlockForTesting(start, end));
1549 }
1550 
1551 // Checks that selection in the same text node with nested non-block element is
1552 // considerered uninterrupted.
TEST_F(TextFragmentSelectorGeneratorTest,IsInSameUninterruptedBlock_NonBlockInterruption)1553 TEST_F(TextFragmentSelectorGeneratorTest,
1554        IsInSameUninterruptedBlock_NonBlockInterruption) {
1555   SimRequest request("https://example.com/test.html", "text/html");
1556   LoadURL("https://example.com/test.html");
1557   request.Complete(R"HTML(
1558     <!DOCTYPE html>
1559     <div id='first'>First <i>styled text</i> paragraph text</div>
1560   )HTML");
1561   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1562   const auto& start = Position(first_paragraph, 0);
1563   const auto& end = Position(first_paragraph->nextSibling()->nextSibling(), 10);
1564   ASSERT_EQ("First styled text paragraph",
1565             PlainText(EphemeralRange(start, end)));
1566 
1567   EXPECT_TRUE(GetDocument()
1568                   .GetFrame()
1569                   ->GetTextFragmentSelectorGenerator()
1570                   ->IsInSameUninterruptedBlockForTesting(start, end));
1571 }
1572 
1573 // Checks that selection in the same text node with nested block element is
1574 // considerered interrupted.
TEST_F(TextFragmentSelectorGeneratorTest,IsInSameUninterruptedBlock_BlockInterruption)1575 TEST_F(TextFragmentSelectorGeneratorTest,
1576        IsInSameUninterruptedBlock_BlockInterruption) {
1577   SimRequest request("https://example.com/test.html", "text/html");
1578   LoadURL("https://example.com/test.html");
1579   request.Complete(R"HTML(
1580     <!DOCTYPE html>
1581     <div id='first'>First <div>block text</div> paragraph text</div>
1582   )HTML");
1583   Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1584   const auto& start = Position(first_paragraph, 0);
1585   const auto& end = Position(first_paragraph->nextSibling()->nextSibling(), 10);
1586   ASSERT_EQ("First\nblock text\nparagraph",
1587             PlainText(EphemeralRange(start, end)));
1588 
1589   EXPECT_FALSE(GetDocument()
1590                    .GetFrame()
1591                    ->GetTextFragmentSelectorGenerator()
1592                    ->IsInSameUninterruptedBlockForTesting(start, end));
1593 }
1594 
1595 }  // namespace blink
1596