TestHTMLEditUtils.cpp (76903B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "gtest/gtest.h" 7 #include "mozilla/BasePrincipal.h" 8 #include "mozilla/OriginAttributes.h" 9 #include "mozilla/dom/CDATASection.h" 10 #include "mozilla/dom/Comment.h" 11 #include "mozilla/dom/Document.h" 12 #include "mozilla/dom/Text.h" 13 #include "EditorDOMPoint.h" 14 #include "HTMLEditUtils.h" 15 #include "nsCOMPtr.h" 16 #include "nsGenericHTMLElement.h" 17 #include "nsIURI.h" 18 #include "nsNetUtil.h" 19 #include "nsString.h" 20 #include "nsTextNode.h" 21 22 namespace mozilla { 23 24 using namespace dom; 25 26 using AncestorType = HTMLEditUtils::AncestorType; 27 using AncestorTypes = HTMLEditUtils::AncestorTypes; 28 using EditablePointOption = HTMLEditUtils::EditablePointOption; 29 using EditablePointOptions = HTMLEditUtils::EditablePointOptions; 30 31 static already_AddRefed<Document> CreateHTMLDoc() { 32 nsCOMPtr<nsIURI> uri; 33 NS_NewURI(getter_AddRefs(uri), "data:text/html,"); 34 35 RefPtr<BasePrincipal> principal = 36 BasePrincipal::CreateContentPrincipal(uri, OriginAttributes()); 37 MOZ_RELEASE_ASSERT(principal); 38 39 nsCOMPtr<Document> doc; 40 MOZ_ALWAYS_SUCCEEDS(NS_NewDOMDocument(getter_AddRefs(doc), 41 u""_ns, // aNamespaceURI 42 u""_ns, // aQualifiedName 43 nullptr, // aDoctype 44 uri, uri, principal, 45 LoadedAsData::No, // aLoadedAsData 46 nullptr, // aEventObject 47 DocumentFlavor::HTML)); 48 MOZ_RELEASE_ASSERT(doc); 49 50 RefPtr<Element> html = doc->CreateHTMLElement(nsGkAtoms::html); 51 html->SetInnerHTMLTrusted(u"<html><head></head><body></body></html>"_ns, 52 principal, IgnoreErrors()); 53 doc->AppendChild(*html, IgnoreErrors()); 54 55 return doc.forget(); 56 } 57 58 struct MOZ_STACK_CLASS DeepestEditablePointTest final { 59 const char16_t* const mInnerHTML; 60 const char* const mContentSelector; 61 const EditablePointOptions mOptions; 62 const char* const mExpectedContainerSelector; 63 const char16_t* const mExpectedTextData; 64 const uint32_t mExpectedOffset; 65 66 friend std::ostream& operator<<(std::ostream& aStream, 67 const DeepestEditablePointTest& aTest) { 68 return aStream << "Scan \"" << aTest.mContentSelector 69 << "\" with options=" << ToString(aTest.mOptions).c_str() 70 << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() 71 << "\""; 72 } 73 }; 74 75 TEST(HTMLEditUtilsTest, GetDeepestEditableStartPointOf) 76 { 77 const RefPtr<Document> doc = CreateHTMLDoc(); 78 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 79 MOZ_RELEASE_ASSERT(body); 80 for (const auto& testData : { 81 DeepestEditablePointTest{ 82 u"<div contenteditable><div><br></div></div>", 83 "div[contenteditable] > div", 84 {}, 85 "div[contenteditable] > div", 86 nullptr, 87 0 // Find <br> 88 }, 89 DeepestEditablePointTest{ 90 u"<div contenteditable><div><img></div></div>", 91 "div[contenteditable] > div", 92 {}, 93 "div[contenteditable] > div", 94 nullptr, 95 0 // Find <img> 96 }, 97 DeepestEditablePointTest{ 98 u"<div contenteditable><div><hr></div></div>", 99 "div[contenteditable] > div", 100 {}, 101 "div[contenteditable] > div", 102 nullptr, 103 0 // Find <hr> 104 }, 105 DeepestEditablePointTest{ 106 u"<div contenteditable><div>abc</div></div>", 107 "div[contenteditable] > div", 108 {}, 109 "div[contenteditable] > div", 110 u"abc", 111 0 // Find "a" 112 }, 113 DeepestEditablePointTest{ 114 u"<div contenteditable><div><p>abc</p></div></div>", 115 "div[contenteditable] > div", 116 {}, 117 "div[contenteditable] > div > p", 118 u"abc", 119 0 // Find "a" 120 }, 121 DeepestEditablePointTest{ 122 u"<div contenteditable><div><span>abc</span></div></div>", 123 "div[contenteditable] > div", 124 {}, 125 "div[contenteditable] > div > span", 126 u"abc", 127 0 // Find "a" 128 }, 129 DeepestEditablePointTest{ 130 u"<div contenteditable><div> abc</div></div>", 131 "div[contenteditable] > div", 132 {}, 133 "div[contenteditable] > div", 134 u" abc", 135 3 // Find "a" 136 }, 137 DeepestEditablePointTest{ 138 u"<div contenteditable><div><span> abc</span></div></div>", 139 "div[contenteditable] > div", 140 {}, 141 "div[contenteditable] > div > span", 142 u" abc", 143 3 // Find "a" 144 }, 145 DeepestEditablePointTest{ 146 u"<div contenteditable><div> abc</div></div>", 147 "div[contenteditable] > div", 148 {EditablePointOption::RecognizeInvisibleWhiteSpaces}, 149 "div[contenteditable] > div", 150 u" abc", 151 0 // Find the first white-space 152 }, 153 DeepestEditablePointTest{ 154 u"<div contenteditable><div><span> abc</span></div></div>", 155 "div[contenteditable] > div", 156 {EditablePointOption::RecognizeInvisibleWhiteSpaces}, 157 "div[contenteditable] > div > span", 158 u" abc", 159 0 // Find the first white-space 160 }, 161 DeepestEditablePointTest{ 162 u"<div contenteditable><div><span></span>abc</div></div>", 163 "div[contenteditable] > div", 164 {}, 165 "div[contenteditable] > div > span", 166 nullptr, 167 0 // Find the empty <span> 168 }, 169 DeepestEditablePointTest{ 170 u"<div contenteditable><div><!-- comment -->abc</div></div>", 171 "div[contenteditable] > div", 172 {}, 173 "div[contenteditable] > div", 174 u"abc", 175 0 // Find "a" 176 }, 177 DeepestEditablePointTest{ 178 u"<div contenteditable><div><!-- comment -->abc</div></div>", 179 "div[contenteditable] > div", 180 {EditablePointOption::StopAtComment}, 181 "div[contenteditable] > div", 182 nullptr, 183 0 // Find the comment 184 }, 185 // inline-block may have leading white-spaces. Therefore, even if 186 // the start container is an inline element which follows visible 187 // characters, it should return the first visible character in the 188 // inline-block. 189 DeepestEditablePointTest{ 190 u"<div contenteditable><div>abc<b><span style=\"display: " 191 u"inline-block\"> def</span></b></div></div>", 192 "div[contenteditable] > div > b", 193 {}, 194 "div[contenteditable] > div > b > span", 195 u" def", 196 3 // Find "d" 197 }, 198 // There is a child <table> 199 DeepestEditablePointTest{ 200 u"<div contenteditable><div><table><td><br></table></div></div>", 201 "div[contenteditable] > div", 202 {}, 203 "div[contenteditable] td", 204 nullptr, 205 0 // Find <br> 206 }, 207 DeepestEditablePointTest{ 208 u"<div contenteditable><div><table><td><br></table></div></div>", 209 "div[contenteditable] > div", 210 {EditablePointOption::StopAtTableElement}, 211 "div[contenteditable] > div", 212 nullptr, 213 0 // Find <table> 214 }, 215 DeepestEditablePointTest{ 216 u"<div contenteditable><div><table><td><br></table></div></div>", 217 "div[contenteditable] > div", 218 {EditablePointOption::StopAtAnyTableElement}, 219 "div[contenteditable] > div", 220 nullptr, 221 0 // Find <table> 222 }, 223 // In a table structure 224 DeepestEditablePointTest{ 225 u"<div contenteditable><div><table><td><br></table></div></div>", 226 "div[contenteditable] table", 227 {}, 228 "div[contenteditable] td", 229 nullptr, 230 0 // Find <br> 231 }, 232 DeepestEditablePointTest{ 233 u"<div contenteditable><div><table><td><br></table></div></div>", 234 "div[contenteditable] table", 235 {EditablePointOption::StopAtTableElement}, 236 "div[contenteditable] td", 237 nullptr, 238 0 // Find <br> 239 }, 240 DeepestEditablePointTest{ 241 u"<div contenteditable><div><table><td><br></table></div></div>", 242 "div[contenteditable] table", 243 {EditablePointOption::StopAtAnyTableElement}, 244 "div[contenteditable] table", 245 nullptr, 246 0 // Find <td> 247 }, 248 // <ul> 249 DeepestEditablePointTest{ 250 u"<div contenteditable><div><ul><li><br></li></ul></div></div>", 251 "div[contenteditable] > div", 252 {}, 253 "div[contenteditable] li", 254 nullptr, 255 0 // Find <br> 256 }, 257 DeepestEditablePointTest{ 258 u"<div contenteditable><div><ul><li><br></li></ul></div></div>", 259 "div[contenteditable] > div", 260 {EditablePointOption::StopAtListItemElement}, 261 "div[contenteditable] ul", 262 nullptr, 263 0 // Find <li> 264 }, 265 DeepestEditablePointTest{ 266 u"<div contenteditable><div><ul><li><br></li></ul></div></div>", 267 "div[contenteditable] > div", 268 {EditablePointOption::StopAtListElement}, 269 "div[contenteditable] > div", 270 nullptr, 271 0 // Find <ul> 272 }, 273 // <ol> 274 DeepestEditablePointTest{ 275 u"<div contenteditable><div><ol><li><br></li></ol></div></div>", 276 "div[contenteditable] > div", 277 {}, 278 "div[contenteditable] li", 279 nullptr, 280 0 // Find <br> 281 }, 282 DeepestEditablePointTest{ 283 u"<div contenteditable><div><ol><li><br></li></ol></div></div>", 284 "div[contenteditable] > div", 285 {EditablePointOption::StopAtListItemElement}, 286 "div[contenteditable] ol", 287 nullptr, 288 0 // Find <li> 289 }, 290 DeepestEditablePointTest{ 291 u"<div contenteditable><div><ol><li><br></li></ol></div></div>", 292 "div[contenteditable] > div", 293 {EditablePointOption::StopAtListElement}, 294 "div[contenteditable] > div", 295 nullptr, 296 0 // Find <ol> 297 }, 298 // <dl> and <dt> 299 DeepestEditablePointTest{ 300 u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>", 301 "div[contenteditable] > div", 302 {}, 303 "div[contenteditable] dt", 304 nullptr, 305 0 // Find <br> 306 }, 307 DeepestEditablePointTest{ 308 u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>", 309 "div[contenteditable] > div", 310 {EditablePointOption::StopAtListItemElement}, 311 "div[contenteditable] dl", 312 nullptr, 313 0 // Find <dt> 314 }, 315 DeepestEditablePointTest{ 316 u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>", 317 "div[contenteditable] > div", 318 {EditablePointOption::StopAtListElement}, 319 "div[contenteditable] > div", 320 nullptr, 321 0 // Find <dl> 322 }, 323 // <dl> and <dd> 324 DeepestEditablePointTest{ 325 u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>", 326 "div[contenteditable] > div", 327 {}, 328 "div[contenteditable] dd", 329 nullptr, 330 0 // Find <br> 331 }, 332 DeepestEditablePointTest{ 333 u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>", 334 "div[contenteditable] > div", 335 {EditablePointOption::StopAtListItemElement}, 336 "div[contenteditable] dl", 337 nullptr, 338 0 // Find <dd> 339 }, 340 DeepestEditablePointTest{ 341 u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>", 342 "div[contenteditable] > div", 343 {EditablePointOption::StopAtListElement}, 344 "div[contenteditable] > div", 345 nullptr, 346 0 // Find <dl> 347 }, 348 }) { 349 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 350 doc->NodePrincipal(), IgnoreErrors()); 351 const Element* const content = body->QuerySelector( 352 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 353 MOZ_RELEASE_ASSERT(content); 354 const nsIContent* const expectedContainer = [&]() -> const nsIContent* { 355 const Element* const containerElement = body->QuerySelector( 356 nsDependentCString(testData.mExpectedContainerSelector), 357 IgnoreErrors()); 358 if (!testData.mExpectedTextData) { 359 return containerElement; 360 } 361 for (const nsIContent* child = containerElement->GetFirstChild(); child; 362 child = child->GetNextSibling()) { 363 if (const auto* text = Text::FromNodeOrNull(child)) { 364 nsAutoString data; 365 text->GetData(data); 366 if (data.Equals(testData.mExpectedTextData)) { 367 return text; 368 } 369 } 370 } 371 return nullptr; 372 }(); 373 MOZ_RELEASE_ASSERT(expectedContainer); 374 const EditorRawDOMPoint result = 375 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorRawDOMPoint>( 376 *content, testData.mOptions); 377 EXPECT_EQ(result.GetContainer(), expectedContainer) 378 << testData << "(Got: " << ToString(RefPtr{result.GetContainer()}) 379 << ")"; 380 EXPECT_EQ(result.Offset(), testData.mExpectedOffset) << testData; 381 } 382 } 383 384 TEST(HTMLEditUtilsTest, GetDeepestEditableEndPointOf) 385 { 386 const RefPtr<Document> doc = CreateHTMLDoc(); 387 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 388 MOZ_RELEASE_ASSERT(body); 389 for (const auto& testData : { 390 DeepestEditablePointTest{ 391 u"<div contenteditable><div><br></div></div>", 392 "div[contenteditable] > div", 393 {}, 394 "div[contenteditable] > div", 395 nullptr, 396 // XXX Should be 0 due to an invisible <br>? 397 1 // Find <br> 398 }, 399 DeepestEditablePointTest{ 400 u"<div contenteditable><div><img></div></div>", 401 "div[contenteditable] > div", 402 {}, 403 "div[contenteditable] > div", 404 nullptr, 405 1 // Find <img> 406 }, 407 DeepestEditablePointTest{ 408 u"<div contenteditable><div><hr></div></div>", 409 "div[contenteditable] > div", 410 {}, 411 "div[contenteditable] > div", 412 nullptr, 413 1 // Find <hr> 414 }, 415 DeepestEditablePointTest{ 416 u"<div contenteditable><div>abc</div></div>", 417 "div[contenteditable] > div", 418 {}, 419 "div[contenteditable] > div", 420 u"abc", 421 3 // Find "c" 422 }, 423 DeepestEditablePointTest{ 424 u"<div contenteditable><div><p>abc</p></div></div>", 425 "div[contenteditable] > div", 426 {}, 427 "div[contenteditable] > div > p", 428 u"abc", 429 3 // Find "c" 430 }, 431 DeepestEditablePointTest{ 432 u"<div contenteditable><div><span>abc</span></div></div>", 433 "div[contenteditable] > div", 434 {}, 435 "div[contenteditable] > div > span", 436 u"abc", 437 3 // Find "c" 438 }, 439 DeepestEditablePointTest{ 440 u"<div contenteditable><div>abc </div></div>", 441 "div[contenteditable] > div", 442 {}, 443 "div[contenteditable] > div", 444 u"abc ", 445 3 // Find "c" 446 }, 447 DeepestEditablePointTest{ 448 u"<div contenteditable><div><span>abc </span></div></div>", 449 "div[contenteditable] > div", 450 {}, 451 "div[contenteditable] > div > span", 452 u"abc ", 453 3 // Find "a" 454 }, 455 DeepestEditablePointTest{ 456 u"<div contenteditable><div>abc </div></div>", 457 "div[contenteditable] > div", 458 {EditablePointOption::RecognizeInvisibleWhiteSpaces}, 459 "div[contenteditable] > div", 460 u"abc ", 461 6 // Find the last white-space 462 }, 463 DeepestEditablePointTest{ 464 u"<div contenteditable><div><span>abc </span></div></div>", 465 "div[contenteditable] > div", 466 {EditablePointOption::RecognizeInvisibleWhiteSpaces}, 467 "div[contenteditable] > div > span", 468 u"abc ", 469 6 // Find the last white-space 470 }, 471 DeepestEditablePointTest{ 472 u"<div contenteditable><div>abc<span></span></div></div>", 473 "div[contenteditable] > div", 474 {}, 475 "div[contenteditable] > div > span", 476 nullptr, 477 0 // Find the empty <span> 478 }, 479 DeepestEditablePointTest{ 480 u"<div contenteditable><div>abc<!-- comment --></div></div>", 481 "div[contenteditable] > div", 482 {}, 483 "div[contenteditable] > div", 484 u"abc", 485 3 // Find "c" 486 }, 487 DeepestEditablePointTest{ 488 u"<div contenteditable><div>abc<!-- comment --></div></div>", 489 "div[contenteditable] > div", 490 {EditablePointOption::StopAtComment}, 491 "div[contenteditable] > div", 492 nullptr, 493 2 // Find the comment 494 }, 495 // inline-block may have leading white-spaces. Therefore, even if 496 // the start container is an inline element which is followed by 497 // visible characters, it should return the last visible character 498 // in the inline-block. 499 DeepestEditablePointTest{ 500 u"<div contenteditable><div><b><span style=\"display: " 501 u"inline-block\">abc </span></b>def</div></div>", 502 "div[contenteditable] > div > b", 503 {}, 504 "div[contenteditable] > div > b > span", 505 u"abc ", 506 3 // Find "c" 507 }, 508 // There is a child <table> 509 DeepestEditablePointTest{ 510 u"<div contenteditable><div><table><td><br></table></div></div>", 511 "div[contenteditable] > div", 512 {}, 513 "div[contenteditable] td", 514 nullptr, 515 1 // Find <br> 516 }, 517 DeepestEditablePointTest{ 518 u"<div contenteditable><div><table><td><br></table></div></div>", 519 "div[contenteditable] > div", 520 {EditablePointOption::StopAtTableElement}, 521 "div[contenteditable] > div", 522 nullptr, 523 1 // Find <table> 524 }, 525 DeepestEditablePointTest{ 526 u"<div contenteditable><div><table><td><br></table></div></div>", 527 "div[contenteditable] > div", 528 {EditablePointOption::StopAtAnyTableElement}, 529 "div[contenteditable] > div", 530 nullptr, 531 1 // Find <table> 532 }, 533 // In a table structure 534 DeepestEditablePointTest{ 535 u"<div contenteditable><div><table><td><br></table></div></div>", 536 "div[contenteditable] table", 537 {}, 538 "div[contenteditable] td", 539 nullptr, 540 1 // Find <br> 541 }, 542 DeepestEditablePointTest{ 543 u"<div contenteditable><div><table><td><br></table></div></div>", 544 "div[contenteditable] table", 545 {EditablePointOption::StopAtTableElement}, 546 "div[contenteditable] td", 547 nullptr, 548 1 // Find <br> 549 }, 550 DeepestEditablePointTest{ 551 u"<div contenteditable><div><table><td><br></table></div></div>", 552 "div[contenteditable] table", 553 {EditablePointOption::StopAtAnyTableElement}, 554 "div[contenteditable] table", 555 nullptr, 556 1 // Find <td> 557 }, 558 // <ul> 559 DeepestEditablePointTest{ 560 u"<div contenteditable><div><ul><li><br></li></ul></div></div>", 561 "div[contenteditable] > div", 562 {}, 563 "div[contenteditable] li", 564 nullptr, 565 1 // Find <br> 566 }, 567 DeepestEditablePointTest{ 568 u"<div contenteditable><div><ul><li><br></li></ul></div></div>", 569 "div[contenteditable] > div", 570 {EditablePointOption::StopAtListItemElement}, 571 "div[contenteditable] ul", 572 nullptr, 573 1 // Find <li> 574 }, 575 DeepestEditablePointTest{ 576 u"<div contenteditable><div><ul><li><br></li></ul></div></div>", 577 "div[contenteditable] > div", 578 {EditablePointOption::StopAtListElement}, 579 "div[contenteditable] > div", 580 nullptr, 581 1 // Find <ul> 582 }, 583 // <ol> 584 DeepestEditablePointTest{ 585 u"<div contenteditable><div><ol><li><br></li></ol></div></div>", 586 "div[contenteditable] > div", 587 {}, 588 "div[contenteditable] li", 589 nullptr, 590 1 // Find <br> 591 }, 592 DeepestEditablePointTest{ 593 u"<div contenteditable><div><ol><li><br></li></ol></div></div>", 594 "div[contenteditable] > div", 595 {EditablePointOption::StopAtListItemElement}, 596 "div[contenteditable] ol", 597 nullptr, 598 1 // Find <li> 599 }, 600 DeepestEditablePointTest{ 601 u"<div contenteditable><div><ol><li><br></li></ol></div></div>", 602 "div[contenteditable] > div", 603 {EditablePointOption::StopAtListElement}, 604 "div[contenteditable] > div", 605 nullptr, 606 1 // Find <ol> 607 }, 608 // <dl> and <dt> 609 DeepestEditablePointTest{ 610 u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>", 611 "div[contenteditable] > div", 612 {}, 613 "div[contenteditable] dt", 614 nullptr, 615 1 // Find <br> 616 }, 617 DeepestEditablePointTest{ 618 u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>", 619 "div[contenteditable] > div", 620 {EditablePointOption::StopAtListItemElement}, 621 "div[contenteditable] dl", 622 nullptr, 623 1 // Find <dt> 624 }, 625 DeepestEditablePointTest{ 626 u"<div contenteditable><div><dl><dt><br></dt></dl></div></div>", 627 "div[contenteditable] > div", 628 {EditablePointOption::StopAtListElement}, 629 "div[contenteditable] > div", 630 nullptr, 631 1 // Find <dl> 632 }, 633 // <dl> and <dd> 634 DeepestEditablePointTest{ 635 u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>", 636 "div[contenteditable] > div", 637 {}, 638 "div[contenteditable] dd", 639 nullptr, 640 1 // Find <br> 641 }, 642 DeepestEditablePointTest{ 643 u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>", 644 "div[contenteditable] > div", 645 {EditablePointOption::StopAtListItemElement}, 646 "div[contenteditable] dl", 647 nullptr, 648 1 // Find <dd> 649 }, 650 DeepestEditablePointTest{ 651 u"<div contenteditable><div><dl><dd><br></dd></dl></div></div>", 652 "div[contenteditable] > div", 653 {EditablePointOption::StopAtListElement}, 654 "div[contenteditable] > div", 655 nullptr, 656 1 // Find <dl> 657 }, 658 }) { 659 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 660 doc->NodePrincipal(), IgnoreErrors()); 661 const Element* const content = body->QuerySelector( 662 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 663 MOZ_RELEASE_ASSERT(content); 664 const nsIContent* const expectedContainer = [&]() -> const nsIContent* { 665 const Element* const containerElement = body->QuerySelector( 666 nsDependentCString(testData.mExpectedContainerSelector), 667 IgnoreErrors()); 668 if (!testData.mExpectedTextData) { 669 return containerElement; 670 } 671 for (const nsIContent* child = containerElement->GetLastChild(); child; 672 child = child->GetPreviousSibling()) { 673 if (const auto* text = Text::FromNodeOrNull(child)) { 674 nsAutoString data; 675 text->GetData(data); 676 if (data.Equals(testData.mExpectedTextData)) { 677 return text; 678 } 679 } 680 } 681 return nullptr; 682 }(); 683 MOZ_RELEASE_ASSERT(expectedContainer); 684 const EditorRawDOMPoint result = 685 HTMLEditUtils::GetDeepestEditableEndPointOf<EditorRawDOMPoint>( 686 *content, testData.mOptions); 687 EXPECT_EQ(result.GetContainer(), expectedContainer) 688 << testData << "(Got: " << ToString(RefPtr{result.GetContainer()}) 689 << ")"; 690 EXPECT_EQ(result.Offset(), testData.mExpectedOffset) << testData; 691 } 692 } 693 694 struct MOZ_STACK_CLASS AncestorElementTest final { 695 const char16_t* const mInnerHTML; 696 const char* const mContentSelector; 697 const AncestorTypes mAncestorTypes; 698 const char* const mAncestorLimiterSelector; 699 const char* const mExpectedSelectorForAncestor; 700 const char* const mExpectedSelectorForInclusiveAncestor; 701 702 friend std::ostream& operator<<(std::ostream& aStream, 703 const AncestorElementTest& aTest) { 704 return aStream << "Scan from \"" << aTest.mContentSelector 705 << "\" with ancestor types=" 706 << ToString(aTest.mAncestorTypes).c_str() << " in \"" 707 << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\""; 708 } 709 }; 710 711 TEST(HTMLEditUtilsTest, GetAncestorElement_ClosestBlockElement) 712 { 713 using AncestorType = HTMLEditUtils::AncestorType; 714 const RefPtr<Document> doc = CreateHTMLDoc(); 715 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 716 MOZ_RELEASE_ASSERT(body); 717 for (const auto& testData : { 718 AncestorElementTest{ 719 u"<div contenteditable><div><span><div><span><br>" 720 u"</span></div></span></div></div>", 721 "[contenteditable] > div > span > div > span", 722 {AncestorType::ClosestBlockElement}, 723 "[contenteditable]", 724 "[contenteditable] > div > span > div", 725 "[contenteditable] > div > span > div"}, 726 AncestorElementTest{ 727 u"<div contenteditable><div><span><div><span><br>" 728 u"</span></div></span></div></div>", 729 "[contenteditable] > div > span > div", 730 {AncestorType::ClosestBlockElement}, 731 "[contenteditable]", 732 "[contenteditable] > div", 733 "[contenteditable] > div > span > div"}, 734 AncestorElementTest{ 735 u"<div contenteditable><div><span><br></span></div></div>", 736 "[contenteditable] > div", 737 {AncestorType::ClosestBlockElement}, 738 "[contenteditable]", 739 // Should return the editing host because of the closest ancestor 740 // of the deepest <div>. 741 "[contenteditable]", 742 "[contenteditable] > div"}, 743 AncestorElementTest{ 744 u"<div contenteditable><span><br></span></div>", 745 "[contenteditable] > span", 746 {AncestorType::ClosestBlockElement}, 747 "[contenteditable]", 748 // Should return the editing host because of a block. 749 "[contenteditable]", 750 "[contenteditable]"}, 751 AncestorElementTest{ 752 u"<div contenteditable><span><br></span></div>", 753 "[contenteditable] > span", 754 {AncestorType::ClosestBlockElement, 755 AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, 756 "[contenteditable]", 757 "[contenteditable]", 758 "[contenteditable]"}, 759 AncestorElementTest{u"<div contenteditable><span><br></span></div>", 760 "[contenteditable] > span", 761 {AncestorType::ClosestBlockElement, 762 AncestorType::EditableElement}, 763 "[contenteditable]", 764 "[contenteditable]", 765 "[contenteditable]"}, 766 AncestorElementTest{ 767 u"<div contenteditable><span><br></span></div>", 768 "[contenteditable] > span", 769 {AncestorType::ClosestBlockElement, 770 AncestorType::EditableElement, 771 AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, 772 "[contenteditable]", 773 "[contenteditable]", 774 "[contenteditable]"}, 775 AncestorElementTest{ 776 u"<span contenteditable><span><br></span></span>", 777 "[contenteditable] > span", 778 {AncestorType::ClosestBlockElement, 779 AncestorType::EditableElement, 780 AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, 781 "[contenteditable]", 782 // Should return the inline editing host because of the ancestor 783 // limiter. 784 "[contenteditable]", 785 "[contenteditable]"}, 786 AncestorElementTest{ 787 u"<span contenteditable><span><br></span></span>", 788 "[contenteditable] > span", 789 {AncestorType::ClosestBlockElement, 790 AncestorType::EditableElement}, 791 "[contenteditable]", 792 // Should not return the body because of not editable. 793 nullptr, 794 nullptr}, 795 AncestorElementTest{ 796 u"<div><span contenteditable><br></span>", 797 "[contenteditable]", 798 {AncestorType::ClosestBlockElement, 799 AncestorType::EditableElement, 800 AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, 801 "div", // parent of the editing host 802 // nullptr because of starting to scan from non-editable element. 803 nullptr, 804 // the editing host. 805 "[contenteditable]", 806 }, 807 }) { 808 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 809 doc->NodePrincipal(), IgnoreErrors()); 810 const Element* const content = body->QuerySelector( 811 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 812 MOZ_RELEASE_ASSERT(content); 813 { 814 const Element* const expectedElement = 815 testData.mExpectedSelectorForAncestor 816 ? body->QuerySelector( 817 nsDependentCString(testData.mExpectedSelectorForAncestor), 818 IgnoreErrors()) 819 : nullptr; 820 const Element* const result = HTMLEditUtils::GetAncestorElement( 821 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 822 IgnoreErrors()), 823 testData.mAncestorTypes, 824 BlockInlineCheck::UseComputedDisplayOutsideStyle, 825 testData.mAncestorLimiterSelector 826 ? body->QuerySelector( 827 nsDependentCString(testData.mAncestorLimiterSelector), 828 IgnoreErrors()) 829 : nullptr); 830 EXPECT_EQ(result, expectedElement) 831 << "GetAncestorElement: " << testData 832 << "(Got: " << ToString(RefPtr{result}) << ")"; 833 } 834 { 835 const Element* const expectedElement = 836 testData.mExpectedSelectorForInclusiveAncestor 837 ? body->QuerySelector( 838 nsDependentCString( 839 testData.mExpectedSelectorForInclusiveAncestor), 840 IgnoreErrors()) 841 : nullptr; 842 const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement( 843 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 844 IgnoreErrors()), 845 testData.mAncestorTypes, 846 BlockInlineCheck::UseComputedDisplayOutsideStyle, 847 testData.mAncestorLimiterSelector 848 ? body->QuerySelector( 849 nsDependentCString(testData.mAncestorLimiterSelector), 850 IgnoreErrors()) 851 : nullptr); 852 EXPECT_EQ(result, expectedElement) 853 << "GetInclusiveAncestorElement: " << testData 854 << "(Got: " << ToString(RefPtr{result}) << ")"; 855 } 856 } 857 } 858 859 TEST(HTMLEditUtilsTest, GetAncestorElement_MostDistantInlineElementInBlock) 860 { 861 using AncestorType = HTMLEditUtils::AncestorType; 862 const RefPtr<Document> doc = CreateHTMLDoc(); 863 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 864 MOZ_RELEASE_ASSERT(body); 865 for (const auto& testData : { 866 AncestorElementTest{ 867 u"<div contenteditable><span><br></span></div>", 868 "[contenteditable] > span", 869 {AncestorType::MostDistantInlineElementInBlock, 870 AncestorType::EditableElement, 871 AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, 872 "[contenteditable]", 873 // Should return the editing host because of editable. 874 "[contenteditable]", 875 "[contenteditable] > span"}, 876 AncestorElementTest{ 877 u"<div contenteditable><b><i><div><u><s><br>" 878 u"</s></u></div></i></b></div>", 879 "[contenteditable] s", 880 {AncestorType::MostDistantInlineElementInBlock}, 881 "[contenteditable]", 882 // Should return the <u> because of the deepest <div>. 883 "[contenteditable] u", 884 "[contenteditable] u"}, 885 AncestorElementTest{u"<div contenteditable><b><i><div><u><s><br>" 886 u"</s></u></div></i></b></div>", 887 "[contenteditable] u", 888 {AncestorType::MostDistantInlineElementInBlock}, 889 "[contenteditable]", 890 // Should return nullptr because of no ancestor 891 // in the deepest <div>. 892 nullptr, 893 "[contenteditable] u"}, 894 AncestorElementTest{ 895 u"<div contenteditable><b><i><div><u><s><br>" 896 u"</s></u></div></i></b></div>", 897 "[contenteditable] div", 898 {AncestorType::MostDistantInlineElementInBlock}, 899 "[contenteditable]", 900 "[contenteditable] b", 901 // Should return nullptr because of starting from the <div>. 902 nullptr}, 903 AncestorElementTest{ 904 u",<s><span contenteditable><b><i><br></i></b></span></s>", 905 "[contenteditable] i", 906 {AncestorType::MostDistantInlineElementInBlock}, 907 "[contenteditable]", 908 // Should return the editing host because of inline. 909 "[contenteditable]", 910 "[contenteditable]"}, 911 AncestorElementTest{ 912 u"<s><span contenteditable><b><i><br></i></b></span></s>", 913 "[contenteditable] i", 914 {AncestorType::MostDistantInlineElementInBlock, 915 AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, 916 "[contenteditable]", 917 // Should return the editing host because of inline. 918 "[contenteditable]", 919 "[contenteditable]"}, 920 AncestorElementTest{ 921 u"<s><span contenteditable><b><i><br></i></b></span></s>", 922 "[contenteditable] i", 923 {AncestorType::MostDistantInlineElementInBlock, 924 AncestorType::EditableElement}, 925 nullptr, 926 // Should return the editing host because of the editable root. 927 "[contenteditable]", 928 "[contenteditable]"}, 929 }) { 930 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 931 doc->NodePrincipal(), IgnoreErrors()); 932 const Element* const content = body->QuerySelector( 933 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 934 MOZ_RELEASE_ASSERT(content); 935 { 936 const Element* const expectedElement = 937 testData.mExpectedSelectorForAncestor 938 ? body->QuerySelector( 939 nsDependentCString(testData.mExpectedSelectorForAncestor), 940 IgnoreErrors()) 941 : nullptr; 942 const Element* const result = HTMLEditUtils::GetAncestorElement( 943 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 944 IgnoreErrors()), 945 testData.mAncestorTypes, 946 BlockInlineCheck::UseComputedDisplayOutsideStyle, 947 testData.mAncestorLimiterSelector 948 ? body->QuerySelector( 949 nsDependentCString(testData.mAncestorLimiterSelector), 950 IgnoreErrors()) 951 : nullptr); 952 EXPECT_EQ(result, expectedElement) 953 << "GetAncestorElement: " << testData 954 << "(Got: " << ToString(RefPtr{result}) << ")"; 955 } 956 { 957 const Element* const expectedElement = 958 testData.mExpectedSelectorForInclusiveAncestor 959 ? body->QuerySelector( 960 nsDependentCString( 961 testData.mExpectedSelectorForInclusiveAncestor), 962 IgnoreErrors()) 963 : nullptr; 964 const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement( 965 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 966 IgnoreErrors()), 967 testData.mAncestorTypes, 968 BlockInlineCheck::UseComputedDisplayOutsideStyle, 969 testData.mAncestorLimiterSelector 970 ? body->QuerySelector( 971 nsDependentCString(testData.mAncestorLimiterSelector), 972 IgnoreErrors()) 973 : nullptr); 974 EXPECT_EQ(result, expectedElement) 975 << "GetInclusiveAncestorElement: " << testData 976 << "(Got: " << ToString(RefPtr{result}) << ")"; 977 } 978 } 979 } 980 981 TEST(HTMLEditUtilsTest, GetAncestorElement_ButtonElement) 982 { 983 using AncestorType = HTMLEditUtils::AncestorType; 984 const RefPtr<Document> doc = CreateHTMLDoc(); 985 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 986 MOZ_RELEASE_ASSERT(body); 987 for (const auto& testData : { 988 AncestorElementTest{ 989 u"<div contenteditable><button><span><br></span></button></div>", 990 "[contenteditable] > button > span", 991 {AncestorType::ClosestBlockElement, 992 AncestorType::ClosestButtonElement}, 993 "[contenteditable]", 994 "[contenteditable] > button", 995 "[contenteditable] > button"}, 996 AncestorElementTest{ 997 u"<div contenteditable><button><br></button></div>", 998 "[contenteditable] > button", 999 {AncestorType::ClosestBlockElement, 1000 AncestorType::ClosestButtonElement}, 1001 "[contenteditable]", 1002 "[contenteditable]", 1003 "[contenteditable] > button"}, 1004 AncestorElementTest{ 1005 u"<div contenteditable><b><button><i><br>" 1006 u"</i></button></b></div>", 1007 "[contenteditable] button > i", 1008 {AncestorType::MostDistantInlineElementInBlock, 1009 AncestorType::StopAtClosestButtonElement}, 1010 "[contenteditable]", 1011 // because of no inline elements between <button> and <i>. 1012 nullptr, 1013 "[contenteditable] button > i"}, 1014 AncestorElementTest{u"<div contenteditable><b><button><i><u><br>" 1015 u"</u></i></button></b></div>", 1016 "[contenteditable] button > i > u", 1017 {AncestorType::MostDistantInlineElementInBlock, 1018 AncestorType::StopAtClosestButtonElement}, 1019 "[contenteditable]", 1020 // because of <i> is a child of <button>. 1021 "i", 1022 "i"}, 1023 AncestorElementTest{u"<div contenteditable><b><button><i><br>" 1024 u"</i></button></b></div>", 1025 "[contenteditable] button > i", 1026 {AncestorType::MostDistantInlineElementInBlock, 1027 AncestorType::ClosestButtonElement}, 1028 "[contenteditable]", 1029 "[contenteditable] button", 1030 "[contenteditable] button"}, 1031 AncestorElementTest{ 1032 u"<div contenteditable><b><button><br></button></b></div>", 1033 "[contenteditable] > b > button", 1034 {AncestorType::MostDistantInlineElementInBlock, 1035 AncestorType::ClosestButtonElement}, 1036 "[contenteditable]", 1037 "[contenteditable] > b", 1038 "[contenteditable] > b > button"}, 1039 }) { 1040 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1041 doc->NodePrincipal(), IgnoreErrors()); 1042 const Element* const content = body->QuerySelector( 1043 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 1044 MOZ_RELEASE_ASSERT(content); 1045 { 1046 const Element* const expectedElement = 1047 testData.mExpectedSelectorForAncestor 1048 ? body->QuerySelector( 1049 nsDependentCString(testData.mExpectedSelectorForAncestor), 1050 IgnoreErrors()) 1051 : nullptr; 1052 const Element* const result = HTMLEditUtils::GetAncestorElement( 1053 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 1054 IgnoreErrors()), 1055 testData.mAncestorTypes, 1056 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1057 testData.mAncestorLimiterSelector 1058 ? body->QuerySelector( 1059 nsDependentCString(testData.mAncestorLimiterSelector), 1060 IgnoreErrors()) 1061 : nullptr); 1062 EXPECT_EQ(result, expectedElement) 1063 << "GetAncestorElement: " << testData 1064 << "(Got: " << ToString(RefPtr{result}) << ")"; 1065 } 1066 { 1067 const Element* const expectedElement = 1068 testData.mExpectedSelectorForInclusiveAncestor 1069 ? body->QuerySelector( 1070 nsDependentCString( 1071 testData.mExpectedSelectorForInclusiveAncestor), 1072 IgnoreErrors()) 1073 : nullptr; 1074 const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement( 1075 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 1076 IgnoreErrors()), 1077 testData.mAncestorTypes, 1078 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1079 testData.mAncestorLimiterSelector 1080 ? body->QuerySelector( 1081 nsDependentCString(testData.mAncestorLimiterSelector), 1082 IgnoreErrors()) 1083 : nullptr); 1084 EXPECT_EQ(result, expectedElement) 1085 << "GetInclusiveAncestorElement: " << testData 1086 << "(Got: " << ToString(RefPtr{result}) << ")"; 1087 } 1088 } 1089 } 1090 1091 TEST(HTMLEditUtilsTest, GetAncestorElement_IgnoreHRElement) 1092 { 1093 using AncestorType = HTMLEditUtils::AncestorType; 1094 const RefPtr<Document> doc = CreateHTMLDoc(); 1095 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1096 MOZ_RELEASE_ASSERT(body); 1097 for (const auto& testData : { 1098 AncestorElementTest{u"<div contenteditable><hr></div>", 1099 "[contenteditable] > hr", 1100 {AncestorType::ClosestBlockElement, 1101 AncestorType::IgnoreHRElement}, 1102 "[contenteditable]", 1103 "[contenteditable]", 1104 "[contenteditable]"}, 1105 AncestorElementTest{ 1106 u"<div contenteditable><button><hr></button></div>", 1107 "[contenteditable] > button > hr", 1108 {AncestorType::ClosestBlockElement, 1109 AncestorType::ClosestButtonElement, 1110 AncestorType::IgnoreHRElement}, 1111 "[contenteditable]", 1112 "[contenteditable] > button", 1113 "[contenteditable] > button"}, 1114 AncestorElementTest{u"<div contenteditable><span><hr></span></div>", 1115 "[contenteditable] > span > hr", 1116 {AncestorType::MostDistantInlineElementInBlock, 1117 AncestorType::IgnoreHRElement}, 1118 "[contenteditable]", 1119 "[contenteditable] > span", 1120 "[contenteditable] > span"}, 1121 }) { 1122 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1123 doc->NodePrincipal(), IgnoreErrors()); 1124 const Element* const content = body->QuerySelector( 1125 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 1126 MOZ_RELEASE_ASSERT(content); 1127 { 1128 const Element* const expectedElement = 1129 testData.mExpectedSelectorForAncestor 1130 ? body->QuerySelector( 1131 nsDependentCString(testData.mExpectedSelectorForAncestor), 1132 IgnoreErrors()) 1133 : nullptr; 1134 const Element* const result = HTMLEditUtils::GetAncestorElement( 1135 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 1136 IgnoreErrors()), 1137 testData.mAncestorTypes, 1138 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1139 testData.mAncestorLimiterSelector 1140 ? body->QuerySelector( 1141 nsDependentCString(testData.mAncestorLimiterSelector), 1142 IgnoreErrors()) 1143 : nullptr); 1144 EXPECT_EQ(result, expectedElement) 1145 << "GetAncestorElement: " << testData 1146 << "(Got: " << ToString(RefPtr{result}) << ")"; 1147 } 1148 { 1149 const Element* const expectedElement = 1150 testData.mExpectedSelectorForInclusiveAncestor 1151 ? body->QuerySelector( 1152 nsDependentCString( 1153 testData.mExpectedSelectorForInclusiveAncestor), 1154 IgnoreErrors()) 1155 : nullptr; 1156 const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement( 1157 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 1158 IgnoreErrors()), 1159 testData.mAncestorTypes, 1160 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1161 testData.mAncestorLimiterSelector 1162 ? body->QuerySelector( 1163 nsDependentCString(testData.mAncestorLimiterSelector), 1164 IgnoreErrors()) 1165 : nullptr); 1166 EXPECT_EQ(result, expectedElement) 1167 << "GetInclusiveAncestorElement: " << testData 1168 << "(Got: " << ToString(RefPtr{result}) << ")"; 1169 } 1170 } 1171 } 1172 1173 TEST(HTMLEditUtilsTest, GetAncestorElement_ClosestContainerElement) 1174 { 1175 using AncestorType = HTMLEditUtils::AncestorType; 1176 const RefPtr<Document> doc = CreateHTMLDoc(); 1177 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1178 MOZ_RELEASE_ASSERT(body); 1179 for (const auto& testData : { 1180 AncestorElementTest{ 1181 u"<div contenteditable><div><span><br></span></div></div>", 1182 "[contenteditable] > div > span > br", 1183 {AncestorType::ClosestContainerElement}, 1184 "[contenteditable]", 1185 "[contenteditable] > div > span", 1186 "[contenteditable] > div > span"}, 1187 AncestorElementTest{ 1188 u"<div contenteditable><div><span><br></span></div></div>", 1189 "[contenteditable] > div > span", 1190 {AncestorType::ClosestContainerElement}, 1191 "[contenteditable]", 1192 "[contenteditable] > div", 1193 "[contenteditable] > div > span"}, 1194 AncestorElementTest{ 1195 u"<div contenteditable><div><span><br></span></div></div>", 1196 "[contenteditable] > div", 1197 {AncestorType::ClosestContainerElement}, 1198 "[contenteditable]", 1199 "[contenteditable]", 1200 "[contenteditable] > div"}, 1201 AncestorElementTest{u"<br contenteditable>", 1202 "br[contenteditable]", 1203 {AncestorType::ClosestContainerElement}, 1204 "br[contenteditable]", 1205 // Should return nullptr because of scanning 1206 // start from the parent of ancestor limiter. 1207 nullptr, 1208 // <br> is not a container. 1209 nullptr}, 1210 AncestorElementTest{ 1211 u"<br contenteditable>", 1212 "br[contenteditable]", 1213 {AncestorType::ClosestContainerElement, 1214 AncestorType::ReturnAncestorLimiterIfNoProperAncestor}, 1215 "br[contenteditable]", 1216 // Should return nullptr because of scanning start from the 1217 // parent of ancestor limiter. 1218 nullptr, 1219 // <br> is the ancestor limiter. 1220 "br[contenteditable]"}, 1221 }) { 1222 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1223 doc->NodePrincipal(), IgnoreErrors()); 1224 const Element* const content = body->QuerySelector( 1225 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 1226 MOZ_RELEASE_ASSERT(content); 1227 { 1228 const Element* const expectedElement = 1229 testData.mExpectedSelectorForAncestor 1230 ? body->QuerySelector( 1231 nsDependentCString(testData.mExpectedSelectorForAncestor), 1232 IgnoreErrors()) 1233 : nullptr; 1234 const Element* const result = HTMLEditUtils::GetAncestorElement( 1235 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 1236 IgnoreErrors()), 1237 testData.mAncestorTypes, 1238 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1239 testData.mAncestorLimiterSelector 1240 ? body->QuerySelector( 1241 nsDependentCString(testData.mAncestorLimiterSelector), 1242 IgnoreErrors()) 1243 : nullptr); 1244 EXPECT_EQ(result, expectedElement) 1245 << "GetAncestorElement: " << testData 1246 << "(Got: " << ToString(RefPtr{result}) << ")"; 1247 } 1248 { 1249 const Element* const expectedElement = 1250 testData.mExpectedSelectorForInclusiveAncestor 1251 ? body->QuerySelector( 1252 nsDependentCString( 1253 testData.mExpectedSelectorForInclusiveAncestor), 1254 IgnoreErrors()) 1255 : nullptr; 1256 const Element* const result = HTMLEditUtils::GetInclusiveAncestorElement( 1257 *body->QuerySelector(nsDependentCString(testData.mContentSelector), 1258 IgnoreErrors()), 1259 testData.mAncestorTypes, 1260 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1261 testData.mAncestorLimiterSelector 1262 ? body->QuerySelector( 1263 nsDependentCString(testData.mAncestorLimiterSelector), 1264 IgnoreErrors()) 1265 : nullptr); 1266 EXPECT_EQ(result, expectedElement) 1267 << "GetInclusiveAncestorElement: " << testData 1268 << "(Got: " << ToString(RefPtr{result}) << ")"; 1269 } 1270 } 1271 } 1272 1273 TEST(HTMLEditUtilsTest, IsContainerNode) 1274 { 1275 const RefPtr<Document> doc = CreateHTMLDoc(); 1276 for (const char16_t* tagName : 1277 {u"html", u"body", u"div", u"span", u"select", u"option", u"form"}) { 1278 const RefPtr<nsAtom> tag = NS_Atomize(tagName); 1279 MOZ_RELEASE_ASSERT(tag); 1280 const RefPtr<Element> element = doc->CreateHTMLElement(tag); 1281 MOZ_RELEASE_ASSERT(element); 1282 EXPECT_EQ(true, HTMLEditUtils::IsContainerNode(*element)) 1283 << "IsContainerNode(<" << NS_ConvertUTF16toUTF8(tagName).get() << ">)"; 1284 } 1285 for (const char16_t* tagName : {u"img", u"input", u"br", u"wbr"}) { 1286 const RefPtr<nsAtom> tag = NS_Atomize(tagName); 1287 MOZ_RELEASE_ASSERT(tag); 1288 const RefPtr<Element> element = doc->CreateHTMLElement(tag); 1289 MOZ_RELEASE_ASSERT(element); 1290 EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*element)) 1291 << "IsContainerNode(<" << NS_ConvertUTF16toUTF8(tagName).get() << ">)"; 1292 } 1293 { 1294 const RefPtr<nsTextNode> text = doc->CreateEmptyTextNode(); 1295 MOZ_RELEASE_ASSERT(text); 1296 EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*text)) 1297 << "IsContainerNode(Text)"; 1298 } 1299 { 1300 const RefPtr<Comment> comment = 1301 doc->CreateComment(nsDependentString(u"abc")); 1302 MOZ_RELEASE_ASSERT(comment); 1303 EXPECT_EQ(false, HTMLEditUtils::IsContainerNode(*comment)) 1304 << "IsContainerNode(Comment)"; 1305 } 1306 } 1307 1308 struct MOZ_STACK_CLASS IsEmptyNodeTest final { 1309 const char16_t* mInnerHTML; 1310 const char* mTargetSelector; 1311 const HTMLEditUtils::EmptyCheckOptions mOptions; 1312 const bool mExpectedValue; 1313 const bool mExpectedSeenBR; 1314 1315 friend std::ostream& operator<<(std::ostream& aStream, 1316 const IsEmptyNodeTest& aTest) { 1317 return aStream << "Check \"" << aTest.mTargetSelector 1318 << "\" with options=" << ToString(aTest.mOptions).c_str() 1319 << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() 1320 << "\""; 1321 } 1322 }; 1323 1324 TEST(HTMLEditUtilsTest, IsEmptyNode) 1325 { 1326 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 1327 const RefPtr<Document> doc = CreateHTMLDoc(); 1328 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1329 MOZ_RELEASE_ASSERT(body); 1330 for (const auto& testData : { 1331 IsEmptyNodeTest{u"<div></div>", "div", {}, true, false}, 1332 IsEmptyNodeTest{u"<div></div>", 1333 "div", 1334 {EmptyCheckOption::TreatBlockAsVisible}, 1335 true, 1336 false}, 1337 IsEmptyNodeTest{u"<div><br></div>", "div", {}, true, true}, 1338 IsEmptyNodeTest{u"<div><br></div>", 1339 "div", 1340 {EmptyCheckOption::TreatBlockAsVisible}, 1341 true, 1342 true}, 1343 IsEmptyNodeTest{u"<div><br></div>", 1344 "div", 1345 {EmptyCheckOption::TreatSingleBRElementAsVisible}, 1346 false, 1347 false}, 1348 IsEmptyNodeTest{u"<div><!--abc--></div>", "div", {}, true, false}, 1349 IsEmptyNodeTest{u"<div><!--abc--></div>", 1350 "div", 1351 {EmptyCheckOption::TreatCommentAsVisible}, 1352 false, 1353 false}, 1354 IsEmptyNodeTest{u"<ul><li><br></li></ul>", "ul", {}, true, true}, 1355 IsEmptyNodeTest{u"<ul><li><br></li></ul>", 1356 "ul", 1357 {EmptyCheckOption::TreatListItemAsVisible}, 1358 false, 1359 false}, 1360 IsEmptyNodeTest{ 1361 u"<table><td><br></td></table>", "table", {}, true, true}, 1362 IsEmptyNodeTest{u"<table><td><br></td></table>", 1363 "table", 1364 {EmptyCheckOption::TreatTableCellAsVisible}, 1365 false, 1366 false}, 1367 IsEmptyNodeTest{u"<div>abc</div>", "div", {}, false, false}, 1368 IsEmptyNodeTest{ 1369 u"<div><span><br></span></div>", "div", {}, true, true}, 1370 IsEmptyNodeTest{ 1371 u"<div><div><br></div></div>", "div", {}, true, true}, 1372 IsEmptyNodeTest{u"<div><div><br></div></div>", 1373 "div", 1374 {EmptyCheckOption::TreatBlockAsVisible}, 1375 false, 1376 false}, 1377 IsEmptyNodeTest{u"<dl><dt><br></dt></dl>", "dl", {}, true, true}, 1378 IsEmptyNodeTest{u"<dl><dt><br</dt></dl>", 1379 "dl", 1380 {EmptyCheckOption::TreatListItemAsVisible}, 1381 false, 1382 false}, 1383 IsEmptyNodeTest{u"<dl><dd><br></dd></dl>", "dl", {}, true, true}, 1384 IsEmptyNodeTest{u"<dl><dd><br</dd></dl>", 1385 "dl", 1386 {EmptyCheckOption::TreatListItemAsVisible}, 1387 false, 1388 false}, 1389 // form controls should be always not empty. 1390 IsEmptyNodeTest{u"<input>", "input", {}, false, false}, 1391 IsEmptyNodeTest{u"<select></select>", "select", {}, false, false}, 1392 IsEmptyNodeTest{u"<button></button>", "button", {}, false, false}, 1393 IsEmptyNodeTest{ 1394 u"<textarea></textarea>", "textarea", {}, false, false}, 1395 IsEmptyNodeTest{u"<output></output>", "output", {}, false, false}, 1396 IsEmptyNodeTest{ 1397 u"<progress></progress>", "progress", {}, false, false}, 1398 IsEmptyNodeTest{u"<meter></meter>", "meter", {}, false, false}, 1399 // void elements should be always not empty. 1400 IsEmptyNodeTest{u"<br>", "br", {}, false, false}, 1401 IsEmptyNodeTest{u"<wbr>", "wbr", {}, false, false}, 1402 IsEmptyNodeTest{u"<img>", "img", {}, false, false}, 1403 // white-spaces should not be treated as visible in block 1404 IsEmptyNodeTest{u"<div> </div>", "div", {}, true, false}, 1405 IsEmptyNodeTest{u"<span> </span>", "span", {}, true, false}, 1406 IsEmptyNodeTest{u"a<span> </span>b", "span", {}, false, false}, 1407 // sublist's list items and table cells should be treated as visible. 1408 IsEmptyNodeTest{u"<ul><li><ol><li><br></li></ol></li></ul>", 1409 "ul", 1410 {}, 1411 false, 1412 false}, 1413 IsEmptyNodeTest{u"<ul><li><table><td><br></td></table></li></ul>", 1414 "ul", 1415 {}, 1416 false, 1417 false}, 1418 IsEmptyNodeTest{ 1419 u"<table><td><table><td><br></td></table></td></table>", 1420 "table", 1421 {}, 1422 false, 1423 false}, 1424 IsEmptyNodeTest{u"<table><td><ul><li><br></li></ul></td></table>", 1425 "table", 1426 {}, 1427 false, 1428 false}, 1429 }) { 1430 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1431 doc->NodePrincipal(), IgnoreErrors()); 1432 const Element* const target = body->QuerySelector( 1433 nsDependentCString(testData.mTargetSelector), IgnoreErrors()); 1434 MOZ_RELEASE_ASSERT(target); 1435 bool seenBR = false; 1436 const bool ret = 1437 HTMLEditUtils::IsEmptyNode(*target, testData.mOptions, &seenBR); 1438 EXPECT_EQ(ret, testData.mExpectedValue) 1439 << "IsEmptyNode(result): " << testData; 1440 EXPECT_EQ(seenBR, testData.mExpectedSeenBR) 1441 << "IsEmptyNode(seenBR): " << testData; 1442 } 1443 } 1444 1445 struct MOZ_STACK_CLASS GetLeafNodeTest final { 1446 const char16_t* mInnerHTML; 1447 const char* mContentSelector; 1448 const HTMLEditUtils::LeafNodeTypes mTypes; 1449 const char* mExpectedTargetSelector; 1450 const char* mExpectedTargetContainerSelector = nullptr; 1451 const uint32_t mExpectedTargetOffset = 0u; 1452 1453 nsIContent* GetExpectedTarget(nsINode& aNode) const { 1454 if (mExpectedTargetSelector) { 1455 return aNode.QuerySelector(nsDependentCString(mExpectedTargetSelector), 1456 IgnoreErrors()); 1457 } 1458 if (!mExpectedTargetContainerSelector) { 1459 return nullptr; 1460 } 1461 Element* const container = aNode.QuerySelector( 1462 nsDependentCString(mExpectedTargetContainerSelector), IgnoreErrors()); 1463 MOZ_RELEASE_ASSERT(container); 1464 MOZ_RELEASE_ASSERT(!mExpectedTargetOffset || 1465 mExpectedTargetOffset < container->Length()); 1466 return container->GetChildAt_Deprecated(mExpectedTargetOffset); 1467 } 1468 1469 friend std::ostream& operator<<(std::ostream& aStream, 1470 const GetLeafNodeTest& aTest) { 1471 return aStream << "Scan from \"" << aTest.mContentSelector 1472 << "\" with options=" << ToString(aTest.mTypes).c_str() 1473 << " in \"" << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() 1474 << "\""; 1475 } 1476 }; 1477 1478 TEST(HTMLEditUtilsTest, GetLastLeafContent) 1479 { 1480 using LeafNodeType = HTMLEditUtils::LeafNodeType; 1481 const RefPtr<Document> doc = CreateHTMLDoc(); 1482 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1483 MOZ_RELEASE_ASSERT(body); 1484 for (const auto& testData : { 1485 GetLeafNodeTest{u"<div></div>", "div", {}, nullptr}, 1486 GetLeafNodeTest{u"<div><br></div>", "div", {}, "div > br"}, 1487 GetLeafNodeTest{u"<div>abc<br></div>", "div", {}, "div > br"}, 1488 GetLeafNodeTest{u"<div>abc</div>", "div", {}, nullptr, "div", 0u}, 1489 GetLeafNodeTest{ 1490 u"<div><div><br></div></div>", "div", {}, "div > div > br"}, 1491 GetLeafNodeTest{u"<div><div><br></div></div>", 1492 "div", 1493 {LeafNodeType::LeafNodeOrChildBlock}, 1494 "div > div"}, 1495 GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", 1496 "div", 1497 {}, 1498 "div > div + div > br"}, 1499 GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", 1500 "div", 1501 {LeafNodeType::LeafNodeOrChildBlock}, 1502 "div > div + div"}, 1503 GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr}, 1504 GetLeafNodeTest{u"<div><!--abc--></div>", 1505 "div", 1506 {LeafNodeType::TreatCommentAsLeafNode}, 1507 nullptr, 1508 "div", 1509 0u}, 1510 GetLeafNodeTest{u"<div><br><!--abc--></div>", "div", {}, "div > br"}, 1511 GetLeafNodeTest{u"<div><br><!--abc--></div>", 1512 "div", 1513 {LeafNodeType::TreatCommentAsLeafNode}, 1514 nullptr, 1515 "div", 1516 1u}, 1517 GetLeafNodeTest{ 1518 u"<div><div><br></div><div><br></div><!--abc--></div>", 1519 "div", 1520 {}, 1521 "div > div + div > br"}, 1522 GetLeafNodeTest{ 1523 u"<div><div><br></div><div><br></div><!--abc--></div>", 1524 "div", 1525 {LeafNodeType::TreatCommentAsLeafNode}, 1526 nullptr, 1527 "div", 1528 2u}, 1529 }) { 1530 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1531 doc->NodePrincipal(), IgnoreErrors()); 1532 const Element* const target = body->QuerySelector( 1533 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 1534 MOZ_RELEASE_ASSERT(target); 1535 const nsIContent* result = HTMLEditUtils::GetLastLeafContent( 1536 *target, testData.mTypes, 1537 BlockInlineCheck::UseComputedDisplayOutsideStyle); 1538 EXPECT_EQ(result, testData.GetExpectedTarget(*body)) 1539 << "GetLastLeafContent: " << testData 1540 << "(Got: " << ToString(RefPtr{result}) << ")"; 1541 } 1542 } 1543 1544 TEST(HTMLEditUtilsTest, GetFirstLeafContent) 1545 { 1546 using LeafNodeType = HTMLEditUtils::LeafNodeType; 1547 const RefPtr<Document> doc = CreateHTMLDoc(); 1548 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1549 MOZ_RELEASE_ASSERT(body); 1550 for (const auto& testData : { 1551 GetLeafNodeTest{u"<div></div>", "div", {}, nullptr}, 1552 GetLeafNodeTest{u"<div><br></div>", "div", {}, "div > br"}, 1553 GetLeafNodeTest{ 1554 u"<div>abc<br></div>", "div", {}, nullptr, "div", 0u}, 1555 GetLeafNodeTest{u"<div>abc</div>", "div", {}, nullptr, "div", 0u}, 1556 GetLeafNodeTest{ 1557 u"<div><div><br></div></div>", "div", {}, "div > div > br"}, 1558 GetLeafNodeTest{u"<div><div><br></div></div>", 1559 "div", 1560 {LeafNodeType::LeafNodeOrChildBlock}, 1561 "div > div"}, 1562 GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", 1563 "div", 1564 {}, 1565 "div > div > br"}, 1566 GetLeafNodeTest{u"<div><div><br></div><div><br></div></div>", 1567 "div", 1568 {LeafNodeType::LeafNodeOrChildBlock}, 1569 "div > div"}, 1570 GetLeafNodeTest{u"<div><!--abc--></div>", "div", {}, nullptr}, 1571 GetLeafNodeTest{u"<div><!--abc--></div>", 1572 "div", 1573 {LeafNodeType::TreatCommentAsLeafNode}, 1574 nullptr, 1575 "div", 1576 0u}, 1577 GetLeafNodeTest{u"<div><!--abc--><br></div>", "div", {}, "div > br"}, 1578 GetLeafNodeTest{u"<div><!--abc--><br></div>", 1579 "div", 1580 {LeafNodeType::TreatCommentAsLeafNode}, 1581 nullptr, 1582 "div", 1583 0u}, 1584 GetLeafNodeTest{ 1585 u"<div><!--abc--><div><br></div><div><br></div></div>", 1586 "div", 1587 {}, 1588 "div > div > br"}, 1589 GetLeafNodeTest{ 1590 u"<div><!--abc--><div><br></div><div><br></div></div>", 1591 "div", 1592 {LeafNodeType::TreatCommentAsLeafNode}, 1593 nullptr, 1594 "div", 1595 0u}, 1596 }) { 1597 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1598 doc->NodePrincipal(), IgnoreErrors()); 1599 const Element* const target = body->QuerySelector( 1600 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 1601 MOZ_RELEASE_ASSERT(target); 1602 const nsIContent* result = HTMLEditUtils::GetFirstLeafContent( 1603 *target, testData.mTypes, 1604 BlockInlineCheck::UseComputedDisplayOutsideStyle); 1605 EXPECT_EQ(result, testData.GetExpectedTarget(*body)) 1606 << "GetFirstLeafContent: " << testData 1607 << "(Got: " << ToString(RefPtr{result}) << ")"; 1608 } 1609 } 1610 1611 TEST(HTMLEditUtilsTest, GetNextLeafContentOrNextBlockElement_Content) 1612 { 1613 using LeafNodeType = HTMLEditUtils::LeafNodeType; 1614 const RefPtr<Document> doc = CreateHTMLDoc(); 1615 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1616 MOZ_RELEASE_ASSERT(body); 1617 for (const auto& testData : { 1618 GetLeafNodeTest{u"<div><br></div><p><br></p>", "div", {}, "p"}, 1619 GetLeafNodeTest{ 1620 u"<div><br></div><!--abc--><p><br></p>", "div", {}, "p"}, 1621 GetLeafNodeTest{u"<div><br></div><!--abc--><p><br></p>", 1622 "div", 1623 {LeafNodeType::TreatCommentAsLeafNode}, 1624 nullptr, 1625 "body", 1626 1u}, 1627 GetLeafNodeTest{ 1628 u"<div><br></div><span><br></span>", "div", {}, "span > br"}, 1629 GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>", 1630 "div", 1631 {}, 1632 "span > br"}, 1633 GetLeafNodeTest{u"<div><br></div><span><!--abc--><br></span>", 1634 "div", 1635 {LeafNodeType::TreatCommentAsLeafNode}, 1636 nullptr, 1637 "span", 1638 0u}, 1639 }) { 1640 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1641 doc->NodePrincipal(), IgnoreErrors()); 1642 const Element* const target = body->QuerySelector( 1643 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 1644 MOZ_RELEASE_ASSERT(target); 1645 const nsIContent* result = 1646 HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1647 *target, testData.mTypes, 1648 BlockInlineCheck::UseComputedDisplayOutsideStyle); 1649 EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode())) 1650 << "GetNextLeafContentOrNextBlockElement: " << testData 1651 << "(Got: " << ToString(RefPtr{result}) << ")"; 1652 } 1653 } 1654 1655 // TODO: Test GetNextLeafContentOrNextBlockElement() which takes EditorDOMPoint 1656 1657 TEST(HTMLEditUtilsTest, GetPreviousLeafContentOrPreviousBlockElement_Content) 1658 { 1659 using LeafNodeType = HTMLEditUtils::LeafNodeType; 1660 const RefPtr<Document> doc = CreateHTMLDoc(); 1661 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1662 MOZ_RELEASE_ASSERT(body); 1663 for (const auto& testData : { 1664 GetLeafNodeTest{u"<p><br></p><div><br></div>", "div", {}, "p"}, 1665 GetLeafNodeTest{ 1666 u"<p><br></p><!--abc--><div><br></div>", "div", {}, "p"}, 1667 GetLeafNodeTest{u"<p><br></p><!--abc--><div><br></div>", 1668 "div", 1669 {LeafNodeType::TreatCommentAsLeafNode}, 1670 nullptr, 1671 "body", 1672 1u}, 1673 GetLeafNodeTest{ 1674 u"<span><br></span><div><br></div>", "div", {}, "span > br"}, 1675 GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>", 1676 "div", 1677 {}, 1678 "span > br"}, 1679 GetLeafNodeTest{u"<span><br><!--abc--></span><div><br></div>", 1680 "div", 1681 {LeafNodeType::TreatCommentAsLeafNode}, 1682 nullptr, 1683 "span", 1684 1u}, 1685 }) { 1686 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1687 doc->NodePrincipal(), IgnoreErrors()); 1688 const Element* const target = body->QuerySelector( 1689 nsDependentCString(testData.mContentSelector), IgnoreErrors()); 1690 MOZ_RELEASE_ASSERT(target); 1691 const nsIContent* result = 1692 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 1693 *target, testData.mTypes, 1694 BlockInlineCheck::UseComputedDisplayOutsideStyle); 1695 EXPECT_EQ(result, testData.GetExpectedTarget(*body->GetParentNode())) 1696 << "GetPreviousLeafContentOrPreviousBlockElement: " << testData 1697 << "(Got: " << ToString(RefPtr{result}) << ")"; 1698 } 1699 } 1700 1701 // TODO: Test GetPreviousLeafContentOrPreviousBlockElement() which takes 1702 // EditorDOMPoint 1703 1704 struct MOZ_STACK_CLASS LineBreakBeforeBlockBoundaryTest final { 1705 const char16_t* const mInnerHTML; 1706 const char* const mContainer; 1707 const Maybe<uint32_t> 1708 mContainerIndex; // Set if need to use CharacterData in mContainer. 1709 const uint32_t mOffset; 1710 const bool mExpectedResult; // true if the method return a line break 1711 1712 friend std::ostream& operator<<( 1713 std::ostream& aStream, const LineBreakBeforeBlockBoundaryTest& aTest) { 1714 aStream << "Scan from { container: " << aTest.mContainer; 1715 if (aTest.mContainerIndex) { 1716 aStream << "'s " << aTest.mContainerIndex.value() + 1 << "th child"; 1717 } 1718 return aStream << ", offset: " << aTest.mOffset << " } in " 1719 << NS_ConvertUTF16toUTF8(aTest.mInnerHTML).get() << "\""; 1720 } 1721 }; 1722 1723 TEST(HTMLEditUtilsTest, GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem) 1724 { 1725 const RefPtr<Document> doc = CreateHTMLDoc(); 1726 const RefPtr<nsGenericHTMLElement> body = doc->GetBody(); 1727 MOZ_RELEASE_ASSERT(body); 1728 for (const auto& testData : { 1729 LineBreakBeforeBlockBoundaryTest{u"<div contenteditable>abc</div>", 1730 "div", Some(0), 3, false}, 1731 LineBreakBeforeBlockBoundaryTest{u"<div contenteditable>abc</div>", 1732 "div", Nothing{}, 1, false}, 1733 LineBreakBeforeBlockBoundaryTest{u"<div contenteditable><br></div>", 1734 "div", Nothing{}, 0, false}, 1735 LineBreakBeforeBlockBoundaryTest{u"<div contenteditable><br></div>", 1736 "div", Nothing{}, 1, true}, 1737 LineBreakBeforeBlockBoundaryTest{ 1738 u"<div contenteditable><br> </div>", "div", Some(1), 2, true}, 1739 LineBreakBeforeBlockBoundaryTest{ 1740 u"<div contenteditable><br><!-- X --></div>", "div", Nothing{}, 1741 2, 1742 // FIXME: Currently, this fails with a bug of WSRunScanner 1743 // (actually, a bug of 1744 // HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement) 1745 false}, 1746 LineBreakBeforeBlockBoundaryTest{ 1747 u"<div contenteditable><br><br></div>", "div", Nothing{}, 1, 1748 false}, 1749 LineBreakBeforeBlockBoundaryTest{ 1750 u"<div contenteditable><br><p>abc</p></div>", "div", Nothing{}, 1751 1, true}, 1752 }) { 1753 body->SetInnerHTMLTrusted(nsDependentString(testData.mInnerHTML), 1754 doc->NodePrincipal(), IgnoreErrors()); 1755 const Element* const containerElement = body->QuerySelector( 1756 nsDependentCString(testData.mContainer), IgnoreErrors()); 1757 MOZ_ASSERT(containerElement); 1758 const Element* const editingHost = 1759 body->QuerySelector("[contenteditable]"_ns, IgnoreErrors()); 1760 MOZ_ASSERT(editingHost); 1761 const nsIContent* const container = 1762 testData.mContainerIndex 1763 ? containerElement->GetChildAt_Deprecated(*testData.mContainerIndex) 1764 : containerElement; 1765 MOZ_RELEASE_ASSERT(container); 1766 const Maybe<EditorRawLineBreak> result = 1767 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem< 1768 EditorRawLineBreak>(EditorRawDOMPoint(container, testData.mOffset), 1769 *editingHost); 1770 EXPECT_EQ(result.isSome(), testData.mExpectedResult) 1771 << "GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem: " << testData; 1772 } 1773 } 1774 1775 } // namespace mozilla