// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "config.h" #include "web/TextFinder.h" #include "bindings/v8/ExceptionStatePlaceholder.h" #include "core/dom/Document.h" #include "core/dom/NodeList.h" #include "core/dom/Range.h" #include "core/dom/shadow/ShadowRoot.h" #include "core/html/HTMLElement.h" #include "public/web/WebDocument.h" #include "web/FindInPageCoordinates.h" #include "web/WebLocalFrameImpl.h" #include "web/tests/FrameTestHelpers.h" #include "wtf/OwnPtr.h" #include using namespace blink; using namespace WebCore; namespace { class TextFinderTest : public ::testing::Test { protected: virtual void SetUp() OVERRIDE; Document& document() const; TextFinder& textFinder() const; static WebFloatRect findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset); private: FrameTestHelpers::WebViewHelper m_webViewHelper; RefPtrWillBePersistent m_document; TextFinder* m_textFinder; }; void TextFinderTest::SetUp() { m_webViewHelper.initialize(); WebLocalFrameImpl& frameImpl = *m_webViewHelper.webViewImpl()->mainFrameImpl(); frameImpl.viewImpl()->resize(WebSize(640, 480)); m_document = PassRefPtrWillBeRawPtr(frameImpl.document()); m_textFinder = &frameImpl.ensureTextFinder(); } Document& TextFinderTest::document() const { return *m_document; } TextFinder& TextFinderTest::textFinder() const { return *m_textFinder; } WebFloatRect TextFinderTest::findInPageRect(Node* startContainer, int startOffset, Node* endContainer, int endOffset) { RefPtrWillBeRawPtr range = Range::create(startContainer->document(), startContainer, startOffset, endContainer, endOffset); return WebFloatRect(findInPageRectFromRange(range.get())); } TEST_F(TextFinderTest, FindTextSimple) { document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION); Node* textNode = document().body()->firstChild(); int identifier = 0; WebString searchText(String("FindMe")); WebFindOptions findOptions; // Default. bool wrapWithinFrame = true; WebRect* selectionRect = 0; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); Range* activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(4, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(10, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(14, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(20, activeMatch->endOffset()); // Should wrap to the first match. ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(4, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(10, activeMatch->endOffset()); // Search in the reverse order. identifier = 1; findOptions = WebFindOptions(); findOptions.forward = false; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(14, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(20, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(4, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(10, activeMatch->endOffset()); // Wrap to the first match (last occurence in the document). ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textNode, activeMatch->startContainer()); EXPECT_EQ(14, activeMatch->startOffset()); EXPECT_EQ(textNode, activeMatch->endContainer()); EXPECT_EQ(20, activeMatch->endOffset()); } TEST_F(TextFinderTest, FindTextNotFound) { document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION); int identifier = 0; WebString searchText(String("Boo")); WebFindOptions findOptions; // Default. bool wrapWithinFrame = true; WebRect* selectionRect = 0; EXPECT_FALSE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); EXPECT_FALSE(textFinder().activeMatch()); } TEST_F(TextFinderTest, FindTextInShadowDOM) { document().body()->setInnerHTML("FOOfoo", ASSERT_NO_EXCEPTION); RefPtrWillBeRawPtr shadowRoot = document().body()->createShadowRoot(ASSERT_NO_EXCEPTION); shadowRoot->setInnerHTML("Foo", ASSERT_NO_EXCEPTION); Node* textInBElement = document().body()->firstChild()->firstChild(); Node* textInIElement = document().body()->lastChild()->firstChild(); Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild(); int identifier = 0; WebString searchText(String("foo")); WebFindOptions findOptions; // Default. bool wrapWithinFrame = true; WebRect* selectionRect = 0; // TextIterator currently returns the matches in the document order, instead of the visual order. It visits // the shadow roots first, so in this case the matches will be returned in the order of -> -> . ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); Range* activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInUElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInUElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInBElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInBElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInIElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInIElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); // Should wrap to the first match. ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInUElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInUElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); // Fresh search in the reverse order. identifier = 1; findOptions = WebFindOptions(); findOptions.forward = false; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInIElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInIElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); findOptions.findNext = true; ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInBElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInBElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInUElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInUElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); // And wrap. ASSERT_TRUE(textFinder().find(identifier, searchText, findOptions, wrapWithinFrame, selectionRect)); activeMatch = textFinder().activeMatch(); ASSERT_TRUE(activeMatch); EXPECT_EQ(textInIElement, activeMatch->startContainer()); EXPECT_EQ(0, activeMatch->startOffset()); EXPECT_EQ(textInIElement, activeMatch->endContainer()); EXPECT_EQ(3, activeMatch->endOffset()); } TEST_F(TextFinderTest, ScopeTextMatchesSimple) { document().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ", ASSERT_NO_EXCEPTION); Node* textNode = document().body()->firstChild(); int identifier = 0; WebString searchText(String("FindMe")); WebFindOptions findOptions; // Default. textFinder().resetMatchCount(); textFinder().scopeStringMatches(identifier, searchText, findOptions, true); while (textFinder().scopingInProgress()) FrameTestHelpers::runPendingTasks(); EXPECT_EQ(2, textFinder().totalMatchCount()); WebVector matchRects; textFinder().findMatchRects(matchRects); ASSERT_EQ(2u, matchRects.size()); EXPECT_EQ(findInPageRect(textNode, 4, textNode, 10), matchRects[0]); EXPECT_EQ(findInPageRect(textNode, 14, textNode, 20), matchRects[1]); } TEST_F(TextFinderTest, ScopeTextMatchesWithShadowDOM) { document().body()->setInnerHTML("FOOfoo", ASSERT_NO_EXCEPTION); RefPtrWillBeRawPtr shadowRoot = document().body()->createShadowRoot(ASSERT_NO_EXCEPTION); shadowRoot->setInnerHTML("Foo", ASSERT_NO_EXCEPTION); Node* textInBElement = document().body()->firstChild()->firstChild(); Node* textInIElement = document().body()->lastChild()->firstChild(); Node* textInUElement = shadowRoot->childNodes()->item(1)->firstChild(); int identifier = 0; WebString searchText(String("fOO")); WebFindOptions findOptions; // Default. textFinder().resetMatchCount(); textFinder().scopeStringMatches(identifier, searchText, findOptions, true); while (textFinder().scopingInProgress()) FrameTestHelpers::runPendingTasks(); // TextIterator currently returns the matches in the document order, instead of the visual order. It visits // the shadow roots first, so in this case the matches will be returned in the order of -> -> . EXPECT_EQ(3, textFinder().totalMatchCount()); WebVector matchRects; textFinder().findMatchRects(matchRects); ASSERT_EQ(3u, matchRects.size()); EXPECT_EQ(findInPageRect(textInUElement, 0, textInUElement, 3), matchRects[0]); EXPECT_EQ(findInPageRect(textInBElement, 0, textInBElement, 3), matchRects[1]); EXPECT_EQ(findInPageRect(textInIElement, 0, textInIElement, 3), matchRects[2]); } } // namespace