2 * Copyright (c) 2013, Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "core/editing/TextIterator.h"
34 #include "bindings/v8/ExceptionStatePlaceholder.h"
35 #include "core/dom/Document.h"
36 #include "core/dom/Element.h"
37 #include "core/dom/Node.h"
38 #include "core/dom/Range.h"
39 #include "core/dom/shadow/ShadowRoot.h"
40 #include "core/frame/FrameView.h"
41 #include "core/html/HTMLDocument.h"
42 #include "core/html/HTMLElement.h"
43 #include "core/testing/DummyPageHolder.h"
44 #include "platform/geometry/IntSize.h"
45 #include "wtf/Compiler.h"
46 #include "wtf/OwnPtr.h"
47 #include "wtf/PassRefPtr.h"
48 #include "wtf/RefPtr.h"
49 #include "wtf/StdLibExtras.h"
50 #include "wtf/Vector.h"
51 #include "wtf/testing/WTFTestHelpers.h"
52 #include <gtest/gtest.h>
54 using namespace WebCore;
58 class TextIteratorTest : public ::testing::Test {
60 virtual void SetUp() OVERRIDE;
62 HTMLDocument& document() const;
64 Vector<String> iterate(TextIteratorBehavior = TextIteratorDefaultBehavior);
65 Vector<String> iteratePartial(const Position& start, const Position& end, TextIteratorBehavior = TextIteratorDefaultBehavior);
67 void setBodyInnerHTML(const char*);
68 PassRefPtrWillBeRawPtr<Range> getBodyRange() const;
71 Vector<String> iterateWithIterator(TextIterator&);
73 OwnPtr<DummyPageHolder> m_dummyPageHolder;
75 HTMLDocument* m_document;
78 void TextIteratorTest::SetUp()
80 m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
81 m_document = toHTMLDocument(&m_dummyPageHolder->document());
85 Vector<String> TextIteratorTest::iterate(TextIteratorBehavior iteratorBehavior)
87 document().view()->updateLayoutAndStyleIfNeededRecursive(); // Force renderers to be created; TextIterator needs them.
88 RefPtrWillBeRawPtr<Range> range = getBodyRange();
89 TextIterator iterator(range.get(), iteratorBehavior);
90 return iterateWithIterator(iterator);
93 Vector<String> TextIteratorTest::iteratePartial(const Position& start, const Position& end, TextIteratorBehavior iteratorBehavior)
95 document().view()->updateLayoutAndStyleIfNeededRecursive();
96 TextIterator iterator(start, end, iteratorBehavior);
97 return iterateWithIterator(iterator);
100 Vector<String> TextIteratorTest::iterateWithIterator(TextIterator& iterator)
102 Vector<String> textChunks;
103 while (!iterator.atEnd()) {
104 textChunks.append(iterator.substring(0, iterator.length()));
110 HTMLDocument& TextIteratorTest::document() const
115 void TextIteratorTest::setBodyInnerHTML(const char* bodyContent)
117 document().body()->setInnerHTML(String::fromUTF8(bodyContent), ASSERT_NO_EXCEPTION);
120 PassRefPtrWillBeRawPtr<Range> TextIteratorTest::getBodyRange() const
122 RefPtrWillBeRawPtr<Range> range(Range::create(document()));
123 range->selectNode(document().body());
124 return range.release();
127 Vector<String> createVectorString(const char* const* rawStrings, size_t size)
129 Vector<String> result;
130 result.append(rawStrings, size);
134 PassRefPtr<ShadowRoot> createShadowRootForElementWithIDAndSetInnerHTML(TreeScope& scope, const char* hostElementID, const char* shadowRootContent)
136 RefPtr<ShadowRoot> shadowRoot = scope.getElementById(AtomicString::fromUTF8(hostElementID))->createShadowRoot(ASSERT_NO_EXCEPTION);
137 shadowRoot->setInnerHTML(String::fromUTF8(shadowRootContent), ASSERT_NO_EXCEPTION);
138 return shadowRoot.release();
141 TEST_F(TextIteratorTest, BasicIteration)
143 static const char* input = "<p>Hello, \ntext</p><p>iterator.</p>";
144 static const char* expectedTextChunksRawString[] = {
151 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
153 setBodyInnerHTML(input);
154 EXPECT_EQ(expectedTextChunks, iterate());
157 TEST_F(TextIteratorTest, NotEnteringTextControls)
159 static const char* input = "<p>Hello <input type=\"text\" value=\"input\">!</p>";
160 static const char* expectedTextChunksRawString[] = {
165 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
167 setBodyInnerHTML(input);
168 EXPECT_EQ(expectedTextChunks, iterate());
171 TEST_F(TextIteratorTest, EnteringTextControlsWithOption)
173 static const char* input = "<p>Hello <input type=\"text\" value=\"input\">!</p>";
174 static const char* expectedTextChunksRawString[] = {
180 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
182 setBodyInnerHTML(input);
183 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersTextControls));
186 TEST_F(TextIteratorTest, EnteringTextControlsWithOptionComplex)
188 static const char* input = "<input type=\"text\" value=\"Beginning of range\"><div><div><input type=\"text\" value=\"Under DOM nodes\"></div></div><input type=\"text\" value=\"End of range\">";
189 static const char* expectedTextChunksRawString[] = {
190 "\n", // FIXME: Why newline here?
191 "Beginning of range",
197 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
199 setBodyInnerHTML(input);
200 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersTextControls));
203 TEST_F(TextIteratorTest, NotEnteringTextControlHostingShadowTreeEvenWithOption)
205 static const char* bodyContent = "<div>Hello, <input type=\"text\" value=\"input\" id=\"input\"> iterator.</div>";
206 static const char* shadowContent = "<span>shadow</span>";
207 // TextIterator doesn't emit "input" nor "shadow" since (1) the renderer for <input> is not created; and
208 // (2) we don't (yet) recurse into shadow trees.
209 static const char* expectedTextChunksRawString[] = {
211 "", // FIXME: Why is an empty string emitted here?
214 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
216 setBodyInnerHTML(bodyContent);
217 createShadowRootForElementWithIDAndSetInnerHTML(document(), "input", shadowContent);
219 EXPECT_EQ(expectedTextChunks, iterate());
222 TEST_F(TextIteratorTest, NotEnteringShadowTree)
224 static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
225 static const char* shadowContent = "<span>shadow</span>";
226 static const char* expectedTextChunksRawString[] = {
227 "Hello, ", // TextIterator doesn't emit "text" since its renderer is not created. The shadow tree is ignored.
230 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
232 setBodyInnerHTML(bodyContent);
233 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
235 EXPECT_EQ(expectedTextChunks, iterate());
238 TEST_F(TextIteratorTest, NotEnteringShadowTreeWithMultipleShadowTrees)
240 static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
241 static const char* shadowContent1 = "<span>first shadow</span>";
242 static const char* shadowContent2 = "<span>second shadow</span>";
243 static const char* expectedTextChunksRawString[] = {
247 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
249 setBodyInnerHTML(bodyContent);
250 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1);
251 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2);
253 EXPECT_EQ(expectedTextChunks, iterate());
256 TEST_F(TextIteratorTest, NotEnteringShadowTreeWithNestedShadowTrees)
258 static const char* bodyContent = "<div>Hello, <span id=\"host-in-document\">text</span> iterator.</div>";
259 static const char* shadowContent1 = "<span>first <span id=\"host-in-shadow\">shadow</span></span>";
260 static const char* shadowContent2 = "<span>second shadow</span>";
261 static const char* expectedTextChunksRawString[] = {
265 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
267 setBodyInnerHTML(bodyContent);
268 RefPtr<ShadowRoot> shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1);
269 createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2);
271 EXPECT_EQ(expectedTextChunks, iterate());
274 TEST_F(TextIteratorTest, NotEnteringShadowTreeWithContentInsertionPoint)
276 static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
277 static const char* shadowContent = "<span>shadow <content>content</content></span>";
278 static const char* expectedTextChunksRawString[] = {
280 "text", // In this case a renderer for "text" is created, so it shows up here.
283 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
285 setBodyInnerHTML(bodyContent);
286 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
288 EXPECT_EQ(expectedTextChunks, iterate());
291 TEST_F(TextIteratorTest, EnteringShadowTreeWithOption)
293 static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
294 static const char* shadowContent = "<span>shadow</span>";
295 static const char* expectedTextChunksRawString[] = {
297 "shadow", // TextIterator emits "shadow" since TextIteratorEntersAuthorShadowRoots is specified.
300 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
302 setBodyInnerHTML(bodyContent);
303 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
305 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
308 TEST_F(TextIteratorTest, EnteringShadowTreeWithMultipleShadowTreesWithOption)
310 static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
311 static const char* shadowContent1 = "<span>first shadow</span>";
312 static const char* shadowContent2 = "<span>second shadow</span>";
313 static const char* expectedTextChunksRawString[] = {
315 "second shadow", // The first isn't emitted because a renderer for the first is not created.
318 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
320 setBodyInnerHTML(bodyContent);
321 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1);
322 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2);
324 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
327 TEST_F(TextIteratorTest, EnteringShadowTreeWithNestedShadowTreesWithOption)
329 static const char* bodyContent = "<div>Hello, <span id=\"host-in-document\">text</span> iterator.</div>";
330 static const char* shadowContent1 = "<span>first <span id=\"host-in-shadow\">shadow</span></span>";
331 static const char* shadowContent2 = "<span>second shadow</span>";
332 static const char* expectedTextChunksRawString[] = {
338 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
340 setBodyInnerHTML(bodyContent);
341 RefPtr<ShadowRoot> shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1);
342 createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2);
344 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
347 TEST_F(TextIteratorTest, EnteringShadowTreeWithContentInsertionPointWithOption)
349 static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
350 static const char* shadowContent = "<span><content>content</content> shadow</span>";
351 // In this case a renderer for "text" is created, and emitted AFTER any nodes in the shadow tree.
352 // This order does not match the order of the rendered texts, but at this moment it's the expected behavior.
353 // FIXME: Fix this. We probably need pure-renderer-based implementation of TextIterator to achieve this.
354 static const char* expectedTextChunksRawString[] = {
360 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
362 setBodyInnerHTML(bodyContent);
363 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
365 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
368 TEST_F(TextIteratorTest, StartingAtNodeInShadowRoot)
370 static const char* bodyContent = "<div id=\"outer\">Hello, <span id=\"host\">text</span> iterator.</div>";
371 static const char* shadowContent = "<span><content>content</content> shadow</span>";
372 static const char* expectedTextChunksRawString[] = {
377 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
379 setBodyInnerHTML(bodyContent);
380 RefPtr<ShadowRoot> shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
381 Node* outerDiv = document().getElementById("outer");
382 Node* spanInShadow = shadowRoot->firstChild();
383 Position start(spanInShadow, Position::PositionIsBeforeChildren);
384 Position end(outerDiv, Position::PositionIsAfterChildren);
386 EXPECT_EQ(expectedTextChunks, iteratePartial(start, end, TextIteratorEntersAuthorShadowRoots));
389 TEST_F(TextIteratorTest, FinishingAtNodeInShadowRoot)
391 static const char* bodyContent = "<div id=\"outer\">Hello, <span id=\"host\">text</span> iterator.</div>";
392 static const char* shadowContent = "<span><content>content</content> shadow</span>";
393 static const char* expectedTextChunksRawString[] = {
397 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
399 setBodyInnerHTML(bodyContent);
400 RefPtr<ShadowRoot> shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
401 Node* outerDiv = document().getElementById("outer");
402 Node* spanInShadow = shadowRoot->firstChild();
403 Position start(outerDiv, Position::PositionIsBeforeChildren);
404 Position end(spanInShadow, Position::PositionIsAfterChildren);
406 EXPECT_EQ(expectedTextChunks, iteratePartial(start, end, TextIteratorEntersAuthorShadowRoots));
409 TEST_F(TextIteratorTest, FullyClipsContents)
411 static const char* bodyContent =
412 "<div style=\"overflow: hidden; width: 200px; height: 0;\">"
415 Vector<String> expectedTextChunks; // Empty.
417 setBodyInnerHTML(bodyContent);
418 EXPECT_EQ(expectedTextChunks, iterate());
421 TEST_F(TextIteratorTest, IgnoresContainerClip)
423 static const char* bodyContent =
424 "<div style=\"overflow: hidden; width: 200px; height: 0;\">"
425 "<div>I'm not visible</div>"
426 "<div style=\"position: absolute; width: 200px; height: 200px; top: 0; right: 0;\">"
430 static const char* expectedTextChunksRawString[] = {
433 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
435 setBodyInnerHTML(bodyContent);
436 EXPECT_EQ(expectedTextChunks, iterate());
439 TEST_F(TextIteratorTest, FullyClippedContentsDistributed)
441 static const char* bodyContent =
443 "<div>Am I visible?</div>"
445 static const char* shadowContent =
446 "<div style=\"overflow: hidden; width: 200px; height: 0;\">"
447 "<content></content>"
449 static const char* expectedTextChunksRawString[] = {
451 // FIXME: The text below is actually invisible but TextIterator currently thinks it's visible.
454 Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
456 setBodyInnerHTML(bodyContent);
457 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
459 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
462 TEST_F(TextIteratorTest, IgnoresContainersClipDistributed)
464 static const char* bodyContent =
465 "<div id=\"host\" style=\"overflow: hidden; width: 200px; height: 0;\">"
466 "<div>Nobody can find me!</div>"
468 static const char* shadowContent =
469 "<div style=\"position: absolute; width: 200px; height: 200px; top: 0; right: 0;\">"
470 "<content></content>"
472 // FIXME: The text below is actually visible but TextIterator currently thinks it's invisible.
473 // static const char* expectedTextChunksRawString[] = {
475 // "Nobody can find me!"
477 // Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
478 Vector<String> expectedTextChunks; // Empty.
480 setBodyInnerHTML(bodyContent);
481 createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
483 EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
486 TEST_F(TextIteratorTest, FindPlainTextInvalidTarget)
488 static const char* bodyContent = "<div>foo bar test</div>";
489 setBodyInnerHTML(bodyContent);
490 RefPtrWillBeRawPtr<Range> range = getBodyRange();
492 RefPtrWillBeRawPtr<Range> expectedRange = range->cloneRange();
493 expectedRange->collapse(false);
495 // A lone lead surrogate (0xDA0A) example taken from fuzz-58.
496 static const UChar invalid1[] = {
497 0x1461u, 0x2130u, 0x129bu, 0xd711u, 0xd6feu, 0xccadu, 0x7064u,
498 0xd6a0u, 0x4e3bu, 0x03abu, 0x17dcu, 0xb8b7u, 0xbf55u, 0xfca0u,
499 0x07fau, 0x0427u, 0xda0au, 0
502 // A lone trailing surrogate (U+DC01).
503 static const UChar invalid2[] = {
504 0x1461u, 0x2130u, 0x129bu, 0xdc01u, 0xd6feu, 0xccadu, 0
506 // A trailing surrogate followed by a lead surrogate (U+DC03 U+D901).
507 static const UChar invalid3[] = {
508 0xd800u, 0xdc00u, 0x0061u, 0xdc03u, 0xd901u, 0xccadu, 0
511 static const UChar* invalidUStrings[] = { invalid1, invalid2, invalid3 };
513 for (size_t i = 0; i < WTF_ARRAY_LENGTH(invalidUStrings); ++i) {
514 String invalidTarget(invalidUStrings[i]);
515 RefPtrWillBeRawPtr<Range> actualRange = findPlainText(range.get(), invalidTarget, 0);
516 EXPECT_TRUE(areRangesEqual(expectedRange.get(), actualRange.get()));