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