| /* |
| * 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/dom/Document.h" |
| #include "core/editing/EphemeralRange.h" |
| #include "core/editing/testing/EditingTestBase.h" |
| #include "core/frame/LocalFrameView.h" |
| #include "core/html/forms/TextControlElement.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| TextIteratorBehavior CollapseTrailingSpaceBehavior() { |
| return TextIteratorBehavior::Builder().SetCollapseTrailingSpace(true).Build(); |
| } |
| |
| TextIteratorBehavior EmitsImageAltTextBehavior() { |
| return TextIteratorBehavior::Builder().SetEmitsImageAltText(true).Build(); |
| } |
| |
| TextIteratorBehavior EntersTextControlsBehavior() { |
| return TextIteratorBehavior::Builder().SetEntersTextControls(true).Build(); |
| } |
| |
| TextIteratorBehavior EntersOpenShadowRootsBehavior() { |
| return TextIteratorBehavior::Builder().SetEntersOpenShadowRoots(true).Build(); |
| } |
| |
| TextIteratorBehavior EmitsObjectReplacementCharacterBehavior() { |
| return TextIteratorBehavior::Builder() |
| .SetEmitsObjectReplacementCharacter(true) |
| .Build(); |
| } |
| |
| } // namespace |
| |
| 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(const TextIteratorBehavior& = TextIteratorBehavior()); |
| |
| template <typename Tree> |
| std::string IteratePartial( |
| const typename Tree::PositionType& start, |
| const typename Tree::PositionType& end, |
| const TextIteratorBehavior& = TextIteratorBehavior()); |
| |
| Range* GetBodyRange() const; |
| |
| private: |
| template <typename Tree> |
| std::string IterateWithIterator(typename Tree::TextIteratorType&); |
| }; |
| |
| template <typename Tree> |
| std::string TextIteratorTest::Iterate( |
| const TextIteratorBehavior& iterator_behavior) { |
| Element* body = GetDocument().body(); |
| using PositionType = typename Tree::PositionType; |
| auto start = PositionType(body, 0); |
| auto end = PositionType(body, Tree::CountChildren(*body)); |
| typename Tree::TextIteratorType iterator(start, end, iterator_behavior); |
| return IterateWithIterator<Tree>(iterator); |
| } |
| |
| template <typename Tree> |
| std::string TextIteratorTest::IteratePartial( |
| const typename Tree::PositionType& start, |
| const typename Tree::PositionType& end, |
| const TextIteratorBehavior& iterator_behavior) { |
| typename Tree::TextIteratorType iterator(start, end, iterator_behavior); |
| return IterateWithIterator<Tree>(iterator); |
| } |
| |
| template <typename Tree> |
| std::string TextIteratorTest::IterateWithIterator( |
| typename Tree::TextIteratorType& iterator) { |
| String text_chunks; |
| for (; !iterator.AtEnd(); iterator.Advance()) { |
| text_chunks.append('['); |
| text_chunks.append( |
| iterator.GetText().Substring(0, iterator.GetText().length())); |
| text_chunks.append(']'); |
| } |
| return std::string(text_chunks.Utf8().data()); |
| } |
| |
| Range* TextIteratorTest::GetBodyRange() const { |
| Range* range = Range::Create(GetDocument()); |
| range->selectNode(GetDocument().body()); |
| return range; |
| } |
| |
| TEST_F(TextIteratorTest, BitStackOverflow) { |
| const unsigned kBitsInWord = sizeof(unsigned) * 8; |
| BitStack bs; |
| |
| for (unsigned i = 0; i < kBitsInWord + 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>(EmitsImageAltTextBehavior())); |
| EXPECT_EQ("[Hello ][][\n][value][\n][!]", |
| Iterate<FlatTree>(EmitsImageAltTextBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, DisplayAltTextInImageControls) { |
| static const char* input = "<p>Hello <input type='image' alt='alt'>!</p>"; |
| SetBodyContent(input); |
| EXPECT_EQ("[Hello ][alt][!]", Iterate<DOMTree>(EmitsImageAltTextBehavior())); |
| EXPECT_EQ("[Hello ][alt][!]", Iterate<FlatTree>(EmitsImageAltTextBehavior())); |
| } |
| |
| 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>(EntersTextControlsBehavior())); |
| EXPECT_EQ("[Hello ][][\n][input][\n][!]", |
| Iterate<FlatTree>(EntersTextControlsBehavior())); |
| } |
| |
| 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>(EntersTextControlsBehavior())); |
| EXPECT_EQ( |
| "[][\n][Beginning of range][\n][][\n][Under DOM nodes][\n][][\n][End of " |
| "range]", |
| Iterate<FlatTree>(EntersTextControlsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, NotEnteringShadowTree) { |
| static const char* body_content = |
| "<div>Hello, <span id='host'>text</span> iterator.</div>"; |
| static const char* shadow_content = "<span>shadow</span>"; |
| SetBodyContent(body_content); |
| CreateShadowRootForElementWithIDAndSetInnerHTML(GetDocument(), "host", |
| shadow_content); |
| // 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, NotEnteringShadowTreeWithNestedShadowTrees) { |
| static const char* body_content = |
| "<div>Hello, <span id='host-in-document'>text</span> iterator.</div>"; |
| static const char* shadow_content1 = |
| "<span>first <span id='host-in-shadow'>shadow</span></span>"; |
| static const char* shadow_content2 = "<span>second shadow</span>"; |
| SetBodyContent(body_content); |
| ShadowRoot* shadow_root1 = CreateShadowRootForElementWithIDAndSetInnerHTML( |
| GetDocument(), "host-in-document", shadow_content1); |
| CreateShadowRootForElementWithIDAndSetInnerHTML( |
| *shadow_root1, "host-in-shadow", shadow_content2); |
| EXPECT_EQ("[Hello, ][ iterator.]", Iterate<DOMTree>()); |
| EXPECT_EQ("[Hello, ][first ][second shadow][ iterator.]", |
| Iterate<FlatTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, NotEnteringShadowTreeWithContentInsertionPoint) { |
| static const char* body_content = |
| "<div>Hello, <span id='host'>text</span> iterator.</div>"; |
| static const char* shadow_content = |
| "<span>shadow <content>content</content></span>"; |
| SetBodyContent(body_content); |
| CreateShadowRootForElementWithIDAndSetInnerHTML(GetDocument(), "host", |
| shadow_content); |
| // 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* body_content = |
| "<div>Hello, <span id='host'>text</span> iterator.</div>"; |
| static const char* shadow_content = "<span>shadow</span>"; |
| SetBodyContent(body_content); |
| CreateShadowRootForElementWithIDAndSetInnerHTML(GetDocument(), "host", |
| shadow_content); |
| // TextIterator emits "shadow" since entersOpenShadowRootsBehavior() is |
| // specified. |
| EXPECT_EQ("[Hello, ][shadow][ iterator.]", |
| Iterate<DOMTree>(EntersOpenShadowRootsBehavior())); |
| EXPECT_EQ("[Hello, ][shadow][ iterator.]", |
| Iterate<FlatTree>(EntersOpenShadowRootsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, EnteringShadowTreeWithNestedShadowTreesWithOption) { |
| static const char* body_content = |
| "<div>Hello, <span id='host-in-document'>text</span> iterator.</div>"; |
| static const char* shadow_content1 = |
| "<span>first <span id='host-in-shadow'>shadow</span></span>"; |
| static const char* shadow_content2 = "<span>second shadow</span>"; |
| SetBodyContent(body_content); |
| ShadowRoot* shadow_root1 = CreateShadowRootForElementWithIDAndSetInnerHTML( |
| GetDocument(), "host-in-document", shadow_content1); |
| CreateShadowRootForElementWithIDAndSetInnerHTML( |
| *shadow_root1, "host-in-shadow", shadow_content2); |
| EXPECT_EQ("[Hello, ][first ][second shadow][ iterator.]", |
| Iterate<DOMTree>(EntersOpenShadowRootsBehavior())); |
| EXPECT_EQ("[Hello, ][first ][second shadow][ iterator.]", |
| Iterate<FlatTree>(EntersOpenShadowRootsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, |
| EnteringShadowTreeWithContentInsertionPointWithOption) { |
| static const char* body_content = |
| "<div>Hello, <span id='host'>text</span> iterator.</div>"; |
| static const char* shadow_content = |
| "<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(body_content); |
| CreateShadowRootForElementWithIDAndSetInnerHTML(GetDocument(), "host", |
| shadow_content); |
| EXPECT_EQ("[Hello, ][ shadow][text][ iterator.]", |
| Iterate<DOMTree>(EntersOpenShadowRootsBehavior())); |
| EXPECT_EQ("[Hello, ][text][ shadow][ iterator.]", |
| Iterate<FlatTree>(EntersOpenShadowRootsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, StartingAtNodeInShadowRoot) { |
| static const char* body_content = |
| "<div id='outer'>Hello, <span id='host'>text</span> iterator.</div>"; |
| static const char* shadow_content = |
| "<span><content>content</content> shadow</span>"; |
| SetBodyContent(body_content); |
| ShadowRoot* shadow_root = CreateShadowRootForElementWithIDAndSetInnerHTML( |
| GetDocument(), "host", shadow_content); |
| Node* outer_div = GetDocument().getElementById("outer"); |
| Node* span_in_shadow = shadow_root->firstChild(); |
| Position start(span_in_shadow, PositionAnchorType::kBeforeChildren); |
| Position end(outer_div, PositionAnchorType::kAfterChildren); |
| EXPECT_EQ( |
| "[ shadow][text][ iterator.]", |
| IteratePartial<DOMTree>(start, end, EntersOpenShadowRootsBehavior())); |
| |
| PositionInFlatTree start_in_flat_tree(span_in_shadow, |
| PositionAnchorType::kBeforeChildren); |
| PositionInFlatTree end_in_flat_tree(outer_div, |
| PositionAnchorType::kAfterChildren); |
| EXPECT_EQ("[text][ shadow][ iterator.]", |
| IteratePartial<FlatTree>(start_in_flat_tree, end_in_flat_tree, |
| EntersOpenShadowRootsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, FinishingAtNodeInShadowRoot) { |
| static const char* body_content = |
| "<div id='outer'>Hello, <span id='host'>text</span> iterator.</div>"; |
| static const char* shadow_content = |
| "<span><content>content</content> shadow</span>"; |
| SetBodyContent(body_content); |
| ShadowRoot* shadow_root = CreateShadowRootForElementWithIDAndSetInnerHTML( |
| GetDocument(), "host", shadow_content); |
| Node* outer_div = GetDocument().getElementById("outer"); |
| Node* span_in_shadow = shadow_root->firstChild(); |
| Position start(outer_div, PositionAnchorType::kBeforeChildren); |
| Position end(span_in_shadow, PositionAnchorType::kAfterChildren); |
| EXPECT_EQ( |
| "[Hello, ][ shadow]", |
| IteratePartial<DOMTree>(start, end, EntersOpenShadowRootsBehavior())); |
| |
| PositionInFlatTree start_in_flat_tree(outer_div, |
| PositionAnchorType::kBeforeChildren); |
| PositionInFlatTree end_in_flat_tree(span_in_shadow, |
| PositionAnchorType::kAfterChildren); |
| EXPECT_EQ("[Hello, ][text][ shadow]", |
| IteratePartial<FlatTree>(start_in_flat_tree, end_in_flat_tree, |
| EntersOpenShadowRootsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, FullyClipsContents) { |
| static const char* body_content = |
| "<div style='overflow: hidden; width: 200px; height: 0;'>" |
| "I'm invisible" |
| "</div>"; |
| SetBodyContent(body_content); |
| EXPECT_EQ("", Iterate<DOMTree>()); |
| EXPECT_EQ("", Iterate<FlatTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, IgnoresContainerClip) { |
| static const char* body_content = |
| "<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(body_content); |
| EXPECT_EQ("[but I am!]", Iterate<DOMTree>()); |
| EXPECT_EQ("[but I am!]", Iterate<FlatTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, FullyClippedContentsDistributed) { |
| static const char* body_content = |
| "<div id='host'>" |
| "<div>Am I visible?</div>" |
| "</div>"; |
| static const char* shadow_content = |
| "<div style='overflow: hidden; width: 200px; height: 0;'>" |
| "<content></content>" |
| "</div>"; |
| SetBodyContent(body_content); |
| CreateShadowRootForElementWithIDAndSetInnerHTML(GetDocument(), "host", |
| shadow_content); |
| // FIXME: The text below is actually invisible but TextIterator currently |
| // thinks it's visible. |
| EXPECT_EQ("[\n][Am I visible?]", |
| Iterate<DOMTree>(EntersOpenShadowRootsBehavior())); |
| EXPECT_EQ("", Iterate<FlatTree>(EntersOpenShadowRootsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, IgnoresContainersClipDistributed) { |
| static const char* body_content = |
| "<div id='host' style='overflow: hidden; width: 200px; height: 0;'>" |
| "<div>Nobody can find me!</div>" |
| "</div>"; |
| static const char* shadow_content = |
| "<div style='position: absolute; width: 200px; height: 200px; top: 0; " |
| "right: 0;'>" |
| "<content></content>" |
| "</div>"; |
| SetBodyContent(body_content); |
| CreateShadowRootForElementWithIDAndSetInnerHTML(GetDocument(), "host", |
| shadow_content); |
| // FIXME: The text below is actually visible but TextIterator currently thinks |
| // it's invisible. |
| // [\n][Nobody can find me!] |
| EXPECT_EQ("", Iterate<DOMTree>(EntersOpenShadowRootsBehavior())); |
| EXPECT_EQ("[Nobody can find me!]", |
| Iterate<FlatTree>(EntersOpenShadowRootsBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, EmitsReplacementCharForInput) { |
| static const char* body_content = |
| "<div contenteditable='true'>" |
| "Before" |
| "<img src='foo.png'>" |
| "After" |
| "</div>"; |
| SetBodyContent(body_content); |
| EXPECT_EQ("[Before][\xEF\xBF\xBC][After]", |
| Iterate<DOMTree>(EmitsObjectReplacementCharacterBehavior())); |
| EXPECT_EQ("[Before][\xEF\xBF\xBC][After]", |
| Iterate<FlatTree>(EmitsObjectReplacementCharacterBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, RangeLengthWithReplacedElements) { |
| static const char* body_content = |
| "<div id='div' contenteditable='true'>1<img src='foo.png'>3</div>"; |
| SetBodyContent(body_content); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| Node* div_node = GetDocument().getElementById("div"); |
| const EphemeralRange range(Position(div_node, 0), Position(div_node, 3)); |
| |
| EXPECT_EQ(3, TextIterator::RangeLength(range)); |
| } |
| |
| TEST_F(TextIteratorTest, RangeLengthInMultilineSpan) { |
| static const char* body_content = |
| "<table style='width:5em'>" |
| "<tbody>" |
| "<tr>" |
| "<td>" |
| "<span id='span1'>one two three four five</span>" |
| "</td>" |
| "</tr>" |
| "</tbody>" |
| "</table>"; |
| |
| SetBodyContent(body_content); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| Node* span_node = GetDocument().getElementById("span1"); |
| Node* text_node = span_node->firstChild(); |
| |
| // Select the word "two", this is the last word on the line. |
| |
| const EphemeralRange range(Position(text_node, 4), Position(text_node, 7)); |
| |
| EXPECT_EQ(4, TextIterator::RangeLength(range)); |
| EXPECT_EQ(3, TextIterator::RangeLength( |
| range, |
| TextIteratorBehavior::NoTrailingSpaceRangeLengthBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, WhitespaceCollapseForReplacedElements) { |
| static const char* body_content = |
| "<span>Some text </span> <input type='button' value='Button " |
| "text'/><span>Some more text</span>"; |
| SetBodyContent(body_content); |
| EXPECT_EQ("[Some text ][][Some more text]", |
| Iterate<DOMTree>(CollapseTrailingSpaceBehavior())); |
| EXPECT_EQ("[Some text ][][Button text][Some more text]", |
| Iterate<FlatTree>(CollapseTrailingSpaceBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, copyTextTo) { |
| const char* body_content = |
| "<a id=host><b id=one>one</b> not appeared <b id=two>two</b></a>"; |
| const char* shadow_content = |
| "three <content select=#two></content> <content select=#one></content> " |
| "zero"; |
| SetBodyContent(body_content); |
| SetShadowContent(shadow_content, "host"); |
| |
| Element* host = GetDocument().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* body_content = |
| "<a id=host><b id=one>one</b> not appeared <b id=two>two</b></a>"; |
| const char* shadow_content = |
| "three <content select=#two></content> <content select=#one></content> " |
| "zero"; |
| SetBodyContent(body_content); |
| SetShadowContent(shadow_content, "host"); |
| |
| Element* host = GetDocument().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* body_content = "𓀀𓀁𓀂 𓅀𓅁."; |
| SetBodyContent(body_content); |
| |
| const UChar kExpected[] = {0xD80C, 0xDC00, 0xD80C, 0xDC01, 0xD80C, 0xDC02, |
| ' ', 0xD80C, 0xDD40, 0xD80C, 0xDD41, '.'}; |
| |
| EphemeralRange range(EphemeralRange::RangeOfContents(GetDocument())); |
| 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(kExpected[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(&GetDocument(), 0); |
| Position end(GetDocument().QuerySelector("span"), 0); |
| TextIterator iter(start, end); |
| EXPECT_TRUE(iter.AtEnd()); |
| } |
| |
| // Trickier regression test for crbug.com/630921 |
| TEST_F(TextIteratorTest, EndingConditionWithDisplayNoneInShadowTree) { |
| const char* body_content = |
| "<div style='display: none'><span id=host><a></a></span>world</div>Lorem " |
| "ipsum dolor sit amet."; |
| const char* shadow_content = "<i><b id=end>he</b></i>llo"; |
| SetBodyContent(body_content); |
| SetShadowContent(shadow_content, "host"); |
| |
| ShadowRoot* shadow_root = |
| GetDocument().getElementById("host")->OpenShadowRoot(); |
| Node* b_in_shadow_tree = shadow_root->getElementById("end"); |
| |
| Position start(&GetDocument(), 0); |
| Position end(b_in_shadow_tree, 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 = GetDocument().QuerySelector("div"); |
| Position start(div->firstChild()->firstChild()->firstChild(), 0); |
| Position end(div->lastChild(), 4); |
| EXPECT_EQ("foo bar", |
| PlainText(EphemeralRange(start, end), EmitsImageAltTextBehavior())); |
| } |
| |
| // We used to have a bug where the leading space was duplicated if we didn't |
| // emit alt text, this tests for that bug |
| TEST_F(TextIteratorTest, PreserveLeadingSpaceWithoutEmittingAltText) { |
| SetBodyContent("<div style='width: 2em;'><b><i>foo</i></b> bar</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Position start(div->firstChild()->firstChild()->firstChild(), 0); |
| Position end(div->lastChild(), 4); |
| EXPECT_EQ("foo bar", PlainText(EphemeralRange(start, end))); |
| } |
| |
| TEST_F(TextIteratorTest, PreserveOnlyLeadingSpace) { |
| SetBodyContent( |
| "<div style='width: 2em;'><b><i id='foo'>foo </i></b> bar</div>"); |
| Element* div = GetDocument().QuerySelector("div"); |
| Position start(GetDocument().getElementById("foo")->firstChild(), 0); |
| Position end(div->lastChild(), 4); |
| EXPECT_EQ("foo bar", |
| PlainText(EphemeralRange(start, end), EmitsImageAltTextBehavior())); |
| } |
| |
| TEST_F(TextIteratorTest, StartAtFirstLetter) { |
| SetBodyContent("<style>div:first-letter {color:red;}</style><div>Axyz</div>"); |
| |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| Position start(text, 0); |
| Position end(text, 4); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(1, iter.length()); |
| EXPECT_EQ(1, iter.CopyTextTo(&buffer, 0)) << "Should emit 'A'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 0), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(3, iter.length()); |
| EXPECT_EQ(3, iter.CopyTextTo(&buffer, 0)) << "Should emit 'xyz'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 4), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("Axyz", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, StartInMultiCharFirstLetterWithCollapsedSpace) { |
| SetBodyContent( |
| "<style>div:first-letter {color:red;}</style><div> (A) xyz</div>"); |
| |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| Position start(text, 3); |
| Position end(text, 10); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(2, iter.length()); |
| EXPECT_EQ(2, iter.CopyTextTo(&buffer, 0)) << "Should emit 'A)'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 3), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 5), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(1, iter.length()); |
| EXPECT_EQ(1, iter.CopyTextTo(&buffer, 0)) << "Should emit ' '."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 5), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 6), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(3, iter.length()); |
| EXPECT_EQ(3, iter.CopyTextTo(&buffer, 0)) << "Should emit 'xyz'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 7), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 10), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("A) xyz", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, StartAndEndInMultiCharFirstLetterWithCollapsedSpace) { |
| SetBodyContent( |
| "<style>div:first-letter {color:red;}</style><div> (A) xyz</div>"); |
| |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| Position start(text, 3); |
| Position end(text, 4); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(1, iter.length()); |
| EXPECT_EQ(1, iter.CopyTextTo(&buffer, 0)) << "Should emit 'A'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 3), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 4), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("A", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, StartAtRemainingText) { |
| SetBodyContent("<style>div:first-letter {color:red;}</style><div>Axyz</div>"); |
| |
| Element* div = GetDocument().QuerySelector("div"); |
| Node* text = div->firstChild(); |
| Position start(text, 1); |
| Position end(text, 4); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(3, iter.length()); |
| EXPECT_EQ(3, iter.CopyTextTo(&buffer, 0)) << "Should emit 'xyz'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 4), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("xyz", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, StartAtFirstLetterInPre) { |
| SetBodyContent("<style>pre:first-letter {color:red;}</style><pre>Axyz</pre>"); |
| |
| Element* pre = GetDocument().QuerySelector("pre"); |
| Node* text = pre->firstChild(); |
| Position start(text, 0); |
| Position end(text, 4); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(1, iter.length()); |
| EXPECT_EQ(1, iter.CopyTextTo(&buffer, 0)) << "Should emit 'A'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 0), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(3, iter.length()); |
| EXPECT_EQ(3, iter.CopyTextTo(&buffer, 0)) << "Should emit 'xyz'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 4), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("Axyz", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, StartInMultiCharFirstLetterInPre) { |
| SetBodyContent( |
| "<style>pre:first-letter {color:red;}</style><pre>(A)xyz</pre>"); |
| |
| Element* pre = GetDocument().QuerySelector("pre"); |
| Node* text = pre->firstChild(); |
| Position start(text, 1); |
| Position end(text, 6); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(2, iter.length()); |
| EXPECT_EQ(2, iter.CopyTextTo(&buffer, 0)) << "Should emit 'A)'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 3), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(3, iter.length()); |
| EXPECT_EQ(3, iter.CopyTextTo(&buffer, 0)) << "Should emit 'xyz'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 3), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 6), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("A)xyz", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, StartAndEndInMultiCharFirstLetterInPre) { |
| SetBodyContent( |
| "<style>pre:first-letter {color:red;}</style><pre>(A)xyz</pre>"); |
| |
| Element* pre = GetDocument().QuerySelector("pre"); |
| Node* text = pre->firstChild(); |
| Position start(text, 1); |
| Position end(text, 2); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(1, iter.length()); |
| EXPECT_EQ(1, iter.CopyTextTo(&buffer, 0)) << "Should emit 'A'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 2), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("A", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, StartAtRemainingTextInPre) { |
| SetBodyContent("<style>pre:first-letter {color:red;}</style><pre>Axyz</pre>"); |
| |
| Element* pre = GetDocument().QuerySelector("pre"); |
| Node* text = pre->firstChild(); |
| Position start(text, 1); |
| Position end(text, 4); |
| TextIterator iter(start, end); |
| ForwardsTextBuffer buffer; |
| |
| EXPECT_FALSE(iter.AtEnd()); |
| EXPECT_EQ(3, iter.length()); |
| EXPECT_EQ(3, iter.CopyTextTo(&buffer, 0)) << "Should emit 'xyz'."; |
| EXPECT_EQ(text, iter.CurrentContainer()); |
| EXPECT_EQ(Position(text, 1), iter.StartPositionInCurrentContainer()); |
| EXPECT_EQ(Position(text, 4), iter.EndPositionInCurrentContainer()); |
| |
| iter.Advance(); |
| EXPECT_TRUE(iter.AtEnd()); |
| |
| EXPECT_EQ("xyz", String(buffer.Data())); |
| } |
| |
| TEST_F(TextIteratorTest, VisitsDisplayContentsChildren) { |
| SetBodyContent( |
| "<p>Hello, \ntext</p><p style='display: contents'>iterator.</p>"); |
| |
| EXPECT_EQ("[Hello, ][text][iterator.]", Iterate<DOMTree>()); |
| EXPECT_EQ("[Hello, ][text][iterator.]", Iterate<FlatTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationEmptyContent) { |
| SetBodyContent(""); |
| EXPECT_EQ("", Iterate<DOMTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationSingleCharacter) { |
| SetBodyContent("a"); |
| EXPECT_EQ("[a]", Iterate<DOMTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationSingleDiv) { |
| SetBodyContent("<div>a</div>"); |
| EXPECT_EQ("[a]", Iterate<DOMTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationMultipleDivs) { |
| SetBodyContent("<div>a</div><div>b</div>"); |
| EXPECT_EQ("[a][\n][b]", Iterate<DOMTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationMultipleDivsWithStyle) { |
| SetBodyContent( |
| "<div style='line-height: 18px; min-height: 436px; '>" |
| "debugging this note" |
| "</div>"); |
| EXPECT_EQ("[debugging this note]", Iterate<DOMTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationMultipleDivsWithChildren) { |
| SetBodyContent("<div>Hello<div><br><span></span></div></div>"); |
| EXPECT_EQ("[Hello][\n][\n]", Iterate<DOMTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationOnChildrenWithStyle) { |
| SetBodyContent( |
| "<div style='left:22px'>" |
| "</div>" |
| "\t\t\n" |
| "<div style='left:26px'>" |
| "</div>" |
| "\t\t\n\n" |
| "<div>" |
| "\t\t\t\n" |
| "<div>" |
| "\t\t\t\t\n" |
| "<div>" |
| "\t\t\t\t\t\n" |
| "<div contenteditable style='line-height: 20px; min-height: 580px; '>" |
| "hey" |
| "</div>" |
| "\t\t\t\t\n" |
| "</div>" |
| "\t\t\t\n" |
| "</div>" |
| "\t\t\n" |
| "</div>" |
| "\n\t\n"); |
| EXPECT_EQ("[hey]", Iterate<DOMTree>()); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationInput) { |
| SetBodyContent("<input id='a' value='b'>"); |
| TextControlElement* input_element = |
| ToTextControlElement(GetDocument().getElementById("a")); |
| const ShadowRoot* shadow_root = input_element->UserAgentShadowRoot(); |
| const Position start = Position::FirstPositionInNode(*shadow_root); |
| const Position end = Position::LastPositionInNode(*shadow_root); |
| EXPECT_EQ("[b]", IteratePartial<DOMTree>(start, end)); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIterationInputiWithBr) { |
| SetBodyContent("<input id='a' value='b'>"); |
| TextControlElement* input_element = |
| ToTextControlElement(GetDocument().getElementById("a")); |
| Element* inner_editor = input_element->InnerEditorElement(); |
| Element* br = GetDocument().createElement("br"); |
| inner_editor->AppendChild(br); |
| const ShadowRoot* shadow_root = input_element->UserAgentShadowRoot(); |
| const Position start = Position::FirstPositionInNode(*shadow_root); |
| const Position end = Position::LastPositionInNode(*shadow_root); |
| GetDocument().UpdateStyleAndLayout(); |
| EXPECT_EQ("[b]", IteratePartial<DOMTree>(start, end)); |
| } |
| |
| } // namespace blink |