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
SetUp()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
document() const51 Document& TextFinderTest::document() const
52 {
53 return *m_document;
54 }
55
textFinder() const56 TextFinder& TextFinderTest::textFinder() const
57 {
58 return *m_textFinder;
59 }
60
findInPageRect(Node * startContainer,int startOffset,Node * endContainer,int endOffset)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
TEST_F(TextFinderTest,FindTextSimple)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
TEST_F(TextFinderTest,FindTextNotFound)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
TEST_F(TextFinderTest,FindTextInShadowDOM)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
TEST_F(TextFinderTest,ScopeTextMatchesSimple)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
TEST_F(TextFinderTest,ScopeTextMatchesWithShadowDOM)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