tor-browser

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

HTMLStyleEditor.cpp (195203B)


      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 "ErrorList.h"
      7 #include "HTMLEditor.h"
      8 #include "HTMLEditorInlines.h"
      9 #include "HTMLEditorNestedClasses.h"
     10 
     11 #include "AutoClonedRangeArray.h"
     12 #include "CSSEditUtils.h"
     13 #include "EditAction.h"
     14 #include "EditorLineBreak.h"
     15 #include "EditorUtils.h"
     16 #include "HTMLEditHelpers.h"
     17 #include "HTMLEditUtils.h"
     18 #include "PendingStyles.h"
     19 #include "SelectionState.h"
     20 #include "WSRunScanner.h"
     21 
     22 #include "mozilla/Assertions.h"
     23 #include "mozilla/ContentIterator.h"
     24 #include "mozilla/EditorForwards.h"
     25 #include "mozilla/mozalloc.h"
     26 #include "mozilla/SelectionState.h"
     27 #include "mozilla/dom/AncestorIterator.h"
     28 #include "mozilla/dom/Element.h"
     29 #include "mozilla/dom/ElementInlines.h"
     30 #include "mozilla/dom/HTMLBRElement.h"
     31 #include "mozilla/dom/NameSpaceConstants.h"
     32 #include "mozilla/dom/Selection.h"
     33 #include "mozilla/dom/Text.h"
     34 
     35 #include "nsAString.h"
     36 #include "nsAtom.h"
     37 #include "nsAttrName.h"
     38 #include "nsAttrValue.h"
     39 #include "nsCaseTreatment.h"
     40 #include "nsColor.h"
     41 #include "nsComponentManagerUtils.h"
     42 #include "nsDebug.h"
     43 #include "nsError.h"
     44 #include "nsGkAtoms.h"
     45 #include "nsIContent.h"
     46 #include "nsINode.h"
     47 #include "nsIPrincipal.h"
     48 #include "nsISupportsImpl.h"
     49 #include "nsLiteralString.h"
     50 #include "nsNameSpaceManager.h"
     51 #include "nsRange.h"
     52 #include "nsReadableUtils.h"
     53 #include "nsString.h"
     54 #include "nsStringFwd.h"
     55 #include "nsStyledElement.h"
     56 #include "nsTArray.h"
     57 #include "nsTextNode.h"
     58 #include "nsUnicharUtils.h"
     59 
     60 namespace mozilla {
     61 
     62 using namespace dom;
     63 
     64 using EditablePointOption = HTMLEditUtils::EditablePointOption;
     65 using EditablePointOptions = HTMLEditUtils::EditablePointOptions;
     66 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
     67 using LeafNodeType = HTMLEditUtils::LeafNodeType;
     68 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
     69 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
     70 
     71 template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
     72    const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet,
     73    const Element& aEditingHost);
     74 template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
     75    const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet,
     76    const Element& aEditingHost);
     77 
     78 template nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
     79    AutoClonedRangeArray& aRanges,
     80    const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet);
     81 template nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
     82    AutoClonedRangeArray& aRanges,
     83    const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet);
     84 
     85 nsresult HTMLEditor::SetInlinePropertyAsAction(nsStaticAtom& aProperty,
     86                                               nsStaticAtom* aAttribute,
     87                                               const nsAString& aValue,
     88                                               nsIPrincipal* aPrincipal) {
     89  AutoEditActionDataSetter editActionData(
     90      *this,
     91      HTMLEditUtils::GetEditActionForFormatText(aProperty, aAttribute, true),
     92      aPrincipal);
     93  if (NS_WARN_IF(!editActionData.CanHandle())) {
     94    return NS_ERROR_NOT_INITIALIZED;
     95  }
     96 
     97  const RefPtr<Element> editingHost =
     98      ComputeEditingHost(LimitInBodyElement::No);
     99  if (NS_WARN_IF(!editingHost)) {
    100    return NS_ERROR_FAILURE;
    101  }
    102  if (IsPlaintextMailComposer() ||
    103      editingHost->IsContentEditablePlainTextOnly()) {
    104    return NS_SUCCESS_DOM_NO_OPERATION;
    105  }
    106 
    107  switch (editActionData.GetEditAction()) {
    108    case EditAction::eSetFontFamilyProperty:
    109      MOZ_ASSERT(!aValue.IsVoid());
    110      // XXX Should we trim unnecessary white-spaces?
    111      editActionData.SetData(aValue);
    112      break;
    113    case EditAction::eSetColorProperty:
    114    case EditAction::eSetBackgroundColorPropertyInline:
    115      editActionData.SetColorData(aValue);
    116      break;
    117    default:
    118      break;
    119  }
    120 
    121  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
    122  if (NS_FAILED(rv)) {
    123    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
    124                         "MaybeDispatchBeforeInputEvent(), failed");
    125    return EditorBase::ToGenericNSResult(rv);
    126  }
    127 
    128  // XXX Due to bug 1659276 and bug 1659924, we should not scroll selection
    129  //     into view after setting the new style.
    130  AutoPlaceholderBatch treatAsOneTransaction(*this, ScrollSelectionIntoView::No,
    131                                             __FUNCTION__);
    132 
    133  nsStaticAtom* property = &aProperty;
    134  nsStaticAtom* attribute = aAttribute;
    135  nsString value(aValue);
    136  if (attribute == nsGkAtoms::color || attribute == nsGkAtoms::bgcolor) {
    137    if (!IsCSSEnabled()) {
    138      // We allow CSS style color value even in the HTML mode.  In the cases,
    139      // we will apply the style with CSS.  For considering it in the value
    140      // as-is if it's a known CSS keyboard, `rgb()` or `rgba()` style.
    141      // NOTE: It may be later that we set the color into the DOM tree and at
    142      // that time, IsCSSEnabled() may return true.  E.g., setting color value
    143      // to collapsed selection, then, change the CSS enabled, finally, user
    144      // types text there.
    145      if (!HTMLEditUtils::MaybeCSSSpecificColorValue(value)) {
    146        HTMLEditUtils::GetNormalizedHTMLColorValue(value, value);
    147      }
    148    } else {
    149      HTMLEditUtils::GetNormalizedCSSColorValue(
    150          value, HTMLEditUtils::ZeroAlphaColor::RGBAValue, value);
    151    }
    152  }
    153 
    154  AutoTArray<EditorInlineStyle, 1> stylesToRemove;
    155  if (&aProperty == nsGkAtoms::sup) {
    156    // Superscript and Subscript styles are mutually exclusive.
    157    stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sub));
    158  } else if (&aProperty == nsGkAtoms::sub) {
    159    // Superscript and Subscript styles are mutually exclusive.
    160    stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sup));
    161  }
    162  // Handling `<tt>` element code was implemented for composer (bug 115922).
    163  // This shouldn't work with `Document.execCommand()`.  Currently, aPrincipal
    164  // is set only when the root caller is Document::ExecCommand() so that
    165  // we should handle `<tt>` element only when aPrincipal is nullptr that
    166  // must be only when XUL command is executed on composer.
    167  else if (!aPrincipal) {
    168    if (&aProperty == nsGkAtoms::tt) {
    169      stylesToRemove.AppendElement(
    170          EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face));
    171    } else if (&aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::face) {
    172      if (!value.LowerCaseEqualsASCII("tt")) {
    173        stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::tt));
    174      } else {
    175        stylesToRemove.AppendElement(
    176            EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face));
    177        // Override property, attribute and value if the new font face value is
    178        // "tt".
    179        property = nsGkAtoms::tt;
    180        attribute = nullptr;
    181        value.Truncate();
    182      }
    183    }
    184  }
    185 
    186  if (!stylesToRemove.IsEmpty()) {
    187    nsresult rv =
    188        RemoveInlinePropertiesAsSubAction(stylesToRemove, *editingHost);
    189    if (NS_FAILED(rv)) {
    190      NS_WARNING("HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
    191      return rv;
    192    }
    193  }
    194 
    195  AutoTArray<EditorInlineStyleAndValue, 1> styleToSet;
    196  styleToSet.AppendElement(
    197      attribute
    198          ? EditorInlineStyleAndValue(*property, *attribute, std::move(value))
    199          : EditorInlineStyleAndValue(*property));
    200  rv = SetInlinePropertiesAsSubAction(styleToSet, *editingHost);
    201  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    202                       "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
    203  return EditorBase::ToGenericNSResult(rv);
    204 }
    205 
    206 NS_IMETHODIMP HTMLEditor::SetInlineProperty(const nsAString& aProperty,
    207                                            const nsAString& aAttribute,
    208                                            const nsAString& aValue) {
    209  nsStaticAtom* property = NS_GetStaticAtom(aProperty);
    210  if (NS_WARN_IF(!property)) {
    211    return NS_ERROR_INVALID_ARG;
    212  }
    213  nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
    214  AutoEditActionDataSetter editActionData(
    215      *this,
    216      HTMLEditUtils::GetEditActionForFormatText(*property, attribute, true));
    217  if (NS_WARN_IF(!editActionData.CanHandle())) {
    218    return NS_ERROR_NOT_INITIALIZED;
    219  }
    220 
    221  const RefPtr<Element> editingHost =
    222      ComputeEditingHost(LimitInBodyElement::No);
    223  if (NS_WARN_IF(!editingHost)) {
    224    return NS_ERROR_FAILURE;
    225  }
    226  if (IsPlaintextMailComposer() ||
    227      editingHost->IsContentEditablePlainTextOnly()) {
    228    return NS_SUCCESS_DOM_NO_OPERATION;
    229  }
    230 
    231  switch (editActionData.GetEditAction()) {
    232    case EditAction::eSetFontFamilyProperty:
    233      MOZ_ASSERT(!aValue.IsVoid());
    234      // XXX Should we trim unnecessary white-spaces?
    235      editActionData.SetData(aValue);
    236      break;
    237    case EditAction::eSetColorProperty:
    238    case EditAction::eSetBackgroundColorPropertyInline:
    239      editActionData.SetColorData(aValue);
    240      break;
    241    default:
    242      break;
    243  }
    244  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
    245  if (NS_FAILED(rv)) {
    246    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
    247                         "MaybeDispatchBeforeInputEvent(), failed");
    248    return EditorBase::ToGenericNSResult(rv);
    249  }
    250 
    251  AutoTArray<EditorInlineStyleAndValue, 1> styleToSet;
    252  styleToSet.AppendElement(
    253      attribute ? EditorInlineStyleAndValue(*property, *attribute, aValue)
    254                : EditorInlineStyleAndValue(*property));
    255  rv = SetInlinePropertiesAsSubAction(styleToSet, *editingHost);
    256  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    257                       "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
    258  return EditorBase::ToGenericNSResult(rv);
    259 }
    260 
    261 template <size_t N>
    262 nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
    263    const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet,
    264    const Element& aEditingHost) {
    265  MOZ_ASSERT(IsEditActionDataAvailable());
    266  MOZ_ASSERT(!aStylesToSet.IsEmpty());
    267 
    268  DebugOnly<nsresult> rvIgnored = CommitComposition();
    269  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    270                       "EditorBase::CommitComposition() failed, but ignored");
    271  if (MOZ_UNLIKELY(&aEditingHost !=
    272                   ComputeEditingHost(LimitInBodyElement::No))) {
    273    NS_WARNING("Editing host has been changed during committing composition");
    274    return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
    275  }
    276 
    277  if (SelectionRef().IsCollapsed()) {
    278    // Manipulating text attributes on a collapsed selection only sets state
    279    // for the next text insertion
    280    mPendingStylesToApplyToNewContent->PreserveStyles(aStylesToSet);
    281    return NS_OK;
    282  }
    283 
    284  {
    285    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
    286    if (MOZ_UNLIKELY(result.isErr())) {
    287      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
    288      return result.unwrapErr();
    289    }
    290    if (result.inspect().Canceled()) {
    291      return NS_OK;
    292    }
    293  }
    294 
    295  AutoPlaceholderBatch treatAsOneTransaction(
    296      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    297  IgnoredErrorResult ignoredError;
    298  AutoEditSubActionNotifier startToHandleEditSubAction(
    299      *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
    300  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    301    return ignoredError.StealNSResult();
    302  }
    303  NS_WARNING_ASSERTION(
    304      !ignoredError.Failed(),
    305      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    306 
    307  // TODO: We don't need AutoTransactionsConserveSelection here in the normal
    308  //       cases, but removing this may cause the behavior with the legacy
    309  //       mutation event listeners.  We should try to delete this in a bug.
    310  AutoTransactionsConserveSelection dontChangeMySelection(*this);
    311 
    312  AutoClonedSelectionRangeArray selectionRanges(SelectionRef());
    313  nsresult rv = SetInlinePropertiesAroundRanges(selectionRanges, aStylesToSet);
    314  if (NS_FAILED(rv)) {
    315    NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
    316    return rv;
    317  }
    318  MOZ_ASSERT(!selectionRanges.HasSavedRanges());
    319  rv = selectionRanges.ApplyTo(SelectionRef());
    320  if (NS_WARN_IF(Destroyed())) {
    321    return NS_ERROR_EDITOR_DESTROYED;
    322  }
    323  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    324                       "AutoClonedSelectionRangeArray::ApplyTo() failed");
    325  return rv;
    326 }
    327 
    328 template <size_t N>
    329 nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
    330    AutoClonedRangeArray& aRanges,
    331    const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet) {
    332  MOZ_ASSERT(!aRanges.HasSavedRanges());
    333  for (const EditorInlineStyleAndValue& styleToSet : aStylesToSet) {
    334    AutoInlineStyleSetter inlineStyleSetter(styleToSet);
    335    for (OwningNonNull<nsRange>& domRange : aRanges.Ranges()) {
    336      inlineStyleSetter.Reset();
    337      auto rangeOrError =
    338          [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMRange, nsresult> {
    339        EditorDOMRange range(domRange);
    340        // If we're setting <font>, we want to remove ancestors which set
    341        // `font-size` or <font size="..."> recursively.  Therefore, for
    342        // extending the ranges to contain all ancestors in the range, we need
    343        // to split ancestors first.
    344        // XXX: Blink and WebKit inserts <font> elements to inner most
    345        // elements, however, we cannot do it under current design because
    346        // once we contain ancestors which have `font-size` or are
    347        // <font size="...">, we lost the original ranges which we wanted to
    348        // apply the style.  For fixing this, we need to manage both ranges, but
    349        // it's too expensive especially we allow to run script when we touch
    350        // the DOM tree.  Additionally, font-size value affects the height
    351        // of the element, but does not affect the height of ancestor inline
    352        // elements.  Therefore, following the behavior may cause similar issue
    353        // as bug 1808906.  So at least for now, we should not do this big work.
    354        if (styleToSet.IsStyleOfFontElement()) {
    355          Result<SplitRangeOffResult, nsresult> splitAncestorsResult =
    356              SplitAncestorStyledInlineElementsAtRangeEdges(
    357                  range, styleToSet, SplitAtEdges::eDoNotCreateEmptyContainer);
    358          if (MOZ_UNLIKELY(splitAncestorsResult.isErr())) {
    359            NS_WARNING(
    360                "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
    361                "failed");
    362            return splitAncestorsResult.propagateErr();
    363          }
    364          SplitRangeOffResult unwrappedResult = splitAncestorsResult.unwrap();
    365          unwrappedResult.IgnoreCaretPointSuggestion();
    366          range = unwrappedResult.RangeRef();
    367          if (NS_WARN_IF(!range.IsPositionedAndValid())) {
    368            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    369          }
    370        }
    371        Result<EditorRawDOMRange, nsresult> rangeOrError =
    372            inlineStyleSetter.ExtendOrShrinkRangeToApplyTheStyle(*this, range);
    373        if (MOZ_UNLIKELY(rangeOrError.isErr())) {
    374          NS_WARNING(
    375              "HTMLEditor::ExtendOrShrinkRangeToApplyTheStyle() failed, but "
    376              "ignored");
    377          return EditorDOMRange();
    378        }
    379        return EditorDOMRange(rangeOrError.unwrap());
    380      }();
    381      if (MOZ_UNLIKELY(rangeOrError.isErr())) {
    382        return rangeOrError.unwrapErr();
    383      }
    384 
    385      const EditorDOMRange range = rangeOrError.unwrap();
    386      if (!range.IsPositioned()) {
    387        continue;
    388      }
    389 
    390      // If the range is collapsed, we should insert new element there.
    391      if (range.Collapsed()) {
    392        Result<RefPtr<Text>, nsresult> emptyTextNodeOrError =
    393            AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
    394                *this, range.StartRef());
    395        if (MOZ_UNLIKELY(emptyTextNodeOrError.isErr())) {
    396          NS_WARNING(
    397              "AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle() "
    398              "failed");
    399          return emptyTextNodeOrError.unwrapErr();
    400        }
    401        if (MOZ_UNLIKELY(!emptyTextNodeOrError.inspect())) {
    402          continue;  // Couldn't insert text node there
    403        }
    404        RefPtr<Text> emptyTextNode = emptyTextNodeOrError.unwrap();
    405        Result<CaretPoint, nsresult> caretPointOrError =
    406            inlineStyleSetter
    407                .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
    408                    *this, *emptyTextNode);
    409        if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
    410          NS_WARNING(
    411              "AutoInlineStyleSetter::"
    412              "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
    413          return caretPointOrError.unwrapErr();
    414        }
    415        DebugOnly<nsresult> rvIgnored = domRange->CollapseTo(emptyTextNode, 0);
    416        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    417                             "nsRange::CollapseTo() failed, but ignored");
    418        continue;
    419      }
    420 
    421      // Use const_cast hack here for preventing the others to update the range.
    422      AutoTrackDOMRange trackRange(RangeUpdaterRef(),
    423                                   const_cast<EditorDOMRange*>(&range));
    424      auto UpdateSelectionRange = [&]() MOZ_CAN_RUN_SCRIPT {
    425        // If inlineStyleSetter creates elements or setting styles, we should
    426        // select between start of first element and end of last element.
    427        if (inlineStyleSetter.FirstHandledPointRef().IsInContentNode()) {
    428          MOZ_ASSERT(inlineStyleSetter.LastHandledPointRef().IsInContentNode());
    429          const auto startPoint =
    430              !inlineStyleSetter.FirstHandledPointRef().IsStartOfContainer()
    431                  ? inlineStyleSetter.FirstHandledPointRef()
    432                        .To<EditorRawDOMPoint>()
    433                  : HTMLEditUtils::GetDeepestEditableStartPointOf<
    434                        EditorRawDOMPoint>(
    435                        *inlineStyleSetter.FirstHandledPointRef()
    436                             .ContainerAs<nsIContent>(),
    437                        {EditablePointOption::RecognizeInvisibleWhiteSpaces,
    438                         EditablePointOption::StopAtComment});
    439          const auto endPoint =
    440              !inlineStyleSetter.LastHandledPointRef().IsEndOfContainer()
    441                  ? inlineStyleSetter.LastHandledPointRef()
    442                        .To<EditorRawDOMPoint>()
    443                  : HTMLEditUtils::GetDeepestEditableEndPointOf<
    444                        EditorRawDOMPoint>(
    445                        *inlineStyleSetter.LastHandledPointRef()
    446                             .ContainerAs<nsIContent>(),
    447                        {EditablePointOption::RecognizeInvisibleWhiteSpaces,
    448                         EditablePointOption::StopAtComment});
    449          nsresult rv = domRange->SetStartAndEnd(
    450              startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
    451          if (NS_SUCCEEDED(rv)) {
    452            trackRange.StopTracking();
    453            return;
    454          }
    455        }
    456        // Otherwise, use the range computed with the tracking original range.
    457        trackRange.FlushAndStopTracking();
    458        domRange->SetStartAndEnd(range.StartRef().ToRawRangeBoundary(),
    459                                 range.EndRef().ToRawRangeBoundary());
    460      };
    461 
    462      // If range is in a text node, apply new style simply.
    463      if (range.InSameContainer() && range.StartRef().IsInTextNode()) {
    464        // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
    465        // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
    466        Result<SplitRangeOffFromNodeResult, nsresult>
    467            wrapTextInStyledElementResult =
    468                inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
    469                    *this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
    470                    range.StartRef().Offset(), range.EndRef().Offset());
    471        if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
    472          NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
    473          return wrapTextInStyledElementResult.unwrapErr();
    474        }
    475        // The caller should handle the ranges as Selection if necessary, and we
    476        // don't want to update aRanges with this result.
    477        wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
    478        UpdateSelectionRange();
    479        continue;
    480      }
    481 
    482      // Collect editable nodes which are entirely contained in the range.
    483      AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsAroundRange;
    484      {
    485        ContentSubtreeIterator subtreeIter;
    486        // If there is no node which is entirely in the range,
    487        // `ContentSubtreeIterator::Init()` fails, but this is possible case,
    488        // don't warn it.
    489        if (NS_SUCCEEDED(
    490                subtreeIter.Init(range.StartRef().ToRawRangeBoundary(),
    491                                 range.EndRef().ToRawRangeBoundary()))) {
    492          for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
    493            nsINode* node = subtreeIter.GetCurrentNode();
    494            if (NS_WARN_IF(!node)) {
    495              return NS_ERROR_FAILURE;
    496            }
    497            if (MOZ_UNLIKELY(!node->IsContent())) {
    498              continue;
    499            }
    500            // We don't need to wrap non-editable node in new inline element
    501            // nor shouldn't modify `style` attribute of non-editable element.
    502            if (!EditorUtils::IsEditableContent(*node->AsContent(),
    503                                                EditorType::HTML)) {
    504              continue;
    505            }
    506            // We shouldn't wrap invisible text node in new inline element.
    507            if (node->IsText() &&
    508                !HTMLEditUtils::IsVisibleTextNode(*node->AsText())) {
    509              continue;
    510            }
    511            arrayOfContentsAroundRange.AppendElement(*node->AsContent());
    512          }
    513        }
    514      }
    515 
    516      // If start node is a text node, apply new style to a part of it.
    517      if (range.StartRef().IsInTextNode() &&
    518          EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(),
    519                                         EditorType::HTML)) {
    520        // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
    521        // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
    522        Result<SplitRangeOffFromNodeResult, nsresult>
    523            wrapTextInStyledElementResult =
    524                inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
    525                    *this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
    526                    range.StartRef().Offset(),
    527                    range.StartRef().ContainerAs<Text>()->TextDataLength());
    528        if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
    529          NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
    530          return wrapTextInStyledElementResult.unwrapErr();
    531        }
    532        // The caller should handle the ranges as Selection if necessary, and we
    533        // don't want to update aRanges with this result.
    534        wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
    535      }
    536 
    537      // Then, apply new style to all nodes in the range entirely.
    538      for (auto& content : arrayOfContentsAroundRange) {
    539        // MOZ_KnownLive due to bug 1622253.
    540        Result<CaretPoint, nsresult> pointToPutCaretOrError =
    541            inlineStyleSetter
    542                .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
    543                    *this, MOZ_KnownLive(*content));
    544        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
    545          NS_WARNING(
    546              "AutoInlineStyleSetter::"
    547              "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
    548          return pointToPutCaretOrError.unwrapErr();
    549        }
    550        // The caller should handle the ranges as Selection if necessary, and we
    551        // don't want to update aRanges with this result.
    552        pointToPutCaretOrError.inspect().IgnoreCaretPointSuggestion();
    553      }
    554 
    555      // Finally, if end node is a text node, apply new style to a part of it.
    556      if (range.EndRef().IsInTextNode() &&
    557          EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(),
    558                                         EditorType::HTML)) {
    559        // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
    560        // MOZ_KnownLive(styleToSet.mAttribute) due to bug 1622253.
    561        Result<SplitRangeOffFromNodeResult, nsresult>
    562            wrapTextInStyledElementResult =
    563                inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
    564                    *this, MOZ_KnownLive(*range.EndRef().ContainerAs<Text>()),
    565                    0, range.EndRef().Offset());
    566        if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
    567          NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
    568          return wrapTextInStyledElementResult.unwrapErr();
    569        }
    570        // The caller should handle the ranges as Selection if necessary, and we
    571        // don't want to update aRanges with this result.
    572        wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
    573      }
    574      UpdateSelectionRange();
    575    }
    576  }
    577  return NS_OK;
    578 }
    579 
    580 // static
    581 Result<RefPtr<Text>, nsresult>
    582 HTMLEditor::AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
    583    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert) {
    584  auto pointToInsertNewText =
    585      HTMLEditUtils::GetBetterCaretPositionToInsertText<EditorDOMPoint>(
    586          aCandidatePointToInsert);
    587  if (MOZ_UNLIKELY(!pointToInsertNewText.IsSet())) {
    588    return RefPtr<Text>();  // cannot insert text there
    589  }
    590  auto pointToInsertNewStyleOrError =
    591      [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMPoint, nsresult> {
    592    if (!pointToInsertNewText.IsInTextNode()) {
    593      return pointToInsertNewText;
    594    }
    595    if (!pointToInsertNewText.ContainerAs<Text>()->TextDataLength()) {
    596      return pointToInsertNewText;  // Use it
    597    }
    598    if (pointToInsertNewText.IsStartOfContainer()) {
    599      return pointToInsertNewText.ParentPoint();
    600    }
    601    if (pointToInsertNewText.IsEndOfContainer()) {
    602      return EditorDOMPoint::After(*pointToInsertNewText.ContainerAs<Text>());
    603    }
    604    Result<SplitNodeResult, nsresult> splitTextNodeResult =
    605        aHTMLEditor.SplitNodeWithTransaction(pointToInsertNewText);
    606    if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
    607      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
    608      return splitTextNodeResult.propagateErr();
    609    }
    610    SplitNodeResult unwrappedSplitTextNodeResult = splitTextNodeResult.unwrap();
    611    unwrappedSplitTextNodeResult.IgnoreCaretPointSuggestion();
    612    return unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
    613  }();
    614  if (MOZ_UNLIKELY(pointToInsertNewStyleOrError.isErr())) {
    615    return pointToInsertNewStyleOrError.propagateErr();
    616  }
    617 
    618  // If we already have empty text node which is available for placeholder in
    619  // new styled element, let's use it.
    620  if (pointToInsertNewStyleOrError.inspect().IsInTextNode()) {
    621    return RefPtr<Text>(
    622        pointToInsertNewStyleOrError.inspect().ContainerAs<Text>());
    623  }
    624 
    625  // Otherwise, we need an empty text node to create new inline style.
    626  RefPtr<Text> newEmptyTextNode = aHTMLEditor.CreateTextNode(u""_ns);
    627  if (MOZ_UNLIKELY(!newEmptyTextNode)) {
    628    NS_WARNING("EditorBase::CreateTextNode() failed");
    629    return Err(NS_ERROR_FAILURE);
    630  }
    631  Result<CreateTextResult, nsresult> insertNewTextNodeResult =
    632      aHTMLEditor.InsertNodeWithTransaction<Text>(
    633          *newEmptyTextNode, pointToInsertNewStyleOrError.inspect());
    634  if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) {
    635    NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
    636    return insertNewTextNodeResult.propagateErr();
    637  }
    638  insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
    639  return newEmptyTextNode;
    640 }
    641 
    642 Result<bool, nsresult>
    643 HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerForTheStyle(
    644    HTMLEditor& aHTMLEditor, Element& aElement) const {
    645  // If the editor is in the CSS mode and the style can be specified with CSS,
    646  // we should not use existing HTML element as a new container.
    647  const bool isCSSEditable = IsCSSSettable(aElement);
    648  if (!aHTMLEditor.IsCSSEnabled() || !isCSSEditable) {
    649    // First check for <b>, <i>, etc.
    650    if (aElement.IsHTMLElement(&HTMLPropertyRef()) &&
    651        !HTMLEditUtils::ElementHasAttribute(aElement) && !mAttribute) {
    652      return true;
    653    }
    654 
    655    // Now look for things like <font>
    656    if (mAttribute) {
    657      nsString attrValue;
    658      if (aElement.IsHTMLElement(&HTMLPropertyRef()) &&
    659          !HTMLEditUtils::ElementHasAttributeExcept(aElement, *mAttribute) &&
    660          aElement.GetAttr(mAttribute, attrValue)) {
    661        if (attrValue.Equals(mAttributeValue,
    662                             nsCaseInsensitiveStringComparator)) {
    663          return true;
    664        }
    665        if (mAttribute == nsGkAtoms::color ||
    666            mAttribute == nsGkAtoms::bgcolor) {
    667          if (aHTMLEditor.IsCSSEnabled()) {
    668            if (HTMLEditUtils::IsSameCSSColorValue(mAttributeValue,
    669                                                   attrValue)) {
    670              return true;
    671            }
    672          } else if (HTMLEditUtils::IsSameHTMLColorValue(
    673                         mAttributeValue, attrValue,
    674                         HTMLEditUtils::TransparentKeyword::Allowed)) {
    675            return true;
    676          }
    677        }
    678      }
    679    }
    680 
    681    if (!isCSSEditable) {
    682      return false;
    683    }
    684  }
    685 
    686  // No luck so far.  Now we check for a <span> with a single style=""
    687  // attribute that sets only the style we're looking for, if this type of
    688  // style supports it
    689  if (!aElement.IsHTMLElement(nsGkAtoms::span) ||
    690      !aElement.HasAttr(nsGkAtoms::style) ||
    691      HTMLEditUtils::ElementHasAttributeExcept(aElement, *nsGkAtoms::style)) {
    692    return false;
    693  }
    694 
    695  nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement);
    696  if (MOZ_UNLIKELY(!styledElement)) {
    697    return false;
    698  }
    699 
    700  // Some CSS styles are not so simple.  For instance, underline is
    701  // "text-decoration: underline", which decomposes into four different text-*
    702  // properties.  So for now, we just create a span, add the desired style, and
    703  // see if it matches.
    704  RefPtr<Element> newSpanElement =
    705      aHTMLEditor.CreateHTMLContent(nsGkAtoms::span);
    706  if (MOZ_UNLIKELY(!newSpanElement)) {
    707    NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed");
    708    return false;
    709  }
    710  nsStyledElement* styledNewSpanElement =
    711      nsStyledElement::FromNode(newSpanElement);
    712  if (MOZ_UNLIKELY(!styledNewSpanElement)) {
    713    return false;
    714  }
    715  // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
    716  // RefPtr.
    717  Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
    718      WithTransaction::No, aHTMLEditor, MOZ_KnownLive(*styledNewSpanElement),
    719      *this, &mAttributeValue);
    720  if (MOZ_UNLIKELY(result.isErr())) {
    721    // The call shouldn't return destroyed error because it must be
    722    // impossible to run script with modifying the new orphan node.
    723    MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?");
    724    if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
    725      return Err(NS_ERROR_EDITOR_DESTROYED);
    726    }
    727    return false;
    728  }
    729  return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement,
    730                                                     *styledElement);
    731 }
    732 
    733 bool HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerToSetStyle(
    734    nsStyledElement& aStyledElement) const {
    735  if (!HTMLEditUtils::IsContainerNode(aStyledElement) ||
    736      !EditorUtils::IsEditableContent(aStyledElement, EditorType::HTML)) {
    737    return false;
    738  }
    739 
    740  // If it has `style` attribute, let's use it.
    741  if (aStyledElement.HasAttr(nsGkAtoms::style)) {
    742    return true;
    743  }
    744 
    745  // If it has `class` or `id` attribute, the element may have specific rule.
    746  // For applying the new style, we may need to set `style` attribute to it
    747  // to override the specified rule.
    748  if (aStyledElement.HasAttr(nsGkAtoms::id) ||
    749      aStyledElement.HasAttr(nsGkAtoms::_class)) {
    750    return true;
    751  }
    752 
    753  // If we're setting text-decoration and the element represents a value of
    754  // text-decoration, <ins> or <del>, let's use it.
    755  if (IsStyleOfTextDecoration(IgnoreSElement::No) &&
    756      aStyledElement.IsAnyOfHTMLElements(nsGkAtoms::u, nsGkAtoms::s,
    757                                         nsGkAtoms::strike, nsGkAtoms::ins,
    758                                         nsGkAtoms::del)) {
    759    return true;
    760  }
    761 
    762  // If we're setting font-size, color or background-color, we should use <font>
    763  // for compatibility with the other browsers.
    764  if (&HTMLPropertyRef() == nsGkAtoms::font &&
    765      aStyledElement.IsHTMLElement(nsGkAtoms::font)) {
    766    return true;
    767  }
    768 
    769  // If the element has one or more <br> (even if it's invisible), we don't
    770  // want to use the <span> for compatibility with the other browsers.
    771  if (aStyledElement.QuerySelector("br"_ns, IgnoreErrors())) {
    772    return false;
    773  }
    774 
    775  // NOTE: The following code does not match with the other browsers not
    776  //       completely.  Blink considers this with relation with the range.
    777  //       However, we cannot do it now.  We should fix here after or at
    778  //       fixing bug 1792386.
    779 
    780  // If it's only visible element child of parent block, let's use it.
    781  // E.g., we don't want to create new <span> when
    782  // `<p>{  <span>abc</span>  }</p>`.
    783  if (aStyledElement.GetParentElement() &&
    784      HTMLEditUtils::IsBlockElement(
    785          *aStyledElement.GetParentElement(),
    786          BlockInlineCheck::UseComputedDisplayStyle)) {
    787    for (nsIContent* previousSibling = aStyledElement.GetPreviousSibling();
    788         previousSibling;
    789         previousSibling = previousSibling->GetPreviousSibling()) {
    790      if (previousSibling->IsElement()) {
    791        return false;  // Assume any elements visible.
    792      }
    793      if (Text* text = Text::FromNode(previousSibling)) {
    794        if (HTMLEditUtils::IsVisibleTextNode(*text)) {
    795          return false;
    796        }
    797        continue;
    798      }
    799    }
    800    for (nsIContent* nextSibling = aStyledElement.GetNextSibling(); nextSibling;
    801         nextSibling = nextSibling->GetNextSibling()) {
    802      if (nextSibling->IsElement()) {
    803        if (!HTMLEditUtils::IsInvisibleBRElement(*nextSibling)) {
    804          return false;
    805        }
    806        continue;  // The invisible <br> element may be followed by a child
    807                   // block, let's continue to check it.
    808      }
    809      if (Text* text = Text::FromNode(nextSibling)) {
    810        if (HTMLEditUtils::IsVisibleTextNode(*text)) {
    811          return false;
    812        }
    813        continue;
    814      }
    815    }
    816    return true;
    817  }
    818 
    819  // Otherwise, wrap it into new <span> for making
    820  // `<span>[abc</span> <span>def]</span>` become
    821  // `<span style="..."><span>abc</span> <span>def</span></span>` rather
    822  // than `<span style="...">abc <span>def</span></span>`.
    823  return false;
    824 }
    825 
    826 Result<SplitRangeOffFromNodeResult, nsresult>
    827 HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode(
    828    HTMLEditor& aHTMLEditor, Text& aText, uint32_t aStartOffset,
    829    uint32_t aEndOffset) {
    830  const RefPtr<Element> element = aText.GetParentElement();
    831  if (!element || !HTMLEditUtils::CanNodeContain(*element, HTMLPropertyRef())) {
    832    OnHandled(EditorDOMPoint(&aText, aStartOffset),
    833              EditorDOMPoint(&aText, aEndOffset));
    834    return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
    835  }
    836 
    837  // Don't need to do anything if no characters actually selected
    838  if (aStartOffset == aEndOffset) {
    839    OnHandled(EditorDOMPoint(&aText, aStartOffset),
    840              EditorDOMPoint(&aText, aEndOffset));
    841    return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
    842  }
    843 
    844  // Don't need to do anything if property already set on node
    845  if (IsCSSSettable(*element)) {
    846    // The HTML styles defined by this have a CSS equivalence for node;
    847    // let's check if it carries those CSS styles
    848    nsAutoString value(mAttributeValue);
    849    Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
    850        CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this,
    851                                                value);
    852    if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
    853      NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
    854      return isComputedCSSEquivalentToStyleOrError.propagateErr();
    855    }
    856    if (isComputedCSSEquivalentToStyleOrError.unwrap()) {
    857      OnHandled(EditorDOMPoint(&aText, aStartOffset),
    858                EditorDOMPoint(&aText, aEndOffset));
    859      return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
    860    }
    861  } else if (HTMLEditUtils::IsInlineStyleSetByElement(aText, *this,
    862                                                      &mAttributeValue)) {
    863    OnHandled(EditorDOMPoint(&aText, aStartOffset),
    864              EditorDOMPoint(&aText, aEndOffset));
    865    return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
    866  }
    867 
    868  // Make the range an independent node.
    869  auto splitAtEndResult =
    870      [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
    871    EditorDOMPoint atEnd(&aText, aEndOffset);
    872    if (atEnd.IsEndOfContainer()) {
    873      return SplitNodeResult::NotHandled(atEnd);
    874    }
    875    // We need to split off back of text node
    876    Result<SplitNodeResult, nsresult> splitNodeResult =
    877        aHTMLEditor.SplitNodeWithTransaction(atEnd);
    878    if (splitNodeResult.isErr()) {
    879      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
    880      return splitNodeResult;
    881    }
    882    if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) {
    883      NS_WARNING(
    884          "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
    885          "point");
    886      return Err(NS_ERROR_FAILURE);
    887    }
    888    return splitNodeResult;
    889  }();
    890  if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
    891    return splitAtEndResult.propagateErr();
    892  }
    893  SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap();
    894  EditorDOMPoint pointToPutCaret = unwrappedSplitAtEndResult.UnwrapCaretPoint();
    895  auto splitAtStartResult =
    896      [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
    897    EditorDOMPoint atStart(unwrappedSplitAtEndResult.DidSplit()
    898                               ? unwrappedSplitAtEndResult.GetPreviousContent()
    899                               : &aText,
    900                           aStartOffset);
    901    if (atStart.IsStartOfContainer()) {
    902      return SplitNodeResult::NotHandled(atStart);
    903    }
    904    // We need to split off front of text node
    905    Result<SplitNodeResult, nsresult> splitNodeResult =
    906        aHTMLEditor.SplitNodeWithTransaction(atStart);
    907    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
    908      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
    909      return splitNodeResult;
    910    }
    911    if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) {
    912      NS_WARNING(
    913          "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
    914          "point");
    915      return Err(NS_ERROR_FAILURE);
    916    }
    917    return splitNodeResult;
    918  }();
    919  if (MOZ_UNLIKELY(splitAtStartResult.isErr())) {
    920    return splitAtStartResult.propagateErr();
    921  }
    922  SplitNodeResult unwrappedSplitAtStartResult = splitAtStartResult.unwrap();
    923  if (unwrappedSplitAtStartResult.HasCaretPointSuggestion()) {
    924    pointToPutCaret = unwrappedSplitAtStartResult.UnwrapCaretPoint();
    925  }
    926 
    927  MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(),
    928                unwrappedSplitAtStartResult.GetPreviousContent()->IsText());
    929  MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(),
    930                unwrappedSplitAtStartResult.GetNextContent()->IsText());
    931  MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(),
    932                unwrappedSplitAtEndResult.GetPreviousContent()->IsText());
    933  MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(),
    934                unwrappedSplitAtEndResult.GetNextContent()->IsText());
    935  // Note that those text nodes are grabbed by unwrappedSplitAtStartResult,
    936  // unwrappedSplitAtEndResult or the callers.  Therefore, we don't need to make
    937  // them strong pointer.
    938  Text* const leftTextNode =
    939      unwrappedSplitAtStartResult.DidSplit()
    940          ? unwrappedSplitAtStartResult.GetPreviousContentAs<Text>()
    941          : nullptr;
    942  Text* const middleTextNode =
    943      unwrappedSplitAtStartResult.DidSplit()
    944          ? unwrappedSplitAtStartResult.GetNextContentAs<Text>()
    945          : (unwrappedSplitAtEndResult.DidSplit()
    946                 ? unwrappedSplitAtEndResult.GetPreviousContentAs<Text>()
    947                 : &aText);
    948  Text* const rightTextNode =
    949      unwrappedSplitAtEndResult.DidSplit()
    950          ? unwrappedSplitAtEndResult.GetNextContentAs<Text>()
    951          : nullptr;
    952  if (mAttribute) {
    953    // Look for siblings that are correct type of node
    954    nsIContent* sibling = HTMLEditUtils::GetPreviousSibling(
    955        *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode});
    956    if (sibling && sibling->IsElement()) {
    957      OwningNonNull<Element> element(*sibling->AsElement());
    958      Result<bool, nsresult> result =
    959          ElementIsGoodContainerForTheStyle(aHTMLEditor, element);
    960      if (MOZ_UNLIKELY(result.isErr())) {
    961        NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
    962        return result.propagateErr();
    963      }
    964      if (result.inspect()) {
    965        // Previous sib is already right kind of inline node; slide this over
    966        Result<MoveNodeResult, nsresult> moveTextNodeResult =
    967            aHTMLEditor.MoveNodeToEndWithTransaction(
    968                MOZ_KnownLive(*middleTextNode), element);
    969        if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
    970          NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
    971          return moveTextNodeResult.propagateErr();
    972        }
    973        MoveNodeResult unwrappedMoveTextNodeResult =
    974            moveTextNodeResult.unwrap();
    975        unwrappedMoveTextNodeResult.MoveCaretPointTo(
    976            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
    977        OnHandled(*middleTextNode);
    978        return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode,
    979                                           rightTextNode,
    980                                           std::move(pointToPutCaret));
    981      }
    982    }
    983    sibling = HTMLEditUtils::GetNextSibling(
    984        *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode});
    985    if (sibling && sibling->IsElement()) {
    986      OwningNonNull<Element> element(*sibling->AsElement());
    987      Result<bool, nsresult> result =
    988          ElementIsGoodContainerForTheStyle(aHTMLEditor, element);
    989      if (MOZ_UNLIKELY(result.isErr())) {
    990        NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
    991        return result.propagateErr();
    992      }
    993      if (result.inspect()) {
    994        // Following sib is already right kind of inline node; slide this over
    995        Result<MoveNodeResult, nsresult> moveTextNodeResult =
    996            aHTMLEditor.MoveNodeWithTransaction(MOZ_KnownLive(*middleTextNode),
    997                                                EditorDOMPoint(sibling, 0u));
    998        if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
    999          NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   1000          return moveTextNodeResult.propagateErr();
   1001        }
   1002        MoveNodeResult unwrappedMoveTextNodeResult =
   1003            moveTextNodeResult.unwrap();
   1004        unwrappedMoveTextNodeResult.MoveCaretPointTo(
   1005            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   1006        OnHandled(*middleTextNode);
   1007        return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode,
   1008                                           rightTextNode,
   1009                                           std::move(pointToPutCaret));
   1010      }
   1011    }
   1012  }
   1013 
   1014  // Wrap the node inside inline node.
   1015  Result<CaretPoint, nsresult> setStyleResult =
   1016      ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
   1017          aHTMLEditor, MOZ_KnownLive(*middleTextNode));
   1018  if (MOZ_UNLIKELY(setStyleResult.isErr())) {
   1019    NS_WARNING(
   1020        "AutoInlineStyleSetter::"
   1021        "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
   1022    return setStyleResult.propagateErr();
   1023  }
   1024  return SplitRangeOffFromNodeResult(
   1025      leftTextNode, middleTextNode, rightTextNode,
   1026      setStyleResult.unwrap().UnwrapCaretPoint());
   1027 }
   1028 
   1029 Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::ApplyStyle(
   1030    HTMLEditor& aHTMLEditor, nsIContent& aContent) {
   1031  // If this is an element that can't be contained in a span, we have to
   1032  // recurse to its children.
   1033  if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span, aContent)) {
   1034    if (!aContent.HasChildren()) {
   1035      return CaretPoint(EditorDOMPoint());
   1036    }
   1037 
   1038    AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents;
   1039    HTMLEditUtils::CollectChildren(
   1040        aContent, arrayOfContents,
   1041        {CollectChildrenOption::IgnoreNonEditableChildren,
   1042         CollectChildrenOption::IgnoreInvisibleTextNodes});
   1043 
   1044    // Then loop through the list, set the property on each node.
   1045    EditorDOMPoint pointToPutCaret;
   1046    for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
   1047      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   1048      // keep it alive.
   1049      Result<CaretPoint, nsresult> setInlinePropertyResult =
   1050          ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
   1051              aHTMLEditor, MOZ_KnownLive(content));
   1052      if (MOZ_UNLIKELY(setInlinePropertyResult.isErr())) {
   1053        NS_WARNING(
   1054            "AutoInlineStyleSetter::"
   1055            "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
   1056        return setInlinePropertyResult;
   1057      }
   1058      setInlinePropertyResult.unwrap().MoveCaretPointTo(
   1059          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   1060    }
   1061    return CaretPoint(std::move(pointToPutCaret));
   1062  }
   1063 
   1064  // First check if there's an adjacent sibling we can put our node into.
   1065  nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling(
   1066      aContent, {WalkTreeOption::IgnoreNonEditableNode});
   1067  nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling(
   1068      aContent, {WalkTreeOption::IgnoreNonEditableNode});
   1069  if (RefPtr<Element> previousElement =
   1070          Element::FromNodeOrNull(previousSibling)) {
   1071    Result<bool, nsresult> canMoveIntoPreviousSibling =
   1072        ElementIsGoodContainerForTheStyle(aHTMLEditor, *previousElement);
   1073    if (MOZ_UNLIKELY(canMoveIntoPreviousSibling.isErr())) {
   1074      NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
   1075      return canMoveIntoPreviousSibling.propagateErr();
   1076    }
   1077    if (canMoveIntoPreviousSibling.inspect()) {
   1078      Result<MoveNodeResult, nsresult> moveNodeResult =
   1079          aHTMLEditor.MoveNodeToEndWithTransaction(aContent, *previousSibling);
   1080      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   1081        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   1082        return moveNodeResult.propagateErr();
   1083      }
   1084      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   1085      RefPtr<Element> nextElement = Element::FromNodeOrNull(nextSibling);
   1086      if (!nextElement) {
   1087        OnHandled(aContent);
   1088        return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint());
   1089      }
   1090      Result<bool, nsresult> canMoveIntoNextSibling =
   1091          ElementIsGoodContainerForTheStyle(aHTMLEditor, *nextElement);
   1092      if (MOZ_UNLIKELY(canMoveIntoNextSibling.isErr())) {
   1093        NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
   1094        unwrappedMoveNodeResult.IgnoreCaretPointSuggestion();
   1095        return canMoveIntoNextSibling.propagateErr();
   1096      }
   1097      if (!canMoveIntoNextSibling.inspect()) {
   1098        OnHandled(aContent);
   1099        return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint());
   1100      }
   1101      unwrappedMoveNodeResult.IgnoreCaretPointSuggestion();
   1102 
   1103      // JoinNodesWithTransaction (DoJoinNodes) tries to collapse selection to
   1104      // the joined point and we want to skip updating `Selection` here.
   1105      AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
   1106      Result<JoinNodesResult, nsresult> joinNodesResult =
   1107          aHTMLEditor.JoinNodesWithTransaction(*previousElement, *nextElement);
   1108      if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
   1109        NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
   1110        return joinNodesResult.propagateErr();
   1111      }
   1112      // So, let's take it.
   1113      OnHandled(aContent);
   1114      return CaretPoint(
   1115          joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>());
   1116    }
   1117  }
   1118 
   1119  if (RefPtr<Element> nextElement = Element::FromNodeOrNull(nextSibling)) {
   1120    Result<bool, nsresult> canMoveIntoNextSibling =
   1121        ElementIsGoodContainerForTheStyle(aHTMLEditor, *nextElement);
   1122    if (MOZ_UNLIKELY(canMoveIntoNextSibling.isErr())) {
   1123      NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
   1124      return canMoveIntoNextSibling.propagateErr();
   1125    }
   1126    if (canMoveIntoNextSibling.inspect()) {
   1127      Result<MoveNodeResult, nsresult> moveNodeResult =
   1128          aHTMLEditor.MoveNodeWithTransaction(aContent,
   1129                                              EditorDOMPoint(nextElement, 0u));
   1130      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   1131        NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   1132        return moveNodeResult.propagateErr();
   1133      }
   1134      OnHandled(aContent);
   1135      return CaretPoint(moveNodeResult.unwrap().UnwrapCaretPoint());
   1136    }
   1137  }
   1138 
   1139  // Don't need to do anything if property already set on node
   1140  if (const RefPtr<Element> element = aContent.GetAsElementOrParentElement()) {
   1141    if (IsCSSSettable(*element)) {
   1142      nsAutoString value(mAttributeValue);
   1143      // MOZ_KnownLive(element) because it's aContent.
   1144      Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
   1145          CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this,
   1146                                                  value);
   1147      if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
   1148        NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
   1149        return isComputedCSSEquivalentToStyleOrError.propagateErr();
   1150      }
   1151      if (isComputedCSSEquivalentToStyleOrError.unwrap()) {
   1152        OnHandled(aContent);
   1153        return CaretPoint(EditorDOMPoint());
   1154      }
   1155    } else if (HTMLEditUtils::IsInlineStyleSetByElement(*element, *this,
   1156                                                        &mAttributeValue)) {
   1157      OnHandled(aContent);
   1158      return CaretPoint(EditorDOMPoint());
   1159    }
   1160  }
   1161 
   1162  auto ShouldUseCSS = [&]() {
   1163    if (aHTMLEditor.IsCSSEnabled() && aContent.GetAsElementOrParentElement() &&
   1164        IsCSSSettable(*aContent.GetAsElementOrParentElement())) {
   1165      return true;
   1166    }
   1167    // bgcolor is always done using CSS
   1168    if (mAttribute == nsGkAtoms::bgcolor) {
   1169      return true;
   1170    }
   1171    // called for removing parent style, we should use CSS with <span> element.
   1172    if (IsStyleToInvert()) {
   1173      return true;
   1174    }
   1175    // If we set color value, the value may be able to specified only with CSS.
   1176    // In that case, we need to use CSS even in the HTML mode.
   1177    if (mAttribute == nsGkAtoms::color) {
   1178      return mAttributeValue.First() != '#' &&
   1179             !HTMLEditUtils::CanConvertToHTMLColorValue(mAttributeValue);
   1180    }
   1181    return false;
   1182  };
   1183 
   1184  if (ShouldUseCSS()) {
   1185    // We need special handlings for text-decoration.
   1186    if (IsStyleOfTextDecoration(IgnoreSElement::No)) {
   1187      Result<CaretPoint, nsresult> result =
   1188          ApplyCSSTextDecoration(aHTMLEditor, aContent);
   1189      NS_WARNING_ASSERTION(
   1190          result.isOk(),
   1191          "AutoInlineStyleSetter::ApplyCSSTextDecoration() failed");
   1192      return result;
   1193    }
   1194    EditorDOMPoint pointToPutCaret;
   1195    RefPtr<nsStyledElement> styledElement = [&]() -> nsStyledElement* {
   1196      auto* const styledElement = nsStyledElement::FromNode(&aContent);
   1197      return styledElement && ElementIsGoodContainerToSetStyle(*styledElement)
   1198                 ? styledElement
   1199                 : nullptr;
   1200    }();
   1201 
   1202    // If we don't have good element to set the style, let's create new <span>.
   1203    if (!styledElement) {
   1204      Result<CreateElementResult, nsresult> wrapInSpanElementResult =
   1205          aHTMLEditor.InsertContainerWithTransaction(aContent,
   1206                                                     *nsGkAtoms::span);
   1207      if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) {
   1208        NS_WARNING(
   1209            "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
   1210            "failed");
   1211        return wrapInSpanElementResult.propagateErr();
   1212      }
   1213      CreateElementResult unwrappedWrapInSpanElementResult =
   1214          wrapInSpanElementResult.unwrap();
   1215      MOZ_ASSERT(unwrappedWrapInSpanElementResult.GetNewNode());
   1216      unwrappedWrapInSpanElementResult.MoveCaretPointTo(
   1217          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   1218      styledElement = nsStyledElement::FromNode(
   1219          unwrappedWrapInSpanElementResult.GetNewNode());
   1220      MOZ_ASSERT(styledElement);
   1221      if (MOZ_UNLIKELY(!styledElement)) {
   1222        // Don't return error to avoid creating new path to throwing error.
   1223        OnHandled(aContent);
   1224        return CaretPoint(pointToPutCaret);
   1225      }
   1226    }
   1227 
   1228    // Add the CSS styles corresponding to the HTML style request
   1229    if (IsCSSSettable(*styledElement)) {
   1230      Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
   1231          WithTransaction::Yes, aHTMLEditor, *styledElement, *this,
   1232          &mAttributeValue);
   1233      if (MOZ_UNLIKELY(result.isErr())) {
   1234        if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
   1235          return Err(NS_ERROR_EDITOR_DESTROYED);
   1236        }
   1237        NS_WARNING(
   1238            "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored");
   1239      }
   1240    }
   1241    OnHandled(aContent);
   1242    return CaretPoint(pointToPutCaret);
   1243  }
   1244 
   1245  nsAutoString attributeValue(mAttributeValue);
   1246  if (mAttribute == nsGkAtoms::color && mAttributeValue.First() != '#') {
   1247    // At here, all color values should be able to be parsed as a CSS color
   1248    // value.  Therefore, we need to convert it to normalized HTML color value.
   1249    HTMLEditUtils::ConvertToNormalizedHTMLColorValue(attributeValue,
   1250                                                     attributeValue);
   1251  }
   1252 
   1253  // is it already the right kind of node, but with wrong attribute?
   1254  if (aContent.IsHTMLElement(&HTMLPropertyRef())) {
   1255    if (NS_WARN_IF(!mAttribute)) {
   1256      return Err(NS_ERROR_INVALID_ARG);
   1257    }
   1258    // Just set the attribute on it.
   1259    nsresult rv = aHTMLEditor.SetAttributeWithTransaction(
   1260        MOZ_KnownLive(*aContent.AsElement()), *mAttribute, attributeValue);
   1261    if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
   1262      return Err(NS_ERROR_EDITOR_DESTROYED);
   1263    }
   1264    if (NS_FAILED(rv)) {
   1265      NS_WARNING("EditorBase::SetAttributeWithTransaction() failed");
   1266      return Err(rv);
   1267    }
   1268    OnHandled(aContent);
   1269    return CaretPoint(EditorDOMPoint());
   1270  }
   1271 
   1272  // ok, chuck it in its very own container
   1273  Result<CreateElementResult, nsresult> wrapWithNewElementToFormatResult =
   1274      aHTMLEditor.InsertContainerWithTransaction(
   1275          aContent, MOZ_KnownLive(HTMLPropertyRef()),
   1276          !mAttribute ? HTMLEditor::DoNothingForNewElement
   1277                      // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   1278                      : [&](HTMLEditor& aHTMLEditor, Element& aNewElement,
   1279                            const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   1280                          nsresult rv =
   1281                              aNewElement.SetAttr(kNameSpaceID_None, mAttribute,
   1282                                                  attributeValue, false);
   1283                          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1284                                               "Element::SetAttr() failed");
   1285                          return rv;
   1286                        });
   1287  if (MOZ_UNLIKELY(wrapWithNewElementToFormatResult.isErr())) {
   1288    NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
   1289    return wrapWithNewElementToFormatResult.propagateErr();
   1290  }
   1291  OnHandled(aContent);
   1292  MOZ_ASSERT(wrapWithNewElementToFormatResult.inspect().GetNewNode());
   1293  return CaretPoint(
   1294      wrapWithNewElementToFormatResult.unwrap().UnwrapCaretPoint());
   1295 }
   1296 
   1297 Result<CaretPoint, nsresult>
   1298 HTMLEditor::AutoInlineStyleSetter::ApplyCSSTextDecoration(
   1299    HTMLEditor& aHTMLEditor, nsIContent& aContent) {
   1300  MOZ_ASSERT(IsStyleOfTextDecoration(IgnoreSElement::No));
   1301 
   1302  EditorDOMPoint pointToPutCaret;
   1303  RefPtr<nsStyledElement> styledElement = nsStyledElement::FromNode(aContent);
   1304  nsAutoString newTextDecorationValue;
   1305  if (&HTMLPropertyRef() == nsGkAtoms::u) {
   1306    newTextDecorationValue.AssignLiteral(u"underline");
   1307  } else if (&HTMLPropertyRef() == nsGkAtoms::s ||
   1308             &HTMLPropertyRef() == nsGkAtoms::strike) {
   1309    newTextDecorationValue.AssignLiteral(u"line-through");
   1310  } else {
   1311    MOZ_ASSERT_UNREACHABLE(
   1312        "Was new value added in "
   1313        "IsStyleOfTextDecoration(IgnoreSElement::No))?");
   1314  }
   1315  if (styledElement && IsCSSSettable(*styledElement) &&
   1316      ElementIsGoodContainerToSetStyle(*styledElement)) {
   1317    nsAutoString textDecorationValue;
   1318    nsresult rv = CSSEditUtils::GetSpecifiedProperty(
   1319        *styledElement, *nsGkAtoms::text_decoration, textDecorationValue);
   1320    if (NS_FAILED(rv)) {
   1321      NS_WARNING(
   1322          "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::text_decoration) "
   1323          "failed");
   1324      return Err(rv);
   1325    }
   1326    // However, if the element is an element to style the text-decoration,
   1327    // replace it with new <span>.
   1328    if (styledElement && styledElement->IsAnyOfHTMLElements(
   1329                             nsGkAtoms::u, nsGkAtoms::s, nsGkAtoms::strike)) {
   1330      Result<CreateElementResult, nsresult> replaceResult =
   1331          aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
   1332              *styledElement, *nsGkAtoms::span);
   1333      if (MOZ_UNLIKELY(replaceResult.isErr())) {
   1334        NS_WARNING(
   1335            "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
   1336            "failed");
   1337        return replaceResult.propagateErr();
   1338      }
   1339      CreateElementResult unwrappedReplaceResult = replaceResult.unwrap();
   1340      MOZ_ASSERT(unwrappedReplaceResult.GetNewNode());
   1341      unwrappedReplaceResult.MoveCaretPointTo(
   1342          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   1343      // The new <span> needs to specify the original element's text-decoration
   1344      // style unless it's specified explicitly.
   1345      if (textDecorationValue.IsEmpty()) {
   1346        if (!newTextDecorationValue.IsEmpty()) {
   1347          newTextDecorationValue.Append(HTMLEditUtils::kSpace);
   1348        }
   1349        if (styledElement->IsHTMLElement(nsGkAtoms::u)) {
   1350          newTextDecorationValue.AppendLiteral(u"underline");
   1351        } else {
   1352          newTextDecorationValue.AppendLiteral(u"line-through");
   1353        }
   1354      }
   1355      styledElement =
   1356          nsStyledElement::FromNode(unwrappedReplaceResult.GetNewNode());
   1357      if (NS_WARN_IF(!styledElement)) {
   1358        OnHandled(aContent);
   1359        return CaretPoint(pointToPutCaret);
   1360      }
   1361    }
   1362    // If the element has default style, we need to keep it after specifying
   1363    // text-decoration.
   1364    else if (textDecorationValue.IsEmpty() &&
   1365             styledElement->IsAnyOfHTMLElements(nsGkAtoms::u, nsGkAtoms::ins)) {
   1366      if (!newTextDecorationValue.IsEmpty()) {
   1367        newTextDecorationValue.Append(HTMLEditUtils::kSpace);
   1368      }
   1369      newTextDecorationValue.AppendLiteral(u"underline");
   1370    } else if (textDecorationValue.IsEmpty() &&
   1371               styledElement->IsAnyOfHTMLElements(
   1372                   nsGkAtoms::s, nsGkAtoms::strike, nsGkAtoms::del)) {
   1373      if (!newTextDecorationValue.IsEmpty()) {
   1374        newTextDecorationValue.Append(HTMLEditUtils::kSpace);
   1375      }
   1376      newTextDecorationValue.AppendLiteral(u"line-through");
   1377    }
   1378  }
   1379  // Otherwise, use new <span> element.
   1380  else {
   1381    Result<CreateElementResult, nsresult> wrapInSpanElementResult =
   1382        aHTMLEditor.InsertContainerWithTransaction(aContent, *nsGkAtoms::span);
   1383    if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) {
   1384      NS_WARNING(
   1385          "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) failed");
   1386      return wrapInSpanElementResult.propagateErr();
   1387    }
   1388    CreateElementResult unwrappedWrapInSpanElementResult =
   1389        wrapInSpanElementResult.unwrap();
   1390    MOZ_ASSERT(unwrappedWrapInSpanElementResult.GetNewNode());
   1391    unwrappedWrapInSpanElementResult.MoveCaretPointTo(
   1392        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   1393    styledElement = nsStyledElement::FromNode(
   1394        unwrappedWrapInSpanElementResult.GetNewNode());
   1395    if (NS_WARN_IF(!styledElement)) {
   1396      OnHandled(aContent);
   1397      return CaretPoint(pointToPutCaret);
   1398    }
   1399  }
   1400 
   1401  nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction(
   1402      aHTMLEditor, *styledElement, *nsGkAtoms::text_decoration,
   1403      newTextDecorationValue);
   1404  if (NS_FAILED(rv)) {
   1405    NS_WARNING("CSSEditUtils::SetCSSPropertyWithTransaction() failed");
   1406    return Err(rv);
   1407  }
   1408  OnHandled(aContent);
   1409  return CaretPoint(pointToPutCaret);
   1410 }
   1411 
   1412 Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::
   1413    ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor& aHTMLEditor,
   1414                                                       nsIContent& aContent) {
   1415  if (NS_WARN_IF(!aContent.GetParentNode())) {
   1416    return Err(NS_ERROR_FAILURE);
   1417  }
   1418  OwningNonNull<nsINode> parent = *aContent.GetParentNode();
   1419  nsCOMPtr<nsIContent> previousSibling = aContent.GetPreviousSibling(),
   1420                       nextSibling = aContent.GetNextSibling();
   1421  EditorDOMPoint pointToPutCaret;
   1422  if (aContent.IsElement()) {
   1423    Result<EditorDOMPoint, nsresult> removeStyleResult =
   1424        aHTMLEditor.RemoveStyleInside(MOZ_KnownLive(*aContent.AsElement()),
   1425                                      *this, SpecifiedStyle::Preserve);
   1426    if (MOZ_UNLIKELY(removeStyleResult.isErr())) {
   1427      NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
   1428      return removeStyleResult.propagateErr();
   1429    }
   1430    if (removeStyleResult.inspect().IsSet()) {
   1431      pointToPutCaret = removeStyleResult.unwrap();
   1432    }
   1433    if (nsStaticAtom* similarElementNameAtom = GetSimilarElementNameAtom()) {
   1434      Result<EditorDOMPoint, nsresult> removeStyleResult =
   1435          aHTMLEditor.RemoveStyleInside(
   1436              MOZ_KnownLive(*aContent.AsElement()),
   1437              EditorInlineStyle(*similarElementNameAtom),
   1438              SpecifiedStyle::Preserve);
   1439      if (MOZ_UNLIKELY(removeStyleResult.isErr())) {
   1440        NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
   1441        return removeStyleResult.propagateErr();
   1442      }
   1443      if (removeStyleResult.inspect().IsSet()) {
   1444        pointToPutCaret = removeStyleResult.unwrap();
   1445      }
   1446    }
   1447  }
   1448 
   1449  if (aContent.GetParentNode()) {
   1450    // The node is still where it was
   1451    Result<CaretPoint, nsresult> pointToPutCaretOrError =
   1452        ApplyStyle(aHTMLEditor, aContent);
   1453    NS_WARNING_ASSERTION(pointToPutCaretOrError.isOk(),
   1454                         "AutoInlineStyleSetter::ApplyStyle() failed");
   1455    return pointToPutCaretOrError;
   1456  }
   1457 
   1458  // It's vanished.  Use the old siblings for reference to construct a
   1459  // list.  But first, verify that the previous/next siblings are still
   1460  // where we expect them; otherwise we have to give up.
   1461  if (NS_WARN_IF(previousSibling &&
   1462                 previousSibling->GetParentNode() != parent) ||
   1463      NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parent) ||
   1464      NS_WARN_IF(!parent->IsInComposedDoc())) {
   1465    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1466  }
   1467  AutoTArray<OwningNonNull<nsIContent>, 24> nodesToSet;
   1468  for (nsIContent* content = previousSibling ? previousSibling->GetNextSibling()
   1469                                             : parent->GetFirstChild();
   1470       content && content != nextSibling; content = content->GetNextSibling()) {
   1471    if (EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
   1472      nodesToSet.AppendElement(*content);
   1473    }
   1474  }
   1475 
   1476  for (OwningNonNull<nsIContent>& content : nodesToSet) {
   1477    // MOZ_KnownLive because 'nodesToSet' is guaranteed to
   1478    // keep it alive.
   1479    Result<CaretPoint, nsresult> pointToPutCaretOrError =
   1480        ApplyStyle(aHTMLEditor, MOZ_KnownLive(content));
   1481    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   1482      NS_WARNING("AutoInlineStyleSetter::ApplyStyle() failed");
   1483      return pointToPutCaretOrError;
   1484    }
   1485    pointToPutCaretOrError.unwrap().MoveCaretPointTo(
   1486        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   1487  }
   1488 
   1489  return CaretPoint(pointToPutCaret);
   1490 }
   1491 
   1492 bool HTMLEditor::AutoInlineStyleSetter::ContentIsElementSettingTheStyle(
   1493    const HTMLEditor& aHTMLEditor, nsIContent& aContent) const {
   1494  Element* const element = Element::FromNode(&aContent);
   1495  if (!element) {
   1496    return false;
   1497  }
   1498  if (IsRepresentedBy(*element)) {
   1499    return true;
   1500  }
   1501  Result<bool, nsresult> specified = IsSpecifiedBy(aHTMLEditor, *element);
   1502  NS_WARNING_ASSERTION(specified.isOk(),
   1503                       "EditorInlineStyle::IsSpecified() failed, but ignored");
   1504  return specified.unwrapOr(false);
   1505 }
   1506 
   1507 // static
   1508 nsIContent* HTMLEditor::AutoInlineStyleSetter::GetNextEditableInlineContent(
   1509    const nsIContent& aContent, const nsINode* aLimiter) {
   1510  auto* const nextContentInRange = [&]() -> nsIContent* {
   1511    for (nsIContent* parent : aContent.InclusiveAncestorsOfType<nsIContent>()) {
   1512      if (parent == aLimiter ||
   1513          !EditorUtils::IsEditableContent(*parent, EditorType::HTML) ||
   1514          (parent->IsElement() &&
   1515           (HTMLEditUtils::IsBlockElement(
   1516                *parent->AsElement(),
   1517                BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   1518            HTMLEditUtils::IsDisplayInsideFlowRoot(*parent->AsElement())))) {
   1519        return nullptr;
   1520      }
   1521      if (nsIContent* nextSibling = parent->GetNextSibling()) {
   1522        return nextSibling;
   1523      }
   1524    }
   1525    return nullptr;
   1526  }();
   1527  return nextContentInRange &&
   1528                 EditorUtils::IsEditableContent(*nextContentInRange,
   1529                                                EditorType::HTML) &&
   1530                 !HTMLEditUtils::IsBlockElement(
   1531                     *nextContentInRange,
   1532                     BlockInlineCheck::UseComputedDisplayOutsideStyle)
   1533             ? nextContentInRange
   1534             : nullptr;
   1535 }
   1536 
   1537 // static
   1538 nsIContent* HTMLEditor::AutoInlineStyleSetter::GetPreviousEditableInlineContent(
   1539    const nsIContent& aContent, const nsINode* aLimiter) {
   1540  auto* const previousContentInRange = [&]() -> nsIContent* {
   1541    for (nsIContent* parent : aContent.InclusiveAncestorsOfType<nsIContent>()) {
   1542      if (parent == aLimiter ||
   1543          !EditorUtils::IsEditableContent(*parent, EditorType::HTML) ||
   1544          (parent->IsElement() &&
   1545           (HTMLEditUtils::IsBlockElement(
   1546                *parent->AsElement(),
   1547                BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   1548            HTMLEditUtils::IsDisplayInsideFlowRoot(*parent->AsElement())))) {
   1549        return nullptr;
   1550      }
   1551      if (nsIContent* previousSibling = parent->GetPreviousSibling()) {
   1552        return previousSibling;
   1553      }
   1554    }
   1555    return nullptr;
   1556  }();
   1557  return previousContentInRange &&
   1558                 EditorUtils::IsEditableContent(*previousContentInRange,
   1559                                                EditorType::HTML) &&
   1560                 !HTMLEditUtils::IsBlockElement(
   1561                     *previousContentInRange,
   1562                     BlockInlineCheck::UseComputedDisplayOutsideStyle)
   1563             ? previousContentInRange
   1564             : nullptr;
   1565 }
   1566 
   1567 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter::GetShrunkenRangeStart(
   1568    const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
   1569    const nsINode& aCommonAncestorOfRange,
   1570    const nsIContent* aFirstEntirelySelectedContentNodeInRange) const {
   1571  const EditorDOMPoint& startRef = aRange.StartRef();
   1572  // <a> cannot be nested and it should be represented with one element as far
   1573  // as possible.  Therefore, we don't need to shrink the range.
   1574  if (IsStyleOfAnchorElement()) {
   1575    return startRef.To<EditorRawDOMPoint>();
   1576  }
   1577  // If the start boundary is at end of a node, we need to shrink the range
   1578  // to next content, e.g., `abc[<b>def` should be `abc<b>[def` unless the
   1579  // <b> is not entirely selected.
   1580  auto* const nextContentOrStartContainer = [&]() -> nsIContent* {
   1581    if (!startRef.IsInContentNode()) {
   1582      return nullptr;
   1583    }
   1584    if (!startRef.IsEndOfContainer()) {
   1585      return startRef.ContainerAs<nsIContent>();
   1586    }
   1587    nsIContent* const nextContent =
   1588        AutoInlineStyleSetter::GetNextEditableInlineContent(
   1589            *startRef.ContainerAs<nsIContent>(), &aCommonAncestorOfRange);
   1590    return nextContent ? nextContent : startRef.ContainerAs<nsIContent>();
   1591  }();
   1592  if (MOZ_UNLIKELY(!nextContentOrStartContainer)) {
   1593    return startRef.To<EditorRawDOMPoint>();
   1594  }
   1595  EditorRawDOMPoint startPoint =
   1596      nextContentOrStartContainer != startRef.ContainerAs<nsIContent>()
   1597          ? EditorRawDOMPoint(nextContentOrStartContainer)
   1598          : startRef.To<EditorRawDOMPoint>();
   1599  MOZ_ASSERT(startPoint.IsSet());
   1600  // If the start point points a content node, let's try to move it down to
   1601  // start of the child recursively.
   1602  while (nsIContent* child = startPoint.GetChild()) {
   1603    // We shouldn't cross editable and block boundary.
   1604    if (!EditorUtils::IsEditableContent(*child, EditorType::HTML) ||
   1605        HTMLEditUtils::IsBlockElement(
   1606            *child, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   1607      break;
   1608    }
   1609    // If we reach a text node, the minimized range starts from start of it.
   1610    if (child->IsText()) {
   1611      startPoint.Set(child, 0u);
   1612      break;
   1613    }
   1614    // Don't shrink the range into element which applies the style to children
   1615    // because we want to update the element.  E.g., if we are setting
   1616    // background color, we want to update style attribute of an element which
   1617    // specifies background color with `style` attribute.
   1618    if (child == aFirstEntirelySelectedContentNodeInRange) {
   1619      break;
   1620    }
   1621    // We should not start from an atomic element such as <br>, <img>, etc.
   1622    if (!HTMLEditUtils::IsContainerNode(*child)) {
   1623      break;
   1624    }
   1625    // If the element specifies the style, we should update it.  Therefore, we
   1626    // need to wrap it in the range.
   1627    if (ContentIsElementSettingTheStyle(aHTMLEditor, *child)) {
   1628      break;
   1629    }
   1630    // If the child is an `<a>`, we should not shrink the range into it
   1631    // because user may not want to keep editing in the link except when user
   1632    // tries to update selection into it obviously.
   1633    if (child->IsHTMLElement(nsGkAtoms::a)) {
   1634      break;
   1635    }
   1636    startPoint.Set(child, 0u);
   1637  }
   1638  return startPoint;
   1639 }
   1640 
   1641 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter::GetShrunkenRangeEnd(
   1642    const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
   1643    const nsINode& aCommonAncestorOfRange,
   1644    const nsIContent* aLastEntirelySelectedContentNodeInRange) const {
   1645  const EditorDOMPoint& endRef = aRange.EndRef();
   1646  // <a> cannot be nested and it should be represented with one element as far
   1647  // as possible.  Therefore, we don't need to shrink the range.
   1648  if (IsStyleOfAnchorElement()) {
   1649    return endRef.To<EditorRawDOMPoint>();
   1650  }
   1651  // If the end boundary is at start of a node, we need to shrink the range
   1652  // to previous content, e.g., `abc</b>]def` should be `abc]</b>def` unless
   1653  // the <b> is not entirely selected.
   1654  auto* const previousContentOrEndContainer = [&]() -> nsIContent* {
   1655    if (!endRef.IsInContentNode()) {
   1656      return nullptr;
   1657    }
   1658    if (!endRef.IsStartOfContainer()) {
   1659      return endRef.ContainerAs<nsIContent>();
   1660    }
   1661    nsIContent* const previousContent =
   1662        AutoInlineStyleSetter::GetPreviousEditableInlineContent(
   1663            *endRef.ContainerAs<nsIContent>(), &aCommonAncestorOfRange);
   1664    return previousContent ? previousContent : endRef.ContainerAs<nsIContent>();
   1665  }();
   1666  if (MOZ_UNLIKELY(!previousContentOrEndContainer)) {
   1667    return endRef.To<EditorRawDOMPoint>();
   1668  }
   1669  EditorRawDOMPoint endPoint =
   1670      previousContentOrEndContainer != endRef.ContainerAs<nsIContent>()
   1671          ? EditorRawDOMPoint::After(*previousContentOrEndContainer)
   1672          : endRef.To<EditorRawDOMPoint>();
   1673  MOZ_ASSERT(endPoint.IsSet());
   1674  // If the end point points after a content node, let's try to move it down
   1675  // to end of the child recursively.
   1676  while (nsIContent* child = endPoint.GetPreviousSiblingOfChild()) {
   1677    // We shouldn't cross editable and block boundary.
   1678    if (!EditorUtils::IsEditableContent(*child, EditorType::HTML) ||
   1679        HTMLEditUtils::IsBlockElement(
   1680            *child, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   1681      break;
   1682    }
   1683    // If we reach a text node, the minimized range starts from start of it.
   1684    if (child->IsText()) {
   1685      endPoint.SetToEndOf(child);
   1686      break;
   1687    }
   1688    // Don't shrink the range into element which applies the style to children
   1689    // because we want to update the element.  E.g., if we are setting
   1690    // background color, we want to update style attribute of an element which
   1691    // specifies background color with `style` attribute.
   1692    if (child == aLastEntirelySelectedContentNodeInRange) {
   1693      break;
   1694    }
   1695    // We should not end in an atomic element such as <br>, <img>, etc.
   1696    if (!HTMLEditUtils::IsContainerNode(*child)) {
   1697      break;
   1698    }
   1699    // If the element specifies the style, we should update it.  Therefore, we
   1700    // need to wrap it in the range.
   1701    if (ContentIsElementSettingTheStyle(aHTMLEditor, *child)) {
   1702      break;
   1703    }
   1704    // If the child is an `<a>`, we should not shrink the range into it
   1705    // because user may not want to keep editing in the link except when user
   1706    // tries to update selection into it obviously.
   1707    if (child->IsHTMLElement(nsGkAtoms::a)) {
   1708      break;
   1709    }
   1710    endPoint.SetToEndOf(child);
   1711  }
   1712  return endPoint;
   1713 }
   1714 
   1715 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter::
   1716    GetExtendedRangeStartToWrapAncestorApplyingSameStyle(
   1717        const HTMLEditor& aHTMLEditor,
   1718        const EditorRawDOMPoint& aStartPoint) const {
   1719  MOZ_ASSERT(aStartPoint.IsSetAndValid());
   1720 
   1721  EditorRawDOMPoint startPoint = aStartPoint;
   1722  // FIXME: This should handle ignore invisible white-spaces before the position
   1723  // if it's in a text node or invisible white-spaces.
   1724  if (!startPoint.IsStartOfContainer() ||
   1725      startPoint.GetContainer()->GetPreviousSibling()) {
   1726    return startPoint;
   1727  }
   1728 
   1729  // FYI: Currently, we don't support setting `font-size` even in the CSS mode.
   1730  // Therefore, if the style is <font size="...">, we always set a <font>.
   1731  const bool isSettingFontElement =
   1732      IsStyleOfFontSize() ||
   1733      (!aHTMLEditor.IsCSSEnabled() && IsStyleOfFontElement());
   1734  Element* mostDistantStartParentHavingStyle = nullptr;
   1735  for (Element* parent :
   1736       startPoint.GetContainer()->InclusiveAncestorsOfType<Element>()) {
   1737    if (!EditorUtils::IsEditableContent(*parent, EditorType::HTML) ||
   1738        HTMLEditUtils::IsBlockElement(
   1739            *parent, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   1740        HTMLEditUtils::IsDisplayInsideFlowRoot(*parent)) {
   1741      break;
   1742    }
   1743    if (ContentIsElementSettingTheStyle(aHTMLEditor, *parent)) {
   1744      mostDistantStartParentHavingStyle = parent;
   1745    }
   1746    // If we're setting <font> element and there is a <font> element which is
   1747    // entirely selected, we should use it.
   1748    else if (isSettingFontElement && parent->IsHTMLElement(nsGkAtoms::font)) {
   1749      mostDistantStartParentHavingStyle = parent;
   1750    }
   1751    if (parent->GetPreviousSibling()) {
   1752      break;  // The parent is not first element in its parent, stop climbing.
   1753    }
   1754  }
   1755  if (mostDistantStartParentHavingStyle) {
   1756    startPoint.Set(mostDistantStartParentHavingStyle);
   1757  }
   1758  return startPoint;
   1759 }
   1760 
   1761 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter::
   1762    GetExtendedRangeEndToWrapAncestorApplyingSameStyle(
   1763        const HTMLEditor& aHTMLEditor,
   1764        const EditorRawDOMPoint& aEndPoint) const {
   1765  MOZ_ASSERT(aEndPoint.IsSetAndValid());
   1766 
   1767  EditorRawDOMPoint endPoint = aEndPoint;
   1768  // FIXME: This should ignore invisible white-spaces after the position if it's
   1769  // in a text node, invisible <br> or following invisible text nodes.
   1770  if (!endPoint.IsEndOfContainer() ||
   1771      endPoint.GetContainer()->GetNextSibling()) {
   1772    return endPoint;
   1773  }
   1774 
   1775  // FYI: Currently, we don't support setting `font-size` even in the CSS mode.
   1776  // Therefore, if the style is <font size="...">, we always set a <font>.
   1777  const bool isSettingFontElement =
   1778      IsStyleOfFontSize() ||
   1779      (!aHTMLEditor.IsCSSEnabled() && IsStyleOfFontElement());
   1780  Element* mostDistantEndParentHavingStyle = nullptr;
   1781  for (Element* parent :
   1782       endPoint.GetContainer()->InclusiveAncestorsOfType<Element>()) {
   1783    if (!EditorUtils::IsEditableContent(*parent, EditorType::HTML) ||
   1784        HTMLEditUtils::IsBlockElement(
   1785            *parent, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   1786        HTMLEditUtils::IsDisplayInsideFlowRoot(*parent)) {
   1787      break;
   1788    }
   1789    if (ContentIsElementSettingTheStyle(aHTMLEditor, *parent)) {
   1790      mostDistantEndParentHavingStyle = parent;
   1791    }
   1792    // If we're setting <font> element and there is a <font> element which is
   1793    // entirely selected, we should use it.
   1794    else if (isSettingFontElement && parent->IsHTMLElement(nsGkAtoms::font)) {
   1795      mostDistantEndParentHavingStyle = parent;
   1796    }
   1797    if (parent->GetNextSibling()) {
   1798      break;  // The parent is not last element in its parent, stop climbing.
   1799    }
   1800  }
   1801  if (mostDistantEndParentHavingStyle) {
   1802    endPoint.SetAfter(mostDistantEndParentHavingStyle);
   1803  }
   1804  return endPoint;
   1805 }
   1806 
   1807 EditorRawDOMRange HTMLEditor::AutoInlineStyleSetter::
   1808    GetExtendedRangeToMinimizeTheNumberOfNewElements(
   1809        const HTMLEditor& aHTMLEditor, const nsINode& aCommonAncestor,
   1810        EditorRawDOMPoint&& aStartPoint, EditorRawDOMPoint&& aEndPoint) const {
   1811  MOZ_ASSERT(aStartPoint.IsSet());
   1812  MOZ_ASSERT(aEndPoint.IsSet());
   1813 
   1814  // For minimizing the number of new elements, we should extend the range as
   1815  // far as possible. E.g., `<span>[abc</span> <span>def]</span>` should be
   1816  // styled as `<b><span>abc</span> <span>def</span></b>`.
   1817  // Similarly, if the range crosses a block boundary, we should do same thing.
   1818  // I.e., `<p><span>[abc</span></p><p><span>def]</span></p>` should become
   1819  // `<p><b><span>abc</span></b></p><p><b><span>def</span></b></p>`.
   1820  if (aStartPoint.GetContainer() != aEndPoint.GetContainer()) {
   1821    while (aStartPoint.GetContainer() != &aCommonAncestor &&
   1822           aStartPoint.IsInContentNode() && aStartPoint.GetContainerParent() &&
   1823           aStartPoint.IsStartOfContainer()) {
   1824      if (!EditorUtils::IsEditableContent(
   1825              *aStartPoint.ContainerAs<nsIContent>(), EditorType::HTML) ||
   1826          (aStartPoint.ContainerAs<nsIContent>()->IsElement() &&
   1827           (HTMLEditUtils::IsBlockElement(
   1828                *aStartPoint.ContainerAs<Element>(),
   1829                BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   1830            HTMLEditUtils::IsDisplayInsideFlowRoot(
   1831                *aStartPoint.ContainerAs<Element>())))) {
   1832        break;
   1833      }
   1834      aStartPoint = aStartPoint.ParentPoint();
   1835    }
   1836    while (aEndPoint.GetContainer() != &aCommonAncestor &&
   1837           aEndPoint.IsInContentNode() && aEndPoint.GetContainerParent() &&
   1838           aEndPoint.IsEndOfContainer()) {
   1839      if (!EditorUtils::IsEditableContent(*aEndPoint.ContainerAs<nsIContent>(),
   1840                                          EditorType::HTML) ||
   1841          (aEndPoint.ContainerAs<nsIContent>()->IsElement() &&
   1842           (HTMLEditUtils::IsBlockElement(
   1843                *aEndPoint.ContainerAs<Element>(),
   1844                BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   1845            HTMLEditUtils::IsDisplayInsideFlowRoot(
   1846                *aEndPoint.ContainerAs<Element>())))) {
   1847        break;
   1848      }
   1849      aEndPoint.SetAfter(aEndPoint.ContainerAs<nsIContent>());
   1850    }
   1851  }
   1852 
   1853  // Additionally, if we'll set a CSS style, we want to wrap elements which
   1854  // should have the new style into the range to avoid creating new <span>
   1855  // element.
   1856  if (!IsRepresentableWithHTML() ||
   1857      (aHTMLEditor.IsCSSEnabled() && IsCSSSettable(*nsGkAtoms::span))) {
   1858    // First, if pointing in a text node, use parent point.
   1859    if (aStartPoint.IsInContentNode() && aStartPoint.IsStartOfContainer() &&
   1860        aStartPoint.GetContainerParentAs<nsIContent>() &&
   1861        EditorUtils::IsEditableContent(
   1862            *aStartPoint.ContainerParentAs<nsIContent>(), EditorType::HTML) &&
   1863        (!aStartPoint.GetContainerAs<Element>() ||
   1864         !HTMLEditUtils::IsContainerNode(
   1865             *aStartPoint.ContainerAs<nsIContent>())) &&
   1866        EditorUtils::IsEditableContent(*aStartPoint.ContainerAs<nsIContent>(),
   1867                                       EditorType::HTML)) {
   1868      aStartPoint = aStartPoint.ParentPoint();
   1869      MOZ_ASSERT(aStartPoint.IsSet());
   1870    }
   1871    if (aEndPoint.IsInContentNode() && aEndPoint.IsEndOfContainer() &&
   1872        aEndPoint.GetContainerParentAs<nsIContent>() &&
   1873        EditorUtils::IsEditableContent(
   1874            *aEndPoint.ContainerParentAs<nsIContent>(), EditorType::HTML) &&
   1875        (!aEndPoint.GetContainerAs<Element>() ||
   1876         !HTMLEditUtils::IsContainerNode(
   1877             *aEndPoint.ContainerAs<nsIContent>())) &&
   1878        EditorUtils::IsEditableContent(*aEndPoint.ContainerAs<nsIContent>(),
   1879                                       EditorType::HTML)) {
   1880      aEndPoint.SetAfter(aEndPoint.GetContainer());
   1881      MOZ_ASSERT(aEndPoint.IsSet());
   1882    }
   1883    // Then, wrap the container if it's a good element to set a CSS property.
   1884    if (aStartPoint.IsInContentNode() && aStartPoint.GetContainerParent() &&
   1885        // The point must be start of the container
   1886        aStartPoint.IsStartOfContainer() &&
   1887        // only if the pointing first child node cannot have `style` attribute
   1888        (!aStartPoint.GetChildAs<nsStyledElement>() ||
   1889         !ElementIsGoodContainerToSetStyle(
   1890             *aStartPoint.ChildAs<nsStyledElement>())) &&
   1891        // but don't cross block boundary at climbing up the tree
   1892        !HTMLEditUtils::IsBlockElement(
   1893            *aStartPoint.ContainerAs<nsIContent>(),
   1894            BlockInlineCheck::UseComputedDisplayOutsideStyle) &&
   1895        // and the container is a good editable element to set CSS style
   1896        aStartPoint.GetContainerAs<nsStyledElement>() &&
   1897        ElementIsGoodContainerToSetStyle(
   1898            *aStartPoint.ContainerAs<nsStyledElement>())) {
   1899      aStartPoint = aStartPoint.ParentPoint();
   1900      MOZ_ASSERT(aStartPoint.IsSet());
   1901    }
   1902    if (aEndPoint.IsInContentNode() && aEndPoint.GetContainerParent() &&
   1903        // The point must be end of the container
   1904        aEndPoint.IsEndOfContainer() &&
   1905        // only if the pointing last child node cannot have `style` attribute
   1906        (aEndPoint.IsStartOfContainer() ||
   1907         !aEndPoint.GetPreviousSiblingOfChildAs<nsStyledElement>() ||
   1908         !ElementIsGoodContainerToSetStyle(
   1909             *aEndPoint.GetPreviousSiblingOfChildAs<nsStyledElement>())) &&
   1910        // but don't cross block boundary at climbing up the tree
   1911        !HTMLEditUtils::IsBlockElement(
   1912            *aEndPoint.ContainerAs<nsIContent>(),
   1913            BlockInlineCheck::UseComputedDisplayOutsideStyle) &&
   1914        // and the container is a good editable element to set CSS style
   1915        aEndPoint.GetContainerAs<nsStyledElement>() &&
   1916        ElementIsGoodContainerToSetStyle(
   1917            *aEndPoint.ContainerAs<nsStyledElement>())) {
   1918      aEndPoint.SetAfter(aEndPoint.GetContainer());
   1919      MOZ_ASSERT(aEndPoint.IsSet());
   1920    }
   1921  }
   1922 
   1923  return EditorRawDOMRange(std::move(aStartPoint), std::move(aEndPoint));
   1924 }
   1925 
   1926 Result<EditorRawDOMRange, nsresult>
   1927 HTMLEditor::AutoInlineStyleSetter::ExtendOrShrinkRangeToApplyTheStyle(
   1928    const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) const {
   1929  if (NS_WARN_IF(!aRange.IsPositioned())) {
   1930    return Err(NS_ERROR_FAILURE);
   1931  }
   1932 
   1933  // For avoiding assertion hits in the utility methods, check whether the
   1934  // range is in same subtree, first. Even if the range crosses a subtree
   1935  // boundary, it's not a bug of this module.
   1936  nsINode* commonAncestor = aRange.GetClosestCommonInclusiveAncestor();
   1937  if (NS_WARN_IF(!commonAncestor)) {
   1938    return Err(NS_ERROR_FAILURE);
   1939  }
   1940 
   1941  // If the range does not select only invisible <br> element, let's extend the
   1942  // range to contain the <br> element.
   1943  EditorDOMRange range(aRange);
   1944  if (range.EndRef().IsInContentNode()) {
   1945    const WSScanResult nextContentData =
   1946        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1947            {WSRunScanner::Option::OnlyEditableNodes}, range.EndRef());
   1948    if (nextContentData.ReachedInvisibleBRElement() &&
   1949        nextContentData.BRElementPtr()->GetParentElement() &&
   1950        HTMLEditUtils::IsInlineContent(
   1951            *nextContentData.BRElementPtr()->GetParentElement(),
   1952            BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   1953      range.SetEnd(EditorDOMPoint::After(*nextContentData.BRElementPtr()));
   1954      MOZ_ASSERT(range.EndRef().IsSet());
   1955      commonAncestor = range.GetClosestCommonInclusiveAncestor();
   1956      if (NS_WARN_IF(!commonAncestor)) {
   1957        return Err(NS_ERROR_FAILURE);
   1958      }
   1959    }
   1960  }
   1961 
   1962  // If the range is collapsed, we don't want to replace ancestors unless it's
   1963  // in an empty element.
   1964  if (range.Collapsed() && range.StartRef().GetContainer()->Length()) {
   1965    return EditorRawDOMRange(range);
   1966  }
   1967 
   1968  // First, shrink the given range to minimize new style applied contents.
   1969  // However, we should not shrink the range into entirely selected element.
   1970  // E.g., if `abc[<i>def</i>]ghi`, shouldn't shrink it as
   1971  // `abc<i>[def]</i>ghi`.
   1972  EditorRawDOMPoint startPoint, endPoint;
   1973  if (range.Collapsed()) {
   1974    startPoint = endPoint = range.StartRef().To<EditorRawDOMPoint>();
   1975  } else {
   1976    ContentSubtreeIterator iter;
   1977    if (NS_FAILED(iter.Init(range.StartRef().ToRawRangeBoundary(),
   1978                            range.EndRef().ToRawRangeBoundary()))) {
   1979      NS_WARNING("ContentSubtreeIterator::Init() failed");
   1980      return Err(NS_ERROR_FAILURE);
   1981    }
   1982    nsIContent* const firstContentEntirelyInRange =
   1983        nsIContent::FromNodeOrNull(iter.GetCurrentNode());
   1984    nsIContent* const lastContentEntirelyInRange = [&]() {
   1985      iter.Last();
   1986      return nsIContent::FromNodeOrNull(iter.GetCurrentNode());
   1987    }();
   1988 
   1989    // Compute the shrunken range boundaries.
   1990    startPoint = GetShrunkenRangeStart(aHTMLEditor, range, *commonAncestor,
   1991                                       firstContentEntirelyInRange);
   1992    MOZ_ASSERT(startPoint.IsSet());
   1993    endPoint = GetShrunkenRangeEnd(aHTMLEditor, range, *commonAncestor,
   1994                                   lastContentEntirelyInRange);
   1995    MOZ_ASSERT(endPoint.IsSet());
   1996 
   1997    // If shrunken range is swapped, it could like this case:
   1998    // `abc[</span><span>]def`, starts at very end of a node and ends at
   1999    // very start of immediately next node.  In this case, we should use
   2000    // the original range instead.
   2001    if (MOZ_UNLIKELY(!startPoint.EqualsOrIsBefore(endPoint))) {
   2002      startPoint = range.StartRef().To<EditorRawDOMPoint>();
   2003      endPoint = range.EndRef().To<EditorRawDOMPoint>();
   2004    }
   2005  }
   2006 
   2007  // Then, we may need to extend the range to wrap parent inline elements
   2008  // which specify same style since we need to remove same style elements to
   2009  // apply new value.  E.g., abc
   2010  //   <span style="background-color: red">
   2011  //     <span style="background-color: blue">[def]</span>
   2012  //   </span>
   2013  // ghi
   2014  // In this case, we need to wrap the other <span> element if setting
   2015  // background color.  Then, the inner <span> element is removed and the
   2016  // other <span> element's style attribute will be updated rather than
   2017  // inserting new <span> element.
   2018  startPoint = GetExtendedRangeStartToWrapAncestorApplyingSameStyle(aHTMLEditor,
   2019                                                                    startPoint);
   2020  MOZ_ASSERT(startPoint.IsSet());
   2021  endPoint =
   2022      GetExtendedRangeEndToWrapAncestorApplyingSameStyle(aHTMLEditor, endPoint);
   2023  MOZ_ASSERT(endPoint.IsSet());
   2024 
   2025  // Finally, we need to extend the range unless the range is in an element to
   2026  // reduce the number of creating new elements.  E.g., if now selects
   2027  // `<span>[abc</span><span>def]</span>`, we should make it
   2028  // `<b><span>abc</span><span>def</span></b>` rather than
   2029  // `<span><b>abc</b></span><span><b>def</b></span>`.
   2030  EditorRawDOMRange finalRange =
   2031      GetExtendedRangeToMinimizeTheNumberOfNewElements(
   2032          aHTMLEditor, *commonAncestor, std::move(startPoint),
   2033          std::move(endPoint));
   2034 #if 0
   2035  fprintf(stderr,
   2036          "ExtendOrShrinkRangeToApplyTheStyle:\n"
   2037          "  Result: {(\n    %s\n  ) - (\n    %s\n  )},\n"
   2038          "  Input: {(\n    %s\n  ) - (\n    %s\n  )}\n",
   2039          ToString(finalRange.StartRef()).c_str(),
   2040          ToString(finalRange.EndRef()).c_str(),
   2041          ToString(aRange.StartRef()).c_str(),
   2042          ToString(aRange.EndRef()).c_str());
   2043 #endif
   2044  return finalRange;
   2045 }
   2046 
   2047 Result<SplitRangeOffResult, nsresult>
   2048 HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
   2049    const EditorDOMRange& aRange, const EditorInlineStyle& aStyle,
   2050    SplitAtEdges aSplitAtEdges) {
   2051  MOZ_ASSERT(IsEditActionDataAvailable());
   2052 
   2053  if (NS_WARN_IF(!aRange.IsPositioned())) {
   2054    return Err(NS_ERROR_FAILURE);
   2055  }
   2056 
   2057  EditorDOMRange range(aRange);
   2058 
   2059  // split any matching style nodes above the start of range
   2060  auto resultAtStart =
   2061      [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
   2062    AutoTrackDOMRange tracker(RangeUpdaterRef(), &range);
   2063    Result<SplitNodeResult, nsresult> result =
   2064        SplitAncestorStyledInlineElementsAt(range.StartRef(), aStyle,
   2065                                            aSplitAtEdges);
   2066    if (MOZ_UNLIKELY(result.isErr())) {
   2067      NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
   2068      return result;
   2069    }
   2070    tracker.FlushAndStopTracking();
   2071    if (result.inspect().Handled()) {
   2072      auto startOfRange = result.inspect().AtSplitPoint<EditorDOMPoint>();
   2073      if (!startOfRange.IsSet()) {
   2074        result.inspect().IgnoreCaretPointSuggestion();
   2075        NS_WARNING(
   2076            "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
   2077            "split point");
   2078        return Err(NS_ERROR_FAILURE);
   2079      }
   2080      range.SetStart(std::move(startOfRange));
   2081    } else if (MOZ_UNLIKELY(!range.IsPositioned())) {
   2082      NS_WARNING(
   2083          "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected "
   2084          "DOM tree");
   2085      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2086    }
   2087    return result;
   2088  }();
   2089  if (MOZ_UNLIKELY(resultAtStart.isErr())) {
   2090    return resultAtStart.propagateErr();
   2091  }
   2092  SplitNodeResult unwrappedResultAtStart = resultAtStart.unwrap();
   2093 
   2094  // second verse, same as the first...
   2095  auto resultAtEnd =
   2096      [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
   2097    AutoTrackDOMRange tracker(RangeUpdaterRef(), &range);
   2098    Result<SplitNodeResult, nsresult> result =
   2099        SplitAncestorStyledInlineElementsAt(range.EndRef(), aStyle,
   2100                                            aSplitAtEdges);
   2101    if (MOZ_UNLIKELY(result.isErr())) {
   2102      NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
   2103      return result;
   2104    }
   2105    tracker.FlushAndStopTracking();
   2106    if (result.inspect().Handled()) {
   2107      auto endOfRange = result.inspect().AtSplitPoint<EditorDOMPoint>();
   2108      if (!endOfRange.IsSet()) {
   2109        result.inspect().IgnoreCaretPointSuggestion();
   2110        NS_WARNING(
   2111            "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
   2112            "split point");
   2113        return Err(NS_ERROR_FAILURE);
   2114      }
   2115      range.SetEnd(std::move(endOfRange));
   2116    } else if (MOZ_UNLIKELY(!range.IsPositioned())) {
   2117      NS_WARNING(
   2118          "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected "
   2119          "DOM tree");
   2120      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2121    }
   2122    return result;
   2123  }();
   2124  if (MOZ_UNLIKELY(resultAtEnd.isErr())) {
   2125    unwrappedResultAtStart.IgnoreCaretPointSuggestion();
   2126    return resultAtEnd.propagateErr();
   2127  }
   2128 
   2129  return SplitRangeOffResult(std::move(range),
   2130                             std::move(unwrappedResultAtStart),
   2131                             resultAtEnd.unwrap());
   2132 }
   2133 
   2134 Result<SplitNodeResult, nsresult>
   2135 HTMLEditor::SplitAncestorStyledInlineElementsAt(
   2136    const EditorDOMPoint& aPointToSplit, const EditorInlineStyle& aStyle,
   2137    SplitAtEdges aSplitAtEdges) {
   2138  // If the point is in a non-content node, e.g., in the document node, we
   2139  // should split nothing.
   2140  if (MOZ_UNLIKELY(!aPointToSplit.IsInContentNode())) {
   2141    return SplitNodeResult::NotHandled(aPointToSplit);
   2142  }
   2143 
   2144  // We assume that this method is called only when we're removing style(s).
   2145  // Even if we're in HTML mode and there is no presentation element in the
   2146  // block, we may need to overwrite the block's style with `<span>` element
   2147  // and CSS.  For example, `<h1>` element has `font-weight: bold;` as its
   2148  // default style.  If `Document.execCommand("bold")` is called for its
   2149  // text, we should make it unbold.  Therefore, we shouldn't check
   2150  // IsCSSEnabled() in most cases.  However, there is an exception.
   2151  // FontFaceStateCommand::SetState() calls RemoveInlinePropertyAsAction()
   2152  // with nsGkAtoms::tt before calling SetInlinePropertyAsAction() if we
   2153  // are handling a XUL command.  Only in that case, we need to check
   2154  // IsCSSEnabled().
   2155  const bool handleCSS =
   2156      aStyle.mHTMLProperty != nsGkAtoms::tt || IsCSSEnabled();
   2157 
   2158  AutoTArray<OwningNonNull<Element>, 24> arrayOfParents;
   2159  for (Element* element :
   2160       aPointToSplit.GetContainer()->InclusiveAncestorsOfType<Element>()) {
   2161    if (element->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::head,
   2162                                     nsGkAtoms::html) ||
   2163        HTMLEditUtils::IsBlockElement(
   2164            *element, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   2165        !element->GetParent() ||
   2166        !EditorUtils::IsEditableContent(*element->GetParent(),
   2167                                        EditorType::HTML) ||
   2168        NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*element))) {
   2169      break;
   2170    }
   2171    arrayOfParents.AppendElement(*element);
   2172  }
   2173 
   2174  // Split any matching style nodes above the point.
   2175  SplitNodeResult result = SplitNodeResult::NotHandled(aPointToSplit);
   2176  MOZ_ASSERT(!result.Handled());
   2177  EditorDOMPoint pointToPutCaret;
   2178  for (const OwningNonNull<Element>& element : arrayOfParents) {
   2179    auto isSetByCSSOrError = [&]() -> Result<bool, nsresult> {
   2180      if (!handleCSS) {
   2181        return false;
   2182      }
   2183      // The HTML style defined by aStyle has a CSS equivalence in this
   2184      // implementation for the node; let's check if it carries those CSS
   2185      // styles
   2186      if (aStyle.IsCSSRemovable(*element)) {
   2187        nsAutoString firstValue;
   2188        Result<bool, nsresult> isSpecifiedByCSSOrError =
   2189            CSSEditUtils::IsSpecifiedCSSEquivalentTo(*this, *element, aStyle,
   2190                                                     firstValue);
   2191        if (MOZ_UNLIKELY(isSpecifiedByCSSOrError.isErr())) {
   2192          result.IgnoreCaretPointSuggestion();
   2193          NS_WARNING("CSSEditUtils::IsSpecifiedCSSEquivalentTo() failed");
   2194          return isSpecifiedByCSSOrError;
   2195        }
   2196        if (isSpecifiedByCSSOrError.unwrap()) {
   2197          return true;
   2198        }
   2199      }
   2200      // If this is <sub> or <sup>, we won't use vertical-align CSS property
   2201      // because <sub>/<sup> changes font size but neither `vertical-align:
   2202      // sub` nor `vertical-align: super` changes it (bug 394304 comment 2).
   2203      // Therefore, they are not equivalents.  However, they're obviously
   2204      // conflict with vertical-align style.  Thus, we need to remove ancestor
   2205      // elements having vertical-align style.
   2206      if (aStyle.IsStyleConflictingWithVerticalAlign()) {
   2207        nsAutoString value;
   2208        nsresult rv = CSSEditUtils::GetSpecifiedProperty(
   2209            *element, *nsGkAtoms::vertical_align, value);
   2210        if (NS_FAILED(rv)) {
   2211          NS_WARNING("CSSEditUtils::GetSpecifiedProperty() failed");
   2212          result.IgnoreCaretPointSuggestion();
   2213          return Err(rv);
   2214        }
   2215        if (!value.IsEmpty()) {
   2216          return true;
   2217        }
   2218      }
   2219      return false;
   2220    }();
   2221    if (MOZ_UNLIKELY(isSetByCSSOrError.isErr())) {
   2222      return isSetByCSSOrError.propagateErr();
   2223    }
   2224    if (!isSetByCSSOrError.inspect()) {
   2225      if (!aStyle.IsStyleToClearAllInlineStyles()) {
   2226        // If we're removing a link style and the element is an <a href>, we
   2227        // need to split it.
   2228        if (aStyle.mHTMLProperty == nsGkAtoms::href &&
   2229            HTMLEditUtils::IsHyperlinkElement(element)) {
   2230        }
   2231        // If we're removing HTML style, we should split only the element
   2232        // which represents the style.
   2233        else if (!element->IsHTMLElement(aStyle.mHTMLProperty) ||
   2234                 (aStyle.mAttribute && !element->HasAttr(aStyle.mAttribute))) {
   2235          continue;
   2236        }
   2237        // If we're setting <font> related styles, it means that we're not
   2238        // toggling the style.  In this case, we need to remove parent <font>
   2239        // elements and/or update parent <font> elements if there are some
   2240        // elements which have the attribute.  However, we should not touch if
   2241        // the value is same as what the caller setting to keep the DOM tree
   2242        // as-is as far as possible.
   2243        if (aStyle.IsStyleOfFontElement() && aStyle.MaybeHasValue()) {
   2244          const nsAttrValue* const attrValue =
   2245              element->GetParsedAttr(aStyle.mAttribute);
   2246          if (attrValue) {
   2247            if (aStyle.mAttribute == nsGkAtoms::size) {
   2248              if (attrValue->Type() == nsAttrValue::eInteger &&
   2249                  nsContentUtils::ParseLegacyFontSize(
   2250                      aStyle.AsInlineStyleAndValue().mAttributeValue) ==
   2251                      attrValue->GetIntegerValue()) {
   2252                continue;
   2253              }
   2254            } else if (aStyle.mAttribute == nsGkAtoms::color) {
   2255              nsAttrValue newValue;
   2256              nscolor oldColor, newColor;
   2257              if (attrValue->Type() == nsAttrValue::eColor &&
   2258                  attrValue->GetColorValue(oldColor) &&
   2259                  newValue.ParseColor(
   2260                      aStyle.AsInlineStyleAndValue().mAttributeValue) &&
   2261                  newValue.GetColorValue(newColor) && oldColor == newColor) {
   2262                continue;
   2263              }
   2264            } else if (attrValue->Equals(
   2265                           aStyle.AsInlineStyleAndValue().mAttributeValue,
   2266                           eIgnoreCase)) {
   2267              continue;
   2268            }
   2269          }
   2270        }
   2271      }
   2272      // If aProperty is nullptr, we need to split any style.
   2273      else if (!EditorUtils::IsEditableContent(element, EditorType::HTML) ||
   2274               !HTMLEditUtils::IsRemovableInlineStyleElement(*element)) {
   2275        continue;
   2276      }
   2277    }
   2278 
   2279    // Found a style node we need to split.
   2280    // XXX If first content is a text node and CSS is enabled, we call this
   2281    //     with text node but in such case, this does nothing, but returns
   2282    //     as handled with setting only previous or next node.  If its parent
   2283    //     is a block, we do nothing but return as handled.
   2284    AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret);
   2285    Result<SplitNodeResult, nsresult> splitNodeResult =
   2286        SplitNodeDeepWithTransaction(MOZ_KnownLive(element),
   2287                                     result.AtSplitPoint<EditorDOMPoint>(),
   2288                                     aSplitAtEdges);
   2289    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
   2290      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
   2291      return splitNodeResult;
   2292    }
   2293    SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
   2294    trackPointToPutCaret.FlushAndStopTracking();
   2295    unwrappedSplitNodeResult.MoveCaretPointTo(
   2296        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   2297 
   2298    // If it's not handled, it means that `content` is not a splittable node
   2299    // like a void element even if it has some children, and the split point
   2300    // is middle of it.
   2301    if (!unwrappedSplitNodeResult.Handled()) {
   2302      continue;
   2303    }
   2304    // Respect the last split result which actually did it.
   2305    if (!result.DidSplit() || unwrappedSplitNodeResult.DidSplit()) {
   2306      result = unwrappedSplitNodeResult.ToHandledResult();
   2307    }
   2308    MOZ_ASSERT(result.Handled());
   2309  }
   2310 
   2311  return pointToPutCaret.IsSet()
   2312             ? SplitNodeResult(std::move(result), std::move(pointToPutCaret))
   2313             : std::move(result);
   2314 }
   2315 
   2316 Result<EditorDOMPoint, nsresult> HTMLEditor::ClearStyleAt(
   2317    const EditorDOMPoint& aPoint, const EditorInlineStyle& aStyleToRemove,
   2318    SpecifiedStyle aSpecifiedStyle, const Element& aEditingHost) {
   2319  MOZ_ASSERT(IsEditActionDataAvailable());
   2320 
   2321  if (NS_WARN_IF(!aPoint.IsSet())) {
   2322    return Err(NS_ERROR_INVALID_ARG);
   2323  }
   2324 
   2325  // TODO: We should rewrite this to stop unnecessary element creation and
   2326  //       deleting it later because it causes the original element may be
   2327  //       removed from the DOM tree even if same element is still in the
   2328  //       DOM tree from point of view of users.
   2329 
   2330  // First, split inline elements at the point.
   2331  // E.g., if aStyleToRemove.mHTMLProperty is nsGkAtoms::b and
   2332  //       `<p><b><i>a[]bc</i></b></p>`, we want to make it as
   2333  //       `<p><b><i>a</i></b><b><i>bc</i></b></p>`.
   2334  EditorDOMPoint pointToPutCaret(aPoint);
   2335  AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret);
   2336  Result<SplitNodeResult, nsresult> splitNodeResult =
   2337      SplitAncestorStyledInlineElementsAt(
   2338          aPoint, aStyleToRemove, SplitAtEdges::eAllowToCreateEmptyContainer);
   2339  if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
   2340    NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
   2341    return splitNodeResult.propagateErr();
   2342  }
   2343  trackPointToPutCaret.FlushAndStopTracking();
   2344  SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
   2345  unwrappedSplitNodeResult.MoveCaretPointTo(
   2346      pointToPutCaret, *this,
   2347      {SuggestCaret::OnlyIfHasSuggestion,
   2348       SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   2349 
   2350  // If there is no styled inline elements of aStyleToRemove, we just return the
   2351  // given point.
   2352  // E.g., `<p><i>a[]bc</i></p>` for nsGkAtoms::b.
   2353  if (!unwrappedSplitNodeResult.Handled()) {
   2354    return pointToPutCaret;
   2355  }
   2356 
   2357  // If it did split nodes, but topmost ancestor inline element is split
   2358  // at start of it, we don't need the empty inline element.  Let's remove
   2359  // it now.  Then, we'll get the following DOM tree if there is no "a" in the
   2360  // above case:
   2361  // <p><b><i>bc</i></b></p>
   2362  //   ^^
   2363  if (unwrappedSplitNodeResult.GetPreviousContent() &&
   2364      HTMLEditUtils::IsEmptyNode(
   2365          *unwrappedSplitNodeResult.GetPreviousContent(),
   2366          {EmptyCheckOption::TreatSingleBRElementAsVisible,
   2367           EmptyCheckOption::TreatListItemAsVisible,
   2368           EmptyCheckOption::TreatTableCellAsVisible})) {
   2369    AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret);
   2370    // Delete previous node if it's empty.
   2371    // MOZ_KnownLive(unwrappedSplitNodeResult.GetPreviousContent()):
   2372    // It's grabbed by unwrappedSplitNodeResult.
   2373    nsresult rv = DeleteNodeWithTransaction(
   2374        MOZ_KnownLive(*unwrappedSplitNodeResult.GetPreviousContent()));
   2375    if (NS_FAILED(rv)) {
   2376      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   2377      return Err(rv);
   2378    }
   2379  }
   2380 
   2381  // If we reached block from end of a text node, we can do nothing here.
   2382  // E.g., `<p style="font-weight: bold;">a[]bc</p>` for nsGkAtoms::b and
   2383  // we're in CSS mode.
   2384  // XXX Chrome resets block style and creates `<span>` elements for each
   2385  //     line in this case.
   2386  if (!unwrappedSplitNodeResult.GetNextContent()) {
   2387    return pointToPutCaret;
   2388  }
   2389 
   2390  // Otherwise, the next node is topmost ancestor inline element which has
   2391  // the style.  We want to put caret between the split nodes, but we need
   2392  // to keep other styles.  Therefore, next, we need to split at start of
   2393  // the next node.  The first example should become
   2394  // `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`.
   2395  //                    ^^^^^^^^^^^^^^
   2396  nsIContent* firstLeafChildOfNextNode = HTMLEditUtils::GetFirstLeafContent(
   2397      *unwrappedSplitNodeResult.GetNextContent(), {LeafNodeType::OnlyLeafNode});
   2398  EditorDOMPoint atStartOfNextNode(
   2399      firstLeafChildOfNextNode ? firstLeafChildOfNextNode
   2400                               : unwrappedSplitNodeResult.GetNextContent(),
   2401      0);
   2402  Maybe<EditorLineBreak> lineBreak;
   2403  // But don't try to split non-containers like `<br>`, `<hr>` and `<img>`
   2404  // element.
   2405  if (!atStartOfNextNode.IsInContentNode() ||
   2406      !HTMLEditUtils::IsContainerNode(
   2407          *atStartOfNextNode.ContainerAs<nsIContent>())) {
   2408    // If it's a `<br>` element, let's move it into new node later.
   2409    auto* const brElement =
   2410        HTMLBRElement::FromNode(atStartOfNextNode.GetContainer());
   2411    if (brElement) {
   2412      lineBreak.emplace(*brElement);
   2413    }
   2414    if (!atStartOfNextNode.GetContainerParentAs<nsIContent>()) {
   2415      NS_WARNING("atStartOfNextNode was in an orphan node");
   2416      return Err(NS_ERROR_FAILURE);
   2417    }
   2418    atStartOfNextNode.Set(atStartOfNextNode.GetContainerParent(), 0);
   2419  }
   2420  AutoTrackDOMPoint trackPointToPutCaret2(RangeUpdaterRef(), &pointToPutCaret);
   2421  Result<SplitNodeResult, nsresult> splitResultAtStartOfNextNode =
   2422      SplitAncestorStyledInlineElementsAt(
   2423          atStartOfNextNode, aStyleToRemove,
   2424          SplitAtEdges::eAllowToCreateEmptyContainer);
   2425  if (MOZ_UNLIKELY(splitResultAtStartOfNextNode.isErr())) {
   2426    NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
   2427    return splitResultAtStartOfNextNode.propagateErr();
   2428  }
   2429  trackPointToPutCaret2.FlushAndStopTracking();
   2430  SplitNodeResult unwrappedSplitResultAtStartOfNextNode =
   2431      splitResultAtStartOfNextNode.unwrap();
   2432  unwrappedSplitResultAtStartOfNextNode.MoveCaretPointTo(
   2433      pointToPutCaret, *this,
   2434      {SuggestCaret::OnlyIfHasSuggestion,
   2435       SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   2436 
   2437  if (unwrappedSplitResultAtStartOfNextNode.Handled() &&
   2438      unwrappedSplitResultAtStartOfNextNode.GetNextContent()) {
   2439    // If the right inline elements are empty, we should remove them.  E.g.,
   2440    // if the split point is at end of a text node (or end of an inline
   2441    // element), e.g., <div><b><i>abc[]</i></b></div>, then now, it's been
   2442    // changed to:
   2443    // <div><b><i>abc</i></b><b><i>[]</i></b><b><i></i></b></div>
   2444    //                                       ^^^^^^^^^^^^^^
   2445    // We will change it to:
   2446    // <div><b><i>abc</i></b><b><i>[]</i></b></div>
   2447    //                                      ^^
   2448    // And if it has only padding <br> element, we should move it into the
   2449    // previous <i> which will have new content.
   2450    bool seenBR = false;
   2451    if (HTMLEditUtils::IsEmptyNode(
   2452            *unwrappedSplitResultAtStartOfNextNode.GetNextContent(),
   2453            {EmptyCheckOption::TreatListItemAsVisible,
   2454             EmptyCheckOption::TreatTableCellAsVisible},
   2455            &seenBR)) {
   2456      // MOZ_KnownLive because of grabbed by
   2457      // unwrappedSplitResultAtStartOfNextNode.
   2458      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(
   2459          *unwrappedSplitResultAtStartOfNextNode.GetNextContent()));
   2460      if (NS_FAILED(rv)) {
   2461        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   2462        return Err(rv);
   2463      }
   2464    }
   2465  }
   2466 
   2467  if (!unwrappedSplitResultAtStartOfNextNode.Handled()) {
   2468    return std::move(pointToPutCaret);
   2469  }
   2470 
   2471  // If there is no content, we should return here.
   2472  // XXX Is this possible case without mutation event listener?
   2473  if (!unwrappedSplitResultAtStartOfNextNode.GetPreviousContent()) {
   2474    // XXX This is really odd, but we return this value...
   2475    const auto splitPoint =
   2476        unwrappedSplitNodeResult.AtSplitPoint<EditorRawDOMPoint>();
   2477    const auto splitPointAtStartOfNextNode =
   2478        unwrappedSplitResultAtStartOfNextNode.AtSplitPoint<EditorRawDOMPoint>();
   2479    return EditorDOMPoint(splitPoint.GetContainer(),
   2480                          splitPointAtStartOfNextNode.Offset());
   2481  }
   2482 
   2483  // Now, we want to put `<br>` element into the empty split node if
   2484  // it was in next node of the first split.
   2485  // E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>`
   2486  nsIContent* firstLeafChildOfPreviousNode = HTMLEditUtils::GetFirstLeafContent(
   2487      *unwrappedSplitResultAtStartOfNextNode.GetPreviousContent(),
   2488      {LeafNodeType::OnlyLeafNode});
   2489  pointToPutCaret.Set(
   2490      firstLeafChildOfPreviousNode
   2491          ? firstLeafChildOfPreviousNode
   2492          : unwrappedSplitResultAtStartOfNextNode.GetPreviousContent(),
   2493      0);
   2494 
   2495  // If the right node starts with a line break, suck it out of right node and
   2496  // into the left node left node.  This is so we you don't revert back to the
   2497  // previous style if you happen to click at the end of a line.
   2498  if (lineBreak.isSome()) {
   2499    if (lineBreak->IsInComposedDoc()) {
   2500      Result<EditorDOMPoint, nsresult> lineBreakPointOrError =
   2501          DeleteLineBreakWithTransaction(lineBreak.ref(), nsIEditor::eStrip,
   2502                                         aEditingHost);
   2503      if (MOZ_UNLIKELY(lineBreakPointOrError.isErr())) {
   2504        NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed");
   2505        return lineBreakPointOrError.propagateErr();
   2506      }
   2507    }
   2508    Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   2509        InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement,
   2510                        pointToPutCaret);
   2511    if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   2512      NS_WARNING(
   2513          "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   2514          "LineBreakType::BRElement) failed");
   2515      return insertBRElementResultOrError.propagateErr();
   2516    }
   2517    CreateLineBreakResult insertBRElementResult =
   2518        insertBRElementResultOrError.unwrap();
   2519    insertBRElementResult.MoveCaretPointTo(
   2520        pointToPutCaret, *this,
   2521        {SuggestCaret::OnlyIfHasSuggestion,
   2522         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   2523 
   2524    if (unwrappedSplitResultAtStartOfNextNode.GetNextContent() &&
   2525        unwrappedSplitResultAtStartOfNextNode.GetNextContent()
   2526            ->IsInComposedDoc()) {
   2527      // If we split inline elements at immediately before <br> element which is
   2528      // the last visible content in the right element, we don't need the right
   2529      // element anymore.  Otherwise, we'll create the following DOM tree:
   2530      // - <b>abc</b>{}<br><b></b>
   2531      //                   ^^^^^^^
   2532      // - <b><i>abc</i></b><i><br></i><b></b>
   2533      //                               ^^^^^^^
   2534      if (HTMLEditUtils::IsEmptyNode(
   2535              *unwrappedSplitResultAtStartOfNextNode.GetNextContent(),
   2536              {EmptyCheckOption::TreatSingleBRElementAsVisible,
   2537               EmptyCheckOption::TreatListItemAsVisible,
   2538               EmptyCheckOption::TreatTableCellAsVisible})) {
   2539        // MOZ_KnownLive because the result is grabbed by
   2540        // unwrappedSplitResultAtStartOfNextNode.
   2541        nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(
   2542            *unwrappedSplitResultAtStartOfNextNode.GetNextContent()));
   2543        if (NS_FAILED(rv)) {
   2544          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   2545          return Err(rv);
   2546        }
   2547      }
   2548      // If the next content has only one <br> element, there may be empty
   2549      // inline elements around it.  We don't need them anymore because user
   2550      // cannot put caret into them.  E.g., <b><i>abc[]<br></i><br></b> has
   2551      // been changed to <b><i>abc</i></b><i>{}<br></i><b><i></i><br></b> now.
   2552      //                                               ^^^^^^^^^^^^^^^^^^
   2553      // We don't need the empty <i>.
   2554      else if (HTMLEditUtils::IsEmptyNode(
   2555                   *unwrappedSplitResultAtStartOfNextNode.GetNextContent(),
   2556                   {EmptyCheckOption::TreatListItemAsVisible,
   2557                    EmptyCheckOption::TreatTableCellAsVisible})) {
   2558        AutoTArray<OwningNonNull<nsIContent>, 4> emptyInlineContainerElements;
   2559        HTMLEditUtils::CollectEmptyInlineContainerDescendants(
   2560            *unwrappedSplitResultAtStartOfNextNode.GetNextContentAs<Element>(),
   2561            emptyInlineContainerElements,
   2562            {EmptyCheckOption::TreatSingleBRElementAsVisible,
   2563             EmptyCheckOption::TreatListItemAsVisible,
   2564             EmptyCheckOption::TreatTableCellAsVisible},
   2565            BlockInlineCheck::UseComputedDisplayOutsideStyle);
   2566        for (const OwningNonNull<nsIContent>& emptyInlineContainerElement :
   2567             emptyInlineContainerElements) {
   2568          // MOZ_KnownLive(emptyInlineContainerElement) due to bug 1622253.
   2569          nsresult rv = DeleteNodeWithTransaction(
   2570              MOZ_KnownLive(emptyInlineContainerElement));
   2571          if (NS_FAILED(rv)) {
   2572            NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   2573            return Err(rv);
   2574          }
   2575        }
   2576      }
   2577    }
   2578 
   2579    // Update the child.
   2580    pointToPutCaret.Set(pointToPutCaret.GetContainer(), 0);
   2581  }
   2582  // Finally, remove the specified style in the previous node at the
   2583  // second split and tells good insertion point to the caller.  I.e., we
   2584  // want to make the first example as:
   2585  // `<p><b><i>a</i></b><i>[]</i><b><i>bc</i></b></p>`
   2586  //                    ^^^^^^^^^
   2587  if (auto* const previousElementOfSplitPoint =
   2588          unwrappedSplitResultAtStartOfNextNode
   2589              .GetPreviousContentAs<Element>()) {
   2590    // Track the point at the new hierarchy.  This is so we can know where
   2591    // to put the selection after we call RemoveStyleInside().
   2592    // RemoveStyleInside() could remove any and all of those nodes, so I
   2593    // have to use the range tracking system to find the right spot to put
   2594    // selection.
   2595    AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToPutCaret);
   2596    // MOZ_KnownLive(previousElementOfSplitPoint):
   2597    // It's grabbed by unwrappedSplitResultAtStartOfNextNode.
   2598    Result<EditorDOMPoint, nsresult> removeStyleResult =
   2599        RemoveStyleInside(MOZ_KnownLive(*previousElementOfSplitPoint),
   2600                          aStyleToRemove, aSpecifiedStyle);
   2601    if (MOZ_UNLIKELY(removeStyleResult.isErr())) {
   2602      NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
   2603      return removeStyleResult;
   2604    }
   2605    // We've already computed a suggested caret position at start of first leaf
   2606    // which is stored in pointToPutCaret, so we don't need to update it here.
   2607  }
   2608  return pointToPutCaret;
   2609 }
   2610 
   2611 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveStyleInside(
   2612    Element& aElement, const EditorInlineStyle& aStyleToRemove,
   2613    SpecifiedStyle aSpecifiedStyle) {
   2614  // First, handle all descendants.
   2615  AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfChildContents;
   2616  HTMLEditUtils::CollectAllChildren(aElement, arrayOfChildContents);
   2617  EditorDOMPoint pointToPutCaret;
   2618  for (const OwningNonNull<nsIContent>& child : arrayOfChildContents) {
   2619    if (!child->IsElement()) {
   2620      continue;
   2621    }
   2622    Result<EditorDOMPoint, nsresult> removeStyleResult = RemoveStyleInside(
   2623        MOZ_KnownLive(*child->AsElement()), aStyleToRemove, aSpecifiedStyle);
   2624    if (MOZ_UNLIKELY(removeStyleResult.isErr())) {
   2625      NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
   2626      return removeStyleResult;
   2627    }
   2628    if (removeStyleResult.inspect().IsSet()) {
   2629      pointToPutCaret = removeStyleResult.unwrap();
   2630    }
   2631  }
   2632 
   2633  // TODO: It seems that if aElement is not editable, we should insert new
   2634  //       container to remove the style if possible.
   2635  if (!EditorUtils::IsEditableContent(aElement, EditorType::HTML)) {
   2636    return pointToPutCaret;
   2637  }
   2638 
   2639  // Next, remove CSS style first.  Then, `style` attribute will be removed if
   2640  // the corresponding CSS property is last one.
   2641  auto isStyleSpecifiedOrError = [&]() -> Result<bool, nsresult> {
   2642    if (!aStyleToRemove.IsCSSRemovable(aElement)) {
   2643      return false;
   2644    }
   2645    MOZ_ASSERT(!aStyleToRemove.IsStyleToClearAllInlineStyles());
   2646    Result<bool, nsresult> elementHasSpecifiedCSSEquivalentStylesOrError =
   2647        CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(*this, aElement,
   2648                                                       aStyleToRemove);
   2649    NS_WARNING_ASSERTION(
   2650        elementHasSpecifiedCSSEquivalentStylesOrError.isOk(),
   2651        "CSSEditUtils::HaveSpecifiedCSSEquivalentStyles() failed");
   2652    return elementHasSpecifiedCSSEquivalentStylesOrError;
   2653  }();
   2654  if (MOZ_UNLIKELY(isStyleSpecifiedOrError.isErr())) {
   2655    return isStyleSpecifiedOrError.propagateErr();
   2656  }
   2657  bool styleSpecified = isStyleSpecifiedOrError.unwrap();
   2658  if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
   2659    if (styleSpecified) {
   2660      // MOZ_KnownLive(*styledElement) because it's an alias of aElement.
   2661      nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle(
   2662          WithTransaction::Yes, *this, MOZ_KnownLive(*styledElement),
   2663          aStyleToRemove, nullptr);
   2664      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2665        return Err(NS_ERROR_EDITOR_DESTROYED);
   2666      }
   2667      NS_WARNING_ASSERTION(
   2668          NS_SUCCEEDED(rv),
   2669          "CSSEditUtils::RemoveCSSEquivalentToStyle() failed, but ignored");
   2670    }
   2671 
   2672    // If the style is <sub> or <sup>, we won't use vertical-align CSS
   2673    // property because <sub>/<sup> changes font size but neither
   2674    // `vertical-align: sub` nor `vertical-align: super` changes it
   2675    // (bug 394304 comment 2).  Therefore, they are not equivalents.  However,
   2676    // they're obviously conflict with vertical-align style.  Thus, we need to
   2677    // remove the vertical-align style from elements.
   2678    if (aStyleToRemove.IsStyleConflictingWithVerticalAlign()) {
   2679      nsAutoString value;
   2680      nsresult rv = CSSEditUtils::GetSpecifiedProperty(
   2681          aElement, *nsGkAtoms::vertical_align, value);
   2682      if (NS_FAILED(rv)) {
   2683        NS_WARNING("CSSEditUtils::GetSpecifiedProperty() failed");
   2684        return Err(rv);
   2685      }
   2686      if (!value.IsEmpty()) {
   2687        // MOZ_KnownLive(*styledElement) because it's an alias of aElement.
   2688        nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction(
   2689            *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::vertical_align,
   2690            value);
   2691        if (NS_FAILED(rv)) {
   2692          NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
   2693          return Err(rv);
   2694        }
   2695        styleSpecified = true;
   2696      }
   2697    }
   2698  }
   2699 
   2700  // Then, if we could and should remove or replace aElement, let's do it. Or
   2701  // just remove attribute.
   2702  const bool isStyleRepresentedByElement =
   2703      !aStyleToRemove.IsStyleToClearAllInlineStyles() &&
   2704      aStyleToRemove.IsRepresentedBy(aElement);
   2705 
   2706  auto ShouldUpdateDOMTree = [&]() {
   2707    // If we're removing any inline styles and aElement is an inline style
   2708    // element, we can remove or replace it.
   2709    if (aStyleToRemove.IsStyleToClearAllInlineStyles() &&
   2710        HTMLEditUtils::IsRemovableInlineStyleElement(aElement)) {
   2711      return true;
   2712    }
   2713    // If we're a specific style and aElement represents it, we can remove or
   2714    // replace the element or remove the corresponding attribute.
   2715    if (isStyleRepresentedByElement) {
   2716      return true;
   2717    }
   2718    // If we've removed a CSS style from the `style` attribute of aElement, we
   2719    // could remove the element.
   2720    return aElement.IsHTMLElement(nsGkAtoms::span) && styleSpecified;
   2721  };
   2722  if (!ShouldUpdateDOMTree()) {
   2723    return pointToPutCaret;
   2724  }
   2725 
   2726  const bool elementHasNecessaryAttributes = [&]() {
   2727    // If we're not removing nor replacing aElement itself, we don't need to
   2728    // take care of its `style` and `class` attributes even if aSpecifiedStyle
   2729    // is `Discard` because aSpecifiedStyle is not intended to be used in this
   2730    // case.
   2731    if (!isStyleRepresentedByElement) {
   2732      return HTMLEditUtils::ElementHasAttributeExcept(aElement,
   2733                                                      *nsGkAtoms::_empty);
   2734    }
   2735    // If we're removing links, we don't need to keep <a> even if it has some
   2736    // specific attributes because it cannot be nested.  However, if and only if
   2737    // it has `style` attribute and aSpecifiedStyle is not `Discard`, we need to
   2738    // replace it with new <span> to keep the style.
   2739    if (aStyleToRemove.IsStyleOfAnchorElement()) {
   2740      return aSpecifiedStyle == SpecifiedStyle::Preserve &&
   2741             (aElement.HasNonEmptyAttr(nsGkAtoms::style) ||
   2742              aElement.HasNonEmptyAttr(nsGkAtoms::_class));
   2743    }
   2744    nsAtom& attrKeepStaying = aStyleToRemove.mAttribute
   2745                                  ? *aStyleToRemove.mAttribute
   2746                                  : *nsGkAtoms::_empty;
   2747    return aSpecifiedStyle == SpecifiedStyle::Preserve
   2748               // If we're try to remove the element but the caller wants to
   2749               // preserve the style, check whether aElement has attributes
   2750               // except the removing attribute since `style` and `class` should
   2751               // keep existing to preserve the style.
   2752               ? HTMLEditUtils::ElementHasAttributeExcept(aElement,
   2753                                                          attrKeepStaying)
   2754               // If we're try to remove the element and the caller wants to
   2755               // discard the style specified to the element, check whether
   2756               // aElement has attributes except the removing attribute, `style`
   2757               // and `class` since we don't want to keep these attributes.
   2758               : HTMLEditUtils::ElementHasAttributeExcept(
   2759                     aElement, attrKeepStaying, *nsGkAtoms::style,
   2760                     *nsGkAtoms::_class);
   2761  }();
   2762 
   2763  // If the element is not a <span> and still has some attributes, we should
   2764  // replace it with new <span>.
   2765  auto ReplaceWithNewSpan = [&]() {
   2766    if (aStyleToRemove.IsStyleToClearAllInlineStyles()) {
   2767      return false;  // Remove it even if it has attributes.
   2768    }
   2769    if (aElement.IsHTMLElement(nsGkAtoms::span)) {
   2770      return false;  // Don't replace <span> with new <span>.
   2771    }
   2772    if (!isStyleRepresentedByElement) {
   2773      return false;  // Keep non-related element as-is.
   2774    }
   2775    if (!elementHasNecessaryAttributes) {
   2776      return false;  // Should remove it instead of replacing it.
   2777    }
   2778    if (aElement.IsHTMLElement(nsGkAtoms::font)) {
   2779      // Replace <font> if it won't have its specific attributes.
   2780      return (aStyleToRemove.mHTMLProperty == nsGkAtoms::color ||
   2781              !aElement.HasAttr(nsGkAtoms::color)) &&
   2782             (aStyleToRemove.mHTMLProperty == nsGkAtoms::face ||
   2783              !aElement.HasAttr(nsGkAtoms::face)) &&
   2784             (aStyleToRemove.mHTMLProperty == nsGkAtoms::size ||
   2785              !aElement.HasAttr(nsGkAtoms::size));
   2786    }
   2787    // The styled element has only global attributes, let's replace it with new
   2788    // <span> with cloning the attributes.
   2789    return true;
   2790  };
   2791 
   2792  if (ReplaceWithNewSpan()) {
   2793    // Before cloning the attribute to new element, let's remove it.
   2794    if (aStyleToRemove.mAttribute) {
   2795      nsresult rv =
   2796          RemoveAttributeWithTransaction(aElement, *aStyleToRemove.mAttribute);
   2797      if (NS_FAILED(rv)) {
   2798        NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
   2799        return Err(rv);
   2800      }
   2801    }
   2802    if (aSpecifiedStyle == SpecifiedStyle::Discard) {
   2803      nsresult rv = RemoveAttributeWithTransaction(aElement, *nsGkAtoms::style);
   2804      if (NS_FAILED(rv)) {
   2805        NS_WARNING(
   2806            "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::style) "
   2807            "failed");
   2808        return Err(rv);
   2809      }
   2810      rv = RemoveAttributeWithTransaction(aElement, *nsGkAtoms::_class);
   2811      if (NS_FAILED(rv)) {
   2812        NS_WARNING(
   2813            "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::_class) "
   2814            "failed");
   2815        return Err(rv);
   2816      }
   2817    }
   2818    // Move `style` attribute and `class` element to span element before
   2819    // removing aElement from the tree.
   2820    auto replaceWithSpanResult =
   2821        [&]() MOZ_CAN_RUN_SCRIPT -> Result<CreateElementResult, nsresult> {
   2822      if (!aStyleToRemove.IsStyleOfAnchorElement()) {
   2823        return ReplaceContainerAndCloneAttributesWithTransaction(
   2824            aElement, *nsGkAtoms::span);
   2825      }
   2826      nsString styleValue;  // Use nsString to avoid copying the buffer at
   2827                            // setting the attribute.
   2828      aElement.GetAttr(nsGkAtoms::style, styleValue);
   2829      return ReplaceContainerWithTransaction(aElement, *nsGkAtoms::span,
   2830                                             *nsGkAtoms::style, styleValue);
   2831    }();
   2832    if (MOZ_UNLIKELY(replaceWithSpanResult.isErr())) {
   2833      NS_WARNING(
   2834          "HTMLEditor::ReplaceContainerWithTransaction(nsGkAtoms::span) "
   2835          "failed");
   2836      return replaceWithSpanResult.propagateErr();
   2837    }
   2838    CreateElementResult unwrappedReplaceWithSpanResult =
   2839        replaceWithSpanResult.unwrap();
   2840    if (AllowsTransactionsToChangeSelection()) {
   2841      unwrappedReplaceWithSpanResult.MoveCaretPointTo(
   2842          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   2843    } else {
   2844      unwrappedReplaceWithSpanResult.IgnoreCaretPointSuggestion();
   2845    }
   2846    return pointToPutCaret;
   2847  }
   2848 
   2849  auto RemoveElement = [&]() {
   2850    if (aStyleToRemove.IsStyleToClearAllInlineStyles()) {
   2851      MOZ_ASSERT(HTMLEditUtils::IsRemovableInlineStyleElement(aElement));
   2852      return true;
   2853    }
   2854    // If the element still has some attributes, we should not remove it to keep
   2855    // current presentation and/or semantics.
   2856    if (elementHasNecessaryAttributes) {
   2857      return false;
   2858    }
   2859    // If the style is represented by the element, let's remove it.
   2860    if (isStyleRepresentedByElement) {
   2861      return true;
   2862    }
   2863    // If we've removed a CSS style and that made the <span> element have no
   2864    // attributes, we can delete it.
   2865    if (styleSpecified && aElement.IsHTMLElement(nsGkAtoms::span)) {
   2866      return true;
   2867    }
   2868    return false;
   2869  };
   2870 
   2871  if (RemoveElement()) {
   2872    Result<EditorDOMPoint, nsresult> unwrapElementResult =
   2873        RemoveContainerWithTransaction(aElement);
   2874    if (MOZ_UNLIKELY(unwrapElementResult.isErr())) {
   2875      NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
   2876      return unwrapElementResult.propagateErr();
   2877    }
   2878    if (AllowsTransactionsToChangeSelection() &&
   2879        unwrapElementResult.inspect().IsSet()) {
   2880      pointToPutCaret = unwrapElementResult.unwrap();
   2881    }
   2882    return pointToPutCaret;
   2883  }
   2884 
   2885  // If the element needs to keep having some attributes, just remove the
   2886  // attribute.  Note that we don't need to remove `style` attribute here when
   2887  // aSpecifiedStyle is `Discard` because we've already removed unnecessary
   2888  // CSS style above.
   2889  if (isStyleRepresentedByElement && aStyleToRemove.mAttribute) {
   2890    nsresult rv =
   2891        RemoveAttributeWithTransaction(aElement, *aStyleToRemove.mAttribute);
   2892    if (NS_FAILED(rv)) {
   2893      NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
   2894      return Err(rv);
   2895    }
   2896  }
   2897  return pointToPutCaret;
   2898 }
   2899 
   2900 EditorRawDOMRange HTMLEditor::GetExtendedRangeWrappingNamedAnchor(
   2901    const EditorRawDOMRange& aRange) const {
   2902  MOZ_ASSERT(aRange.StartRef().IsSet());
   2903  MOZ_ASSERT(aRange.EndRef().IsSet());
   2904 
   2905  // FYI: We don't want to stop at ancestor block boundaries to extend the range
   2906  // because <a name> can have block elements with low level DOM API.  We want
   2907  // to remove any <a name> ancestors to remove the style.
   2908 
   2909  EditorRawDOMRange newRange(aRange);
   2910  for (Element* element :
   2911       aRange.StartRef().GetContainer()->InclusiveAncestorsOfType<Element>()) {
   2912    if (!HTMLEditUtils::IsNamedAnchorElement(*element)) {
   2913      continue;
   2914    }
   2915    newRange.SetStart(EditorRawDOMPoint(element));
   2916  }
   2917  for (Element* element :
   2918       aRange.EndRef().GetContainer()->InclusiveAncestorsOfType<Element>()) {
   2919    if (!HTMLEditUtils::IsNamedAnchorElement(*element)) {
   2920      continue;
   2921    }
   2922    newRange.SetEnd(EditorRawDOMPoint::After(*element));
   2923  }
   2924  return newRange;
   2925 }
   2926 
   2927 EditorRawDOMRange HTMLEditor::GetExtendedRangeWrappingEntirelySelectedElements(
   2928    const EditorRawDOMRange& aRange) const {
   2929  MOZ_ASSERT(aRange.StartRef().IsSet());
   2930  MOZ_ASSERT(aRange.EndRef().IsSet());
   2931 
   2932  // FYI: We don't want to stop at ancestor block boundaries to extend the range
   2933  // because the style may come from inline parents of block elements which may
   2934  // occur in invalid DOM tree.  We want to split any (even invalid) ancestors
   2935  // at removing the styles.
   2936 
   2937  EditorRawDOMRange newRange(aRange);
   2938  while (newRange.StartRef().IsInContentNode() &&
   2939         newRange.StartRef().IsStartOfContainer()) {
   2940    if (!EditorUtils::IsEditableContent(
   2941            *newRange.StartRef().ContainerAs<nsIContent>(), EditorType::HTML)) {
   2942      break;
   2943    }
   2944    newRange.SetStart(newRange.StartRef().ParentPoint());
   2945  }
   2946  while (newRange.EndRef().IsInContentNode() &&
   2947         newRange.EndRef().IsEndOfContainer()) {
   2948    if (!EditorUtils::IsEditableContent(
   2949            *newRange.EndRef().ContainerAs<nsIContent>(), EditorType::HTML)) {
   2950      break;
   2951    }
   2952    newRange.SetEnd(
   2953        EditorRawDOMPoint::After(*newRange.EndRef().ContainerAs<nsIContent>()));
   2954  }
   2955  return newRange;
   2956 }
   2957 
   2958 nsresult HTMLEditor::GetInlinePropertyBase(const EditorInlineStyle& aStyle,
   2959                                           const nsAString* aValue,
   2960                                           bool* aFirst, bool* aAny, bool* aAll,
   2961                                           nsAString* outValue) const {
   2962  MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles());
   2963  MOZ_ASSERT(IsEditActionDataAvailable());
   2964 
   2965  *aAny = false;
   2966  *aAll = true;
   2967  *aFirst = false;
   2968  bool first = true;
   2969 
   2970  const bool isCollapsed = SelectionRef().IsCollapsed();
   2971  RefPtr<nsRange> range = SelectionRef().GetRangeAt(0);
   2972  // XXX: Should be a while loop, to get each separate range
   2973  // XXX: ERROR_HANDLING can currentItem be null?
   2974  if (range) {
   2975    // For each range, set a flag
   2976    bool firstNodeInRange = true;
   2977 
   2978    if (isCollapsed) {
   2979      if (NS_WARN_IF(!range->GetStartContainer())) {
   2980        return NS_ERROR_FAILURE;
   2981      }
   2982      nsString tOutString;
   2983      const PendingStyleState styleState = [&]() {
   2984        if (aStyle.mAttribute) {
   2985          auto state = mPendingStylesToApplyToNewContent->GetStyleState(
   2986              *aStyle.mHTMLProperty, aStyle.mAttribute, &tOutString);
   2987          if (outValue) {
   2988            outValue->Assign(tOutString);
   2989          }
   2990          return state;
   2991        }
   2992        return mPendingStylesToApplyToNewContent->GetStyleState(
   2993            *aStyle.mHTMLProperty);
   2994      }();
   2995      if (styleState != PendingStyleState::NotUpdated) {
   2996        *aFirst = *aAny = *aAll =
   2997            (styleState == PendingStyleState::BeingPreserved);
   2998        return NS_OK;
   2999      }
   3000 
   3001      nsIContent* const collapsedContent =
   3002          nsIContent::FromNode(range->GetStartContainer());
   3003      if (MOZ_LIKELY(collapsedContent &&
   3004                     collapsedContent->GetAsElementOrParentElement()) &&
   3005          aStyle.IsCSSSettable(
   3006              *collapsedContent->GetAsElementOrParentElement())) {
   3007        if (aValue) {
   3008          tOutString.Assign(*aValue);
   3009        }
   3010        Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
   3011            CSSEditUtils::IsComputedCSSEquivalentTo(
   3012                *this, MOZ_KnownLive(*collapsedContent), aStyle, tOutString);
   3013        if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
   3014          NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
   3015          return isComputedCSSEquivalentToStyleOrError.unwrapErr();
   3016        }
   3017        *aFirst = *aAny = *aAll =
   3018            isComputedCSSEquivalentToStyleOrError.unwrap();
   3019        if (outValue) {
   3020          outValue->Assign(tOutString);
   3021        }
   3022        return NS_OK;
   3023      }
   3024 
   3025      *aFirst = *aAny = *aAll =
   3026          collapsedContent && HTMLEditUtils::IsInlineStyleSetByElement(
   3027                                  *collapsedContent, aStyle, aValue, outValue);
   3028      return NS_OK;
   3029    }
   3030 
   3031    // Non-collapsed selection
   3032 
   3033    nsAutoString firstValue, theValue;
   3034 
   3035    nsCOMPtr<nsINode> endNode = range->GetEndContainer();
   3036    uint32_t endOffset = range->EndOffset();
   3037 
   3038    PostContentIterator postOrderIter;
   3039    DebugOnly<nsresult> rvIgnored = postOrderIter.Init(range);
   3040    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   3041                         "Failed to initialize post-order content iterator");
   3042    for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
   3043      if (postOrderIter.GetCurrentNode()->IsHTMLElement(nsGkAtoms::body)) {
   3044        break;
   3045      }
   3046      RefPtr<Text> textNode = Text::FromNode(postOrderIter.GetCurrentNode());
   3047      if (!textNode) {
   3048        continue;
   3049      }
   3050 
   3051      // just ignore any non-editable nodes
   3052      if (!EditorUtils::IsEditableContent(*textNode, EditorType::HTML) ||
   3053          !HTMLEditUtils::IsVisibleTextNode(*textNode)) {
   3054        continue;
   3055      }
   3056 
   3057      if (!isCollapsed && first && firstNodeInRange) {
   3058        firstNodeInRange = false;
   3059        if (range->StartOffset() == textNode->TextDataLength()) {
   3060          continue;
   3061        }
   3062      } else if (textNode == endNode && !endOffset) {
   3063        continue;
   3064      }
   3065 
   3066      const RefPtr<Element> element = textNode->GetParentElement();
   3067 
   3068      bool isSet = false;
   3069      if (first) {
   3070        if (element) {
   3071          if (aStyle.IsCSSSettable(*element)) {
   3072            // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
   3073            // equivalence in this implementation for node; let's check if it
   3074            // carries those CSS styles
   3075            if (aValue) {
   3076              firstValue.Assign(*aValue);
   3077            }
   3078            Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
   3079                CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle,
   3080                                                        firstValue);
   3081            if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
   3082              NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
   3083              return isComputedCSSEquivalentToStyleOrError.unwrapErr();
   3084            }
   3085            isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
   3086          } else {
   3087            isSet = HTMLEditUtils::IsInlineStyleSetByElement(
   3088                *element, aStyle, aValue, &firstValue);
   3089          }
   3090        }
   3091        *aFirst = isSet;
   3092        first = false;
   3093        if (outValue) {
   3094          *outValue = firstValue;
   3095        }
   3096      } else {
   3097        if (element) {
   3098          if (aStyle.IsCSSSettable(*element)) {
   3099            // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
   3100            // equivalence in this implementation for node; let's check if it
   3101            // carries those CSS styles
   3102            if (aValue) {
   3103              theValue.Assign(*aValue);
   3104            }
   3105            Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
   3106                CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle,
   3107                                                        theValue);
   3108            if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
   3109              NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
   3110              return isComputedCSSEquivalentToStyleOrError.unwrapErr();
   3111            }
   3112            isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
   3113          } else {
   3114            isSet = HTMLEditUtils::IsInlineStyleSetByElement(*element, aStyle,
   3115                                                             aValue, &theValue);
   3116          }
   3117        }
   3118 
   3119        if (firstValue != theValue &&
   3120            // For text-decoration related HTML properties, i.e. <u> and
   3121            // <strike>, we have to also check |isSet| because text-decoration
   3122            // is a shorthand property, and it may contains other unrelated
   3123            // longhand components, e.g. text-decoration-color, so we have to do
   3124            // an extra check before setting |*aAll| to false.
   3125            // e.g.
   3126            //   firstValue: "underline rgb(0, 0, 0)"
   3127            //   theValue: "underline rgb(0, 0, 238)" // <a> uses blue color
   3128            // These two values should be the same if we are checking `<u>`.
   3129            // That's why we need to check |*aFirst| and |isSet|.
   3130            //
   3131            // This is a work-around for text-decoration.
   3132            // The spec issue: https://github.com/w3c/editing/issues/241.
   3133            // Once this spec issue is resolved, we could drop this work-around
   3134            // check.
   3135            (!aStyle.IsStyleOfTextDecoration(
   3136                 EditorInlineStyle::IgnoreSElement::Yes) ||
   3137             *aFirst != isSet)) {
   3138          *aAll = false;
   3139        }
   3140      }
   3141 
   3142      if (isSet) {
   3143        *aAny = true;
   3144      } else {
   3145        *aAll = false;
   3146      }
   3147    }
   3148  }
   3149  if (!*aAny) {
   3150    // make sure that if none of the selection is set, we don't report all is
   3151    // set
   3152    *aAll = false;
   3153  }
   3154  return NS_OK;
   3155 }
   3156 
   3157 nsresult HTMLEditor::GetInlineProperty(nsStaticAtom& aHTMLProperty,
   3158                                       nsAtom* aAttribute,
   3159                                       const nsAString& aValue, bool* aFirst,
   3160                                       bool* aAny, bool* aAll) const {
   3161  if (NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) || NS_WARN_IF(!aAll)) {
   3162    return NS_ERROR_INVALID_ARG;
   3163  }
   3164 
   3165  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
   3166  if (NS_WARN_IF(!editActionData.CanHandle())) {
   3167    return NS_ERROR_NOT_INITIALIZED;
   3168  }
   3169 
   3170  const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr;
   3171  nsresult rv =
   3172      GetInlinePropertyBase(EditorInlineStyle(aHTMLProperty, aAttribute), val,
   3173                            aFirst, aAny, aAll, nullptr);
   3174  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3175                       "HTMLEditor::GetInlinePropertyBase() failed");
   3176  return EditorBase::ToGenericNSResult(rv);
   3177 }
   3178 
   3179 NS_IMETHODIMP HTMLEditor::GetInlinePropertyWithAttrValue(
   3180    const nsAString& aHTMLProperty, const nsAString& aAttribute,
   3181    const nsAString& aValue, bool* aFirst, bool* aAny, bool* aAll,
   3182    nsAString& outValue) {
   3183  nsStaticAtom* property = NS_GetStaticAtom(aHTMLProperty);
   3184  if (NS_WARN_IF(!property)) {
   3185    return NS_ERROR_INVALID_ARG;
   3186  }
   3187  nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
   3188  // MOZ_KnownLive because nsStaticAtom is available until shutting down.
   3189  nsresult rv = GetInlinePropertyWithAttrValue(MOZ_KnownLive(*property),
   3190                                               MOZ_KnownLive(attribute), aValue,
   3191                                               aFirst, aAny, aAll, outValue);
   3192  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3193                       "HTMLEditor::GetInlinePropertyWithAttrValue() failed");
   3194  return rv;
   3195 }
   3196 
   3197 nsresult HTMLEditor::GetInlinePropertyWithAttrValue(
   3198    nsStaticAtom& aHTMLProperty, nsAtom* aAttribute, const nsAString& aValue,
   3199    bool* aFirst, bool* aAny, bool* aAll, nsAString& outValue) {
   3200  if (NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) || NS_WARN_IF(!aAll)) {
   3201    return NS_ERROR_INVALID_ARG;
   3202  }
   3203 
   3204  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
   3205  if (NS_WARN_IF(!editActionData.CanHandle())) {
   3206    return NS_ERROR_NOT_INITIALIZED;
   3207  }
   3208 
   3209  const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr;
   3210  nsresult rv =
   3211      GetInlinePropertyBase(EditorInlineStyle(aHTMLProperty, aAttribute), val,
   3212                            aFirst, aAny, aAll, &outValue);
   3213  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3214                       "HTMLEditor::GetInlinePropertyBase() failed");
   3215  return EditorBase::ToGenericNSResult(rv);
   3216 }
   3217 
   3218 nsresult HTMLEditor::RemoveAllInlinePropertiesAsAction(
   3219    nsIPrincipal* aPrincipal) {
   3220  AutoEditActionDataSetter editActionData(
   3221      *this, EditAction::eRemoveAllInlineStyleProperties, aPrincipal);
   3222  if (NS_WARN_IF(!editActionData.CanHandle())) {
   3223    return NS_ERROR_NOT_INITIALIZED;
   3224  }
   3225 
   3226  const RefPtr<Element> editingHost =
   3227      ComputeEditingHost(LimitInBodyElement::No);
   3228  if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) {
   3229    return NS_SUCCESS_DOM_NO_OPERATION;
   3230  }
   3231 
   3232  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
   3233  if (NS_FAILED(rv)) {
   3234    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3235                         "MaybeDispatchBeforeInputEvent(), failed");
   3236    return EditorBase::ToGenericNSResult(rv);
   3237  }
   3238 
   3239  AutoPlaceholderBatch treatAsOneTransaction(
   3240      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3241  IgnoredErrorResult ignoredError;
   3242  AutoEditSubActionNotifier startToHandleEditSubAction(
   3243      *this, EditSubAction::eRemoveAllTextProperties, nsIEditor::eNext,
   3244      ignoredError);
   3245  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3246    return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
   3247  }
   3248  NS_WARNING_ASSERTION(
   3249      !ignoredError.Failed(),
   3250      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3251 
   3252  AutoTArray<EditorInlineStyle, 1> removeAllInlineStyles;
   3253  removeAllInlineStyles.AppendElement(EditorInlineStyle::RemoveAllStyles());
   3254  rv = RemoveInlinePropertiesAsSubAction(removeAllInlineStyles, *editingHost);
   3255  NS_WARNING_ASSERTION(
   3256      NS_SUCCEEDED(rv),
   3257      "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
   3258  return EditorBase::ToGenericNSResult(rv);
   3259 }
   3260 
   3261 nsresult HTMLEditor::RemoveInlinePropertyAsAction(nsStaticAtom& aHTMLProperty,
   3262                                                  nsStaticAtom* aAttribute,
   3263                                                  nsIPrincipal* aPrincipal) {
   3264  AutoEditActionDataSetter editActionData(
   3265      *this,
   3266      HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty, aAttribute,
   3267                                                false),
   3268      aPrincipal);
   3269  if (NS_WARN_IF(!editActionData.CanHandle())) {
   3270    return NS_ERROR_NOT_INITIALIZED;
   3271  }
   3272 
   3273  const RefPtr<Element> editingHost =
   3274      ComputeEditingHost(LimitInBodyElement::No);
   3275  if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) {
   3276    return NS_SUCCESS_DOM_NO_OPERATION;
   3277  }
   3278 
   3279  switch (editActionData.GetEditAction()) {
   3280    case EditAction::eRemoveFontFamilyProperty:
   3281      MOZ_ASSERT(!u""_ns.IsVoid());
   3282      editActionData.SetData(u""_ns);
   3283      break;
   3284    case EditAction::eRemoveColorProperty:
   3285    case EditAction::eRemoveBackgroundColorPropertyInline:
   3286      editActionData.SetColorData(u""_ns);
   3287      break;
   3288    default:
   3289      break;
   3290  }
   3291  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
   3292  if (NS_FAILED(rv)) {
   3293    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3294                         "MaybeDispatchBeforeInputEvent(), failed");
   3295    return EditorBase::ToGenericNSResult(rv);
   3296  }
   3297 
   3298  AutoTArray<EditorInlineStyle, 8> removeInlineStyleAndRelatedElements;
   3299  AppendInlineStyleAndRelatedStyle(EditorInlineStyle(aHTMLProperty, aAttribute),
   3300                                   removeInlineStyleAndRelatedElements);
   3301  rv = RemoveInlinePropertiesAsSubAction(removeInlineStyleAndRelatedElements,
   3302                                         *editingHost);
   3303  NS_WARNING_ASSERTION(
   3304      NS_SUCCEEDED(rv),
   3305      "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
   3306  return EditorBase::ToGenericNSResult(rv);
   3307 }
   3308 
   3309 NS_IMETHODIMP HTMLEditor::RemoveInlineProperty(const nsAString& aProperty,
   3310                                               const nsAString& aAttribute) {
   3311  nsStaticAtom* property = NS_GetStaticAtom(aProperty);
   3312  nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
   3313 
   3314  AutoEditActionDataSetter editActionData(
   3315      *this,
   3316      HTMLEditUtils::GetEditActionForFormatText(*property, attribute, false));
   3317  if (NS_WARN_IF(!editActionData.CanHandle())) {
   3318    return NS_ERROR_NOT_INITIALIZED;
   3319  }
   3320 
   3321  const RefPtr<Element> editingHost =
   3322      ComputeEditingHost(LimitInBodyElement::No);
   3323  if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) {
   3324    return NS_SUCCESS_DOM_NO_OPERATION;
   3325  }
   3326 
   3327  switch (editActionData.GetEditAction()) {
   3328    case EditAction::eRemoveFontFamilyProperty:
   3329      MOZ_ASSERT(!EmptyString().IsVoid());
   3330      editActionData.SetData(EmptyString());
   3331      break;
   3332    case EditAction::eRemoveColorProperty:
   3333    case EditAction::eRemoveBackgroundColorPropertyInline:
   3334      editActionData.SetColorData(EmptyString());
   3335      break;
   3336    default:
   3337      break;
   3338  }
   3339  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
   3340  if (NS_FAILED(rv)) {
   3341    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3342                         "MaybeDispatchBeforeInputEvent(), failed");
   3343    return EditorBase::ToGenericNSResult(rv);
   3344  }
   3345 
   3346  AutoTArray<EditorInlineStyle, 1> removeOneInlineStyle;
   3347  removeOneInlineStyle.AppendElement(EditorInlineStyle(*property, attribute));
   3348  rv = RemoveInlinePropertiesAsSubAction(removeOneInlineStyle, *editingHost);
   3349  NS_WARNING_ASSERTION(
   3350      NS_SUCCEEDED(rv),
   3351      "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
   3352  return EditorBase::ToGenericNSResult(rv);
   3353 }
   3354 
   3355 void HTMLEditor::AppendInlineStyleAndRelatedStyle(
   3356    const EditorInlineStyle& aStyleToRemove,
   3357    nsTArray<EditorInlineStyle>& aStylesToRemove) const {
   3358  if (nsStaticAtom* similarElementName =
   3359          aStyleToRemove.GetSimilarElementNameAtom()) {
   3360    EditorInlineStyle anotherStyle(*similarElementName);
   3361    if (!aStylesToRemove.Contains(anotherStyle)) {
   3362      aStylesToRemove.AppendElement(std::move(anotherStyle));
   3363    }
   3364  } else if (aStyleToRemove.mHTMLProperty == nsGkAtoms::font) {
   3365    if (aStyleToRemove.mAttribute == nsGkAtoms::size) {
   3366      EditorInlineStyle big(*nsGkAtoms::big), small(*nsGkAtoms::small);
   3367      if (!aStylesToRemove.Contains(big)) {
   3368        aStylesToRemove.AppendElement(std::move(big));
   3369      }
   3370      if (!aStylesToRemove.Contains(small)) {
   3371        aStylesToRemove.AppendElement(std::move(small));
   3372      }
   3373    }
   3374    // Handling <tt> element code was implemented for composer (bug 115922).
   3375    // This shouldn't work with Document.execCommand() for compatibility with
   3376    // the other browsers.  Currently, edit action principal is set only when
   3377    // the root caller is Document::ExecCommand() so that we should handle <tt>
   3378    // element only when the principal is nullptr that must be only when XUL
   3379    // command is executed on composer.
   3380    else if (aStyleToRemove.mAttribute == nsGkAtoms::face &&
   3381             !GetEditActionPrincipal()) {
   3382      EditorInlineStyle tt(*nsGkAtoms::tt);
   3383      if (!aStylesToRemove.Contains(tt)) {
   3384        aStylesToRemove.AppendElement(std::move(tt));
   3385      }
   3386    }
   3387  }
   3388  if (!aStylesToRemove.Contains(aStyleToRemove)) {
   3389    aStylesToRemove.AppendElement(aStyleToRemove);
   3390  }
   3391 }
   3392 
   3393 nsresult HTMLEditor::RemoveInlinePropertiesAsSubAction(
   3394    const nsTArray<EditorInlineStyle>& aStylesToRemove,
   3395    const Element& aEditingHost) {
   3396  MOZ_ASSERT(IsEditActionDataAvailable());
   3397  MOZ_ASSERT(!aStylesToRemove.IsEmpty());
   3398 
   3399  DebugOnly<nsresult> rvIgnored = CommitComposition();
   3400  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   3401                       "EditorBase::CommitComposition() failed, but ignored");
   3402 
   3403  if (SelectionRef().IsCollapsed()) {
   3404    // Manipulating text attributes on a collapsed selection only sets state
   3405    // for the next text insertion
   3406    mPendingStylesToApplyToNewContent->ClearStyles(aStylesToRemove);
   3407    return NS_OK;
   3408  }
   3409 
   3410  {
   3411    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   3412    if (MOZ_UNLIKELY(result.isErr())) {
   3413      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   3414      return result.unwrapErr();
   3415    }
   3416    if (result.inspect().Canceled()) {
   3417      return NS_OK;
   3418    }
   3419  }
   3420 
   3421  AutoPlaceholderBatch treatAsOneTransaction(
   3422      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3423  IgnoredErrorResult ignoredError;
   3424  AutoEditSubActionNotifier startToHandleEditSubAction(
   3425      *this, EditSubAction::eRemoveTextProperty, nsIEditor::eNext,
   3426      ignoredError);
   3427  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3428    return ignoredError.StealNSResult();
   3429  }
   3430  NS_WARNING_ASSERTION(
   3431      !ignoredError.Failed(),
   3432      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3433 
   3434  // TODO: We don't need AutoTransactionsConserveSelection here in the normal
   3435  //       cases, but removing this may cause the behavior with the legacy
   3436  //       mutation event listeners.  We should try to delete this in a bug.
   3437  AutoTransactionsConserveSelection dontChangeMySelection(*this);
   3438 
   3439  AutoClonedSelectionRangeArray selectionRanges(SelectionRef());
   3440  for (const EditorInlineStyle& styleToRemove : aStylesToRemove) {
   3441    // The ranges may be updated by changing the DOM tree.  In strictly
   3442    // speaking, we should save and restore the ranges at every range loop,
   3443    // but we've never done so and it may be expensive if there are a lot of
   3444    // ranges.  Therefore, we should do it for every style handling for now.
   3445    // TODO: We should collect everything required for removing the style before
   3446    //       touching the DOM tree.  Then, we need to save and restore the
   3447    //       ranges only once.
   3448    Maybe<AutoInlineStyleSetter> styleInverter;
   3449    if (styleToRemove.IsInvertibleWithCSS()) {
   3450      styleInverter.emplace(EditorInlineStyleAndValue::ToInvert(styleToRemove));
   3451    }
   3452    for (OwningNonNull<nsRange>& selectionRange : selectionRanges.Ranges()) {
   3453      AutoTrackDOMRange trackSelectionRange(RangeUpdaterRef(), &selectionRange);
   3454      // If we're removing <a name>, we don't want to split ancestors because
   3455      // the split fragment will keep working as named anchor.  Therefore, we
   3456      // need to remove all <a name> elements which the selection range even
   3457      // partially contains.
   3458      const EditorDOMRange range(
   3459          styleToRemove.mHTMLProperty == nsGkAtoms::name
   3460              ? GetExtendedRangeWrappingNamedAnchor(
   3461                    EditorRawDOMRange(selectionRange))
   3462              : GetExtendedRangeWrappingEntirelySelectedElements(
   3463                    EditorRawDOMRange(selectionRange)));
   3464      if (NS_WARN_IF(!range.IsPositioned())) {
   3465        continue;
   3466      }
   3467 
   3468      // Remove this style from ancestors of our range endpoints, splitting
   3469      // them as appropriate
   3470      Result<SplitRangeOffResult, nsresult> splitRangeOffResult =
   3471          SplitAncestorStyledInlineElementsAtRangeEdges(
   3472              range, styleToRemove, SplitAtEdges::eAllowToCreateEmptyContainer);
   3473      if (MOZ_UNLIKELY(splitRangeOffResult.isErr())) {
   3474        NS_WARNING(
   3475            "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
   3476            "failed");
   3477        return splitRangeOffResult.unwrapErr();
   3478      }
   3479      // There is AutoTransactionsConserveSelection, so we don't need to
   3480      // update selection here.
   3481      splitRangeOffResult.inspect().IgnoreCaretPointSuggestion();
   3482 
   3483      // XXX Modifying `range` means that we may modify ranges in `Selection`.
   3484      //     Is this intentional?  Note that the range may be not in
   3485      //     `Selection` too.  It seems that at least one of them is not
   3486      //     an unexpected case.
   3487      const EditorDOMRange& splitRange =
   3488          splitRangeOffResult.inspect().RangeRef();
   3489      if (NS_WARN_IF(!splitRange.IsPositioned())) {
   3490        continue;
   3491      }
   3492 
   3493      AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsToInvertStyle;
   3494      {
   3495        // Collect top level children in the range first.
   3496        // TODO: Perhaps, HTMLEditUtils::IsSplittableNode should be used here
   3497        //       instead of EditorUtils::IsEditableContent.
   3498        AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsAroundRange;
   3499        if (splitRange.InSameContainer() &&
   3500            splitRange.StartRef().IsInTextNode()) {
   3501          if (!EditorUtils::IsEditableContent(
   3502                  *splitRange.StartRef().ContainerAs<Text>(),
   3503                  EditorType::HTML)) {
   3504            continue;
   3505          }
   3506          arrayOfContentsAroundRange.AppendElement(
   3507              *splitRange.StartRef().ContainerAs<Text>());
   3508        } else if (splitRange.IsInTextNodes() &&
   3509                   splitRange.InAdjacentSiblings()) {
   3510          // Adjacent siblings are in a same element, so the editable state of
   3511          // both text nodes are always same.
   3512          if (!EditorUtils::IsEditableContent(
   3513                  *splitRange.StartRef().ContainerAs<Text>(),
   3514                  EditorType::HTML)) {
   3515            continue;
   3516          }
   3517          arrayOfContentsAroundRange.AppendElement(
   3518              *splitRange.StartRef().ContainerAs<Text>());
   3519          arrayOfContentsAroundRange.AppendElement(
   3520              *splitRange.EndRef().ContainerAs<Text>());
   3521        } else {
   3522          // Append first node if it's a text node but selected not entirely.
   3523          if (splitRange.StartRef().IsInTextNode() &&
   3524              !splitRange.StartRef().IsStartOfContainer() &&
   3525              EditorUtils::IsEditableContent(
   3526                  *splitRange.StartRef().ContainerAs<Text>(),
   3527                  EditorType::HTML)) {
   3528            arrayOfContentsAroundRange.AppendElement(
   3529                *splitRange.StartRef().ContainerAs<Text>());
   3530          }
   3531          // Append all entirely selected nodes.
   3532          ContentSubtreeIterator subtreeIter;
   3533          if (NS_SUCCEEDED(
   3534                  subtreeIter.Init(splitRange.StartRef().ToRawRangeBoundary(),
   3535                                   splitRange.EndRef().ToRawRangeBoundary()))) {
   3536            for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
   3537              nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
   3538              if (NS_WARN_IF(!node)) {
   3539                return NS_ERROR_FAILURE;
   3540              }
   3541              if (node->IsContent() &&
   3542                  EditorUtils::IsEditableContent(*node->AsContent(),
   3543                                                 EditorType::HTML)) {
   3544                arrayOfContentsAroundRange.AppendElement(*node->AsContent());
   3545              }
   3546            }
   3547          }
   3548          // Append last node if it's a text node but selected not entirely.
   3549          if (!splitRange.InSameContainer() &&
   3550              splitRange.EndRef().IsInTextNode() &&
   3551              !splitRange.EndRef().IsEndOfContainer() &&
   3552              EditorUtils::IsEditableContent(
   3553                  *splitRange.EndRef().ContainerAs<Text>(), EditorType::HTML)) {
   3554            arrayOfContentsAroundRange.AppendElement(
   3555                *splitRange.EndRef().ContainerAs<Text>());
   3556          }
   3557        }
   3558        if (styleToRemove.IsInvertibleWithCSS()) {
   3559          arrayOfContentsToInvertStyle.SetCapacity(
   3560              arrayOfContentsAroundRange.Length());
   3561        }
   3562 
   3563        for (OwningNonNull<nsIContent>& content : arrayOfContentsAroundRange) {
   3564          // We should remove style from the element and its descendants.
   3565          if (content->IsElement()) {
   3566            Result<EditorDOMPoint, nsresult> removeStyleResult =
   3567                RemoveStyleInside(MOZ_KnownLive(*content->AsElement()),
   3568                                  styleToRemove, SpecifiedStyle::Preserve);
   3569            if (MOZ_UNLIKELY(removeStyleResult.isErr())) {
   3570              NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
   3571              return removeStyleResult.unwrapErr();
   3572            }
   3573            // There is AutoTransactionsConserveSelection, so we don't need to
   3574            // update selection here.
   3575 
   3576            // If the element was removed from the DOM tree by
   3577            // RemoveStyleInside, we need to do nothing for it anymore.
   3578            if (!content->GetParentNode()) {
   3579              continue;
   3580            }
   3581          }
   3582 
   3583          if (styleToRemove.IsInvertibleWithCSS()) {
   3584            arrayOfContentsToInvertStyle.AppendElement(content);
   3585          }
   3586        }  // for-loop for arrayOfContentsAroundRange
   3587      }
   3588 
   3589      auto FlushAndStopTrackingAndShrinkSelectionRange =
   3590          [&]() MOZ_CAN_RUN_SCRIPT {
   3591            trackSelectionRange.FlushAndStopTracking();
   3592            if (NS_WARN_IF(!selectionRange->IsPositioned())) {
   3593              return;
   3594            }
   3595            EditorRawDOMRange range(selectionRange);
   3596            nsINode* const commonAncestor =
   3597                range.GetClosestCommonInclusiveAncestor();
   3598            // Shrink range for compatibility between browsers.
   3599            nsIContent* const maybeNextContent =
   3600                range.StartRef().IsInContentNode() &&
   3601                        range.StartRef().IsEndOfContainer()
   3602                    ? AutoInlineStyleSetter::GetNextEditableInlineContent(
   3603                          *range.StartRef().ContainerAs<nsIContent>(),
   3604                          commonAncestor)
   3605                    : nullptr;
   3606            nsIContent* const maybePreviousContent =
   3607                range.EndRef().IsInContentNode() &&
   3608                        range.EndRef().IsStartOfContainer()
   3609                    ? AutoInlineStyleSetter::GetPreviousEditableInlineContent(
   3610                          *range.EndRef().ContainerAs<nsIContent>(),
   3611                          commonAncestor)
   3612                    : nullptr;
   3613            if (!maybeNextContent && !maybePreviousContent) {
   3614              return;
   3615            }
   3616            const auto startPoint =
   3617                maybeNextContent &&
   3618                        maybeNextContent != selectionRange->GetStartContainer()
   3619                    ? HTMLEditUtils::GetDeepestEditableStartPointOf<
   3620                          EditorRawDOMPoint>(
   3621                          *maybeNextContent,
   3622                          {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   3623                           EditablePointOption::StopAtComment})
   3624                    : range.StartRef();
   3625            const auto endPoint =
   3626                maybePreviousContent && maybePreviousContent !=
   3627                                            selectionRange->GetEndContainer()
   3628                    ? HTMLEditUtils::GetDeepestEditableEndPointOf<
   3629                          EditorRawDOMPoint>(
   3630                          *maybePreviousContent,
   3631                          {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   3632                           EditablePointOption::StopAtComment})
   3633                    : range.EndRef();
   3634            DebugOnly<nsresult> rvIgnored = selectionRange->SetStartAndEnd(
   3635                startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
   3636            NS_WARNING_ASSERTION(
   3637                NS_SUCCEEDED(rvIgnored),
   3638                "nsRange::SetStartAndEnd() failed, but ignored");
   3639          };
   3640 
   3641      if (arrayOfContentsToInvertStyle.IsEmpty()) {
   3642        FlushAndStopTrackingAndShrinkSelectionRange();
   3643        continue;
   3644      }
   3645      MOZ_ASSERT(styleToRemove.IsInvertibleWithCSS());
   3646 
   3647      // If the style is specified in parent block and we can remove the
   3648      // style with inserting new <span> element, we should do it.
   3649      for (OwningNonNull<nsIContent>& content : arrayOfContentsToInvertStyle) {
   3650        if (Element* element = Element::FromNode(content)) {
   3651          // XXX Do we need to call this even when data node or something?  If
   3652          //     so, for what?
   3653          // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   3654          // keep it alive.
   3655          nsresult rv = styleInverter->InvertStyleIfApplied(
   3656              *this, MOZ_KnownLive(*element));
   3657          if (NS_FAILED(rv)) {
   3658            if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3659              NS_WARNING(
   3660                  "AutoInlineStyleSetter::InvertStyleIfApplied() failed");
   3661              return NS_ERROR_EDITOR_DESTROYED;
   3662            }
   3663            NS_WARNING(
   3664                "AutoInlineStyleSetter::InvertStyleIfApplied() failed, but "
   3665                "ignored");
   3666          }
   3667          continue;
   3668        }
   3669 
   3670        // Unfortunately, all browsers don't join text nodes when removing a
   3671        // style.  Therefore, there may be multiple text nodes as adjacent
   3672        // siblings.  That's the reason why we need to handle text nodes in this
   3673        // loop.
   3674        if (Text* textNode = Text::FromNode(content)) {
   3675          const uint32_t startOffset =
   3676              content == splitRange.StartRef().GetContainer()
   3677                  ? splitRange.StartRef().Offset()
   3678                  : 0u;
   3679          const uint32_t endOffset =
   3680              content == splitRange.EndRef().GetContainer()
   3681                  ? splitRange.EndRef().Offset()
   3682                  : textNode->TextDataLength();
   3683          Result<SplitRangeOffFromNodeResult, nsresult>
   3684              wrapTextInStyledElementResult =
   3685                  styleInverter->InvertStyleIfApplied(
   3686                      *this, MOZ_KnownLive(*textNode), startOffset, endOffset);
   3687          if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
   3688            NS_WARNING("AutoInlineStyleSetter::InvertStyleIfApplied() failed");
   3689            return wrapTextInStyledElementResult.unwrapErr();
   3690          }
   3691          SplitRangeOffFromNodeResult unwrappedWrapTextInStyledElementResult =
   3692              wrapTextInStyledElementResult.unwrap();
   3693          // There is AutoTransactionsConserveSelection, so we don't need to
   3694          // update selection here.
   3695          unwrappedWrapTextInStyledElementResult.IgnoreCaretPointSuggestion();
   3696          // If we've split the content, let's swap content in
   3697          // arrayOfContentsToInvertStyle with the text node which is applied
   3698          // the style.
   3699          if (unwrappedWrapTextInStyledElementResult.DidSplit() &&
   3700              styleToRemove.IsInvertibleWithCSS()) {
   3701            MOZ_ASSERT(unwrappedWrapTextInStyledElementResult
   3702                           .GetMiddleContentAs<Text>());
   3703            if (Text* styledTextNode = unwrappedWrapTextInStyledElementResult
   3704                                           .GetMiddleContentAs<Text>()) {
   3705              if (styledTextNode != content) {
   3706                arrayOfContentsToInvertStyle.ReplaceElementAt(
   3707                    arrayOfContentsToInvertStyle.Length() - 1,
   3708                    OwningNonNull<nsIContent>(*styledTextNode));
   3709              }
   3710            }
   3711          }
   3712          continue;
   3713        }
   3714 
   3715        // If the node is not an element nor a text node, it's invisible.
   3716        // In this case, we don't need to make it wrapped in new element.
   3717      }
   3718 
   3719      // Finally, we should remove the style from all leaf text nodes if
   3720      // they still have the style.
   3721      AutoTArray<OwningNonNull<Text>, 32> leafTextNodes;
   3722      for (const OwningNonNull<nsIContent>& content :
   3723           arrayOfContentsToInvertStyle) {
   3724        // XXX Should we ignore content which has already removed from the
   3725        //     DOM tree by the previous for-loop?
   3726        if (content->IsElement()) {
   3727          CollectEditableLeafTextNodes(*content->AsElement(), leafTextNodes);
   3728        }
   3729      }
   3730      for (const OwningNonNull<Text>& textNode : leafTextNodes) {
   3731        Result<SplitRangeOffFromNodeResult, nsresult>
   3732            wrapTextInStyledElementResult = styleInverter->InvertStyleIfApplied(
   3733                *this, MOZ_KnownLive(*textNode), 0, textNode->TextLength());
   3734        if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
   3735          NS_WARNING(
   3736              "AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode() "
   3737              "failed");
   3738          return wrapTextInStyledElementResult.unwrapErr();
   3739        }
   3740        // There is AutoTransactionsConserveSelection, so we don't need to
   3741        // update selection here.
   3742        wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
   3743      }  // for-loop of leafTextNodes
   3744 
   3745      // styleInverter may have touched a part of the range.  Therefore, we
   3746      // cannot adjust the range without comparing DOM node position and
   3747      // first/last touched positions, but it may be too expensive.  I think
   3748      // that shrinking only the tracked range boundaries must be enough in most
   3749      // cases.
   3750      FlushAndStopTrackingAndShrinkSelectionRange();
   3751    }  // for-loop of selectionRanges
   3752  }  // for-loop of styles
   3753 
   3754  MOZ_ASSERT(!selectionRanges.HasSavedRanges());
   3755  nsresult rv = selectionRanges.ApplyTo(SelectionRef());
   3756  if (NS_WARN_IF(Destroyed())) {
   3757    return NS_ERROR_EDITOR_DESTROYED;
   3758  }
   3759  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3760                       "AutoClonedSelectionRangeArray::ApplyTo() failed");
   3761  return rv;
   3762 }
   3763 
   3764 nsresult HTMLEditor::AutoInlineStyleSetter::InvertStyleIfApplied(
   3765    HTMLEditor& aHTMLEditor, Element& aElement) {
   3766  MOZ_ASSERT(IsStyleToInvert());
   3767 
   3768  Result<bool, nsresult> isRemovableParentStyleOrError =
   3769      aHTMLEditor.IsRemovableParentStyleWithNewSpanElement(aElement, *this);
   3770  if (MOZ_UNLIKELY(isRemovableParentStyleOrError.isErr())) {
   3771    NS_WARNING("HTMLEditor::IsRemovableParentStyleWithNewSpanElement() failed");
   3772    return isRemovableParentStyleOrError.unwrapErr();
   3773  }
   3774  if (!isRemovableParentStyleOrError.unwrap()) {
   3775    // E.g., text-decoration cannot be override visually in children.
   3776    // In such cases, we can do nothing.
   3777    return NS_OK;
   3778  }
   3779 
   3780  // Wrap it into a new element, move it into direct child which has same style,
   3781  // or specify the style to its parent.
   3782  Result<CaretPoint, nsresult> pointToPutCaretOrError =
   3783      ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(aHTMLEditor, aElement);
   3784  if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   3785    NS_WARNING(
   3786        "AutoInlineStyleSetter::"
   3787        "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
   3788    return pointToPutCaretOrError.unwrapErr();
   3789  }
   3790  // The caller must update `Selection` later so that we don't need this.
   3791  pointToPutCaretOrError.unwrap().IgnoreCaretPointSuggestion();
   3792  return NS_OK;
   3793 }
   3794 
   3795 Result<SplitRangeOffFromNodeResult, nsresult>
   3796 HTMLEditor::AutoInlineStyleSetter::InvertStyleIfApplied(HTMLEditor& aHTMLEditor,
   3797                                                        Text& aTextNode,
   3798                                                        uint32_t aStartOffset,
   3799                                                        uint32_t aEndOffset) {
   3800  MOZ_ASSERT(IsStyleToInvert());
   3801 
   3802  Result<bool, nsresult> isRemovableParentStyleOrError =
   3803      aHTMLEditor.IsRemovableParentStyleWithNewSpanElement(aTextNode, *this);
   3804  if (MOZ_UNLIKELY(isRemovableParentStyleOrError.isErr())) {
   3805    NS_WARNING("HTMLEditor::IsRemovableParentStyleWithNewSpanElement() failed");
   3806    return isRemovableParentStyleOrError.propagateErr();
   3807  }
   3808  if (!isRemovableParentStyleOrError.unwrap()) {
   3809    // E.g., text-decoration cannot be override visually in children.
   3810    // In such cases, we can do nothing.
   3811    return SplitRangeOffFromNodeResult(nullptr, &aTextNode, nullptr);
   3812  }
   3813 
   3814  // We need to use new `<span>` element or existing element if it's available
   3815  // to overwrite parent style.
   3816  Result<SplitRangeOffFromNodeResult, nsresult> wrapTextInStyledElementResult =
   3817      SplitTextNodeAndApplyStyleToMiddleNode(aHTMLEditor, aTextNode,
   3818                                             aStartOffset, aEndOffset);
   3819  NS_WARNING_ASSERTION(
   3820      wrapTextInStyledElementResult.isOk(),
   3821      "AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode() failed");
   3822  return wrapTextInStyledElementResult;
   3823 }
   3824 
   3825 Result<bool, nsresult> HTMLEditor::IsRemovableParentStyleWithNewSpanElement(
   3826    nsIContent& aContent, const EditorInlineStyle& aStyle) const {
   3827  // We don't support to remove all inline styles with this path.
   3828  if (aStyle.IsStyleToClearAllInlineStyles()) {
   3829    return false;
   3830  }
   3831 
   3832  // First check whether the style is invertible since this is the fastest
   3833  // check.
   3834  if (!aStyle.IsInvertibleWithCSS()) {
   3835    return false;
   3836  }
   3837 
   3838  // If aContent is not an element and it's not in an element, it means that
   3839  // aContent is disconnected non-element node.  In this case, it's never
   3840  // applied any styles which are invertible.
   3841  const RefPtr<Element> element = aContent.GetAsElementOrParentElement();
   3842  if (MOZ_UNLIKELY(!element)) {
   3843    return false;
   3844  }
   3845 
   3846  // If parent block has invertible style, we should remove the style with
   3847  // creating new `<span>` element even in HTML mode because Chrome does it.
   3848  if (!aStyle.IsCSSSettable(*element)) {
   3849    return false;
   3850  }
   3851  nsAutoString emptyString;
   3852  Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
   3853      CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle,
   3854                                              emptyString);
   3855  NS_WARNING_ASSERTION(isComputedCSSEquivalentToStyleOrError.isOk(),
   3856                       "CSSEditUtils::IsComputedCSSEquivalentTo() failed");
   3857  return isComputedCSSEquivalentToStyleOrError;
   3858 }
   3859 
   3860 void HTMLEditor::CollectEditableLeafTextNodes(
   3861    Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const {
   3862  for (nsIContent* child = aElement.GetFirstChild(); child;
   3863       child = child->GetNextSibling()) {
   3864    if (child->IsElement()) {
   3865      CollectEditableLeafTextNodes(*child->AsElement(), aLeafTextNodes);
   3866      continue;
   3867    }
   3868    if (child->IsText()) {
   3869      aLeafTextNodes.AppendElement(*child->AsText());
   3870    }
   3871  }
   3872 }
   3873 
   3874 nsresult HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
   3875  AutoEditActionDataSetter editActionData(*this, EditAction::eIncrementFontSize,
   3876                                          aPrincipal);
   3877  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3878  if (NS_FAILED(rv)) {
   3879    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3880                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3881    return EditorBase::ToGenericNSResult(rv);
   3882  }
   3883 
   3884  rv = IncrementOrDecrementFontSizeAsSubAction(FontSize::incr);
   3885  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3886                       "HTMLEditor::IncrementOrDecrementFontSizeAsSubAction("
   3887                       "FontSize::incr) failed");
   3888  return EditorBase::ToGenericNSResult(rv);
   3889 }
   3890 
   3891 nsresult HTMLEditor::DecreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
   3892  AutoEditActionDataSetter editActionData(*this, EditAction::eDecrementFontSize,
   3893                                          aPrincipal);
   3894  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3895  if (NS_FAILED(rv)) {
   3896    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3897                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3898    return EditorBase::ToGenericNSResult(rv);
   3899  }
   3900 
   3901  rv = IncrementOrDecrementFontSizeAsSubAction(FontSize::decr);
   3902  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3903                       "HTMLEditor::IncrementOrDecrementFontSizeAsSubAction("
   3904                       "FontSize::decr) failed");
   3905  return EditorBase::ToGenericNSResult(rv);
   3906 }
   3907 
   3908 nsresult HTMLEditor::IncrementOrDecrementFontSizeAsSubAction(
   3909    FontSize aIncrementOrDecrement) {
   3910  MOZ_ASSERT(IsEditActionDataAvailable());
   3911 
   3912  // Committing composition and changing font size should be undone together.
   3913  AutoPlaceholderBatch treatAsOneTransaction(
   3914      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3915 
   3916  DebugOnly<nsresult> rvIgnored = CommitComposition();
   3917  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   3918                       "EditorBase::CommitComposition() failed, but ignored");
   3919 
   3920  // If selection is collapsed, set typing state
   3921  if (SelectionRef().IsCollapsed()) {
   3922    nsStaticAtom& bigOrSmallTagName = aIncrementOrDecrement == FontSize::incr
   3923                                          ? *nsGkAtoms::big
   3924                                          : *nsGkAtoms::small;
   3925 
   3926    // Let's see in what kind of element the selection is
   3927    if (!SelectionRef().RangeCount()) {
   3928      return NS_OK;
   3929    }
   3930    const auto firstRangeStartPoint =
   3931        EditorBase::GetFirstSelectionStartPoint<EditorRawDOMPoint>();
   3932    if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
   3933      return NS_OK;
   3934    }
   3935    Element* element =
   3936        firstRangeStartPoint.GetContainerOrContainerParentElement();
   3937    if (NS_WARN_IF(!element)) {
   3938      return NS_OK;
   3939    }
   3940    if (!HTMLEditUtils::CanNodeContain(*element, bigOrSmallTagName)) {
   3941      return NS_OK;
   3942    }
   3943 
   3944    // Manipulating text attributes on a collapsed selection only sets state
   3945    // for the next text insertion
   3946    mPendingStylesToApplyToNewContent->PreserveStyle(bigOrSmallTagName, nullptr,
   3947                                                     u""_ns);
   3948    return NS_OK;
   3949  }
   3950 
   3951  IgnoredErrorResult ignoredError;
   3952  AutoEditSubActionNotifier startToHandleEditSubAction(
   3953      *this, EditSubAction::eSetTextProperty, nsIEditor::eNext, ignoredError);
   3954  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3955    return ignoredError.StealNSResult();
   3956  }
   3957  NS_WARNING_ASSERTION(
   3958      !ignoredError.Failed(),
   3959      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3960 
   3961  // TODO: We don't need AutoTransactionsConserveSelection here in the normal
   3962  //       cases, but removing this may cause the behavior with the legacy
   3963  //       mutation event listeners.  We should try to delete this in a bug.
   3964  AutoTransactionsConserveSelection dontChangeMySelection(*this);
   3965 
   3966  AutoClonedSelectionRangeArray selectionRanges(SelectionRef());
   3967  MOZ_ALWAYS_TRUE(selectionRanges.SaveAndTrackRanges(*this));
   3968  for (const OwningNonNull<nsRange>& domRange : selectionRanges.Ranges()) {
   3969    // TODO: We should stop extending the range outside ancestor blocks because
   3970    //       we don't need to do it for setting inline styles.  However, here is
   3971    //       chrome only handling path.  Therefore, we don't need to fix here
   3972    //       soon.
   3973    const EditorDOMRange range(GetExtendedRangeWrappingEntirelySelectedElements(
   3974        EditorRawDOMRange(domRange)));
   3975    if (NS_WARN_IF(!range.IsPositioned())) {
   3976      continue;
   3977    }
   3978 
   3979    if (range.InSameContainer() && range.StartRef().IsInTextNode()) {
   3980      Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult =
   3981          SetFontSizeOnTextNode(
   3982              MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
   3983              range.StartRef().Offset(), range.EndRef().Offset(),
   3984              aIncrementOrDecrement);
   3985      if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) {
   3986        NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
   3987        return wrapInBigOrSmallElementResult.unwrapErr();
   3988      }
   3989      // There is an AutoTransactionsConserveSelection instance so that we don't
   3990      // need to update selection for this change.
   3991      wrapInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
   3992      continue;
   3993    }
   3994 
   3995    // Not the easy case.  Range not contained in single text node.  There
   3996    // are up to three phases here.  There are all the nodes reported by the
   3997    // subtree iterator to be processed.  And there are potentially a
   3998    // starting textnode and an ending textnode which are only partially
   3999    // contained by the range.
   4000 
   4001    // Let's handle the nodes reported by the iterator.  These nodes are
   4002    // entirely contained in the selection range.  We build up a list of them
   4003    // (since doing operations on the document during iteration would perturb
   4004    // the iterator).
   4005 
   4006    // Iterate range and build up array
   4007    ContentSubtreeIterator subtreeIter;
   4008    if (NS_SUCCEEDED(subtreeIter.Init(range.StartRef().ToRawRangeBoundary(),
   4009                                      range.EndRef().ToRawRangeBoundary()))) {
   4010      nsTArray<OwningNonNull<nsIContent>> arrayOfContents;
   4011      for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
   4012        if (NS_WARN_IF(!subtreeIter.GetCurrentNode()->IsContent())) {
   4013          return NS_ERROR_FAILURE;
   4014        }
   4015        OwningNonNull<nsIContent> content =
   4016            *subtreeIter.GetCurrentNode()->AsContent();
   4017 
   4018        if (EditorUtils::IsEditableContent(content, EditorType::HTML)) {
   4019          arrayOfContents.AppendElement(content);
   4020        }
   4021      }
   4022 
   4023      // Now that we have the list, do the font size change on each node
   4024      for (OwningNonNull<nsIContent>& content : arrayOfContents) {
   4025        // MOZ_KnownLive because of bug 1622253
   4026        Result<EditorDOMPoint, nsresult> fontChangeOnNodeResult =
   4027            SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(content),
   4028                                             aIncrementOrDecrement);
   4029        if (MOZ_UNLIKELY(fontChangeOnNodeResult.isErr())) {
   4030          NS_WARNING("HTMLEditor::SetFontSizeWithBigOrSmallElement() failed");
   4031          return fontChangeOnNodeResult.unwrapErr();
   4032        }
   4033        // There is an AutoTransactionsConserveSelection, so we don't need to
   4034        // update selection here.
   4035      }
   4036    }
   4037    // Now check the start and end parents of the range to see if they need
   4038    // to be separately handled (they do if they are text nodes, due to how
   4039    // the subtree iterator works - it will not have reported them).
   4040    if (range.StartRef().IsInTextNode() &&
   4041        !range.StartRef().IsEndOfContainer() &&
   4042        EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(),
   4043                                       EditorType::HTML)) {
   4044      Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult =
   4045          SetFontSizeOnTextNode(
   4046              MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
   4047              range.StartRef().Offset(),
   4048              range.StartRef().ContainerAs<Text>()->TextDataLength(),
   4049              aIncrementOrDecrement);
   4050      if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) {
   4051        NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
   4052        return wrapInBigOrSmallElementResult.unwrapErr();
   4053      }
   4054      // There is an AutoTransactionsConserveSelection instance so that we
   4055      // don't need to update selection for this change.
   4056      wrapInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
   4057    }
   4058    if (range.EndRef().IsInTextNode() && !range.EndRef().IsStartOfContainer() &&
   4059        EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(),
   4060                                       EditorType::HTML)) {
   4061      Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult =
   4062          SetFontSizeOnTextNode(
   4063              MOZ_KnownLive(*range.EndRef().ContainerAs<Text>()), 0u,
   4064              range.EndRef().Offset(), aIncrementOrDecrement);
   4065      if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) {
   4066        NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
   4067        return wrapInBigOrSmallElementResult.unwrapErr();
   4068      }
   4069      // There is an AutoTransactionsConserveSelection instance so that we
   4070      // don't need to update selection for this change.
   4071      wrapInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
   4072    }
   4073  }
   4074 
   4075  MOZ_ASSERT(selectionRanges.HasSavedRanges());
   4076  selectionRanges.RestoreFromSavedRanges();
   4077  nsresult rv = selectionRanges.ApplyTo(SelectionRef());
   4078  if (NS_WARN_IF(Destroyed())) {
   4079    return NS_ERROR_EDITOR_DESTROYED;
   4080  }
   4081  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4082                       "AutoClonedSelectionRangeArray::ApplyTo() failed");
   4083  return rv;
   4084 }
   4085 
   4086 Result<CreateElementResult, nsresult> HTMLEditor::SetFontSizeOnTextNode(
   4087    Text& aTextNode, uint32_t aStartOffset, uint32_t aEndOffset,
   4088    FontSize aIncrementOrDecrement) {
   4089  // Don't need to do anything if no characters actually selected
   4090  if (aStartOffset == aEndOffset) {
   4091    return CreateElementResult::NotHandled();
   4092  }
   4093 
   4094  if (!aTextNode.GetParentNode() ||
   4095      !HTMLEditUtils::CanNodeContain(*aTextNode.GetParentNode(),
   4096                                     *nsGkAtoms::big)) {
   4097    return CreateElementResult::NotHandled();
   4098  }
   4099 
   4100  aEndOffset = std::min(aTextNode.Length(), aEndOffset);
   4101 
   4102  // Make the range an independent node.
   4103  RefPtr<Text> textNodeForTheRange = &aTextNode;
   4104 
   4105  EditorDOMPoint pointToPutCaret;
   4106  {
   4107    auto pointToPutCaretOrError =
   4108        [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMPoint, nsresult> {
   4109      EditorDOMPoint pointToPutCaret;
   4110      // Split at the end of the range.
   4111      EditorDOMPoint atEnd(textNodeForTheRange, aEndOffset);
   4112      if (!atEnd.IsEndOfContainer()) {
   4113        // We need to split off back of text node
   4114        Result<SplitNodeResult, nsresult> splitAtEndResult =
   4115            SplitNodeWithTransaction(atEnd);
   4116        if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
   4117          NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
   4118          return splitAtEndResult.propagateErr();
   4119        }
   4120        SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap();
   4121        if (MOZ_UNLIKELY(
   4122                !unwrappedSplitAtEndResult.HasCaretPointSuggestion())) {
   4123          NS_WARNING(
   4124              "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
   4125              "point");
   4126          return Err(NS_ERROR_FAILURE);
   4127        }
   4128        unwrappedSplitAtEndResult.MoveCaretPointTo(pointToPutCaret, *this, {});
   4129        MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(),
   4130                      pointToPutCaret.IsSet());
   4131        textNodeForTheRange =
   4132            unwrappedSplitAtEndResult.GetPreviousContentAs<Text>();
   4133        MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange);
   4134      }
   4135 
   4136      // Split at the start of the range.
   4137      EditorDOMPoint atStart(textNodeForTheRange, aStartOffset);
   4138      if (!atStart.IsStartOfContainer()) {
   4139        // We need to split off front of text node
   4140        Result<SplitNodeResult, nsresult> splitAtStartResult =
   4141            SplitNodeWithTransaction(atStart);
   4142        if (MOZ_UNLIKELY(splitAtStartResult.isErr())) {
   4143          NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
   4144          return splitAtStartResult.propagateErr();
   4145        }
   4146        SplitNodeResult unwrappedSplitAtStartResult =
   4147            splitAtStartResult.unwrap();
   4148        if (MOZ_UNLIKELY(
   4149                !unwrappedSplitAtStartResult.HasCaretPointSuggestion())) {
   4150          NS_WARNING(
   4151              "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
   4152              "point");
   4153          return Err(NS_ERROR_FAILURE);
   4154        }
   4155        unwrappedSplitAtStartResult.MoveCaretPointTo(pointToPutCaret, *this,
   4156                                                     {});
   4157        MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(),
   4158                      pointToPutCaret.IsSet());
   4159        textNodeForTheRange =
   4160            unwrappedSplitAtStartResult.GetNextContentAs<Text>();
   4161        MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange);
   4162      }
   4163 
   4164      return pointToPutCaret;
   4165    }();
   4166    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   4167      // Don't warn here since it should be done in the lambda.
   4168      return pointToPutCaretOrError.propagateErr();
   4169    }
   4170    pointToPutCaret = pointToPutCaretOrError.unwrap();
   4171  }
   4172 
   4173  // Look for siblings that are correct type of node
   4174  nsStaticAtom* const bigOrSmallTagName =
   4175      aIncrementOrDecrement == FontSize::incr ? nsGkAtoms::big
   4176                                              : nsGkAtoms::small;
   4177  nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
   4178      *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
   4179  if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
   4180    // Previous sib is already right kind of inline node; slide this over
   4181    Result<MoveNodeResult, nsresult> moveTextNodeResult =
   4182        MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling);
   4183    if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
   4184      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   4185      return moveTextNodeResult.propagateErr();
   4186    }
   4187    MoveNodeResult unwrappedMoveTextNodeResult = moveTextNodeResult.unwrap();
   4188    unwrappedMoveTextNodeResult.MoveCaretPointTo(
   4189        pointToPutCaret, *this, {SuggestCaret::OnlyIfHasSuggestion});
   4190    // XXX Should we return the new container?
   4191    return CreateElementResult::NotHandled(std::move(pointToPutCaret));
   4192  }
   4193  sibling = HTMLEditUtils::GetNextSibling(
   4194      *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
   4195  if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
   4196    // Following sib is already right kind of inline node; slide this over
   4197    Result<MoveNodeResult, nsresult> moveTextNodeResult =
   4198        MoveNodeWithTransaction(*textNodeForTheRange,
   4199                                EditorDOMPoint(sibling, 0u));
   4200    if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
   4201      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   4202      return moveTextNodeResult.propagateErr();
   4203    }
   4204    MoveNodeResult unwrappedMoveTextNodeResult = moveTextNodeResult.unwrap();
   4205    unwrappedMoveTextNodeResult.MoveCaretPointTo(
   4206        pointToPutCaret, *this, {SuggestCaret::OnlyIfHasSuggestion});
   4207    // XXX Should we return the new container?
   4208    return CreateElementResult::NotHandled(std::move(pointToPutCaret));
   4209  }
   4210 
   4211  // Else wrap the node inside font node with appropriate relative size
   4212  Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult =
   4213      InsertContainerWithTransaction(*textNodeForTheRange,
   4214                                     MOZ_KnownLive(*bigOrSmallTagName));
   4215  if (wrapTextInBigOrSmallElementResult.isErr()) {
   4216    NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
   4217    return wrapTextInBigOrSmallElementResult;
   4218  }
   4219  CreateElementResult unwrappedWrapTextInBigOrSmallElementResult =
   4220      wrapTextInBigOrSmallElementResult.unwrap();
   4221  unwrappedWrapTextInBigOrSmallElementResult.MoveCaretPointTo(
   4222      pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   4223  return CreateElementResult(
   4224      unwrappedWrapTextInBigOrSmallElementResult.UnwrapNewNode(),
   4225      std::move(pointToPutCaret));
   4226 }
   4227 
   4228 Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeOfFontElementChildren(
   4229    nsIContent& aContent, FontSize aIncrementOrDecrement) {
   4230  // This routine looks for all the font nodes in the tree rooted by aNode,
   4231  // including aNode itself, looking for font nodes that have the size attr
   4232  // set.  Any such nodes need to have big or small put inside them, since
   4233  // they override any big/small that are above them.
   4234 
   4235  // If this is a font node with size, put big/small inside it.
   4236  if (aContent.IsHTMLElement(nsGkAtoms::font) &&
   4237      aContent.AsElement()->HasAttr(nsGkAtoms::size)) {
   4238    EditorDOMPoint pointToPutCaret;
   4239 
   4240    // Cycle through children and adjust relative font size.
   4241    AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents;
   4242    HTMLEditUtils::CollectAllChildren(aContent, arrayOfContents);
   4243    for (const auto& child : arrayOfContents) {
   4244      // MOZ_KnownLive because of bug 1622253
   4245      Result<EditorDOMPoint, nsresult> setFontSizeOfChildResult =
   4246          SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(child),
   4247                                           aIncrementOrDecrement);
   4248      if (MOZ_UNLIKELY(setFontSizeOfChildResult.isErr())) {
   4249        NS_WARNING("HTMLEditor::WrapContentInBigOrSmallElement() failed");
   4250        return setFontSizeOfChildResult;
   4251      }
   4252      if (setFontSizeOfChildResult.inspect().IsSet()) {
   4253        pointToPutCaret = setFontSizeOfChildResult.unwrap();
   4254      }
   4255    }
   4256 
   4257    // WrapContentInBigOrSmallElement already calls us recursively,
   4258    // so we don't need to check our children again.
   4259    return pointToPutCaret;
   4260  }
   4261 
   4262  // Otherwise cycle through the children.
   4263  EditorDOMPoint pointToPutCaret;
   4264  AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents;
   4265  HTMLEditUtils::CollectAllChildren(aContent, arrayOfContents);
   4266  for (const auto& child : arrayOfContents) {
   4267    // MOZ_KnownLive because of bug 1622253
   4268    Result<EditorDOMPoint, nsresult> fontSizeChangeResult =
   4269        SetFontSizeOfFontElementChildren(MOZ_KnownLive(child),
   4270                                         aIncrementOrDecrement);
   4271    if (MOZ_UNLIKELY(fontSizeChangeResult.isErr())) {
   4272      NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed");
   4273      return fontSizeChangeResult;
   4274    }
   4275    if (fontSizeChangeResult.inspect().IsSet()) {
   4276      pointToPutCaret = fontSizeChangeResult.unwrap();
   4277    }
   4278  }
   4279 
   4280  return pointToPutCaret;
   4281 }
   4282 
   4283 Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeWithBigOrSmallElement(
   4284    nsIContent& aContent, FontSize aIncrementOrDecrement) {
   4285  nsStaticAtom* const bigOrSmallTagName =
   4286      aIncrementOrDecrement == FontSize::incr ? nsGkAtoms::big
   4287                                              : nsGkAtoms::small;
   4288 
   4289  // Is aContent the opposite of what we want?
   4290  if ((aIncrementOrDecrement == FontSize::incr &&
   4291       aContent.IsHTMLElement(nsGkAtoms::small)) ||
   4292      (aIncrementOrDecrement == FontSize::decr &&
   4293       aContent.IsHTMLElement(nsGkAtoms::big))) {
   4294    // First, populate any nested font elements that have the size attr set
   4295    Result<EditorDOMPoint, nsresult> fontSizeChangeOfDescendantsResult =
   4296        SetFontSizeOfFontElementChildren(aContent, aIncrementOrDecrement);
   4297    if (MOZ_UNLIKELY(fontSizeChangeOfDescendantsResult.isErr())) {
   4298      NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed");
   4299      return fontSizeChangeOfDescendantsResult;
   4300    }
   4301    EditorDOMPoint pointToPutCaret = fontSizeChangeOfDescendantsResult.unwrap();
   4302    // In that case, just unwrap the <big> or <small> element.
   4303    Result<EditorDOMPoint, nsresult> unwrapBigOrSmallElementResult =
   4304        RemoveContainerWithTransaction(MOZ_KnownLive(*aContent.AsElement()));
   4305    if (MOZ_UNLIKELY(unwrapBigOrSmallElementResult.isErr())) {
   4306      NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
   4307      return unwrapBigOrSmallElementResult;
   4308    }
   4309    if (unwrapBigOrSmallElementResult.inspect().IsSet()) {
   4310      pointToPutCaret = unwrapBigOrSmallElementResult.unwrap();
   4311    }
   4312    return pointToPutCaret;
   4313  }
   4314 
   4315  if (HTMLEditUtils::CanNodeContain(*bigOrSmallTagName, aContent)) {
   4316    // First, populate any nested font tags that have the size attr set
   4317    Result<EditorDOMPoint, nsresult> fontSizeChangeOfDescendantsResult =
   4318        SetFontSizeOfFontElementChildren(aContent, aIncrementOrDecrement);
   4319    if (MOZ_UNLIKELY(fontSizeChangeOfDescendantsResult.isErr())) {
   4320      NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed");
   4321      return fontSizeChangeOfDescendantsResult;
   4322    }
   4323 
   4324    EditorDOMPoint pointToPutCaret = fontSizeChangeOfDescendantsResult.unwrap();
   4325 
   4326    // Next, if next or previous is <big> or <small>, move aContent into it.
   4327    nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
   4328        aContent, {WalkTreeOption::IgnoreNonEditableNode});
   4329    if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
   4330      Result<MoveNodeResult, nsresult> moveNodeResult =
   4331          MoveNodeToEndWithTransaction(aContent, *sibling);
   4332      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   4333        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   4334        return moveNodeResult.propagateErr();
   4335      }
   4336      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   4337      unwrappedMoveNodeResult.MoveCaretPointTo(
   4338          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   4339      return pointToPutCaret;
   4340    }
   4341 
   4342    sibling = HTMLEditUtils::GetNextSibling(
   4343        aContent, {WalkTreeOption::IgnoreNonEditableNode});
   4344    if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) {
   4345      Result<MoveNodeResult, nsresult> moveNodeResult =
   4346          MoveNodeWithTransaction(aContent, EditorDOMPoint(sibling, 0u));
   4347      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   4348        NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   4349        return moveNodeResult.propagateErr();
   4350      }
   4351      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   4352      unwrappedMoveNodeResult.MoveCaretPointTo(
   4353          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   4354      return pointToPutCaret;
   4355    }
   4356 
   4357    // Otherwise, wrap aContent in new <big> or <small>
   4358    Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult =
   4359        InsertContainerWithTransaction(aContent,
   4360                                       MOZ_KnownLive(*bigOrSmallTagName));
   4361    if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) {
   4362      NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
   4363      return Err(wrapInBigOrSmallElementResult.unwrapErr());
   4364    }
   4365    CreateElementResult unwrappedWrapInBigOrSmallElementResult =
   4366        wrapInBigOrSmallElementResult.unwrap();
   4367    MOZ_ASSERT(unwrappedWrapInBigOrSmallElementResult.GetNewNode());
   4368    unwrappedWrapInBigOrSmallElementResult.MoveCaretPointTo(
   4369        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   4370    return pointToPutCaret;
   4371  }
   4372 
   4373  // none of the above?  then cycle through the children.
   4374  // MOOSE: we should group the children together if possible
   4375  // into a single "big" or "small".  For the moment they are
   4376  // each getting their own.
   4377  EditorDOMPoint pointToPutCaret;
   4378  AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents;
   4379  HTMLEditUtils::CollectAllChildren(aContent, arrayOfContents);
   4380  for (const auto& child : arrayOfContents) {
   4381    // MOZ_KnownLive because of bug 1622253
   4382    Result<EditorDOMPoint, nsresult> setFontSizeOfChildResult =
   4383        SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(child),
   4384                                         aIncrementOrDecrement);
   4385    if (MOZ_UNLIKELY(setFontSizeOfChildResult.isErr())) {
   4386      NS_WARNING("HTMLEditor::SetFontSizeWithBigOrSmallElement() failed");
   4387      return setFontSizeOfChildResult;
   4388    }
   4389    if (setFontSizeOfChildResult.inspect().IsSet()) {
   4390      pointToPutCaret = setFontSizeOfChildResult.unwrap();
   4391    }
   4392  }
   4393 
   4394  return pointToPutCaret;
   4395 }
   4396 
   4397 NS_IMETHODIMP HTMLEditor::GetFontFaceState(bool* aMixed, nsAString& outFace) {
   4398  if (NS_WARN_IF(!aMixed)) {
   4399    return NS_ERROR_INVALID_ARG;
   4400  }
   4401 
   4402  *aMixed = true;
   4403  outFace.Truncate();
   4404 
   4405  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
   4406  if (NS_WARN_IF(!editActionData.CanHandle())) {
   4407    return NS_ERROR_NOT_INITIALIZED;
   4408  }
   4409 
   4410  bool first, any, all;
   4411 
   4412  nsresult rv = GetInlinePropertyBase(
   4413      EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face), nullptr, &first,
   4414      &any, &all, &outFace);
   4415  if (NS_FAILED(rv)) {
   4416    NS_WARNING(
   4417        "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) "
   4418        "failed");
   4419    return EditorBase::ToGenericNSResult(rv);
   4420  }
   4421  if (any && !all) {
   4422    return NS_OK;  // mixed
   4423  }
   4424  if (all) {
   4425    *aMixed = false;
   4426    return NS_OK;
   4427  }
   4428 
   4429  // if there is no font face, check for tt
   4430  rv = GetInlinePropertyBase(EditorInlineStyle(*nsGkAtoms::tt), nullptr, &first,
   4431                             &any, &all, nullptr);
   4432  if (NS_FAILED(rv)) {
   4433    NS_WARNING("HTMLEditor::GetInlinePropertyBase(nsGkAtoms::tt) failed");
   4434    return EditorBase::ToGenericNSResult(rv);
   4435  }
   4436  if (any && !all) {
   4437    return NS_OK;  // mixed
   4438  }
   4439  if (all) {
   4440    *aMixed = false;
   4441    outFace.AssignLiteral("tt");
   4442  }
   4443 
   4444  if (!any) {
   4445    // there was no font face attrs of any kind.  We are in normal font.
   4446    outFace.Truncate();
   4447    *aMixed = false;
   4448  }
   4449  return NS_OK;
   4450 }
   4451 
   4452 nsresult HTMLEditor::GetFontColorState(bool* aMixed, nsAString& aOutColor) {
   4453  if (NS_WARN_IF(!aMixed)) {
   4454    return NS_ERROR_INVALID_ARG;
   4455  }
   4456 
   4457  *aMixed = true;
   4458  aOutColor.Truncate();
   4459 
   4460  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
   4461  if (NS_WARN_IF(!editActionData.CanHandle())) {
   4462    return NS_ERROR_NOT_INITIALIZED;
   4463  }
   4464 
   4465  bool first, any, all;
   4466  nsresult rv = GetInlinePropertyBase(
   4467      EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::color), nullptr, &first,
   4468      &any, &all, &aOutColor);
   4469  if (NS_FAILED(rv)) {
   4470    NS_WARNING(
   4471        "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) "
   4472        "failed");
   4473    return EditorBase::ToGenericNSResult(rv);
   4474  }
   4475 
   4476  if (any && !all) {
   4477    return NS_OK;  // mixed
   4478  }
   4479  if (all) {
   4480    *aMixed = false;
   4481    return NS_OK;
   4482  }
   4483 
   4484  if (!any) {
   4485    // there was no font color attrs of any kind..
   4486    aOutColor.Truncate();
   4487    *aMixed = false;
   4488  }
   4489  return NS_OK;
   4490 }
   4491 
   4492 // The return value is true only if the instance of the HTML editor we created
   4493 // can handle CSS styles and if the CSS preference is checked.
   4494 NS_IMETHODIMP HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled) {
   4495  *aIsCSSEnabled = IsCSSEnabled();
   4496  return NS_OK;
   4497 }
   4498 
   4499 bool HTMLEditor::HasStyleOrIdOrClassAttribute(Element& aElement) {
   4500  return aElement.HasNonEmptyAttr(nsGkAtoms::style) ||
   4501         aElement.HasNonEmptyAttr(nsGkAtoms::_class) ||
   4502         aElement.HasNonEmptyAttr(nsGkAtoms::id);
   4503 }
   4504 
   4505 }  // namespace mozilla