| /* |
| * 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 "config.h" |
| #include "core/editing/TextIterator.h" |
| |
| #include "bindings/core/v8/ExceptionStatePlaceholder.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/Node.h" |
| #include "core/dom/Range.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/frame/FrameView.h" |
| #include "core/html/HTMLDocument.h" |
| #include "core/html/HTMLElement.h" |
| #include "core/testing/DummyPageHolder.h" |
| #include "platform/geometry/IntSize.h" |
| #include "wtf/Compiler.h" |
| #include "wtf/OwnPtr.h" |
| #include "wtf/PassRefPtr.h" |
| #include "wtf/RefPtr.h" |
| #include "wtf/StdLibExtras.h" |
| #include "wtf/Vector.h" |
| #include "wtf/testing/WTFTestHelpers.h" |
| #include <gtest/gtest.h> |
| |
| using namespace blink; |
| |
| namespace { |
| |
| class TextIteratorTest : public ::testing::Test { |
| protected: |
| virtual void SetUp() OVERRIDE; |
| |
| HTMLDocument& document() const; |
| |
| Vector<String> iterate(TextIteratorBehavior = TextIteratorDefaultBehavior); |
| Vector<String> iteratePartial(const Position& start, const Position& end, TextIteratorBehavior = TextIteratorDefaultBehavior); |
| |
| void setBodyInnerHTML(const char*); |
| PassRefPtrWillBeRawPtr<Range> getBodyRange() const; |
| |
| private: |
| Vector<String> iterateWithIterator(TextIterator&); |
| |
| OwnPtr<DummyPageHolder> m_dummyPageHolder; |
| |
| HTMLDocument* m_document; |
| }; |
| |
| void TextIteratorTest::SetUp() |
| { |
| m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600)); |
| m_document = toHTMLDocument(&m_dummyPageHolder->document()); |
| ASSERT(m_document); |
| } |
| |
| Vector<String> TextIteratorTest::iterate(TextIteratorBehavior iteratorBehavior) |
| { |
| RefPtrWillBeRawPtr<Range> range = getBodyRange(); |
| TextIterator iterator(range.get(), iteratorBehavior); |
| return iterateWithIterator(iterator); |
| } |
| |
| Vector<String> TextIteratorTest::iteratePartial(const Position& start, const Position& end, TextIteratorBehavior iteratorBehavior) |
| { |
| TextIterator iterator(start, end, iteratorBehavior); |
| return iterateWithIterator(iterator); |
| } |
| |
| Vector<String> TextIteratorTest::iterateWithIterator(TextIterator& iterator) |
| { |
| Vector<String> textChunks; |
| while (!iterator.atEnd()) { |
| textChunks.append(iterator.substring(0, iterator.length())); |
| iterator.advance(); |
| } |
| return textChunks; |
| } |
| |
| HTMLDocument& TextIteratorTest::document() const |
| { |
| return *m_document; |
| } |
| |
| void TextIteratorTest::setBodyInnerHTML(const char* bodyContent) |
| { |
| document().body()->setInnerHTML(String::fromUTF8(bodyContent), ASSERT_NO_EXCEPTION); |
| } |
| |
| PassRefPtrWillBeRawPtr<Range> TextIteratorTest::getBodyRange() const |
| { |
| RefPtrWillBeRawPtr<Range> range(Range::create(document())); |
| range->selectNode(document().body()); |
| return range.release(); |
| } |
| |
| Vector<String> createVectorString(const char* const* rawStrings, size_t size) |
| { |
| Vector<String> result; |
| result.append(rawStrings, size); |
| return result; |
| } |
| |
| PassRefPtrWillBeRawPtr<ShadowRoot> createShadowRootForElementWithIDAndSetInnerHTML(TreeScope& scope, const char* hostElementID, const char* shadowRootContent) |
| { |
| RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = scope.getElementById(AtomicString::fromUTF8(hostElementID))->createShadowRoot(ASSERT_NO_EXCEPTION); |
| shadowRoot->setInnerHTML(String::fromUTF8(shadowRootContent), ASSERT_NO_EXCEPTION); |
| return shadowRoot.release(); |
| } |
| |
| TEST_F(TextIteratorTest, BasicIteration) |
| { |
| static const char* input = "<p>Hello, \ntext</p><p>iterator.</p>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| "text", |
| "\n", |
| "\n", |
| "iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(input); |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| TEST_F(TextIteratorTest, NotEnteringTextControls) |
| { |
| static const char* input = "<p>Hello <input type=\"text\" value=\"input\">!</p>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello ", |
| "", |
| "!", |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(input); |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| TEST_F(TextIteratorTest, EnteringTextControlsWithOption) |
| { |
| static const char* input = "<p>Hello <input type=\"text\" value=\"input\">!</p>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello ", |
| "\n", |
| "input", |
| "!", |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(input); |
| EXPECT_EQ(expectedTextChunks, iterate(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\">"; |
| static const char* expectedTextChunksRawString[] = { |
| "\n", // FIXME: Why newline here? |
| "Beginning of range", |
| "\n", |
| "Under DOM nodes", |
| "\n", |
| "End of range" |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(input); |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersTextControls)); |
| } |
| |
| TEST_F(TextIteratorTest, NotEnteringTextControlHostingShadowTreeEvenWithOption) |
| { |
| static const char* bodyContent = "<div>Hello, <input type=\"text\" value=\"input\" id=\"input\"> iterator.</div>"; |
| static const char* shadowContent = "<span>shadow</span>"; |
| // TextIterator doesn't emit "input" nor "shadow" since (1) the renderer for <input> is not created; and |
| // (2) we don't (yet) recurse into shadow trees. |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| "", // FIXME: Why is an empty string emitted here? |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "input", shadowContent); |
| |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| TEST_F(TextIteratorTest, NotEnteringShadowTree) |
| { |
| static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>"; |
| static const char* shadowContent = "<span>shadow</span>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", // TextIterator doesn't emit "text" since its renderer is not created. The shadow tree is ignored. |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2); |
| |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| RefPtrWillBeRawPtr<ShadowRoot> shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1); |
| createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2); |
| |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| "text", // In this case a renderer for "text" is created, so it shows up here. |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| TEST_F(TextIteratorTest, EnteringShadowTreeWithOption) |
| { |
| static const char* bodyContent = "<div>Hello, <span id=\"host\">text</span> iterator.</div>"; |
| static const char* shadowContent = "<span>shadow</span>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| "shadow", // TextIterator emits "shadow" since TextIteratorEntersAuthorShadowRoots is specified. |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| "second shadow", // The first isn't emitted because a renderer for the first is not created. |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent1); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent2); |
| |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| "first ", |
| "second shadow", |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| RefPtrWillBeRawPtr<ShadowRoot> shadowRoot1 = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host-in-document", shadowContent1); |
| createShadowRootForElementWithIDAndSetInnerHTML(*shadowRoot1, "host-in-shadow", shadowContent2); |
| |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| 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 renderer 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. |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| " shadow", |
| "text", |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| " shadow", |
| "text", |
| " iterator." |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| Node* outerDiv = document().getElementById("outer"); |
| Node* spanInShadow = shadowRoot->firstChild(); |
| Position start(spanInShadow, Position::PositionIsBeforeChildren); |
| Position end(outerDiv, Position::PositionIsAfterChildren); |
| |
| EXPECT_EQ(expectedTextChunks, iteratePartial(start, end, TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "Hello, ", |
| " shadow" |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| RefPtrWillBeRawPtr<ShadowRoot> shadowRoot = createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| Node* outerDiv = document().getElementById("outer"); |
| Node* spanInShadow = shadowRoot->firstChild(); |
| Position start(outerDiv, Position::PositionIsBeforeChildren); |
| Position end(spanInShadow, Position::PositionIsAfterChildren); |
| |
| EXPECT_EQ(expectedTextChunks, iteratePartial(start, end, TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| TEST_F(TextIteratorTest, FullyClipsContents) |
| { |
| static const char* bodyContent = |
| "<div style=\"overflow: hidden; width: 200px; height: 0;\">" |
| "I'm invisible" |
| "</div>"; |
| Vector<String> expectedTextChunks; // Empty. |
| |
| setBodyInnerHTML(bodyContent); |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "but I am!" |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| EXPECT_EQ(expectedTextChunks, iterate()); |
| } |
| |
| 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>"; |
| static const char* expectedTextChunksRawString[] = { |
| "\n", |
| // FIXME: The text below is actually invisible but TextIterator currently thinks it's visible. |
| "Am I visible?" |
| }; |
| Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| 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>"; |
| // FIXME: The text below is actually visible but TextIterator currently thinks it's invisible. |
| // static const char* expectedTextChunksRawString[] = { |
| // "\n", |
| // "Nobody can find me!" |
| // }; |
| // Vector<String> expectedTextChunks = createVectorString(expectedTextChunksRawString, WTF_ARRAY_LENGTH(expectedTextChunksRawString)); |
| Vector<String> expectedTextChunks; // Empty. |
| |
| setBodyInnerHTML(bodyContent); |
| createShadowRootForElementWithIDAndSetInnerHTML(document(), "host", shadowContent); |
| |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEntersAuthorShadowRoots)); |
| } |
| |
| TEST_F(TextIteratorTest, FindPlainTextInvalidTarget) |
| { |
| static const char* bodyContent = "<div>foo bar test</div>"; |
| setBodyInnerHTML(bodyContent); |
| RefPtrWillBeRawPtr<Range> range = getBodyRange(); |
| |
| RefPtrWillBeRawPtr<Range> expectedRange = range->cloneRange(); |
| expectedRange->collapse(false); |
| |
| // A lone lead surrogate (0xDA0A) example taken from fuzz-58. |
| static const UChar invalid1[] = { |
| 0x1461u, 0x2130u, 0x129bu, 0xd711u, 0xd6feu, 0xccadu, 0x7064u, |
| 0xd6a0u, 0x4e3bu, 0x03abu, 0x17dcu, 0xb8b7u, 0xbf55u, 0xfca0u, |
| 0x07fau, 0x0427u, 0xda0au, 0 |
| }; |
| |
| // A lone trailing surrogate (U+DC01). |
| static const UChar invalid2[] = { |
| 0x1461u, 0x2130u, 0x129bu, 0xdc01u, 0xd6feu, 0xccadu, 0 |
| }; |
| // A trailing surrogate followed by a lead surrogate (U+DC03 U+D901). |
| static const UChar invalid3[] = { |
| 0xd800u, 0xdc00u, 0x0061u, 0xdc03u, 0xd901u, 0xccadu, 0 |
| }; |
| |
| static const UChar* invalidUStrings[] = { invalid1, invalid2, invalid3 }; |
| |
| for (size_t i = 0; i < WTF_ARRAY_LENGTH(invalidUStrings); ++i) { |
| String invalidTarget(invalidUStrings[i]); |
| RefPtrWillBeRawPtr<Range> actualRange = findPlainText(range.get(), invalidTarget, 0); |
| EXPECT_TRUE(areRangesEqual(expectedRange.get(), actualRange.get())); |
| } |
| } |
| |
| TEST_F(TextIteratorTest, EmitsReplacementCharForInput) |
| { |
| static const char* bodyContent = |
| "<div contenteditable=\"true\">" |
| "Before" |
| "<img src=\"foo.png\">" |
| "After" |
| "</div>"; |
| // "Before". |
| static const UChar expectedRawString1[] = { 0x42, 0x65, 0x66, 0x6F, 0x72, 0x65, 0 }; |
| // Object replacement char. |
| static const UChar expectedRawString2[] = { 0xFFFC, 0 }; |
| // "After". |
| static const UChar expectedRawString3[] = { 0x41, 0x66, 0x74, 0x65, 0x72, 0 }; |
| static const UChar* expectedRawStrings[] = { expectedRawString1, expectedRawString2, expectedRawString3 }; |
| Vector<String> expectedTextChunks; |
| expectedTextChunks.append(expectedRawStrings, WTF_ARRAY_LENGTH(expectedRawStrings)); |
| |
| setBodyInnerHTML(bodyContent); |
| EXPECT_EQ(expectedTextChunks, iterate(TextIteratorEmitsObjectReplacementCharacter)); |
| } |
| |
| TEST_F(TextIteratorTest, RangeLengthWithReplacedElements) |
| { |
| static const char* bodyContent = |
| "<div id=\"div\" contenteditable=\"true\">1<img src=\"foo.png\">3</div>"; |
| setBodyInnerHTML(bodyContent); |
| document().view()->updateLayoutAndStyleIfNeededRecursive(); |
| |
| Node* divNode = document().getElementById("div"); |
| RefPtrWillBeRawPtr<Range> range = Range::create(document(), divNode, 0, divNode, 3); |
| |
| EXPECT_EQ(3, TextIterator::rangeLength(range.get())); |
| } |
| |
| } |