HTMLEditorState.cpp (26817B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 sw=2 et tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "HTMLEditor.h" 8 9 #include "AutoClonedRangeArray.h" 10 #include "CSSEditUtils.h" 11 #include "EditAction.h" 12 #include "EditorUtils.h" 13 #include "HTMLEditHelpers.h" 14 #include "HTMLEditUtils.h" 15 16 #include "mozilla/Assertions.h" 17 #include "mozilla/OwningNonNull.h" 18 #include "mozilla/dom/Element.h" 19 #include "mozilla/dom/Selection.h" 20 21 #include "nsAString.h" 22 #include "nsAtom.h" 23 #include "nsDebug.h" 24 #include "nsError.h" 25 #include "nsGkAtoms.h" 26 #include "nsIContent.h" 27 #include "nsINode.h" 28 #include "nsRange.h" 29 #include "nsString.h" 30 #include "nsStringFwd.h" 31 #include "nsTArray.h" 32 33 // NOTE: This file was split from: 34 // https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp 35 36 namespace mozilla { 37 38 using namespace dom; 39 40 using EditorType = EditorUtils::EditorType; 41 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 42 43 /***************************************************************************** 44 * ListElementSelectionState 45 ****************************************************************************/ 46 47 ListElementSelectionState::ListElementSelectionState(HTMLEditor& aHTMLEditor, 48 ErrorResult& aRv) { 49 MOZ_ASSERT(!aRv.Failed()); 50 51 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 52 aRv.Throw(NS_ERROR_EDITOR_DESTROYED); 53 return; 54 } 55 56 // XXX Should we create another constructor which won't create 57 // AutoEditActionDataSetter? Or should we create another 58 // AutoEditActionDataSetter which won't nest edit action? 59 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor, 60 EditAction::eNotEditing); 61 if (NS_WARN_IF(!editActionData.CanHandle())) { 62 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 63 return; 64 } 65 66 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost(); 67 if (!editingHostOrRoot) { 68 // This is not a handler of editing command so that if there is no active 69 // editing host, let's use the <body> or document element instead. 70 editingHostOrRoot = aHTMLEditor.GetRoot(); 71 if (!editingHostOrRoot) { 72 return; 73 } 74 } 75 76 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 77 { 78 AutoClonedSelectionRangeArray extendedSelectionRanges( 79 aHTMLEditor.SelectionRef()); 80 extendedSelectionRanges.ExtendRangesToWrapLines( 81 EditSubAction::eCreateOrChangeList, 82 BlockInlineCheck::UseHTMLDefaultStyle, *editingHostOrRoot); 83 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes( 84 aHTMLEditor, arrayOfContents, EditSubAction::eCreateOrChangeList, 85 AutoClonedRangeArray::CollectNonEditableNodes::No); 86 if (NS_FAILED(rv)) { 87 NS_WARNING( 88 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 89 "eCreateOrChangeList, CollectNonEditableNodes::No) failed"); 90 aRv = EditorBase::ToGenericNSResult(rv); 91 return; 92 } 93 } 94 95 // Examine list type for nodes in selection. 96 for (const auto& content : arrayOfContents) { 97 if (!content->IsElement()) { 98 mIsOtherContentSelected = true; 99 } else if (content->IsHTMLElement(nsGkAtoms::ul)) { 100 mIsULElementSelected = true; 101 } else if (content->IsHTMLElement(nsGkAtoms::ol)) { 102 mIsOLElementSelected = true; 103 } else if (content->IsHTMLElement(nsGkAtoms::li)) { 104 if (dom::Element* parent = content->GetParentElement()) { 105 if (parent->IsHTMLElement(nsGkAtoms::ul)) { 106 mIsULElementSelected = true; 107 } else if (parent->IsHTMLElement(nsGkAtoms::ol)) { 108 mIsOLElementSelected = true; 109 } 110 } 111 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::dl, nsGkAtoms::dt, 112 nsGkAtoms::dd)) { 113 mIsDLElementSelected = true; 114 } else { 115 mIsOtherContentSelected = true; 116 } 117 118 if (mIsULElementSelected && mIsOLElementSelected && mIsDLElementSelected && 119 mIsOtherContentSelected) { 120 break; 121 } 122 } 123 } 124 125 /***************************************************************************** 126 * ListItemElementSelectionState 127 ****************************************************************************/ 128 129 ListItemElementSelectionState::ListItemElementSelectionState( 130 HTMLEditor& aHTMLEditor, ErrorResult& aRv) { 131 MOZ_ASSERT(!aRv.Failed()); 132 133 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 134 aRv.Throw(NS_ERROR_EDITOR_DESTROYED); 135 return; 136 } 137 138 // XXX Should we create another constructor which won't create 139 // AutoEditActionDataSetter? Or should we create another 140 // AutoEditActionDataSetter which won't nest edit action? 141 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor, 142 EditAction::eNotEditing); 143 if (NS_WARN_IF(!editActionData.CanHandle())) { 144 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 145 return; 146 } 147 148 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost(); 149 if (!editingHostOrRoot) { 150 // This is not a handler of editing command so that if there is no active 151 // editing host, let's use the <body> or document element instead. 152 editingHostOrRoot = aHTMLEditor.GetRoot(); 153 if (!editingHostOrRoot) { 154 return; 155 } 156 } 157 158 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 159 { 160 AutoClonedSelectionRangeArray extendedSelectionRanges( 161 aHTMLEditor.SelectionRef()); 162 extendedSelectionRanges.ExtendRangesToWrapLines( 163 EditSubAction::eCreateOrChangeList, 164 BlockInlineCheck::UseHTMLDefaultStyle, *editingHostOrRoot); 165 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes( 166 aHTMLEditor, arrayOfContents, EditSubAction::eCreateOrChangeList, 167 AutoClonedRangeArray::CollectNonEditableNodes::No); 168 if (NS_FAILED(rv)) { 169 NS_WARNING_ASSERTION( 170 NS_SUCCEEDED(rv), 171 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 172 "eCreateOrChangeList, CollectNonEditableNodes::No) failed"); 173 aRv = EditorBase::ToGenericNSResult(rv); 174 return; 175 } 176 } 177 178 // examine list type for nodes in selection 179 for (const auto& content : arrayOfContents) { 180 if (!content->IsElement()) { 181 mIsOtherElementSelected = true; 182 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol, 183 nsGkAtoms::li)) { 184 mIsLIElementSelected = true; 185 } else if (content->IsHTMLElement(nsGkAtoms::dt)) { 186 mIsDTElementSelected = true; 187 } else if (content->IsHTMLElement(nsGkAtoms::dd)) { 188 mIsDDElementSelected = true; 189 } else if (content->IsHTMLElement(nsGkAtoms::dl)) { 190 if (mIsDTElementSelected && mIsDDElementSelected) { 191 continue; 192 } 193 // need to look inside dl and see which types of items it has 194 DefinitionListItemScanner scanner(*content->AsElement()); 195 mIsDTElementSelected |= scanner.DTElementFound(); 196 mIsDDElementSelected |= scanner.DDElementFound(); 197 } else { 198 mIsOtherElementSelected = true; 199 } 200 201 if (mIsLIElementSelected && mIsDTElementSelected && mIsDDElementSelected && 202 mIsOtherElementSelected) { 203 break; 204 } 205 } 206 } 207 208 /***************************************************************************** 209 * AlignStateAtSelection 210 ****************************************************************************/ 211 212 AlignStateAtSelection::AlignStateAtSelection(HTMLEditor& aHTMLEditor, 213 ErrorResult& aRv) { 214 MOZ_ASSERT(!aRv.Failed()); 215 216 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 217 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 218 return; 219 } 220 221 // XXX Should we create another constructor which won't create 222 // AutoEditActionDataSetter? Or should we create another 223 // AutoEditActionDataSetter which won't nest edit action? 224 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor, 225 EditAction::eNotEditing); 226 if (NS_WARN_IF(!editActionData.CanHandle())) { 227 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 228 return; 229 } 230 231 if (aHTMLEditor.IsSelectionRangeContainerNotContent()) { 232 NS_WARNING("Some selection containers are not content node, but ignored"); 233 return; 234 } 235 236 // For now, just return first alignment. We don't check if it's mixed. 237 // This is for efficiency given that our current UI doesn't care if it's 238 // mixed. 239 // cmanske: NOT TRUE! We would like to pay attention to mixed state in 240 // [Format] -> [Align] submenu! 241 242 // This routine assumes that alignment is done ONLY by `<div>` elements 243 // if aHTMLEditor is not in CSS mode. 244 245 if (NS_WARN_IF(!aHTMLEditor.GetRoot())) { 246 aRv.Throw(NS_ERROR_FAILURE); 247 return; 248 } 249 250 OwningNonNull<dom::Element> bodyOrDocumentElement = *aHTMLEditor.GetRoot(); 251 EditorRawDOMPoint atBodyOrDocumentElement(bodyOrDocumentElement); 252 253 const nsRange* firstRange = aHTMLEditor.SelectionRef().GetRangeAt(0); 254 mFoundSelectionRanges = !!firstRange; 255 if (!mFoundSelectionRanges) { 256 NS_WARNING("There was no selection range"); 257 aRv.Throw(NS_ERROR_FAILURE); 258 return; 259 } 260 EditorRawDOMPoint atStartOfSelection(firstRange->StartRef()); 261 if (NS_WARN_IF(!atStartOfSelection.IsSet())) { 262 aRv.Throw(NS_ERROR_FAILURE); 263 return; 264 } 265 MOZ_ASSERT(atStartOfSelection.IsSetAndValid()); 266 267 nsIContent* editTargetContent = nullptr; 268 // If selection is collapsed or in a text node, take the container. 269 if (aHTMLEditor.SelectionRef().IsCollapsed() || 270 atStartOfSelection.IsInTextNode()) { 271 editTargetContent = atStartOfSelection.GetContainerAs<nsIContent>(); 272 if (NS_WARN_IF(!editTargetContent)) { 273 aRv.Throw(NS_ERROR_FAILURE); 274 return; 275 } 276 } 277 // If selection container is the `<body>` element which is set to 278 // `HTMLDocument.body`, take first editable node in it. 279 // XXX Why don't we just compare `atStartOfSelection.GetChild()` and 280 // `bodyOrDocumentElement`? Then, we can avoid computing the 281 // offset. 282 else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) && 283 atBodyOrDocumentElement.IsSet() && 284 atStartOfSelection.Offset() == atBodyOrDocumentElement.Offset()) { 285 editTargetContent = HTMLEditUtils::GetNextContent( 286 atStartOfSelection, {WalkTreeOption::IgnoreNonEditableNode}, 287 BlockInlineCheck::Unused, aHTMLEditor.ComputeEditingHost()); 288 if (NS_WARN_IF(!editTargetContent)) { 289 aRv.Throw(NS_ERROR_FAILURE); 290 return; 291 } 292 } 293 // Otherwise, use first selected node. 294 // XXX Only for retrieving it, the following block treats all selected 295 // ranges. `HTMLEditor` should have 296 // `GetFirstSelectionRangeExtendedToHardLineStartAndEnd()`. 297 else { 298 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost(); 299 if (!editingHostOrRoot) { 300 // This is not a handler of editing command so that if there is no active 301 // editing host, let's use the <body> or document element instead. 302 editingHostOrRoot = aHTMLEditor.GetRoot(); 303 if (!editingHostOrRoot) { 304 return; 305 } 306 } 307 AutoClonedSelectionRangeArray extendedSelectionRanges( 308 aHTMLEditor.SelectionRef()); 309 extendedSelectionRanges.ExtendRangesToWrapLines( 310 EditSubAction::eSetOrClearAlignment, 311 BlockInlineCheck::UseHTMLDefaultStyle, *editingHostOrRoot); 312 313 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 314 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes( 315 aHTMLEditor, arrayOfContents, EditSubAction::eSetOrClearAlignment, 316 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 317 if (NS_FAILED(rv)) { 318 NS_WARNING( 319 "AutoClonedRangeArray::CollectEditTargetNodes(eSetOrClearAlignment, " 320 "CollectNonEditableNodes::Yes) failed"); 321 aRv.Throw(NS_ERROR_FAILURE); 322 return; 323 } 324 if (arrayOfContents.IsEmpty()) { 325 NS_WARNING( 326 "AutoClonedRangeArray::CollectEditTargetNodes(eSetOrClearAlignment, " 327 "CollectNonEditableNodes::Yes) returned no contents"); 328 aRv.Throw(NS_ERROR_FAILURE); 329 return; 330 } 331 editTargetContent = arrayOfContents[0]; 332 } 333 334 const RefPtr<dom::Element> maybeNonEditableBlockElement = 335 HTMLEditUtils::GetInclusiveAncestorElement( 336 *editTargetContent, HTMLEditUtils::ClosestBlockElement, 337 BlockInlineCheck::UseHTMLDefaultStyle); 338 if (NS_WARN_IF(!maybeNonEditableBlockElement)) { 339 aRv.Throw(NS_ERROR_FAILURE); 340 return; 341 } 342 343 if (aHTMLEditor.IsCSSEnabled() && EditorElementStyle::Align().IsCSSSettable( 344 *maybeNonEditableBlockElement)) { 345 // We are in CSS mode and we know how to align this element with CSS 346 nsAutoString value; 347 // Let's get the value(s) of text-align or margin-left/margin-right 348 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedCSSEquivalentTo( 349 *maybeNonEditableBlockElement, EditorElementStyle::Align(), value); 350 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 351 aRv.Throw(NS_ERROR_EDITOR_DESTROYED); 352 return; 353 } 354 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 355 "CSSEditUtils::GetComputedCSSEquivalentTo(" 356 "EditorElementStyle::Align()) failed, but ignored"); 357 if (value.EqualsLiteral(u"center") || value.EqualsLiteral(u"-moz-center") || 358 value.EqualsLiteral(u"auto auto")) { 359 mFirstAlign = nsIHTMLEditor::eCenter; 360 return; 361 } 362 if (value.EqualsLiteral(u"right") || value.EqualsLiteral(u"-moz-right") || 363 value.EqualsLiteral(u"auto 0px")) { 364 mFirstAlign = nsIHTMLEditor::eRight; 365 return; 366 } 367 if (value.EqualsLiteral(u"justify")) { 368 mFirstAlign = nsIHTMLEditor::eJustify; 369 return; 370 } 371 // XXX In RTL document, is this expected? 372 mFirstAlign = nsIHTMLEditor::eLeft; 373 return; 374 } 375 376 for (Element* const containerElement : 377 editTargetContent->InclusiveAncestorsOfType<Element>()) { 378 // If the node is a parent `<table>` element of edit target, let's break 379 // here to materialize the 'inline-block' behaviour of html tables 380 // regarding to text alignment. 381 if (containerElement != editTargetContent && 382 containerElement->IsHTMLElement(nsGkAtoms::table)) { 383 return; 384 } 385 386 if (EditorElementStyle::Align().IsCSSSettable(*containerElement)) { 387 nsAutoString value; 388 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty( 389 *containerElement, *nsGkAtoms::textAlign, value); 390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 391 "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::" 392 "textAlign) failed, but ignored"); 393 if (!value.IsEmpty()) { 394 if (value.EqualsLiteral("center")) { 395 mFirstAlign = nsIHTMLEditor::eCenter; 396 return; 397 } 398 if (value.EqualsLiteral("right")) { 399 mFirstAlign = nsIHTMLEditor::eRight; 400 return; 401 } 402 if (value.EqualsLiteral("justify")) { 403 mFirstAlign = nsIHTMLEditor::eJustify; 404 return; 405 } 406 if (value.EqualsLiteral("left")) { 407 mFirstAlign = nsIHTMLEditor::eLeft; 408 return; 409 } 410 // XXX 411 // text-align: start and end aren't supported yet 412 } 413 } 414 415 if (!HTMLEditUtils::IsAlignAttrSupported(*containerElement)) { 416 continue; 417 } 418 419 nsAutoString alignAttributeValue; 420 containerElement->GetAttr(nsGkAtoms::align, alignAttributeValue); 421 if (alignAttributeValue.IsEmpty()) { 422 continue; 423 } 424 425 if (alignAttributeValue.LowerCaseEqualsASCII("center")) { 426 mFirstAlign = nsIHTMLEditor::eCenter; 427 return; 428 } 429 if (alignAttributeValue.LowerCaseEqualsASCII("right")) { 430 mFirstAlign = nsIHTMLEditor::eRight; 431 return; 432 } 433 // XXX This is odd case. `<div align="justify">` is not in any standards. 434 if (alignAttributeValue.LowerCaseEqualsASCII("justify")) { 435 mFirstAlign = nsIHTMLEditor::eJustify; 436 return; 437 } 438 // XXX In RTL document, is this expected? 439 mFirstAlign = nsIHTMLEditor::eLeft; 440 return; 441 } 442 } 443 444 /***************************************************************************** 445 * ParagraphStateAtSelection 446 ****************************************************************************/ 447 448 ParagraphStateAtSelection::ParagraphStateAtSelection( 449 HTMLEditor& aHTMLEditor, FormatBlockMode aFormatBlockMode, 450 ErrorResult& aRv) { 451 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 452 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 453 return; 454 } 455 456 // XXX Should we create another constructor which won't create 457 // AutoEditActionDataSetter? Or should we create another 458 // AutoEditActionDataSetter which won't nest edit action? 459 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor, 460 EditAction::eNotEditing); 461 if (NS_WARN_IF(!editActionData.CanHandle())) { 462 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 463 return; 464 } 465 466 if (aHTMLEditor.IsSelectionRangeContainerNotContent()) { 467 NS_WARNING("Some selection containers are not content node, but ignored"); 468 return; 469 } 470 471 if (MOZ_UNLIKELY(!aHTMLEditor.SelectionRef().RangeCount())) { 472 aRv.Throw(NS_ERROR_FAILURE); 473 return; 474 } 475 476 const Element* const editingHostOrBodyOrDocumentElement = [&]() -> Element* { 477 if (Element* editingHost = aHTMLEditor.ComputeEditingHost()) { 478 return editingHost; 479 } 480 return aHTMLEditor.GetRoot(); 481 }(); 482 if (!editingHostOrBodyOrDocumentElement || 483 !HTMLEditUtils::IsSimplyEditableNode( 484 *editingHostOrBodyOrDocumentElement)) { 485 return; 486 } 487 488 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 489 nsresult rv = CollectEditableFormatNodesInSelection( 490 aHTMLEditor, aFormatBlockMode, *editingHostOrBodyOrDocumentElement, 491 arrayOfContents); 492 if (NS_FAILED(rv)) { 493 NS_WARNING( 494 "ParagraphStateAtSelection::CollectEditableFormatNodesInSelection() " 495 "failed"); 496 aRv.Throw(rv); 497 return; 498 } 499 500 // We need to append descendant format block if block nodes are not format 501 // block. This is so we only have to look "up" the hierarchy to find 502 // format nodes, instead of both up and down. 503 for (size_t index : Reversed(IntegerRange(arrayOfContents.Length()))) { 504 OwningNonNull<nsIContent>& content = arrayOfContents[index]; 505 if (HTMLEditUtils::IsBlockElement(content, 506 BlockInlineCheck::UseHTMLDefaultStyle) && 507 !HTMLEditor::IsFormatElement(aFormatBlockMode, content)) { 508 // XXX This RemoveObject() call has already been commented out and 509 // the above comment explained we're trying to replace non-format 510 // block nodes in the array. According to the following blocks and 511 // `AppendDescendantFormatNodesAndFirstInlineNode()`, replacing 512 // non-format block with descendants format blocks makes sense. 513 // arrayOfContents.RemoveObject(node); 514 ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode( 515 arrayOfContents, aFormatBlockMode, *content->AsElement()); 516 } 517 } 518 519 // We might have an empty node list. if so, find selection parent 520 // and put that on the list 521 if (arrayOfContents.IsEmpty()) { 522 const auto atCaret = 523 aHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>(); 524 if (NS_WARN_IF(!atCaret.IsInContentNode())) { 525 MOZ_ASSERT(false, 526 "We've already checked whether there is a selection range, " 527 "but we have no range right now."); 528 aRv.Throw(NS_ERROR_FAILURE); 529 return; 530 } 531 arrayOfContents.AppendElement(*atCaret.ContainerAs<nsIContent>()); 532 } 533 534 for (auto& content : Reversed(arrayOfContents)) { 535 const Element* formatElement = nullptr; 536 if (HTMLEditor::IsFormatElement(aFormatBlockMode, content)) { 537 formatElement = content->AsElement(); 538 } 539 // Ignore inline contents since its children have been appended 540 // the list above so that we'll handle this descendants later. 541 // XXX: It's odd to ignore block children to consider the mixed state. 542 else if (HTMLEditUtils::IsBlockElement( 543 content, BlockInlineCheck::UseHTMLDefaultStyle)) { 544 continue; 545 } 546 // If we meet an inline node, let's get its parent format. 547 else { 548 for (Element* parentElement : content->AncestorsOfType<Element>()) { 549 // If we reach `HTMLDocument.body` or `Document.documentElement`, 550 // there is no format. 551 if (parentElement == editingHostOrBodyOrDocumentElement) { 552 break; 553 } 554 if (HTMLEditor::IsFormatElement(aFormatBlockMode, *parentElement)) { 555 MOZ_ASSERT(parentElement->NodeInfo()->NameAtom()); 556 formatElement = parentElement; 557 break; 558 } 559 } 560 } 561 562 auto FormatElementIsInclusiveDescendantOfFormatDLElement = [&]() { 563 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) { 564 return false; 565 } 566 if (!formatElement) { 567 return false; 568 } 569 for (const Element* const element : 570 formatElement->InclusiveAncestorsOfType<Element>()) { 571 if (element->IsHTMLElement(nsGkAtoms::dl)) { 572 return true; 573 } 574 if (element->IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt)) { 575 continue; 576 } 577 if (HTMLEditUtils::IsFormatElementForFormatBlockCommand( 578 *formatElement)) { 579 return false; 580 } 581 } 582 return false; 583 }; 584 585 // if this is the first node, we've found, remember it as the format 586 if (!mFirstParagraphState) { 587 mFirstParagraphState = formatElement 588 ? formatElement->NodeInfo()->NameAtom() 589 : nsGkAtoms::_empty; 590 mIsInDLElement = FormatElementIsInclusiveDescendantOfFormatDLElement(); 591 continue; 592 } 593 mIsInDLElement &= FormatElementIsInclusiveDescendantOfFormatDLElement(); 594 // else make sure it matches previously found format 595 if ((!formatElement && mFirstParagraphState != nsGkAtoms::_empty) || 596 (formatElement && 597 !formatElement->IsHTMLElement(mFirstParagraphState))) { 598 mIsMixed = true; 599 break; 600 } 601 } 602 } 603 604 // static 605 void ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode( 606 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 607 FormatBlockMode aFormatBlockMode, dom::Element& aNonFormatBlockElement) { 608 MOZ_ASSERT(HTMLEditUtils::IsBlockElement( 609 aNonFormatBlockElement, BlockInlineCheck::UseHTMLDefaultStyle)); 610 MOZ_ASSERT( 611 !HTMLEditor::IsFormatElement(aFormatBlockMode, aNonFormatBlockElement)); 612 613 // We only need to place any one inline inside this node onto 614 // the list. They are all the same for purposes of determining 615 // paragraph style. We use foundInline to track this as we are 616 // going through the children in the loop below. 617 bool foundInline = false; 618 for (nsIContent* childContent = aNonFormatBlockElement.GetFirstChild(); 619 childContent; childContent = childContent->GetNextSibling()) { 620 const bool isBlock = HTMLEditUtils::IsBlockElement( 621 *childContent, BlockInlineCheck::UseHTMLDefaultStyle); 622 const bool isFormat = 623 HTMLEditor::IsFormatElement(aFormatBlockMode, *childContent); 624 // If the child is a non-format block element, let's check its children 625 // recursively. 626 if (isBlock && !isFormat) { 627 ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode( 628 aArrayOfContents, aFormatBlockMode, *childContent->AsElement()); 629 continue; 630 } 631 632 // If it's a format block, append it. 633 if (isFormat) { 634 aArrayOfContents.AppendElement(*childContent); 635 continue; 636 } 637 638 MOZ_ASSERT(!isBlock); 639 640 // If we haven't found inline node, append only this first inline node. 641 // XXX I think that this makes sense if caller of this removes 642 // aNonFormatBlockElement from aArrayOfContents because the last loop 643 // of the constructor can check parent format block with 644 // aNonFormatBlockElement. 645 if (!foundInline) { 646 foundInline = true; 647 aArrayOfContents.AppendElement(*childContent); 648 continue; 649 } 650 } 651 } 652 653 // static 654 nsresult ParagraphStateAtSelection::CollectEditableFormatNodesInSelection( 655 HTMLEditor& aHTMLEditor, FormatBlockMode aFormatBlockMode, 656 const Element& aEditingHost, 657 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents) { 658 { 659 AutoClonedSelectionRangeArray extendedSelectionRanges( 660 aHTMLEditor.SelectionRef()); 661 extendedSelectionRanges.ExtendRangesToWrapLines( 662 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand 663 ? EditSubAction::eFormatBlockForHTMLCommand 664 : EditSubAction::eCreateOrRemoveBlock, 665 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 666 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes( 667 aHTMLEditor, aArrayOfContents, 668 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand 669 ? EditSubAction::eFormatBlockForHTMLCommand 670 : EditSubAction::eCreateOrRemoveBlock, 671 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 672 if (NS_FAILED(rv)) { 673 NS_WARNING( 674 "AutoClonedRangeArray::CollectEditTargetNodes(" 675 "CollectNonEditableNodes::Yes) failed"); 676 return rv; 677 } 678 } 679 680 // Pre-process our list of nodes 681 for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) { 682 const OwningNonNull<nsIContent> content = aArrayOfContents[index]; 683 684 // Remove all non-editable nodes. Leave them be. 685 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { 686 aArrayOfContents.RemoveElementAt(index); 687 continue; 688 } 689 690 // Scan for table elements. If we find table elements other than table, 691 // replace it with a list of any editable non-table content. Ditto for 692 // list elements. 693 if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(content) || 694 HTMLEditUtils::IsListElement(*content) || 695 HTMLEditUtils::IsListItemElement(*content)) { 696 aArrayOfContents.RemoveElementAt(index); 697 HTMLEditUtils::CollectChildren( 698 content, aArrayOfContents, index, 699 {CollectChildrenOption::CollectListChildren, 700 CollectChildrenOption::CollectTableChildren}); 701 } 702 } 703 return NS_OK; 704 } 705 706 } // namespace mozilla