blob: 46b11e94f93c1601fa2d3adda753f0a7a85d10c7 [file] [log] [blame]
/*
* Copyright (c) 2013, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/editing/iterators/TextIterator.h"
#include "core/editing/EditingTestBase.h"
#include "core/frame/FrameView.h"
namespace blink {
struct DOMTree : NodeTraversal {
using PositionType = Position;
using TextIteratorType = TextIterator;
};
struct FlatTree : FlatTreeTraversal {
using PositionType = PositionInFlatTree;
using TextIteratorType = TextIteratorInFlatTree;
};
class TextIteratorTest : public EditingTestBase {
protected:
template <typename Tree>
std::string iterate(TextIteratorBehavior = TextIteratorDefaultBehavior);
template <typename Tree>
std::string iteratePartial(const typename Tree::PositionType& start, const typename Tree::PositionType& end, TextIteratorBehavior = TextIteratorDefaultBehavior);
Range* getBodyRange() const;
private:
template <typename Tree>
std::string iterateWithIterator(typename Tree::TextIteratorType&);
};
template <typename Tree>
std::string TextIteratorTest::iterate(TextIteratorBehavior iteratorBehavior)
{
Element* body = document().body();
using PositionType = typename Tree::PositionType;
auto start = PositionType(body, 0);
auto end = PositionType(body, Tree::countChildren(*body));
typename Tree::TextIteratorType iterator(start, end, iteratorBehavior);
return iterateWithIterator<Tree>(iterator);
}
template <typename Tree>
std::string TextIteratorTest::iteratePartial(const typename Tree::PositionType& start, const typename Tree::PositionType& end, TextIteratorBehavior iteratorBehavior)
{
typename Tree::TextIteratorType iterator(start, end, iteratorBehavior);
return iterateWithIterator<Tree>(iterator);
}
template <typename Tree>
std::string TextIteratorTest::iterateWithIterator(typename Tree::TextIteratorType& iterator)
{
String textChunks;
for (; !iterator.atEnd(); iterator.advance()) {
textChunks.append('[');
textChunks.append(iterator.text().substring(0, iterator.text().length()));
textChunks.append(']');
}
return std::string(textChunks.utf8().data());
}
Range* TextIteratorTest::getBodyRange() const
{
Range* range = Range::create(document());
range->selectNode(document().body());
return range;
}
TEST_F(TextIteratorTest, BitStackOverflow)
{
const unsigned bitsInWord = sizeof(unsigned) * 8;
BitStack bs;
for (unsigned i = 0; i < bitsInWord + 1u; i++)
bs.push(true);
bs.pop();
EXPECT_TRUE(bs.top());
}
TEST_F(TextIteratorTest, BasicIteration)
{
static const char* input = "<p>Hello, \ntext</p><p>iterator.</p>";
setBodyContent(input);
EXPECT_EQ("[Hello, ][text][\n][\n][iterator.]", iterate<DOMTree>());
EXPECT_EQ("[Hello, ][text][\n][\n][iterator.]", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, IgnoreAltTextInTextControls)
{
static const char* input = "<p>Hello <input type='text' value='value'>!</p>";
setBodyContent(input);
EXPECT_EQ("[Hello ][][!]", iterate<DOMTree>(TextIteratorEmitsImageAltText));
EXPECT_EQ("[Hello ][][\n][value][\n][!]", iterate<FlatTree>(TextIteratorEmitsImageAltText));
}
TEST_F(TextIteratorTest, DisplayAltTextInImageControls)
{
static const char* input = "<p>Hello <input type='image' alt='alt'>!</p>";
setBodyContent(input);
EXPECT_EQ("[Hello ][alt][!]", iterate<DOMTree>(TextIteratorEmitsImageAltText));
EXPECT_EQ("[Hello ][alt][!]", iterate<FlatTree>(TextIteratorEmitsImageAltText));
}
TEST_F(TextIteratorTest, NotEnteringTextControls)
{
static const char* input = "<p>Hello <input type='text' value='input'>!</p>";
setBodyContent(input);
EXPECT_EQ("[Hello ][][!]", iterate<DOMTree>());
EXPECT_EQ("[Hello ][][\n][input][\n][!]", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, EnteringTextControlsWithOption)
{
static const char* input = "<p>Hello <input type='text' value='input'>!</p>";
setBodyContent(input);
EXPECT_EQ("[Hello ][\n][input][!]", iterate<DOMTree>(TextIteratorEntersTextControls));
EXPECT_EQ("[Hello ][][\n][input][\n][!]", iterate<FlatTree>(TextIteratorEntersTextControls));
}
TEST_F(TextIteratorTest, EnteringTextControlsWithOptionComplex)
{
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'>";
setBodyContent(input);
EXPECT_EQ("[\n][Beginning of range][\n][Under DOM nodes][\n][End of range]", iterate<DOMTree>(TextIteratorEntersTextControls));
EXPECT_EQ("[][\n][Beginning of range][\n][][\n][Under DOM nodes][\n][][\n][End of range]", iterate<FlatTree>(TextIteratorEntersTextControls));
}
TEST_F(TextIteratorTest, NotEnteringShadowTree)
{
static const char* bodyContent = "<div>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent = "<span>shadow</span>";
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
// TextIterator doesn't emit "text" since its layoutObject is not created. The shadow tree is ignored.
EXPECT_EQ("[Hello, ][ iterator.]", iterate<DOMTree>());
EXPECT_EQ("[Hello, ][shadow][ iterator.]", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, NotEnteringShadowTreeWithMultipleShadowTrees)
{
static const char* bodyContent = "<div>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent1 = "<span>first shadow</span>";
static const char* shadowContent2 = "<span>second shadow</span>";
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2);
EXPECT_EQ("[Hello, ][ iterator.]", iterate<DOMTree>());
EXPECT_EQ("[Hello, ][second shadow][ iterator.]", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, NotEnteringShadowTreeWithNestedShadowTrees)
{
static const char* bodyContent = "<div>Hello, <span id='host-in-document'>text</span> iterator.</div>";
static const char* shadowContent1 = "<span>first <span id='host-in-shadow'>shadow</span></span>";
static const char* shadowContent2 = "<span>second shadow</span>";
setBodyContent(bodyContent);
ShadowRoot* shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1);
createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2);
EXPECT_EQ("[Hello, ][ iterator.]", iterate<DOMTree>());
EXPECT_EQ("[Hello, ][first ][second shadow][ iterator.]", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, NotEnteringShadowTreeWithContentInsertionPoint)
{
static const char* bodyContent = "<div>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent = "<span>shadow <content>content</content></span>";
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
// In this case a layoutObject for "text" is created, so it shows up here.
EXPECT_EQ("[Hello, ][text][ iterator.]", iterate<DOMTree>());
EXPECT_EQ("[Hello, ][shadow ][text][ iterator.]", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, EnteringShadowTreeWithOption)
{
static const char* bodyContent = "<div>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent = "<span>shadow</span>";
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
// TextIterator emits "shadow" since TextIteratorEntersOpenShadowRoots is specified.
EXPECT_EQ("[Hello, ][shadow][ iterator.]", iterate<DOMTree>(TextIteratorEntersOpenShadowRoots));
EXPECT_EQ("[Hello, ][shadow][ iterator.]", iterate<FlatTree>(TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, EnteringShadowTreeWithMultipleShadowTreesWithOption)
{
static const char* bodyContent = "<div>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent1 = "<span>first shadow</span>";
static const char* shadowContent2 = "<span>second shadow</span>";
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2);
// The first isn't emitted because a layoutObject for the first is not created.
EXPECT_EQ("[Hello, ][second shadow][ iterator.]", iterate<DOMTree>(TextIteratorEntersOpenShadowRoots));
EXPECT_EQ("[Hello, ][second shadow][ iterator.]", iterate<FlatTree>(TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, EnteringShadowTreeWithNestedShadowTreesWithOption)
{
static const char* bodyContent = "<div>Hello, <span id='host-in-document'>text</span> iterator.</div>";
static const char* shadowContent1 = "<span>first <span id='host-in-shadow'>shadow</span></span>";
static const char* shadowContent2 = "<span>second shadow</span>";
setBodyContent(bodyContent);
ShadowRoot* shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1);
createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2);
EXPECT_EQ("[Hello, ][first ][second shadow][ iterator.]", iterate<DOMTree>(TextIteratorEntersOpenShadowRoots));
EXPECT_EQ("[Hello, ][first ][second shadow][ iterator.]", iterate<FlatTree>(TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, EnteringShadowTreeWithContentInsertionPointWithOption)
{
static const char* bodyContent = "<div>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent = "<span><content>content</content> shadow</span>";
// In this case a layoutObject for "text" is created, and emitted AFTER any nodes in the shadow tree.
// This order does not match the order of the rendered texts, but at this moment it's the expected behavior.
// FIXME: Fix this. We probably need pure-renderer-based implementation of TextIterator to achieve this.
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
EXPECT_EQ("[Hello, ][ shadow][text][ iterator.]", iterate<DOMTree>(TextIteratorEntersOpenShadowRoots));
EXPECT_EQ("[Hello, ][text][ shadow][ iterator.]", iterate<FlatTree>(TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, StartingAtNodeInShadowRoot)
{
static const char* bodyContent = "<div id='outer'>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent = "<span><content>content</content> shadow</span>";
setBodyContent(bodyContent);
ShadowRoot* shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
Node* outerDiv = document().getElementById("outer");
Node* spanInShadow = shadowRoot->firstChild();
Position start(spanInShadow, PositionAnchorType::BeforeChildren);
Position end(outerDiv, PositionAnchorType::AfterChildren);
EXPECT_EQ("[ shadow][text][ iterator.]", iteratePartial<DOMTree>(start, end, TextIteratorEntersOpenShadowRoots));
PositionInFlatTree startInFlatTree(spanInShadow, PositionAnchorType::BeforeChildren);
PositionInFlatTree endInFlatTree(outerDiv, PositionAnchorType::AfterChildren);
EXPECT_EQ("[text][ shadow][ iterator.]", iteratePartial<FlatTree>(startInFlatTree, endInFlatTree, TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, FinishingAtNodeInShadowRoot)
{
static const char* bodyContent = "<div id='outer'>Hello, <span id='host'>text</span> iterator.</div>";
static const char* shadowContent = "<span><content>content</content> shadow</span>";
setBodyContent(bodyContent);
ShadowRoot* shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
Node* outerDiv = document().getElementById("outer");
Node* spanInShadow = shadowRoot->firstChild();
Position start(outerDiv, PositionAnchorType::BeforeChildren);
Position end(spanInShadow, PositionAnchorType::AfterChildren);
EXPECT_EQ("[Hello, ][ shadow]", iteratePartial<DOMTree>(start, end, TextIteratorEntersOpenShadowRoots));
PositionInFlatTree startInFlatTree(outerDiv, PositionAnchorType::BeforeChildren);
PositionInFlatTree endInFlatTree(spanInShadow, PositionAnchorType::AfterChildren);
EXPECT_EQ("[Hello, ][text][ shadow]", iteratePartial<FlatTree>(startInFlatTree, endInFlatTree, TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, FullyClipsContents)
{
static const char* bodyContent =
"<div style='overflow: hidden; width: 200px; height: 0;'>"
"I'm invisible"
"</div>";
setBodyContent(bodyContent);
EXPECT_EQ("", iterate<DOMTree>());
EXPECT_EQ("", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, IgnoresContainerClip)
{
static const char* bodyContent =
"<div style='overflow: hidden; width: 200px; height: 0;'>"
"<div>I'm not visible</div>"
"<div style='position: absolute; width: 200px; height: 200px; top: 0; right: 0;'>"
"but I am!"
"</div>"
"</div>";
setBodyContent(bodyContent);
EXPECT_EQ("[but I am!]", iterate<DOMTree>());
EXPECT_EQ("[but I am!]", iterate<FlatTree>());
}
TEST_F(TextIteratorTest, FullyClippedContentsDistributed)
{
static const char* bodyContent =
"<div id='host'>"
"<div>Am I visible?</div>"
"</div>";
static const char* shadowContent =
"<div style='overflow: hidden; width: 200px; height: 0;'>"
"<content></content>"
"</div>";
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
// FIXME: The text below is actually invisible but TextIterator currently thinks it's visible.
EXPECT_EQ("[\n][Am I visible?]", iterate<DOMTree>(TextIteratorEntersOpenShadowRoots));
EXPECT_EQ("", iterate<FlatTree>(TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, IgnoresContainersClipDistributed)
{
static const char* bodyContent =
"<div id='host' style='overflow: hidden; width: 200px; height: 0;'>"
"<div>Nobody can find me!</div>"
"</div>";
static const char* shadowContent =
"<div style='position: absolute; width: 200px; height: 200px; top: 0; right: 0;'>"
"<content></content>"
"</div>";
setBodyContent(bodyContent);
createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent);
// FIXME: The text below is actually visible but TextIterator currently thinks it's invisible.
// [\n][Nobody can find me!]
EXPECT_EQ("", iterate<DOMTree>(TextIteratorEntersOpenShadowRoots));
EXPECT_EQ("[Nobody can find me!]", iterate<FlatTree>(TextIteratorEntersOpenShadowRoots));
}
TEST_F(TextIteratorTest, EmitsReplacementCharForInput)
{
static const char* bodyContent =
"<div contenteditable='true'>"
"Before"
"<img src='foo.png'>"
"After"
"</div>";
setBodyContent(bodyContent);
EXPECT_EQ("[Before][\xEF\xBF\xBC][After]", iterate<DOMTree>(TextIteratorEmitsObjectReplacementCharacter));
EXPECT_EQ("[Before][\xEF\xBF\xBC][After]", iterate<FlatTree>(TextIteratorEmitsObjectReplacementCharacter));
}
TEST_F(TextIteratorTest, RangeLengthWithReplacedElements)
{
static const char* bodyContent =
"<div id='div' contenteditable='true'>1<img src='foo.png'>3</div>";
setBodyContent(bodyContent);
document().view()->updateAllLifecyclePhases();
Node* divNode = document().getElementById("div");
Range* range = Range::create(document(), divNode, 0, divNode, 3);
EXPECT_EQ(3, TextIterator::rangeLength(range->startPosition(), range->endPosition()));
}
TEST_F(TextIteratorTest, WhitespaceCollapseForReplacedElements)
{
static const char* bodyContent = "<span>Some text </span> <input type='button' value='Button text'/><span>Some more text</span>";
setBodyContent(bodyContent);
EXPECT_EQ("[Some text ][][Some more text]", iterate<DOMTree>(TextIteratorCollapseTrailingSpace));
EXPECT_EQ("[Some text ][][Button text][Some more text]", iterate<FlatTree>(TextIteratorCollapseTrailingSpace));
}
TEST_F(TextIteratorTest, copyTextTo)
{
const char* bodyContent = "<a id=host><b id=one>one</b> not appeared <b id=two>two</b></a>";
const char* shadowContent = "three <content select=#two></content> <content select=#one></content> zero";
setBodyContent(bodyContent);
setShadowContent(shadowContent, "host");
Element* host = document().getElementById("host");
const char* message = "|iter%d| should have emitted '%s'.";
EphemeralRangeTemplate<EditingStrategy> range1(EphemeralRangeTemplate<EditingStrategy>::rangeOfContents(*host));
TextIteratorAlgorithm<EditingStrategy> iter1(range1.startPosition(), range1.endPosition());
ForwardsTextBuffer output1;
iter1.copyTextTo(&output1, 0, 2);
EXPECT_EQ("on", String(output1.data(), output1.size())) << String::format(message, 1, "on").utf8().data();
iter1.copyTextTo(&output1, 2, 1);
EXPECT_EQ("one", String(output1.data(), output1.size())) << String::format(message, 1, "one").utf8().data();
iter1.advance();
iter1.copyTextTo(&output1, 0, 1);
EXPECT_EQ("onet", String(output1.data(), output1.size())) << String::format(message, 1, "onet").utf8().data();
iter1.copyTextTo(&output1, 1, 2);
EXPECT_EQ("onetwo", String(output1.data(), output1.size())) << String::format(message, 1, "onetwo").utf8().data();
EphemeralRangeTemplate<EditingInFlatTreeStrategy> range2(EphemeralRangeTemplate<EditingInFlatTreeStrategy>::rangeOfContents(*host));
TextIteratorAlgorithm<EditingInFlatTreeStrategy> iter2(range2.startPosition(), range2.endPosition());
ForwardsTextBuffer output2;
iter2.copyTextTo(&output2, 0, 3);
EXPECT_EQ("thr", String(output2.data(), output2.size())) << String::format(message, 2, "thr").utf8().data();
iter2.copyTextTo(&output2, 3, 3);
EXPECT_EQ("three ", String(output2.data(), output2.size())) << String::format(message, 2, "three ").utf8().data();
iter2.advance();
iter2.copyTextTo(&output2, 0, 2);
EXPECT_EQ("three tw", String(output2.data(), output2.size())) << String::format(message, 2, "three tw").utf8().data();
iter2.copyTextTo(&output2, 2, 1);
EXPECT_EQ("three two", String(output2.data(), output2.size())) << String::format(message, 2, "three two").utf8().data();
iter2.advance();
iter2.copyTextTo(&output2, 0, 1);
EXPECT_EQ("three two ", String(output2.data(), output2.size())) << String::format(message, 2, "three two ").utf8().data();
iter2.advance();
iter2.copyTextTo(&output2, 0, 1);
EXPECT_EQ("three two o", String(output2.data(), output2.size())) << String::format(message, 2, "three two o").utf8().data();
iter2.copyTextTo(&output2, 1, 2);
EXPECT_EQ("three two one", String(output2.data(), output2.size())) << String::format(message, 2, "three two one").utf8().data();
iter2.advance();
iter2.copyTextTo(&output2, 0, 2);
EXPECT_EQ("three two one z", String(output2.data(), output2.size())) << String::format(message, 2, "three two one z").utf8().data();
iter2.copyTextTo(&output2, 2, 3);
EXPECT_EQ("three two one zero", String(output2.data(), output2.size())) << String::format(message, 2, "three two one zero").utf8().data();
}
TEST_F(TextIteratorTest, characterAt)
{
const char* bodyContent = "<a id=host><b id=one>one</b> not appeared <b id=two>two</b></a>";
const char* shadowContent = "three <content select=#two></content> <content select=#one></content> zero";
setBodyContent(bodyContent);
setShadowContent(shadowContent, "host");
Element* host = document().getElementById("host");
EphemeralRangeTemplate<EditingStrategy> range1(EphemeralRangeTemplate<EditingStrategy>::rangeOfContents(*host));
TextIteratorAlgorithm<EditingStrategy> iter1(range1.startPosition(), range1.endPosition());
const char* message1 = "|iter1| should emit 'one' and 'two'.";
EXPECT_EQ('o', iter1.characterAt(0)) << message1;
EXPECT_EQ('n', iter1.characterAt(1)) << message1;
EXPECT_EQ('e', iter1.characterAt(2)) << message1;
iter1.advance();
EXPECT_EQ('t', iter1.characterAt(0)) << message1;
EXPECT_EQ('w', iter1.characterAt(1)) << message1;
EXPECT_EQ('o', iter1.characterAt(2)) << message1;
EphemeralRangeTemplate<EditingInFlatTreeStrategy> range2(EphemeralRangeTemplate<EditingInFlatTreeStrategy>::rangeOfContents(*host));
TextIteratorAlgorithm<EditingInFlatTreeStrategy> iter2(range2.startPosition(), range2.endPosition());
const char* message2 = "|iter2| should emit 'three ', 'two', ' ', 'one' and ' zero'.";
EXPECT_EQ('t', iter2.characterAt(0)) << message2;
EXPECT_EQ('h', iter2.characterAt(1)) << message2;
EXPECT_EQ('r', iter2.characterAt(2)) << message2;
EXPECT_EQ('e', iter2.characterAt(3)) << message2;
EXPECT_EQ('e', iter2.characterAt(4)) << message2;
EXPECT_EQ(' ', iter2.characterAt(5)) << message2;
iter2.advance();
EXPECT_EQ('t', iter2.characterAt(0)) << message2;
EXPECT_EQ('w', iter2.characterAt(1)) << message2;
EXPECT_EQ('o', iter2.characterAt(2)) << message2;
iter2.advance();
EXPECT_EQ(' ', iter2.characterAt(0)) << message2;
iter2.advance();
EXPECT_EQ('o', iter2.characterAt(0)) << message2;
EXPECT_EQ('n', iter2.characterAt(1)) << message2;
EXPECT_EQ('e', iter2.characterAt(2)) << message2;
iter2.advance();
EXPECT_EQ(' ', iter2.characterAt(0)) << message2;
EXPECT_EQ('z', iter2.characterAt(1)) << message2;
EXPECT_EQ('e', iter2.characterAt(2)) << message2;
EXPECT_EQ('r', iter2.characterAt(3)) << message2;
EXPECT_EQ('o', iter2.characterAt(4)) << message2;
}
TEST_F(TextIteratorTest, CopyWholeCodePoints)
{
const char* bodyContent = "&#x13000;&#x13001;&#x13002; &#x13140;&#x13141;.";
setBodyContent(bodyContent);
const UChar expected[] = {0xD80C, 0xDC00, 0xD80C, 0xDC01, 0xD80C, 0xDC02, ' ', 0xD80C, 0xDD40, 0xD80C, 0xDD41, '.'};
EphemeralRange range(EphemeralRange::rangeOfContents(document()));
TextIterator iter(range.startPosition(), range.endPosition());
ForwardsTextBuffer buffer;
EXPECT_EQ(2, iter.copyTextTo(&buffer, 0, 1)) << "Should emit 2 UChars for 'U+13000'.";
EXPECT_EQ(4, iter.copyTextTo(&buffer, 2, 3)) << "Should emit 4 UChars for 'U+13001U+13002'.";
EXPECT_EQ(3, iter.copyTextTo(&buffer, 6, 2)) << "Should emit 3 UChars for ' U+13140'.";
EXPECT_EQ(2, iter.copyTextTo(&buffer, 9, 2)) << "Should emit 2 UChars for 'U+13141'.";
EXPECT_EQ(1, iter.copyTextTo(&buffer, 11, 1)) << "Should emit 1 UChar for '.'.";
for (int i = 0; i < 12; i++)
EXPECT_EQ(expected[i], buffer[i]);
}
// Regression test for crbug.com/630921
TEST_F(TextIteratorTest, EndingConditionWithDisplayNone)
{
setBodyContent("<div style='display: none'><span>hello</span>world</div>Lorem ipsum dolor sit amet.");
Position start(&document(), 0);
Position end(document().querySelector("span"), 0);
TextIterator iter(start, end);
EXPECT_TRUE(iter.atEnd());
}
// Trickier regression test for crbug.com/630921
TEST_F(TextIteratorTest, EndingConditionWithDisplayNoneInShadowTree)
{
const char* bodyContent = "<div style='display: none'><span id=host><a></a></span>world</div>Lorem ipsum dolor sit amet.";
const char* shadowContent = "<i><b id=end>he</b></i>llo";
setBodyContent(bodyContent);
setShadowContent(shadowContent, "host");
ShadowRoot* shadowRoot = document().getElementById("host")->openShadowRoot();
Node* bInShadowTree = shadowRoot->getElementById("end");
Position start(&document(), 0);
Position end(bInShadowTree, 0);
TextIterator iter(start, end);
EXPECT_TRUE(iter.atEnd());
}
TEST_F(TextIteratorTest, PreserveLeadingSpace)
{
setBodyContent("<div style='width: 2em;'><b><i>foo</i></b> bar</div>");
Element* div = document().querySelector("div");
Position start(div->firstChild()->firstChild()->firstChild(), 0);
Position end(div->lastChild(), 4);
EXPECT_EQ("foo bar", plainText(EphemeralRange(start, end), TextIteratorEmitsImageAltText));
}
} // namespace blink