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. 4 5 #include "config.h" 6 7 #include "web/TextFinder.h" 8 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> 22 23 using namespace blink; 24 25 namespace { 26 27 class TextFinderTest : public ::testing::Test { 28 protected: 29 virtual void SetUp() OVERRIDE; 30 31 Document& document() const; 32 TextFinder& textFinder() const; 33 34 static WebFloatRect findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset); 35 36 private: 37 FrameTestHelpers::WebViewHelper m_webViewHelper; 38 RefPtrWillBePersistent<Document> m_document; 39 TextFinder* m_textFinder; 40 }; 41 42 void TextFinderTest::SetUp() 43 { 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(); 49 } 50 51 Document& TextFinderTest::document() const 52 { 53 return *m_document; 54 } 55 56 TextFinder& TextFinderTest::textFinder() const 57 { 58 return *m_textFinder; 59 } 60 61 WebFloatRect TextFinderTest::findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset) 62 { 63 RefPtrWillBeRawPtr<Range> range = Range::create(startContainer->document(), startContainer, startOffset, endContainer, endOffset); 64 return WebFloatRect(findInPageRectFromRange(range.get())); 65 } 66 67 TEST_F(TextFinderTest, FindTextSimple) 68 { 69 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION); 70 Node* textNode = document().body()->firstChild(); 71 72 int identifier = 0; 73 WebString searchText(String("FindMe")); 74 WebFindOptions findOptions; // Default. 75 bool wrapWithinFrame = true; 76 WebRect* selectionRect = 0; 77 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()); 85 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()); 94 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()); 103 104 // Search in the reverse order. 105 identifier = 1; 106 findOptions = WebFindOptions(); 107 findOptions.forward = false; 108 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()); 116 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()); 125 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()); 134 } 135 136 TEST_F(TextFinderTest, FindTextNotFound) 137 { 138 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION); 139 140 int identifier = 0; 141 WebString searchText(String("Boo")); 142 WebFindOptions findOptions; // Default. 143 bool wrapWithinFrame = true; 144 WebRect* selectionRect = 0; 145 146 EXPECT_FALSE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); 147 EXPECT_FALSE(textFinder().activeMatch()); 148 } 149 150 TEST_F(TextFinderTest, FindTextInShadowDOM) 151 { 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(); 158 159 int identifier = 0; 160 WebString searchText(String("foo")); 161 WebFindOptions findOptions; // Default. 162 bool wrapWithinFrame = true; 163 WebRect* selectionRect = 0; 164 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()); 174 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()); 183 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()); 191 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()); 200 201 // Fresh search in the reverse order. 202 identifier = 1; 203 findOptions = WebFindOptions(); 204 findOptions.forward = false; 205 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()); 213 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()); 222 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()); 230 231 // And wrap. 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()); 239 } 240 241 TEST_F(TextFinderTest, ScopeTextMatchesSimple) 242 { 243 document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION); 244 Node* textNode = document().body()->firstChild(); 245 246 int identifier = 0; 247 WebString searchText(String("FindMe")); 248 WebFindOptions findOptions; // Default. 249 250 textFinder().resetMatchCount(); 251 textFinder().scopeStringMatches(identifier, searchText, findOptions, true); 252 while (textFinder().scopingInProgress()) 253 FrameTestHelpers::runPendingTasks(); 254 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]); 261 } 262 263 TEST_F(TextFinderTest, ScopeTextMatchesWithShadowDOM) 264 { 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(); 271 272 int identifier = 0; 273 WebString searchText(String("fOO")); 274 WebFindOptions findOptions; // Default. 275 276 textFinder().resetMatchCount(); 277 textFinder().scopeStringMatches(identifier, searchText, findOptions, true); 278 while (textFinder().scopingInProgress()) 279 FrameTestHelpers::runPendingTasks(); 280 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]); 290 } 291 292 TEST_F(TextFinderTest, ScopeRepeatPatternTextMatches) 293 { 294 document().body()->setInnerHTML("ab ab ab ab ab", ASSERT_NO_EXCEPTION); 295 Node* textNode = document().body()->firstChild(); 296 297 int identifier = 0; 298 WebString searchText(String("ab ab")); 299 WebFindOptions findOptions; // Default. 300 301 textFinder().resetMatchCount(); 302 textFinder().scopeStringMatches(identifier, searchText, findOptions, true); 303 while (textFinder().scopingInProgress()) 304 FrameTestHelpers::runPendingTasks(); 305 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]); 312 } 313 314 TEST_F(TextFinderTest, OverlappingMatches) 315 { 316 document().body()->setInnerHTML("aababaa", ASSERT_NO_EXCEPTION); 317 Node* textNode = document().body()->firstChild(); 318 319 int identifier = 0; 320 WebString searchText(String("aba")); 321 WebFindOptions findOptions; // Default. 322 323 textFinder().resetMatchCount(); 324 textFinder().scopeStringMatches(identifier, searchText, findOptions, true); 325 while (textFinder().scopingInProgress()) 326 FrameTestHelpers::runPendingTasks(); 327 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]); 334 } 335 336 TEST_F(TextFinderTest, SequentialMatches) 337 { 338 document().body()->setInnerHTML("ababab", ASSERT_NO_EXCEPTION); 339 Node* textNode = document().body()->firstChild(); 340 341 int identifier = 0; 342 WebString searchText(String("ab")); 343 WebFindOptions findOptions; // Default. 344 345 textFinder().resetMatchCount(); 346 textFinder().scopeStringMatches(identifier, searchText, findOptions, true); 347 while (textFinder().scopingInProgress()) 348 FrameTestHelpers::runPendingTasks(); 349 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]); 357 } 358 359 class TextFinderFakeTimerTest : public TextFinderTest { 360 protected: 361 virtual void SetUp() OVERRIDE; 362 virtual void TearDown() OVERRIDE; 363 364 // A simple platform that mocks out the clock. 365 class TimeProxyPlatform : public Platform { 366 public: 367 TimeProxyPlatform() 368 : m_timeCounter(0.) 369 , m_fallbackPlatform(0) 370 { } 371 372 void install() 373 { 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); 380 } 381 382 void remove() 383 { 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; 389 } 390 391 private: 392 Platform& ensureFallback() 393 { 394 ASSERT(m_fallbackPlatform); 395 return *m_fallbackPlatform; 396 } 397 398 // From blink::Platform: 399 virtual double currentTime() OVERRIDE 400 { 401 return ++m_timeCounter; 402 } 403 404 // These blink::Platform methods must be overriden to make a usable object. 405 virtual void cryptographicallyRandomValues(unsigned char* buffer, size_t length) OVERRIDE 406 { 407 ensureFallback().cryptographicallyRandomValues(buffer, length); 408 } 409 410 virtual const unsigned char* getTraceCategoryEnabledFlag(const char* categoryName) OVERRIDE 411 { 412 return ensureFallback().getTraceCategoryEnabledFlag(categoryName); 413 } 414 415 // These two methods allow timers to work correctly. 416 virtual double monotonicallyIncreasingTime() OVERRIDE 417 { 418 return ensureFallback().monotonicallyIncreasingTime(); 419 } 420 421 virtual void setSharedTimerFireInterval(double interval) OVERRIDE 422 { 423 ensureFallback().setSharedTimerFireInterval(interval); 424 } 425 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(); } 430 431 double m_timeCounter; 432 Platform* m_fallbackPlatform; 433 }; 434 435 TimeProxyPlatform m_proxyTimePlatform; 436 }; 437 438 void TextFinderFakeTimerTest::SetUp() 439 { 440 TextFinderTest::SetUp(); 441 m_proxyTimePlatform.install(); 442 } 443 444 void TextFinderFakeTimerTest::TearDown() 445 { 446 m_proxyTimePlatform.remove(); 447 TextFinderTest::TearDown(); 448 } 449 450 TEST_F(TextFinderFakeTimerTest, ScopeWithTimeouts) 451 { 452 // Make a long string. 453 String text(Vector<UChar>(100)); 454 text.fill('a'); 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); 461 462 document().body()->setInnerHTML(text, ASSERT_NO_EXCEPTION); 463 464 int identifier = 0; 465 WebFindOptions findOptions; // Default. 466 467 textFinder().resetMatchCount(); 468 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(); 474 475 EXPECT_EQ(4, textFinder().totalMatchCount()); 476 } 477 478 } // namespace 479