tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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