Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (c) 2013, Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      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
     13  * distribution.
     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.
     17  *
     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.
     29  */
     30 
     31 #include "config.h"
     32 #include "core/editing/TextIterator.h"
     33 
     34 #include "bindings/core/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>
     53 
     54 using namespace blink;
     55 
     56 namespace {
     57 
     58 class TextIteratorTest : public ::testing::Test {
     59 protected:
     60     virtual void SetUp() OVERRIDE;
     61 
     62     HTMLDocument& document() const;
     63 
     64     Vector<String> iterate(TextIteratorBehavior = TextIteratorDefaultBehavior);
     65     Vector<String> iteratePartial(const Position& start, const Position& end, TextIteratorBehavior = TextIteratorDefaultBehavior);
     66 
     67     void setBodyInnerHTML(const char*);
     68     PassRefPtrWillBeRawPtr<Range> getBodyRange() const;
     69 
     70 private:
     71     Vector<String> iterateWithIterator(TextIterator&);
     72 
     73     OwnPtr<DummyPageHolder> m_dummyPageHolder;
     74 
     75     HTMLDocument* m_document;
     76 };
     77 
     78 void TextIteratorTest::SetUp()
     79 {
     80     m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
     81     m_document = toHTMLDocument(&m_dummyPageHolder->document());
     82     ASSERT(m_document);
     83 }
     84 
     85 Vector<String> TextIteratorTest::iterate(TextIteratorBehavior iteratorBehavior)
     86 {
     87     RefPtrWillBeRawPtr<Range> range = getBodyRange();
     88     TextIterator iterator(range.get(), iteratorBehavior);
     89     return iterateWithIterator(iterator);
     90 }
     91 
     92 Vector<String> TextIteratorTest::iteratePartial(const Position& start, const Position& end, TextIteratorBehavior iteratorBehavior)
     93 {
     94     TextIterator iterator(start, end, iteratorBehavior);
     95     return iterateWithIterator(iterator);
     96 }
     97 
     98 Vector<String> TextIteratorTest::iterateWithIterator(TextIterator& iterator)
     99 {
    100     Vector<String> textChunks;
    101     while (!iterator.atEnd()) {
    102         textChunks.append(iterator.substring(0, iterator.length()));
    103         iterator.advance();
    104     }
    105     return textChunks;
    106 }
    107 
    108 HTMLDocument& TextIteratorTest::document() const
    109 {
    110     return *m_document;
    111 }
    112 
    113 void TextIteratorTest::setBodyInnerHTML(const char* bodyContent)
    114 {
    115     document().body()->setInnerHTML(String::fromUTF8(bodyContent), ASSERT_NO_EXCEPTION);
    116 }
    117 
    118 PassRefPtrWillBeRawPtr<Range> TextIteratorTest::getBodyRange() const
    119 {
    120     RefPtrWillBeRawPtr<Range> range(Range::create(document()));
    121     range->selectNode(document().body());
    122     return range.release();
    123 }
    124 
    125 Vector<String> createVectorString(const char* const* rawStrings, size_t size)
    126 {
    127     Vector<String> result;
    128     result.append(rawStrings, size);
    129     return result;
    130 }
    131 
    132 PassRefPtrWillBeRawPtr<ShadowRoot> createShadowRootForElementWithIDAndSetInnerHTML(TreeScope& scope, const char* hostElementID, const char* shadowRootContent)
    133 {
    134     RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = scope.getElementById(AtomicString::fromUTF8(hostElementID))->createShadowRoot(ASSERT_NO_EXCEPTION);
    135     shadowRoot->setInnerHTML(String::fromUTF8(shadowRootContent), ASSERT_NO_EXCEPTION);
    136     return shadowRoot.release();
    137 }
    138 
    139 TEST_F(TextIteratorTest, BasicIteration)
    140 {
    141     static const char* input = "<p>Hello, \ntext</p><p>iterator.</p>";
    142     static const char* expectedTextChunksRawString[] = {
    143         "Hello, ",
    144         "text",
    145         "\n",
    146         "\n",
    147         "iterator."
    148     };
    149     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    150 
    151     setBodyInnerHTML(input);
    152     EXPECT_EQ(expectedTextChunks, iterate());
    153 }
    154 
    155 TEST_F(TextIteratorTest, NotEnteringTextControls)
    156 {
    157     static const char* input = "<p>Hello <input type=\"text\" value=\"input\">!</p>";
    158     static const char* expectedTextChunksRawString[] = {
    159         "Hello ",
    160         "",
    161         "!",
    162     };
    163     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    164 
    165     setBodyInnerHTML(input);
    166     EXPECT_EQ(expectedTextChunks, iterate());
    167 }
    168 
    169 TEST_F(TextIteratorTest, EnteringTextControlsWithOption)
    170 {
    171     static const char* input = "<p>Hello <input type=\"text\" value=\"input\">!</p>";
    172     static const char* expectedTextChunksRawString[] = {
    173         "Hello ",
    174         "\n",
    175         "input",
    176         "!",
    177     };
    178     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    179 
    180     setBodyInnerHTML(input);
    181     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersTextControls));
    182 }
    183 
    184 TEST_F(TextIteratorTest, EnteringTextControlsWithOptionComplex)
    185 {
    186     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\">";
    187     static const char* expectedTextChunksRawString[] = {
    188         "\n", // FIXME: Why newline here?
    189         "Beginning of range",
    190         "\n",
    191         "Under DOM nodes",
    192         "\n",
    193         "End of range"
    194     };
    195     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    196 
    197     setBodyInnerHTML(input);
    198     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersTextControls));
    199 }
    200 
    201 TEST_F(TextIteratorTest, NotEnteringTextControlHostingShadowTreeEvenWithOption)
    202 {
    203     static const char* bodyContent = "<div>Hello, <input type=\"text\" value=\"input\" id=\"input\"> iterator.</div>";
    204     static const char* shadowContent = "<span>shadow</span>";
    205     // TextIterator doesn't emit "input" nor "shadow" since (1) the renderer for <input> is not created; and
    206     // (2) we don't (yet) recurse into shadow trees.
    207     static const char* expectedTextChunksRawString[] = {
    208         "Hello, ",
    209         "", // FIXME: Why is an empty string emitted here?
    210         " iterator."
    211     };
    212     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    213 
    214     setBodyInnerHTML(bodyContent);
    215     createShadowRootForElementWithIDAndSetInnerHTML(document(), "input", shadowContent);
    216 
    217     EXPECT_EQ(expectedTextChunks, iterate());
    218 }
    219 
    220 TEST_F(TextIteratorTest, NotEnteringShadowTree)
    221 {
    222     static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
    223     static const char* shadowContent = "<span>shadow</span>";
    224     static const char* expectedTextChunksRawString[] = {
    225         "Hello, ", // TextIterator doesn't emit "text" since its renderer is not created. The shadow tree is ignored.
    226         " iterator."
    227     };
    228     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    229 
    230     setBodyInnerHTML(bodyContent);
    231     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    232 
    233     EXPECT_EQ(expectedTextChunks, iterate());
    234 }
    235 
    236 TEST_F(TextIteratorTest, NotEnteringShadowTreeWithMultipleShadowTrees)
    237 {
    238     static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
    239     static const char* shadowContent1 = "<span>first shadow</span>";
    240     static const char* shadowContent2 = "<span>second shadow</span>";
    241     static const char* expectedTextChunksRawString[] = {
    242         "Hello, ",
    243         " iterator."
    244     };
    245     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    246 
    247     setBodyInnerHTML(bodyContent);
    248     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1);
    249     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2);
    250 
    251     EXPECT_EQ(expectedTextChunks, iterate());
    252 }
    253 
    254 TEST_F(TextIteratorTest, NotEnteringShadowTreeWithNestedShadowTrees)
    255 {
    256     static const char* bodyContent = "<div>Hello, <span id=\"host-in-document\">text</span> iterator.</div>";
    257     static const char* shadowContent1 = "<span>first <span id=\"host-in-shadow\">shadow</span></span>";
    258     static const char* shadowContent2 = "<span>second shadow</span>";
    259     static const char* expectedTextChunksRawString[] = {
    260         "Hello, ",
    261         " iterator."
    262     };
    263     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    264 
    265     setBodyInnerHTML(bodyContent);
    266     RefPtrWillBeRawPtr<ShadowRoot> shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1);
    267     createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2);
    268 
    269     EXPECT_EQ(expectedTextChunks, iterate());
    270 }
    271 
    272 TEST_F(TextIteratorTest, NotEnteringShadowTreeWithContentInsertionPoint)
    273 {
    274     static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
    275     static const char* shadowContent = "<span>shadow <content>content</content></span>";
    276     static const char* expectedTextChunksRawString[] = {
    277         "Hello, ",
    278         "text", // In this case a renderer for "text" is created, so it shows up here.
    279         " iterator."
    280     };
    281     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    282 
    283     setBodyInnerHTML(bodyContent);
    284     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    285 
    286     EXPECT_EQ(expectedTextChunks, iterate());
    287 }
    288 
    289 TEST_F(TextIteratorTest, EnteringShadowTreeWithOption)
    290 {
    291     static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
    292     static const char* shadowContent = "<span>shadow</span>";
    293     static const char* expectedTextChunksRawString[] = {
    294         "Hello, ",
    295         "shadow", // TextIterator emits "shadow" since TextIteratorEntersAuthorShadowRoots is specified.
    296         " iterator."
    297     };
    298     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    299 
    300     setBodyInnerHTML(bodyContent);
    301     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    302 
    303     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
    304 }
    305 
    306 TEST_F(TextIteratorTest, EnteringShadowTreeWithMultipleShadowTreesWithOption)
    307 {
    308     static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
    309     static const char* shadowContent1 = "<span>first shadow</span>";
    310     static const char* shadowContent2 = "<span>second shadow</span>";
    311     static const char* expectedTextChunksRawString[] = {
    312         "Hello, ",
    313         "second shadow", // The first isn't emitted because a renderer for the first is not created.
    314         " iterator."
    315     };
    316     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    317 
    318     setBodyInnerHTML(bodyContent);
    319     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1);
    320     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2);
    321 
    322     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
    323 }
    324 
    325 TEST_F(TextIteratorTest, EnteringShadowTreeWithNestedShadowTreesWithOption)
    326 {
    327     static const char* bodyContent = "<div>Hello, <span id=\"host-in-document\">text</span> iterator.</div>";
    328     static const char* shadowContent1 = "<span>first <span id=\"host-in-shadow\">shadow</span></span>";
    329     static const char* shadowContent2 = "<span>second shadow</span>";
    330     static const char* expectedTextChunksRawString[] = {
    331         "Hello, ",
    332         "first ",
    333         "second shadow",
    334         " iterator."
    335     };
    336     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    337 
    338     setBodyInnerHTML(bodyContent);
    339     RefPtrWillBeRawPtr<ShadowRoot> shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1);
    340     createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2);
    341 
    342     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
    343 }
    344 
    345 TEST_F(TextIteratorTest, EnteringShadowTreeWithContentInsertionPointWithOption)
    346 {
    347     static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>";
    348     static const char* shadowContent = "<span><content>content</content> shadow</span>";
    349     // In this case a renderer for "text" is created, and emitted AFTER any nodes in the shadow tree.
    350     // This order does not match the order of the rendered texts, but at this moment it's the expected behavior.
    351     // FIXME: Fix this. We probably need pure-renderer-based implementation of TextIterator to achieve this.
    352     static const char* expectedTextChunksRawString[] = {
    353         "Hello, ",
    354         " shadow",
    355         "text",
    356         " iterator."
    357     };
    358     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    359 
    360     setBodyInnerHTML(bodyContent);
    361     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    362 
    363     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
    364 }
    365 
    366 TEST_F(TextIteratorTest, StartingAtNodeInShadowRoot)
    367 {
    368     static const char* bodyContent = "<div id=\"outer\">Hello, <span id=\"host\">text</span> iterator.</div>";
    369     static const char* shadowContent = "<span><content>content</content> shadow</span>";
    370     static const char* expectedTextChunksRawString[] = {
    371         " shadow",
    372         "text",
    373         " iterator."
    374     };
    375     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    376 
    377     setBodyInnerHTML(bodyContent);
    378     RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    379     Node* outerDiv = document().getElementById("outer");
    380     Node* spanInShadow = shadowRoot->firstChild();
    381     Position start(spanInShadow, Position::PositionIsBeforeChildren);
    382     Position end(outerDiv, Position::PositionIsAfterChildren);
    383 
    384     EXPECT_EQ(expectedTextChunks, iteratePartial(start, end, TextIteratorEntersAuthorShadowRoots));
    385 }
    386 
    387 TEST_F(TextIteratorTest, FinishingAtNodeInShadowRoot)
    388 {
    389     static const char* bodyContent = "<div id=\"outer\">Hello, <span id=\"host\">text</span> iterator.</div>";
    390     static const char* shadowContent = "<span><content>content</content> shadow</span>";
    391     static const char* expectedTextChunksRawString[] = {
    392         "Hello, ",
    393         " shadow"
    394     };
    395     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    396 
    397     setBodyInnerHTML(bodyContent);
    398     RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    399     Node* outerDiv = document().getElementById("outer");
    400     Node* spanInShadow = shadowRoot->firstChild();
    401     Position start(outerDiv, Position::PositionIsBeforeChildren);
    402     Position end(spanInShadow, Position::PositionIsAfterChildren);
    403 
    404     EXPECT_EQ(expectedTextChunks, iteratePartial(start, end, TextIteratorEntersAuthorShadowRoots));
    405 }
    406 
    407 TEST_F(TextIteratorTest, FullyClipsContents)
    408 {
    409     static const char* bodyContent =
    410         "<div style=\"overflow: hidden; width: 200px; height: 0;\">"
    411         "I'm invisible"
    412         "</div>";
    413     Vector<String> expectedTextChunks; // Empty.
    414 
    415     setBodyInnerHTML(bodyContent);
    416     EXPECT_EQ(expectedTextChunks, iterate());
    417 }
    418 
    419 TEST_F(TextIteratorTest, IgnoresContainerClip)
    420 {
    421     static const char* bodyContent =
    422         "<div style=\"overflow: hidden; width: 200px; height: 0;\">"
    423         "<div>I'm not visible</div>"
    424         "<div style=\"position: absolute; width: 200px; height: 200px; top: 0; right: 0;\">"
    425         "but I am!"
    426         "</div>"
    427         "</div>";
    428     static const char* expectedTextChunksRawString[] = {
    429         "but I am!"
    430     };
    431     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    432 
    433     setBodyInnerHTML(bodyContent);
    434     EXPECT_EQ(expectedTextChunks, iterate());
    435 }
    436 
    437 TEST_F(TextIteratorTest, FullyClippedContentsDistributed)
    438 {
    439     static const char* bodyContent =
    440         "<div id=\"host\">"
    441         "<div>Am I visible?</div>"
    442         "</div>";
    443     static const char* shadowContent =
    444         "<div style=\"overflow: hidden; width: 200px; height: 0;\">"
    445         "<content></content>"
    446         "</div>";
    447     static const char* expectedTextChunksRawString[] = {
    448         "\n",
    449         // FIXME: The text below is actually invisible but TextIterator currently thinks it's visible.
    450         "Am I visible?"
    451     };
    452     Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    453 
    454     setBodyInnerHTML(bodyContent);
    455     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    456 
    457     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
    458 }
    459 
    460 TEST_F(TextIteratorTest, IgnoresContainersClipDistributed)
    461 {
    462     static const char* bodyContent =
    463         "<div id=\"host\" style=\"overflow: hidden; width: 200px; height: 0;\">"
    464         "<div>Nobody can find me!</div>"
    465         "</div>";
    466     static const char* shadowContent =
    467         "<div style=\"position: absolute; width: 200px; height: 200px; top: 0; right: 0;\">"
    468         "<content></content>"
    469         "</div>";
    470     // FIXME: The text below is actually visible but TextIterator currently thinks it's invisible.
    471     // static const char* expectedTextChunksRawString[] = {
    472     //     "\n",
    473     //     "Nobody can find me!"
    474     // };
    475     // Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString));
    476     Vector<String> expectedTextChunks; // Empty.
    477 
    478     setBodyInnerHTML(bodyContent);
    479     createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
    480 
    481     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots));
    482 }
    483 
    484 TEST_F(TextIteratorTest, FindPlainTextInvalidTarget)
    485 {
    486     static const char* bodyContent = "<div>foo bar test</div>";
    487     setBodyInnerHTML(bodyContent);
    488     RefPtrWillBeRawPtr<Range> range = getBodyRange();
    489 
    490     RefPtrWillBeRawPtr<Range> expectedRange = range->cloneRange();
    491     expectedRange->collapse(false);
    492 
    493     // A lone lead surrogate (0xDA0A) example taken from fuzz-58.
    494     static const UChar invalid1[] = {
    495         0x1461u, 0x2130u, 0x129bu, 0xd711u, 0xd6feu, 0xccadu, 0x7064u,
    496         0xd6a0u, 0x4e3bu, 0x03abu, 0x17dcu, 0xb8b7u, 0xbf55u, 0xfca0u,
    497         0x07fau, 0x0427u, 0xda0au, 0
    498     };
    499 
    500     // A lone trailing surrogate (U+DC01).
    501     static const UChar invalid2[] = {
    502         0x1461u, 0x2130u, 0x129bu, 0xdc01u, 0xd6feu, 0xccadu, 0
    503     };
    504     // A trailing surrogate followed by a lead surrogate (U+DC03 U+D901).
    505     static const UChar invalid3[] = {
    506         0xd800u, 0xdc00u, 0x0061u, 0xdc03u, 0xd901u, 0xccadu, 0
    507     };
    508 
    509     static const UChar* invalidUStrings[] = { invalid1, invalid2, invalid3 };
    510 
    511     for (size_t i = 0; i < WTF_ARRAY_LENGTH(invalidUStrings); ++i) {
    512         String invalidTarget(invalidUStrings[i]);
    513         RefPtrWillBeRawPtr<Range> actualRange = findPlainText(range.get(), invalidTarget, 0);
    514         EXPECT_TRUE(areRangesEqual(expectedRange.get(), actualRange.get()));
    515     }
    516 }
    517 
    518 TEST_F(TextIteratorTest, EmitsReplacementCharForInput)
    519 {
    520     static const char* bodyContent =
    521         "<div contenteditable=\"true\">"
    522         "Before"
    523         "<img src=\"foo.png\">"
    524         "After"
    525         "</div>";
    526     // "Before".
    527     static const UChar expectedRawString1[] = { 0x42, 0x65, 0x66, 0x6F, 0x72, 0x65, 0 };
    528     // Object replacement char.
    529     static const UChar expectedRawString2[] = { 0xFFFC, 0 };
    530     // "After".
    531     static const UChar expectedRawString3[] = { 0x41, 0x66, 0x74, 0x65, 0x72, 0 };
    532     static const UChar* expectedRawStrings[] = { expectedRawString1, expectedRawString2, expectedRawString3 };
    533     Vector<String> expectedTextChunks;
    534     expectedTextChunks.append(expectedRawStrings, WTF_ARRAY_LENGTH(expectedRawStrings));
    535 
    536     setBodyInnerHTML(bodyContent);
    537     EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEmitsObjectReplacementCharacter));
    538 }
    539 
    540 TEST_F(TextIteratorTest, RangeLengthWithReplacedElements)
    541 {
    542     static const char* bodyContent =
    543         "<div id=\"div\" contenteditable=\"true\">1<img src=\"foo.png\">3</div>";
    544     setBodyInnerHTML(bodyContent);
    545     document().view()->updateLayoutAndStyleIfNeededRecursive();
    546 
    547     Node* divNode = document().getElementById("div");
    548     RefPtrWillBeRawPtr<Range> range = Range::create(document(), divNode, 0, divNode, 3);
    549 
    550     EXPECT_EQ(3, TextIterator::rangeLength(range.get()));
    551 }
    552 
    553 }
    554