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