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.
7 #include "web/TextFinder.h"
9 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
10 #include "core/dom/Document.h"
11 #include "core/dom/NodeList.h"
12 #include "core/dom/Range.h"
13 #include "core/dom/shadow/ShadowRoot.h"
14 #include "core/html/HTMLElement.h"
15 #include "public/platform/Platform.h"
16 #include "public/web/WebDocument.h"
17 #include "web/FindInPageCoordinates.h"
18 #include "web/WebLocalFrameImpl.h"
19 #include "web/tests/FrameTestHelpers.h"
20 #include "wtf/OwnPtr.h"
21 #include <gtest/gtest.h>
23 using namespace blink;
27 class TextFinderTest : public ::testing::Test {
29 virtual void SetUp() override;
31 Document& document() const;
32 TextFinder& textFinder() const;
34 static WebFloatRect findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset);
37 FrameTestHelpers::WebViewHelper m_webViewHelper;
38 RefPtrWillBePersistent<Document> m_document;
39 TextFinder* m_textFinder;
42 void TextFinderTest::SetUp()
44 m_webViewHelper.initialize();
45 WebLocalFrameImpl& frameImpl = *m_webViewHelper.webViewImpl()->mainFrameImpl();
46 frameImpl.viewImpl()->resize(WebSize(640, 480));
47 m_document = PassRefPtrWillBeRawPtr<Document>(frameImpl.document());
48 m_textFinder = &frameImpl.ensureTextFinder();
51 Document& TextFinderTest::document() const
56 TextFinder& TextFinderTest::textFinder() const
61 WebFloatRect TextFinderTest::findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset)
63 RefPtrWillBeRawPtr<Range> range = Range::create(startContainer->document(), startContainer, startOffset, endContainer, endOffset);
64 return WebFloatRect(findInPageRectFromRange(range.get()));
67 TEST_F(TextFinderTest, FindTextSimple)
69 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
70 Node* textNode = document().body()->firstChild();
73 WebString searchText(String("FindMe"));
74 WebFindOptions findOptions; // Default.
75 bool wrapWithinFrame = true;
76 WebRect* selectionRect = 0;
78 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
79 Range* activeMatch = textFinder().activeMatch();
80 ASSERT_TRUE(activeMatch);
81 EXPECT_EQ(textNode, activeMatch->startContainer());
82 EXPECT_EQ(4, activeMatch->startOffset());
83 EXPECT_EQ(textNode, activeMatch->endContainer());
84 EXPECT_EQ(10, activeMatch->endOffset());
86 findOptions.findNext = true;
87 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
88 activeMatch = textFinder().activeMatch();
89 ASSERT_TRUE(activeMatch);
90 EXPECT_EQ(textNode, activeMatch->startContainer());
91 EXPECT_EQ(14, activeMatch->startOffset());
92 EXPECT_EQ(textNode, activeMatch->endContainer());
93 EXPECT_EQ(20, activeMatch->endOffset());
95 // Should wrap to the first match.
96 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
97 activeMatch = textFinder().activeMatch();
98 ASSERT_TRUE(activeMatch);
99 EXPECT_EQ(textNode, activeMatch->startContainer());
100 EXPECT_EQ(4, activeMatch->startOffset());
101 EXPECT_EQ(textNode, activeMatch->endContainer());
102 EXPECT_EQ(10, activeMatch->endOffset());
104 // Search in the reverse order.
106 findOptions = WebFindOptions();
107 findOptions.forward = false;
109 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
110 activeMatch = textFinder().activeMatch();
111 ASSERT_TRUE(activeMatch);
112 EXPECT_EQ(textNode, activeMatch->startContainer());
113 EXPECT_EQ(14, activeMatch->startOffset());
114 EXPECT_EQ(textNode, activeMatch->endContainer());
115 EXPECT_EQ(20, activeMatch->endOffset());
117 findOptions.findNext = true;
118 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
119 activeMatch = textFinder().activeMatch();
120 ASSERT_TRUE(activeMatch);
121 EXPECT_EQ(textNode, activeMatch->startContainer());
122 EXPECT_EQ(4, activeMatch->startOffset());
123 EXPECT_EQ(textNode, activeMatch->endContainer());
124 EXPECT_EQ(10, activeMatch->endOffset());
126 // Wrap to the first match (last occurence in the document).
127 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
128 activeMatch = textFinder().activeMatch();
129 ASSERT_TRUE(activeMatch);
130 EXPECT_EQ(textNode, activeMatch->startContainer());
131 EXPECT_EQ(14, activeMatch->startOffset());
132 EXPECT_EQ(textNode, activeMatch->endContainer());
133 EXPECT_EQ(20, activeMatch->endOffset());
136 TEST_F(TextFinderTest, FindTextNotFound)
138 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
141 WebString searchText(String("Boo"));
142 WebFindOptions findOptions; // Default.
143 bool wrapWithinFrame = true;
144 WebRect* selectionRect = 0;
146 EXPECT_FALSE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
147 EXPECT_FALSE(textFinder().activeMatch());
150 TEST_F(TextFinderTest, FindTextInShadowDOM)
152 document().body()->setInnerHTML("<b>FOO</b><i>foo</i>", ASSERT_NO_EXCEPTION);
153 RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = document().body()->createShadowRoot(ASSERT_NO_EXCEPTION);
154 shadowRoot->setInnerHTML("<content select=\"i\"></content><u>Foo</u><content></content>", ASSERT_NO_EXCEPTION);
155 Node* textInBElement = document().body()->firstChild()->firstChild();
156 Node* textInIElement = document().body()->lastChild()->firstChild();
157 Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild();
160 WebString searchText(String("foo"));
161 WebFindOptions findOptions; // Default.
162 bool wrapWithinFrame = true;
163 WebRect* selectionRect = 0;
165 // TextIterator currently returns the matches in the document order, instead of the visual order. It visits
166 // the shadow roots first, so in this case the matches will be returned in the order of <u> -> <b> -> <i>.
167 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
168 Range* activeMatch = textFinder().activeMatch();
169 ASSERT_TRUE(activeMatch);
170 EXPECT_EQ(textInUElement, activeMatch->startContainer());
171 EXPECT_EQ(0, activeMatch->startOffset());
172 EXPECT_EQ(textInUElement, activeMatch->endContainer());
173 EXPECT_EQ(3, activeMatch->endOffset());
175 findOptions.findNext = true;
176 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
177 activeMatch = textFinder().activeMatch();
178 ASSERT_TRUE(activeMatch);
179 EXPECT_EQ(textInBElement, activeMatch->startContainer());
180 EXPECT_EQ(0, activeMatch->startOffset());
181 EXPECT_EQ(textInBElement, activeMatch->endContainer());
182 EXPECT_EQ(3, activeMatch->endOffset());
184 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
185 activeMatch = textFinder().activeMatch();
186 ASSERT_TRUE(activeMatch);
187 EXPECT_EQ(textInIElement, activeMatch->startContainer());
188 EXPECT_EQ(0, activeMatch->startOffset());
189 EXPECT_EQ(textInIElement, activeMatch->endContainer());
190 EXPECT_EQ(3, activeMatch->endOffset());
192 // Should wrap to the first match.
193 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
194 activeMatch = textFinder().activeMatch();
195 ASSERT_TRUE(activeMatch);
196 EXPECT_EQ(textInUElement, activeMatch->startContainer());
197 EXPECT_EQ(0, activeMatch->startOffset());
198 EXPECT_EQ(textInUElement, activeMatch->endContainer());
199 EXPECT_EQ(3, activeMatch->endOffset());
201 // Fresh search in the reverse order.
203 findOptions = WebFindOptions();
204 findOptions.forward = false;
206 ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect));
207 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(textInBElement, activeMatch->startContainer());
219 EXPECT_EQ(0, activeMatch->startOffset());
220 EXPECT_EQ(textInBElement, 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(textInUElement, activeMatch->startContainer());
227 EXPECT_EQ(0, activeMatch->startOffset());
228 EXPECT_EQ(textInUElement, activeMatch->endContainer());
229 EXPECT_EQ(3, activeMatch->endOffset());
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());
241 TEST_F(TextFinderTest, ScopeTextMatchesSimple)
243 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION);
244 Node* textNode = document().body()->firstChild();
247 WebString searchText(String("FindMe"));
248 WebFindOptions findOptions; // Default.
250 textFinder().resetMatchCount();
251 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
252 while (textFinder().scopingInProgress())
253 FrameTestHelpers::runPendingTasks();
255 EXPECT_EQ(2, textFinder().totalMatchCount());
256 WebVector<WebFloatRect> matchRects;
257 textFinder().findMatchRects(matchRects);
258 ASSERT_EQ(2u, matchRects.size());
259 EXPECT_EQ(findInPageRect(textNode, 4, textNode, 10), matchRects[0]);
260 EXPECT_EQ(findInPageRect(textNode, 14, textNode, 20), matchRects[1]);
263 TEST_F(TextFinderTest, ScopeTextMatchesWithShadowDOM)
265 document().body()->setInnerHTML("<b>FOO</b><i>foo</i>", ASSERT_NO_EXCEPTION);
266 RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = document().body()->createShadowRoot(ASSERT_NO_EXCEPTION);
267 shadowRoot->setInnerHTML("<content select=\"i\"></content><u>Foo</u><content></content>", ASSERT_NO_EXCEPTION);
268 Node* textInBElement = document().body()->firstChild()->firstChild();
269 Node* textInIElement = document().body()->lastChild()->firstChild();
270 Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild();
273 WebString searchText(String("fOO"));
274 WebFindOptions findOptions; // Default.
276 textFinder().resetMatchCount();
277 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
278 while (textFinder().scopingInProgress())
279 FrameTestHelpers::runPendingTasks();
281 // TextIterator currently returns the matches in the document order, instead of the visual order. It visits
282 // the shadow roots first, so in this case the matches will be returned in the order of <u> -> <b> -> <i>.
283 EXPECT_EQ(3, textFinder().totalMatchCount());
284 WebVector<WebFloatRect> matchRects;
285 textFinder().findMatchRects(matchRects);
286 ASSERT_EQ(3u, matchRects.size());
287 EXPECT_EQ(findInPageRect(textInUElement, 0, textInUElement, 3), matchRects[0]);
288 EXPECT_EQ(findInPageRect(textInBElement, 0, textInBElement, 3), matchRects[1]);
289 EXPECT_EQ(findInPageRect(textInIElement, 0, textInIElement, 3), matchRects[2]);
292 TEST_F(TextFinderTest, ScopeRepeatPatternTextMatches)
294 document().body()->setInnerHTML("ab ab ab ab ab", ASSERT_NO_EXCEPTION);
295 Node* textNode = document().body()->firstChild();
298 WebString searchText(String("ab ab"));
299 WebFindOptions findOptions; // Default.
301 textFinder().resetMatchCount();
302 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
303 while (textFinder().scopingInProgress())
304 FrameTestHelpers::runPendingTasks();
306 EXPECT_EQ(2, textFinder().totalMatchCount());
307 WebVector<WebFloatRect> matchRects;
308 textFinder().findMatchRects(matchRects);
309 ASSERT_EQ(2u, matchRects.size());
310 EXPECT_EQ(findInPageRect(textNode, 0, textNode, 5), matchRects[0]);
311 EXPECT_EQ(findInPageRect(textNode, 6, textNode, 11), matchRects[1]);
314 TEST_F(TextFinderTest, OverlappingMatches)
316 document().body()->setInnerHTML("aababaa", ASSERT_NO_EXCEPTION);
317 Node* textNode = document().body()->firstChild();
320 WebString searchText(String("aba"));
321 WebFindOptions findOptions; // Default.
323 textFinder().resetMatchCount();
324 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
325 while (textFinder().scopingInProgress())
326 FrameTestHelpers::runPendingTasks();
328 // We shouldn't find overlapped matches.
329 EXPECT_EQ(1, textFinder().totalMatchCount());
330 WebVector<WebFloatRect> matchRects;
331 textFinder().findMatchRects(matchRects);
332 ASSERT_EQ(1u, matchRects.size());
333 EXPECT_EQ(findInPageRect(textNode, 1, textNode, 4), matchRects[0]);
336 TEST_F(TextFinderTest, SequentialMatches)
338 document().body()->setInnerHTML("ababab", ASSERT_NO_EXCEPTION);
339 Node* textNode = document().body()->firstChild();
342 WebString searchText(String("ab"));
343 WebFindOptions findOptions; // Default.
345 textFinder().resetMatchCount();
346 textFinder().scopeStringMatches(identifier, searchText, findOptions, true);
347 while (textFinder().scopingInProgress())
348 FrameTestHelpers::runPendingTasks();
350 EXPECT_EQ(3, textFinder().totalMatchCount());
351 WebVector<WebFloatRect> matchRects;
352 textFinder().findMatchRects(matchRects);
353 ASSERT_EQ(3u, matchRects.size());
354 EXPECT_EQ(findInPageRect(textNode, 0, textNode, 2), matchRects[0]);
355 EXPECT_EQ(findInPageRect(textNode, 2, textNode, 4), matchRects[1]);
356 EXPECT_EQ(findInPageRect(textNode, 4, textNode, 6), matchRects[2]);
359 class TextFinderFakeTimerTest : public TextFinderTest {
361 virtual void SetUp() override;
362 virtual void TearDown() override;
364 // A simple platform that mocks out the clock.
365 class TimeProxyPlatform : public Platform {
369 , m_fallbackPlatform(0)
374 // Check that the proxy wasn't installed yet.
375 ASSERT_NE(Platform::current(), this);
376 m_fallbackPlatform = Platform::current();
377 m_timeCounter = m_fallbackPlatform->currentTime();
378 Platform::initialize(this);
379 ASSERT_EQ(Platform::current(), this);
384 // Check that the proxy was installed.
385 ASSERT_EQ(Platform::current(), this);
386 Platform::initialize(m_fallbackPlatform);
387 ASSERT_EQ(Platform::current(), m_fallbackPlatform);
388 m_fallbackPlatform = 0;
392 Platform& ensureFallback()
394 ASSERT(m_fallbackPlatform);
395 return *m_fallbackPlatform;
398 // From blink::Platform:
399 virtual double currentTime() override
401 return ++m_timeCounter;
404 // These blink::Platform methods must be overriden to make a usable object.
405 virtual void cryptographicallyRandomValues(unsigned char* buffer, size_t length) override
407 ensureFallback().cryptographicallyRandomValues(buffer, length);
410 virtual const unsigned char* getTraceCategoryEnabledFlag(const char* categoryName) override
412 return ensureFallback().getTraceCategoryEnabledFlag(categoryName);
415 // These two methods allow timers to work correctly.
416 virtual double monotonicallyIncreasingTime() override
418 return ensureFallback().monotonicallyIncreasingTime();
421 virtual void setSharedTimerFireInterval(double interval) override
423 ensureFallback().setSharedTimerFireInterval(interval);
426 virtual WebThread* currentThread() override { return ensureFallback().currentThread(); }
427 virtual WebUnitTestSupport* unitTestSupport() override { return ensureFallback().unitTestSupport(); }
428 virtual WebString defaultLocale() override { return ensureFallback().defaultLocale(); }
429 virtual WebCompositorSupport* compositorSupport() override { return ensureFallback().compositorSupport(); }
431 double m_timeCounter;
432 Platform* m_fallbackPlatform;
435 TimeProxyPlatform m_proxyTimePlatform;
438 void TextFinderFakeTimerTest::SetUp()
440 TextFinderTest::SetUp();
441 m_proxyTimePlatform.install();
444 void TextFinderFakeTimerTest::TearDown()
446 m_proxyTimePlatform.remove();
447 TextFinderTest::TearDown();
450 TEST_F(TextFinderFakeTimerTest, ScopeWithTimeouts)
452 // Make a long string.
453 String text(Vector<UChar>(100));
455 String searchPattern("abc");
456 // Make 4 substrings "abc" in text.
457 text.insert(searchPattern, 1);
458 text.insert(searchPattern, 10);
459 text.insert(searchPattern, 50);
460 text.insert(searchPattern, 90);
462 document().body()->setInnerHTML(text, ASSERT_NO_EXCEPTION);
465 WebFindOptions findOptions; // Default.
467 textFinder().resetMatchCount();
469 // There will be only one iteration before timeout, because increment
470 // of the TimeProxyPlatform timer is greater than timeout threshold.
471 textFinder().scopeStringMatches(identifier, searchPattern, findOptions, true);
472 while (textFinder().scopingInProgress())
473 FrameTestHelpers::runPendingTasks();
475 EXPECT_EQ(4, textFinder().totalMatchCount());