Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / web / tests / TextFinderTest.cpp
blob58ea0069d5ee034ef8b5f8222cd754c8fc911723
1 // Copyright 2014 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.
5 #include "config.h"
6 #include "web/TextFinder.h"
8 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
9 #include "core/dom/Document.h"
10 #include "core/dom/NodeList.h"
11 #include "core/dom/Range.h"
12 #include "core/dom/shadow/ShadowRoot.h"
13 #include "core/frame/FrameHost.h"
14 #include "core/html/HTMLElement.h"
15 #include "core/layout/TextAutosizer.h"
16 #include "core/page/Page.h"
17 #include "platform/testing/UnitTestHelpers.h"
18 #include "public/platform/Platform.h"
19 #include "public/web/WebDocument.h"
20 #include "web/FindInPageCoordinates.h"
21 #include "web/WebLocalFrameImpl.h"
22 #include "web/tests/FrameTestHelpers.h"
23 #include "wtf/OwnPtr.h"
24 #include <gtest/gtest.h>
26 using blink::testing::runPendingTasks;
28 namespace blink {
30 class TextFinderTest : public ::testing::Test {
31 protected:
32 void SetUp() override;
34 Document& document() const;
35 TextFinder& textFinder() const;
37 static WebFloatRect findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset);
39 private:
40 FrameTestHelpers::WebViewHelper m_webViewHelper;
41 RefPtrWillBePersistent<Document> m_document;
42 RawPtrWillBePersistent<TextFinder> m_textFinder;
45 void TextFinderTest::SetUp()
47 m_webViewHelper.initialize();
48 WebLocalFrameImpl& frameImpl = *m_webViewHelper.webViewImpl()->mainFrameImpl();
49 frameImpl.viewImpl()->resize(WebSize(640, 480));
50 frameImpl.viewImpl()->layout();
51 m_document = PassRefPtrWillBeRawPtr<Document>(frameImpl.document());
52 m_textFinder = &frameImpl.ensureTextFinder();
55 Document& TextFinderTest::document() const
57 return *m_document;
60 TextFinder& TextFinderTest::textFinder() const
62 return *m_textFinder;
65 WebFloatRect TextFinderTest::findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset)
67 RefPtrWillBeRawPtr<Range> range = Range::create(startContainer->document(), startContainer, startOffset, endContainer, endOffset);
68 return WebFloatRect(findInPageRectFromRange(range.get()));
71 TEST_F(TextFinderTest, FindTextSimple)
73 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
74 Node* textNode = document().body()->firstChild();
76 int identifier = 0;
77 WebString searchText(String("FindMe"));
78 WebFindOptions findOptions; // Default.
79 bool wrapWithinFrame = true;
80 WebRect* selectionRect = nullptr;
82 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
83 Range* activeMatch = textFinder().activeMatch();
84 ASSERT_TRUE(activeMatch);
85 EXPECT_EQ(textNode, activeMatch->startContainer());
86 EXPECT_EQ(4, activeMatch->startOffset());
87 EXPECT_EQ(textNode, activeMatch->endContainer());
88 EXPECT_EQ(10, activeMatch->endOffset());
90 findOptions.findNext = true;
91 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
92 activeMatch = textFinder().activeMatch();
93 ASSERT_TRUE(activeMatch);
94 EXPECT_EQ(textNode, activeMatch->startContainer());
95 EXPECT_EQ(14, activeMatch->startOffset());
96 EXPECT_EQ(textNode, activeMatch->endContainer());
97 EXPECT_EQ(20, activeMatch->endOffset());
99 // Should wrap to the first match.
100 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
101 activeMatch = textFinder().activeMatch();
102 ASSERT_TRUE(activeMatch);
103 EXPECT_EQ(textNode, activeMatch->startContainer());
104 EXPECT_EQ(4, activeMatch->startOffset());
105 EXPECT_EQ(textNode, activeMatch->endContainer());
106 EXPECT_EQ(10, activeMatch->endOffset());
108 // Search in the reverse order.
109 identifier = 1;
110 findOptions = WebFindOptions();
111 findOptions.forward = false;
113 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
114 activeMatch = textFinder().activeMatch();
115 ASSERT_TRUE(activeMatch);
116 EXPECT_EQ(textNode, activeMatch->startContainer());
117 EXPECT_EQ(14, activeMatch->startOffset());
118 EXPECT_EQ(textNode, activeMatch->endContainer());
119 EXPECT_EQ(20, activeMatch->endOffset());
121 findOptions.findNext = true;
122 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
123 activeMatch = textFinder().activeMatch();
124 ASSERT_TRUE(activeMatch);
125 EXPECT_EQ(textNode, activeMatch->startContainer());
126 EXPECT_EQ(4, activeMatch->startOffset());
127 EXPECT_EQ(textNode, activeMatch->endContainer());
128 EXPECT_EQ(10, activeMatch->endOffset());
130 // Wrap to the first match (last occurence in the document).
131 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
132 activeMatch = textFinder().activeMatch();
133 ASSERT_TRUE(activeMatch);
134 EXPECT_EQ(textNode, activeMatch->startContainer());
135 EXPECT_EQ(14, activeMatch->startOffset());
136 EXPECT_EQ(textNode, activeMatch->endContainer());
137 EXPECT_EQ(20, activeMatch->endOffset());
140 TEST_F(TextFinderTest, FindTextAutosizing)
142 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
144 int identifier = 0;
145 WebString searchText(String("FindMe"));
146 WebFindOptions findOptions; // Default.
147 bool wrapWithinFrame = true;
148 WebRect* selectionRect = nullptr;
150 // Set viewport scale to 20 in order to simulate zoom-in
151 VisualViewport& visualViewport = document().page()->frameHost().visualViewport();
152 visualViewport.setScale(20);
154 // Enforce autosizing
155 document().settings()->setTextAutosizingEnabled(true);
156 document().settings()->setTextAutosizingWindowSizeOverride(IntSize(20, 20));
157 document().textAutosizer()->updatePageInfo();
159 // In case of autosizing, scale _should_ change
160 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
161 ASSERT_TRUE(textFinder().activeMatch());
162 ASSERT_EQ(1, visualViewport.scale()); // in this case to 1
164 // Disable autosizing and reset scale to 20
165 visualViewport.setScale(20);
166 document().settings()->setTextAutosizingEnabled(false);
167 document().textAutosizer()->updatePageInfo();
169 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
170 ASSERT_TRUE(textFinder().activeMatch());
171 ASSERT_EQ(20, visualViewport.scale());
174 TEST_F(TextFinderTest, FindTextNotFound)
176 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
178 int identifier = 0;
179 WebString searchText(String("Boo"));
180 WebFindOptions findOptions; // Default.
181 bool wrapWithinFrame = true;
182 WebRect* selectionRect = nullptr;
184 EXPECT_FALSE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
185 EXPECT_FALSE(textFinder().activeMatch());
188 TEST_F(TextFinderTest, FindTextInShadowDOM)
190 document().body()->setInnerHTML("<b>FOO</b><i>foo</i>", ASSERT_NO_EXCEPTION);
191 RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = document().body()->createShadowRootInternal(ShadowRootType::OpenByDefault, ASSERT_NO_EXCEPTION);
192 shadowRoot->setInnerHTML("<content select=\"i\"></content><u>Foo</u><content></content>", ASSERT_NO_EXCEPTION);
193 Node* textInBElement = document().body()->firstChild()->firstChild();
194 Node* textInIElement = document().body()->lastChild()->firstChild();
195 Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild();
197 int identifier = 0;
198 WebString searchText(String("foo"));
199 WebFindOptions findOptions; // Default.
200 bool wrapWithinFrame = true;
201 WebRect* selectionRect = nullptr;
203 // TextIterator currently returns the matches in the composed treeorder, so
204 // in this case the matches will be returned in the order of
205 // <i> -> <u> -> <b>.
206 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
207 Range* activeMatch = textFinder().activeMatch();
208 ASSERT_TRUE(activeMatch);
209 EXPECT_EQ(textInIElement, activeMatch->startContainer());
210 EXPECT_EQ(0, activeMatch->startOffset());
211 EXPECT_EQ(textInIElement, activeMatch->endContainer());
212 EXPECT_EQ(3, activeMatch->endOffset());
214 findOptions.findNext = true;
215 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
216 activeMatch = textFinder().activeMatch();
217 ASSERT_TRUE(activeMatch);
218 EXPECT_EQ(textInUElement, activeMatch->startContainer());
219 EXPECT_EQ(0, activeMatch->startOffset());
220 EXPECT_EQ(textInUElement, activeMatch->endContainer());
221 EXPECT_EQ(3, activeMatch->endOffset());
223 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
224 activeMatch = textFinder().activeMatch();
225 ASSERT_TRUE(activeMatch);
226 EXPECT_EQ(textInBElement, activeMatch->startContainer());
227 EXPECT_EQ(0, activeMatch->startOffset());
228 EXPECT_EQ(textInBElement, activeMatch->endContainer());
229 EXPECT_EQ(3, activeMatch->endOffset());
231 // Should wrap to the first match.
232 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
233 activeMatch = textFinder().activeMatch();
234 ASSERT_TRUE(activeMatch);
235 EXPECT_EQ(textInIElement, activeMatch->startContainer());
236 EXPECT_EQ(0, activeMatch->startOffset());
237 EXPECT_EQ(textInIElement, activeMatch->endContainer());
238 EXPECT_EQ(3, activeMatch->endOffset());
240 // Fresh search in the reverse order.
241 identifier = 1;
242 findOptions = WebFindOptions();
243 findOptions.forward = false;
245 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
246 activeMatch = textFinder().activeMatch();
247 ASSERT_TRUE(activeMatch);
248 EXPECT_EQ(textInBElement, activeMatch->startContainer());
249 EXPECT_EQ(0, activeMatch->startOffset());
250 EXPECT_EQ(textInBElement, activeMatch->endContainer());
251 EXPECT_EQ(3, activeMatch->endOffset());
253 findOptions.findNext = true;
254 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
255 activeMatch = textFinder().activeMatch();
256 ASSERT_TRUE(activeMatch);
257 EXPECT_EQ(textInUElement, activeMatch->startContainer());
258 EXPECT_EQ(0, activeMatch->startOffset());
259 EXPECT_EQ(textInUElement, activeMatch->endContainer());
260 EXPECT_EQ(3, activeMatch->endOffset());
262 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
263 activeMatch = textFinder().activeMatch();
264 ASSERT_TRUE(activeMatch);
265 EXPECT_EQ(textInIElement, activeMatch->startContainer());
266 EXPECT_EQ(0, activeMatch->startOffset());
267 EXPECT_EQ(textInIElement, activeMatch->endContainer());
268 EXPECT_EQ(3, activeMatch->endOffset());
270 // And wrap.
271 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
272 activeMatch = textFinder().activeMatch();
273 ASSERT_TRUE(activeMatch);
274 EXPECT_EQ(textInBElement, activeMatch->startContainer());
275 EXPECT_EQ(0, activeMatch->startOffset());
276 EXPECT_EQ(textInBElement, activeMatch->endContainer());
277 EXPECT_EQ(3, activeMatch->endOffset());
280 TEST_F(TextFinderTest, ScopeTextMatchesSimple)
282 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
283 Node* textNode = document().body()->firstChild();
285 int identifier = 0;
286 WebString searchText(String("FindMe"));
287 WebFindOptions findOptions; // Default.
289 textFinder().resetMatchCount();
290 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
291 while (textFinder().scopingInProgress())
292 runPendingTasks();
294 EXPECT_EQ(2, textFinder().totalMatchCount());
295 WebVector<WebFloatRect> matchRects;
296 textFinder().findMatchRects(matchRects);
297 ASSERT_EQ(2u, matchRects.size());
298 EXPECT_EQ(findInPageRect(textNode, 4, textNode, 10), matchRects[0]);
299 EXPECT_EQ(findInPageRect(textNode, 14, textNode, 20), matchRects[1]);
302 TEST_F(TextFinderTest, ScopeTextMatchesWithShadowDOM)
304 document().body()->setInnerHTML("<b>FOO</b><i>foo</i>", ASSERT_NO_EXCEPTION);
305 RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = document().body()->createShadowRootInternal(ShadowRootType::Open, ASSERT_NO_EXCEPTION);
306 shadowRoot->setInnerHTML("<content select=\"i\"></content><u>Foo</u><content></content>", ASSERT_NO_EXCEPTION);
307 Node* textInBElement = document().body()->firstChild()->firstChild();
308 Node* textInIElement = document().body()->lastChild()->firstChild();
309 Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild();
311 int identifier = 0;
312 WebString searchText(String("fOO"));
313 WebFindOptions findOptions; // Default.
315 textFinder().resetMatchCount();
316 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
317 while (textFinder().scopingInProgress())
318 runPendingTasks();
320 // TextIterator currently returns the matches in the composed tree order,
321 // so in this case the matches will be returned in the order of
322 // <i> -> <u> -> <b>.
323 EXPECT_EQ(3, textFinder().totalMatchCount());
324 WebVector<WebFloatRect> matchRects;
325 textFinder().findMatchRects(matchRects);
326 ASSERT_EQ(3u, matchRects.size());
327 EXPECT_EQ(findInPageRect(textInIElement, 0, textInIElement, 3), matchRects[0]);
328 EXPECT_EQ(findInPageRect(textInUElement, 0, textInUElement, 3), matchRects[1]);
329 EXPECT_EQ(findInPageRect(textInBElement, 0, textInBElement, 3), matchRects[2]);
332 TEST_F(TextFinderTest, ScopeRepeatPatternTextMatches)
334 document().body()->setInnerHTML("ab ab ab ab ab", ASSERT_NO_EXCEPTION);
335 Node* textNode = document().body()->firstChild();
337 int identifier = 0;
338 WebString searchText(String("ab ab"));
339 WebFindOptions findOptions; // Default.
341 textFinder().resetMatchCount();
342 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
343 while (textFinder().scopingInProgress())
344 runPendingTasks();
346 EXPECT_EQ(2, textFinder().totalMatchCount());
347 WebVector<WebFloatRect> matchRects;
348 textFinder().findMatchRects(matchRects);
349 ASSERT_EQ(2u, matchRects.size());
350 EXPECT_EQ(findInPageRect(textNode, 0, textNode, 5), matchRects[0]);
351 EXPECT_EQ(findInPageRect(textNode, 6, textNode, 11), matchRects[1]);
354 TEST_F(TextFinderTest, OverlappingMatches)
356 document().body()->setInnerHTML("aababaa", ASSERT_NO_EXCEPTION);
357 Node* textNode = document().body()->firstChild();
359 int identifier = 0;
360 WebString searchText(String("aba"));
361 WebFindOptions findOptions; // Default.
363 textFinder().resetMatchCount();
364 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
365 while (textFinder().scopingInProgress())
366 runPendingTasks();
368 // We shouldn't find overlapped matches.
369 EXPECT_EQ(1, textFinder().totalMatchCount());
370 WebVector<WebFloatRect> matchRects;
371 textFinder().findMatchRects(matchRects);
372 ASSERT_EQ(1u, matchRects.size());
373 EXPECT_EQ(findInPageRect(textNode, 1, textNode, 4), matchRects[0]);
376 TEST_F(TextFinderTest, SequentialMatches)
378 document().body()->setInnerHTML("ababab", ASSERT_NO_EXCEPTION);
379 Node* textNode = document().body()->firstChild();
381 int identifier = 0;
382 WebString searchText(String("ab"));
383 WebFindOptions findOptions; // Default.
385 textFinder().resetMatchCount();
386 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
387 while (textFinder().scopingInProgress())
388 runPendingTasks();
390 EXPECT_EQ(3, textFinder().totalMatchCount());
391 WebVector<WebFloatRect> matchRects;
392 textFinder().findMatchRects(matchRects);
393 ASSERT_EQ(3u, matchRects.size());
394 EXPECT_EQ(findInPageRect(textNode, 0, textNode, 2), matchRects[0]);
395 EXPECT_EQ(findInPageRect(textNode, 2, textNode, 4), matchRects[1]);
396 EXPECT_EQ(findInPageRect(textNode, 4, textNode, 6), matchRects[2]);
399 class TextFinderFakeTimerTest : public TextFinderTest {
400 protected:
401 void SetUp() override;
402 void TearDown() override;
404 // A simple platform that mocks out the clock.
405 class TimeProxyPlatform : public Platform {
406 public:
407 TimeProxyPlatform()
408 : m_timeCounter(0.)
409 , m_fallbackPlatform(0)
412 void install()
414 // Check that the proxy wasn't installed yet.
415 ASSERT_NE(Platform::current(), this);
416 m_fallbackPlatform = Platform::current();
417 m_timeCounter = m_fallbackPlatform->currentTime();
418 Platform::initialize(this);
419 ASSERT_EQ(Platform::current(), this);
422 void remove()
424 // Check that the proxy was installed.
425 ASSERT_EQ(Platform::current(), this);
426 Platform::initialize(m_fallbackPlatform);
427 ASSERT_EQ(Platform::current(), m_fallbackPlatform);
428 m_fallbackPlatform = 0;
431 private:
432 Platform& ensureFallback()
434 ASSERT(m_fallbackPlatform);
435 return *m_fallbackPlatform;
438 // From blink::Platform:
439 double currentTime() override
441 return ++m_timeCounter;
444 // These blink::Platform methods must be overriden to make a usable object.
445 void cryptographicallyRandomValues(unsigned char* buffer, size_t length) override
447 ensureFallback().cryptographicallyRandomValues(buffer, length);
450 const unsigned char* getTraceCategoryEnabledFlag(const char* categoryName) override
452 return ensureFallback().getTraceCategoryEnabledFlag(categoryName);
455 // These two methods allow timers to work correctly.
456 double monotonicallyIncreasingTime() override
458 return ensureFallback().monotonicallyIncreasingTime();
461 WebThread* currentThread() override { return ensureFallback().currentThread(); }
462 WebUnitTestSupport* unitTestSupport() override { return ensureFallback().unitTestSupport(); }
463 WebString defaultLocale() override { return ensureFallback().defaultLocale(); }
464 WebCompositorSupport* compositorSupport() override { return ensureFallback().compositorSupport(); }
466 double m_timeCounter;
467 Platform* m_fallbackPlatform;
470 TimeProxyPlatform m_proxyTimePlatform;
473 void TextFinderFakeTimerTest::SetUp()
475 TextFinderTest::SetUp();
476 m_proxyTimePlatform.install();
479 void TextFinderFakeTimerTest::TearDown()
481 m_proxyTimePlatform.remove();
482 TextFinderTest::TearDown();
485 TEST_F(TextFinderFakeTimerTest, ScopeWithTimeouts)
487 // Make a long string.
488 String text(Vector<UChar>(100));
489 text.fill('a');
490 String searchPattern("abc");
491 // Make 4 substrings "abc" in text.
492 text.insert(searchPattern, 1);
493 text.insert(searchPattern, 10);
494 text.insert(searchPattern, 50);
495 text.insert(searchPattern, 90);
497 document().body()->setInnerHTML(text, ASSERT_NO_EXCEPTION);
499 int identifier = 0;
500 WebFindOptions findOptions; // Default.
502 textFinder().resetMatchCount();
504 // There will be only one iteration before timeout, because increment
505 // of the TimeProxyPlatform timer is greater than timeout threshold.
506 textFinder().scopeStringMatches(identifier, searchPattern, findOptions, true);
507 while (textFinder().scopingInProgress())
508 runPendingTasks();
510 EXPECT_EQ(4, textFinder().totalMatchCount());
513 } // namespace blink