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/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/web/WebDocument.h" 16 #include "web/FindInPageCoordinates.h" 17 #include "web/WebLocalFrameImpl.h" 18 #include "web/tests/FrameTestHelpers.h" 19 #include "wtf/OwnPtr.h" 20 #include <gtest/gtest.h> 21 22 using namespace blink; 23 using namespace WebCore; 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 } // namespace 293