Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/editor/libeditor/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 189 kB image not shown  

Quellverzeichnis  HTMLStyleEditor.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "ErrorList.h"
#include "HTMLEditor.h"
#include "HTMLEditorInlines.h"
#include "HTMLEditorNestedClasses.h"

#include "AutoClonedRangeArray.h"
#include "CSSEditUtils.h"
#include "EditAction.h"
#include "EditorLineBreak.h"
#include "EditorUtils.h"
#include "HTMLEditHelpers.h"
#include "HTMLEditUtils.h"
#include "PendingStyles.h"
#include "SelectionState.h"
#include "WSRunScanner.h"

#include "mozilla/Assertions.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/EditorForwards.h"
#include "mozilla/mozalloc.h"
#include "mozilla/SelectionState.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/NameSpaceConstants.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Text.h"

#include "nsAString.h"
#include "nsAtom.h"
#include "nsAttrName.h"
#include "nsAttrValue.h"
#include "nsCaseTreatment.h"
#include "nsColor.h"
#include "nsComponentManagerUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsINode.h"
#include "nsIPrincipal.h"
#include "nsISupportsImpl.h"
#include "nsLiteralString.h"
#include "nsNameSpaceManager.h"
#include "nsRange.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsStyledElement.h"
#include "nsTArray.h"
#include "nsTextNode.h"
#include "nsUnicharUtils.h"

namespace mozilla {

using namespace dom;

using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeType = HTMLEditUtils::LeafNodeType;
using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;

template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
    const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet,
    const Element& aEditingHost);
template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
    const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet,
    const Element& aEditingHost);

template nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
    AutoClonedRangeArray& aRanges,
    const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet);
template nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
    AutoClonedRangeArray& aRanges,
    const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet);

nsresult HTMLEditor::SetInlinePropertyAsAction(nsStaticAtom& aProperty,
                                               nsStaticAtom* aAttribute,
                                               const nsAString& aValue,
                                               nsIPrincipal* aPrincipal) {
  AutoEditActionDataSetter editActionData(
      *this,
      HTMLEditUtils::GetEditActionForFormatText(aProperty, aAttribute, true),
      aPrincipal);
  if (NS_WARN_IF(!editActionData.CanHandle())) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(!editingHost)) {
    return NS_ERROR_FAILURE;
  }
  if (IsPlaintextMailComposer() ||
      editingHost->IsContentEditablePlainTextOnly()) {
    return NS_SUCCESS_DOM_NO_OPERATION;
  }

  switch (editActionData.GetEditAction()) {
    case EditAction::eSetFontFamilyProperty:
      MOZ_ASSERT(!aValue.IsVoid());
      // XXX Should we trim unnecessary white-spaces?
      editActionData.SetData(aValue);
      break;
    case EditAction::eSetColorProperty:
    case EditAction::eSetBackgroundColorPropertyInline:
      editActionData.SetColorData(aValue);
      break;
    default:
      break;
  }

  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  // XXX Due to bug 1659276 and bug 1659924, we should not scroll selection
  //     into view after setting the new style.
  AutoPlaceholderBatch treatAsOneTransaction(*this, ScrollSelectionIntoView::No,
                                             __FUNCTION__);

  nsStaticAtom* property = &aProperty;
  nsStaticAtom* attribute = aAttribute;
  nsString value(aValue);
  if (attribute == nsGkAtoms::color || attribute == nsGkAtoms::bgcolor) {
    if (!IsCSSEnabled()) {
      // We allow CSS style color value even in the HTML mode.  In the cases,
      // we will apply the style with CSS.  For considering it in the value
      // as-is if it's a known CSS keyboard, `rgb()` or `rgba()` style.
      // NOTE: It may be later that we set the color into the DOM tree and at
      // that time, IsCSSEnabled() may return true.  E.g., setting color value
      // to collapsed selection, then, change the CSS enabled, finally, user
      // types text there.
      if (!HTMLEditUtils::MaybeCSSSpecificColorValue(value)) {
        HTMLEditUtils::GetNormalizedHTMLColorValue(value, value);
      }
    } else {
      HTMLEditUtils::GetNormalizedCSSColorValue(
          value, HTMLEditUtils::ZeroAlphaColor::RGBAValue, value);
    }
  }

  AutoTArray<EditorInlineStyle, 1> stylesToRemove;
  if (&aProperty == nsGkAtoms::sup) {
    // Superscript and Subscript styles are mutually exclusive.
    stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sub));
  } else if (&aProperty == nsGkAtoms::sub) {
    // Superscript and Subscript styles are mutually exclusive.
    stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sup));
  }
  // Handling `<tt>` element code was implemented for composer (bug 115922).
  // This shouldn't work with `Document.execCommand()`.  Currently, aPrincipal
  // is set only when the root caller is Document::ExecCommand() so that
  // we should handle `<tt>` element only when aPrincipal is nullptr that
  // must be only when XUL command is executed on composer.
  else if (!aPrincipal) {
    if (&aProperty == nsGkAtoms::tt) {
      stylesToRemove.AppendElement(
          EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face));
    } else if (&aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::face) {
      if (!value.LowerCaseEqualsASCII("tt")) {
        stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::tt));
      } else {
        stylesToRemove.AppendElement(
            EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face));
        // Override property, attribute and value if the new font face value is
        // "tt".
        property = nsGkAtoms::tt;
        attribute = nullptr;
        value.Truncate();
      }
    }
  }

  if (!stylesToRemove.IsEmpty()) {
    nsresult rv =
        RemoveInlinePropertiesAsSubAction(stylesToRemove, *editingHost);
    if (NS_FAILED(rv)) {
      NS_WARNING("HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
      return rv;
    }
  }

  AutoTArray<EditorInlineStyleAndValue, 1> styleToSet;
  styleToSet.AppendElement(
      attribute
          ? EditorInlineStyleAndValue(*property, *attribute, std::move(value))
          : EditorInlineStyleAndValue(*property));
  rv = SetInlinePropertiesAsSubAction(styleToSet, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
  return EditorBase::ToGenericNSResult(rv);
}

NS_IMETHODIMP HTMLEditor::SetInlineProperty(const nsAString& aProperty,
                                            const nsAString& aAttribute,
                                            const nsAString& aValue) {
  nsStaticAtom* property = NS_GetStaticAtom(aProperty);
  if (NS_WARN_IF(!property)) {
    return NS_ERROR_INVALID_ARG;
  }
  nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
  AutoEditActionDataSetter editActionData(
      *this,
      HTMLEditUtils::GetEditActionForFormatText(*property, attribute, true));
  if (NS_WARN_IF(!editActionData.CanHandle())) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(!editingHost)) {
    return NS_ERROR_FAILURE;
  }
  if (IsPlaintextMailComposer() ||
      editingHost->IsContentEditablePlainTextOnly()) {
    return NS_SUCCESS_DOM_NO_OPERATION;
  }

  switch (editActionData.GetEditAction()) {
    case EditAction::eSetFontFamilyProperty:
      MOZ_ASSERT(!aValue.IsVoid());
      // XXX Should we trim unnecessary white-spaces?
      editActionData.SetData(aValue);
      break;
    case EditAction::eSetColorProperty:
    case EditAction::eSetBackgroundColorPropertyInline:
      editActionData.SetColorData(aValue);
      break;
    default:
      break;
  }
  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  AutoTArray<EditorInlineStyleAndValue, 1> styleToSet;
  styleToSet.AppendElement(
      attribute ? EditorInlineStyleAndValue(*property, *attribute, aValue)
                : EditorInlineStyleAndValue(*property));
  rv = SetInlinePropertiesAsSubAction(styleToSet, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
  return EditorBase::ToGenericNSResult(rv);
}

template <size_t N>
nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
    const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet,
    const Element& aEditingHost) {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(!aStylesToSet.IsEmpty());

  DebugOnly<nsresult> rvIgnored = CommitComposition();
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                       "EditorBase::CommitComposition() failed, but ignored");
  if (MOZ_UNLIKELY(&aEditingHost !=
                   ComputeEditingHost(LimitInBodyElement::No))) {
    NS_WARNING("Editing host has been changed during committing composition");
    return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
  }

  if (SelectionRef().IsCollapsed()) {
    // Manipulating text attributes on a collapsed selection only sets state
    // for the next text insertion
    mPendingStylesToApplyToNewContent->PreserveStyles(aStylesToSet);
    return NS_OK;
  }

  {
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
    if (MOZ_UNLIKELY(result.isErr())) {
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
      return result.unwrapErr();
    }
    if (result.inspect().Canceled()) {
      return NS_OK;
    }
  }

  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  IgnoredErrorResult ignoredError;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    return ignoredError.StealNSResult();
  }
  NS_WARNING_ASSERTION(
      !ignoredError.Failed(),
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");

  // TODO: We don't need AutoTransactionsConserveSelection here in the normal
  //       cases, but removing this may cause the behavior with the legacy
  //       mutation event listeners.  We should try to delete this in a bug.
  AutoTransactionsConserveSelection dontChangeMySelection(*this);

  AutoClonedSelectionRangeArray selectionRanges(SelectionRef());
  nsresult rv = SetInlinePropertiesAroundRanges(selectionRanges, aStylesToSet);
  if (NS_FAILED(rv)) {
    NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
    return rv;
  }
  MOZ_ASSERT(!selectionRanges.HasSavedRanges());
  rv = selectionRanges.ApplyTo(SelectionRef());
  if (NS_WARN_IF(Destroyed())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "AutoClonedSelectionRangeArray::ApplyTo() failed");
  return rv;
}

template <size_t N>
nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
    AutoClonedRangeArray& aRanges,
    const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet) {
  MOZ_ASSERT(!aRanges.HasSavedRanges());
  for (const EditorInlineStyleAndValue& styleToSet : aStylesToSet) {
    AutoInlineStyleSetter inlineStyleSetter(styleToSet);
    for (OwningNonNull<nsRange>& domRange : aRanges.Ranges()) {
      inlineStyleSetter.Reset();
      auto rangeOrError =
          [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMRange, nsresult> {
        EditorDOMRange range(domRange);
        // If we're setting <font>, we want to remove ancestors which set
        // `font-size` or <font size="..."> recursively.  Therefore, for
        // extending the ranges to contain all ancestors in the range, we need
        // to split ancestors first.
        // XXX: Blink and WebKit inserts <font> elements to inner most
        // elements, however, we cannot do it under current design because
        // once we contain ancestors which have `font-size` or are
        // <font size="...">, we lost the original ranges which we wanted to
        // apply the style.  For fixing this, we need to manage both ranges, but
        // it's too expensive especially we allow to run script when we touch
        // the DOM tree.  Additionally, font-size value affects the height
        // of the element, but does not affect the height of ancestor inline
        // elements.  Therefore, following the behavior may cause similar issue
        // as bug 1808906.  So at least for now, we should not do this big work.
        if (styleToSet.IsStyleOfFontElement()) {
          Result<SplitRangeOffResult, nsresult> splitAncestorsResult =
              SplitAncestorStyledInlineElementsAtRangeEdges(
                  range, styleToSet, SplitAtEdges::eDoNotCreateEmptyContainer);
          if (MOZ_UNLIKELY(splitAncestorsResult.isErr())) {
            NS_WARNING(
                "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
                "failed");
            return splitAncestorsResult.propagateErr();
          }
          SplitRangeOffResult unwrappedResult = splitAncestorsResult.unwrap();
          unwrappedResult.IgnoreCaretPointSuggestion();
          range = unwrappedResult.RangeRef();
          if (NS_WARN_IF(!range.IsPositionedAndValid())) {
            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
          }
        }
        Result<EditorRawDOMRange, nsresult> rangeOrError =
            inlineStyleSetter.ExtendOrShrinkRangeToApplyTheStyle(*this, range);
        if (MOZ_UNLIKELY(rangeOrError.isErr())) {
          NS_WARNING(
              "HTMLEditor::ExtendOrShrinkRangeToApplyTheStyle() failed, but "
              "ignored");
          return EditorDOMRange();
        }
        return EditorDOMRange(rangeOrError.unwrap());
      }();
      if (MOZ_UNLIKELY(rangeOrError.isErr())) {
        return rangeOrError.unwrapErr();
      }

      const EditorDOMRange range = rangeOrError.unwrap();
      if (!range.IsPositioned()) {
        continue;
      }

      // If the range is collapsed, we should insert new element there.
      if (range.Collapsed()) {
        Result<RefPtr<Text>, nsresult> emptyTextNodeOrError =
            AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
                *this, range.StartRef());
        if (MOZ_UNLIKELY(emptyTextNodeOrError.isErr())) {
          NS_WARNING(
              "AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle() "
              "failed");
          return emptyTextNodeOrError.unwrapErr();
        }
        if (MOZ_UNLIKELY(!emptyTextNodeOrError.inspect())) {
          continue;  // Couldn't insert text node there
        }
        RefPtr<Text> emptyTextNode = emptyTextNodeOrError.unwrap();
        Result<CaretPoint, nsresult> caretPointOrError =
            inlineStyleSetter
                .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
                    *this, *emptyTextNode);
        if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
          NS_WARNING(
              "AutoInlineStyleSetter::"
              "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
          return caretPointOrError.unwrapErr();
        }
        DebugOnly<nsresult> rvIgnored = domRange->CollapseTo(emptyTextNode, 0);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsRange::CollapseTo() failed, but ignored");
        continue;
      }

      // Use const_cast hack here for preventing the others to update the range.
      AutoTrackDOMRange trackRange(RangeUpdaterRef(),
                                   const_cast<EditorDOMRange*>(&range));
      auto UpdateSelectionRange = [&]() MOZ_CAN_RUN_SCRIPT {
        // If inlineStyleSetter creates elements or setting styles, we should
        // select between start of first element and end of last element.
        if (inlineStyleSetter.FirstHandledPointRef().IsInContentNode()) {
          MOZ_ASSERT(inlineStyleSetter.LastHandledPointRef().IsInContentNode());
          const auto startPoint =
              !inlineStyleSetter.FirstHandledPointRef().IsStartOfContainer()
                  ? inlineStyleSetter.FirstHandledPointRef()
                        .To<EditorRawDOMPoint>()
                  : HTMLEditUtils::GetDeepestEditableStartPointOf<
                        EditorRawDOMPoint>(
                        *inlineStyleSetter.FirstHandledPointRef()
                             .ContainerAs<nsIContent>());
          const auto endPoint =
              !inlineStyleSetter.LastHandledPointRef().IsEndOfContainer()
                  ? inlineStyleSetter.LastHandledPointRef()
                        .To<EditorRawDOMPoint>()
                  : HTMLEditUtils::GetDeepestEditableEndPointOf<
                        EditorRawDOMPoint>(
                        *inlineStyleSetter.LastHandledPointRef()
                             .ContainerAs<nsIContent>());
          nsresult rv = domRange->SetStartAndEnd(
              startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
          if (NS_SUCCEEDED(rv)) {
            trackRange.StopTracking();
            return;
          }
        }
        // Otherwise, use the range computed with the tracking original range.
        trackRange.FlushAndStopTracking();
        domRange->SetStartAndEnd(range.StartRef().ToRawRangeBoundary(),
                                 range.EndRef().ToRawRangeBoundary());
      };

      // If range is in a text node, apply new style simply.
      if (range.InSameContainer() && range.StartRef().IsInTextNode()) {
        // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
        // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
        Result<SplitRangeOffFromNodeResult, nsresult>
            wrapTextInStyledElementResult =
                inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
                    *this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
                    range.StartRef().Offset(), range.EndRef().Offset());
        if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
          NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
          return wrapTextInStyledElementResult.unwrapErr();
        }
        // The caller should handle the ranges as Selection if necessary, and we
        // don't want to update aRanges with this result.
        wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
        UpdateSelectionRange();
        continue;
      }

      // Collect editable nodes which are entirely contained in the range.
      AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsAroundRange;
      {
        ContentSubtreeIterator subtreeIter;
        // If there is no node which is entirely in the range,
        // `ContentSubtreeIterator::Init()` fails, but this is possible case,
        // don't warn it.
        if (NS_SUCCEEDED(
                subtreeIter.Init(range.StartRef().ToRawRangeBoundary(),
                                 range.EndRef().ToRawRangeBoundary()))) {
          for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
            nsINode* node = subtreeIter.GetCurrentNode();
            if (NS_WARN_IF(!node)) {
              return NS_ERROR_FAILURE;
            }
            if (MOZ_UNLIKELY(!node->IsContent())) {
              continue;
            }
            // We don't need to wrap non-editable node in new inline element
            // nor shouldn't modify `style` attribute of non-editable element.
            if (!EditorUtils::IsEditableContent(*node->AsContent(),
                                                EditorType::HTML)) {
              continue;
            }
            // We shouldn't wrap invisible text node in new inline element.
            if (node->IsText() &&
                !HTMLEditUtils::IsVisibleTextNode(*node->AsText())) {
              continue;
            }
            arrayOfContentsAroundRange.AppendElement(*node->AsContent());
          }
        }
      }

      // If start node is a text node, apply new style to a part of it.
      if (range.StartRef().IsInTextNode() &&
          EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(),
                                         EditorType::HTML)) {
        // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
        // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
        Result<SplitRangeOffFromNodeResult, nsresult>
            wrapTextInStyledElementResult =
                inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
                    *this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
                    range.StartRef().Offset(),
                    range.StartRef().ContainerAs<Text>()->TextDataLength());
        if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
          NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
          return wrapTextInStyledElementResult.unwrapErr();
        }
        // The caller should handle the ranges as Selection if necessary, and we
        // don't want to update aRanges with this result.
        wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
      }

      // Then, apply new style to all nodes in the range entirely.
      for (auto& content : arrayOfContentsAroundRange) {
        // MOZ_KnownLive due to bug 1622253.
        Result<CaretPoint, nsresult> pointToPutCaretOrError =
            inlineStyleSetter
                .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
                    *this, MOZ_KnownLive(*content));
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
          NS_WARNING(
              "AutoInlineStyleSetter::"
              "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
          return pointToPutCaretOrError.unwrapErr();
        }
        // The caller should handle the ranges as Selection if necessary, and we
        // don't want to update aRanges with this result.
        pointToPutCaretOrError.inspect().IgnoreCaretPointSuggestion();
      }

      // Finally, if end node is a text node, apply new style to a part of it.
      if (range.EndRef().IsInTextNode() &&
          EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(),
                                         EditorType::HTML)) {
        // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
        // MOZ_KnownLive(styleToSet.mAttribute) due to bug 1622253.
        Result<SplitRangeOffFromNodeResult, nsresult>
            wrapTextInStyledElementResult =
                inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
                    *this, MOZ_KnownLive(*range.EndRef().ContainerAs<Text>()),
                    0, range.EndRef().Offset());
        if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
          NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
          return wrapTextInStyledElementResult.unwrapErr();
        }
        // The caller should handle the ranges as Selection if necessary, and we
        // don't want to update aRanges with this result.
        wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
      }
      UpdateSelectionRange();
    }
  }
  return NS_OK;
}

// static
Result<RefPtr<Text>, nsresult>
HTMLEditor::AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert) {
  auto pointToInsertNewText =
      HTMLEditUtils::GetBetterCaretPositionToInsertText<EditorDOMPoint>(
          aCandidatePointToInsert);
  if (MOZ_UNLIKELY(!pointToInsertNewText.IsSet())) {
    return RefPtr<Text>();  // cannot insert text there
  }
  auto pointToInsertNewStyleOrError =
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMPoint, nsresult> {
    if (!pointToInsertNewText.IsInTextNode()) {
      return pointToInsertNewText;
    }
    if (!pointToInsertNewText.ContainerAs<Text>()->TextDataLength()) {
      return pointToInsertNewText;  // Use it
    }
    if (pointToInsertNewText.IsStartOfContainer()) {
      return pointToInsertNewText.ParentPoint();
    }
    if (pointToInsertNewText.IsEndOfContainer()) {
      return EditorDOMPoint::After(*pointToInsertNewText.ContainerAs<Text>());
    }
    Result<SplitNodeResult, nsresult> splitTextNodeResult =
        aHTMLEditor.SplitNodeWithTransaction(pointToInsertNewText);
    if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
      return splitTextNodeResult.propagateErr();
    }
    SplitNodeResult unwrappedSplitTextNodeResult = splitTextNodeResult.unwrap();
    unwrappedSplitTextNodeResult.IgnoreCaretPointSuggestion();
    return unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
  }();
  if (MOZ_UNLIKELY(pointToInsertNewStyleOrError.isErr())) {
    return pointToInsertNewStyleOrError.propagateErr();
  }

  // If we already have empty text node which is available for placeholder in
  // new styled element, let's use it.
  if (pointToInsertNewStyleOrError.inspect().IsInTextNode()) {
    return RefPtr<Text>(
        pointToInsertNewStyleOrError.inspect().ContainerAs<Text>());
  }

  // Otherwise, we need an empty text node to create new inline style.
  RefPtr<Text> newEmptyTextNode = aHTMLEditor.CreateTextNode(u""_ns);
  if (MOZ_UNLIKELY(!newEmptyTextNode)) {
    NS_WARNING("EditorBase::CreateTextNode() failed");
    return Err(NS_ERROR_FAILURE);
  }
  Result<CreateTextResult, nsresult> insertNewTextNodeResult =
      aHTMLEditor.InsertNodeWithTransaction<Text>(
          *newEmptyTextNode, pointToInsertNewStyleOrError.inspect());
  if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) {
    NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
    return insertNewTextNodeResult.propagateErr();
  }
  insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
  return newEmptyTextNode;
}

Result<bool, nsresult>
HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerForTheStyle(
    HTMLEditor& aHTMLEditor, Element& aElement) const {
  // If the editor is in the CSS mode and the style can be specified with CSS,
  // we should not use existing HTML element as a new container.
  const bool isCSSEditable = IsCSSSettable(aElement);
  if (!aHTMLEditor.IsCSSEnabled() || !isCSSEditable) {
    // First check for <b>, <i>, etc.
    if (aElement.IsHTMLElement(&HTMLPropertyRef()) &&
        !HTMLEditUtils::ElementHasAttribute(aElement) && !mAttribute) {
      return true;
    }

    // Now look for things like <font>
    if (mAttribute) {
      nsString attrValue;
      if (aElement.IsHTMLElement(&HTMLPropertyRef()) &&
          !HTMLEditUtils::ElementHasAttributeExcept(aElement, *mAttribute) &&
          aElement.GetAttr(mAttribute, attrValue)) {
        if (attrValue.Equals(mAttributeValue,
                             nsCaseInsensitiveStringComparator)) {
          return true;
        }
        if (mAttribute == nsGkAtoms::color ||
            mAttribute == nsGkAtoms::bgcolor) {
          if (aHTMLEditor.IsCSSEnabled()) {
            if (HTMLEditUtils::IsSameCSSColorValue(mAttributeValue,
                                                   attrValue)) {
              return true;
            }
          } else if (HTMLEditUtils::IsSameHTMLColorValue(
                         mAttributeValue, attrValue,
                         HTMLEditUtils::TransparentKeyword::Allowed)) {
            return true;
          }
        }
      }
    }

    if (!isCSSEditable) {
      return false;
    }
  }

  // No luck so far.  Now we check for a <span> with a single style=""
  // attribute that sets only the style we're looking for, if this type of
  // style supports it
  if (!aElement.IsHTMLElement(nsGkAtoms::span) ||
      !aElement.HasAttr(nsGkAtoms::style) ||
      HTMLEditUtils::ElementHasAttributeExcept(aElement, *nsGkAtoms::style)) {
    return false;
  }

  nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement);
  if (MOZ_UNLIKELY(!styledElement)) {
    return false;
  }

  // Some CSS styles are not so simple.  For instance, underline is
  // "text-decoration: underline", which decomposes into four different text-*
  // properties.  So for now, we just create a span, add the desired style, and
  // see if it matches.
  RefPtr<Element> newSpanElement =
      aHTMLEditor.CreateHTMLContent(nsGkAtoms::span);
  if (MOZ_UNLIKELY(!newSpanElement)) {
    NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed");
    return false;
  }
  nsStyledElement* styledNewSpanElement =
      nsStyledElement::FromNode(newSpanElement);
  if (MOZ_UNLIKELY(!styledNewSpanElement)) {
    return false;
  }
  // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
  // RefPtr.
  Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
      WithTransaction::No, aHTMLEditor, MOZ_KnownLive(*styledNewSpanElement),
      *this, &mAttributeValue);
  if (MOZ_UNLIKELY(result.isErr())) {
    // The call shouldn't return destroyed error because it must be
    // impossible to run script with modifying the new orphan node.
    MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?");
    if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
      return Err(NS_ERROR_EDITOR_DESTROYED);
    }
    return false;
  }
  return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement,
                                                     *styledElement);
}

bool HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerToSetStyle(
    nsStyledElement& aStyledElement) const {
  if (!HTMLEditUtils::IsContainerNode(aStyledElement) ||
      !EditorUtils::IsEditableContent(aStyledElement, EditorType::HTML)) {
    return false;
  }

  // If it has `style` attribute, let's use it.
  if (aStyledElement.HasAttr(nsGkAtoms::style)) {
    return true;
  }

  // If it has `class` or `id` attribute, the element may have specific rule.
  // For applying the new style, we may need to set `style` attribute to it
  // to override the specified rule.
  if (aStyledElement.HasAttr(nsGkAtoms::id) ||
      aStyledElement.HasAttr(nsGkAtoms::_class)) {
    return true;
  }

  // If we're setting text-decoration and the element represents a value of
  // text-decoration, <ins> or <del>, let's use it.
  if (IsStyleOfTextDecoration(IgnoreSElement::No) &&
      aStyledElement.IsAnyOfHTMLElements(nsGkAtoms::u, nsGkAtoms::s,
                                         nsGkAtoms::strike, nsGkAtoms::ins,
                                         nsGkAtoms::del)) {
    return true;
  }

  // If we're setting font-size, color or background-color, we should use <font>
  // for compatibility with the other browsers.
  if (&HTMLPropertyRef() == nsGkAtoms::font &&
      aStyledElement.IsHTMLElement(nsGkAtoms::font)) {
    return true;
  }

  // If the element has one or more <br> (even if it's invisible), we don't
  // want to use the <span> for compatibility with the other browsers.
  if (aStyledElement.QuerySelector("br"_ns, IgnoreErrors())) {
    return false;
  }

  // NOTE: The following code does not match with the other browsers not
  //       completely.  Blink considers this with relation with the range.
  //       However, we cannot do it now.  We should fix here after or at
  //       fixing bug 1792386.

  // If it's only visible element child of parent block, let's use it.
  // E.g., we don't want to create new <span> when
  // `<p>{  <span>abc</span>  }</p>`.
  if (aStyledElement.GetParentElement() &&
      HTMLEditUtils::IsBlockElement(
          *aStyledElement.GetParentElement(),
          BlockInlineCheck::UseComputedDisplayStyle)) {
    for (nsIContent* previousSibling = aStyledElement.GetPreviousSibling();
         previousSibling;
         previousSibling = previousSibling->GetPreviousSibling()) {
      if (previousSibling->IsElement()) {
        return false;  // Assume any elements visible.
      }
      if (Text* text = Text::FromNode(previousSibling)) {
        if (HTMLEditUtils::IsVisibleTextNode(*text)) {
          return false;
        }
        continue;
      }
    }
    for (nsIContent* nextSibling = aStyledElement.GetNextSibling(); nextSibling;
         nextSibling = nextSibling->GetNextSibling()) {
      if (nextSibling->IsElement()) {
        if (!HTMLEditUtils::IsInvisibleBRElement(*nextSibling)) {
          return false;
        }
        continue;  // The invisible <br> element may be followed by a child
                   // block, let's continue to check it.
      }
      if (Text* text = Text::FromNode(nextSibling)) {
        if (HTMLEditUtils::IsVisibleTextNode(*text)) {
          return false;
        }
        continue;
      }
    }
    return true;
  }

  // Otherwise, wrap it into new <span> for making
  // `<span>[abc</span> <span>def]</span>` become
  // `<span style="..."><span>abc</span> <span>def</span></span>` rather
  // than `<span style="...">abc <span>def</span></span>`.
  return false;
}

Result<SplitRangeOffFromNodeResult, nsresult>
HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode(
    HTMLEditor& aHTMLEditor, Text& aText, uint32_t aStartOffset,
    uint32_t aEndOffset) {
  const RefPtr<Element> element = aText.GetParentElement();
  if (!element || !HTMLEditUtils::CanNodeContain(*element, HTMLPropertyRef())) {
    OnHandled(EditorDOMPoint(&aText, aStartOffset),
              EditorDOMPoint(&aText, aEndOffset));
    return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
  }

  // Don't need to do anything if no characters actually selected
  if (aStartOffset == aEndOffset) {
    OnHandled(EditorDOMPoint(&aText, aStartOffset),
              EditorDOMPoint(&aText, aEndOffset));
    return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
  }

  // Don't need to do anything if property already set on node
  if (IsCSSSettable(*element)) {
    // The HTML styles defined by this have a CSS equivalence for node;
    // let's check if it carries those CSS styles
    nsAutoString value(mAttributeValue);
    Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
        CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this,
                                                value);
    if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
      NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
      return isComputedCSSEquivalentToStyleOrError.propagateErr();
    }
    if (isComputedCSSEquivalentToStyleOrError.unwrap()) {
      OnHandled(EditorDOMPoint(&aText, aStartOffset),
                EditorDOMPoint(&aText, aEndOffset));
      return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
    }
  } else if (HTMLEditUtils::IsInlineStyleSetByElement(aText, *this,
                                                      &mAttributeValue)) {
    OnHandled(EditorDOMPoint(&aText, aStartOffset),
              EditorDOMPoint(&aText, aEndOffset));
    return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
  }

  // Make the range an independent node.
  auto splitAtEndResult =
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
    EditorDOMPoint atEnd(&aText, aEndOffset);
    if (atEnd.IsEndOfContainer()) {
      return SplitNodeResult::NotHandled(atEnd);
    }
    // We need to split off back of text node
    Result<SplitNodeResult, nsresult> splitNodeResult =
        aHTMLEditor.SplitNodeWithTransaction(atEnd);
    if (splitNodeResult.isErr()) {
      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
      return splitNodeResult;
    }
    if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) {
      NS_WARNING(
          "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
          "point");
      return Err(NS_ERROR_FAILURE);
    }
    return splitNodeResult;
  }();
  if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
    return splitAtEndResult.propagateErr();
  }
  SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap();
  EditorDOMPoint pointToPutCaret = unwrappedSplitAtEndResult.UnwrapCaretPoint();
  auto splitAtStartResult =
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
    EditorDOMPoint atStart(unwrappedSplitAtEndResult.DidSplit()
                               ? unwrappedSplitAtEndResult.GetPreviousContent()
                               : &aText,
                           aStartOffset);
    if (atStart.IsStartOfContainer()) {
      return SplitNodeResult::NotHandled(atStart);
    }
    // We need to split off front of text node
    Result<SplitNodeResult, nsresult> splitNodeResult =
        aHTMLEditor.SplitNodeWithTransaction(atStart);
    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
      return splitNodeResult;
    }
    if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) {
      NS_WARNING(
          "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
          "point");
      return Err(NS_ERROR_FAILURE);
    }
    return splitNodeResult;
  }();
  if (MOZ_UNLIKELY(splitAtStartResult.isErr())) {
    return splitAtStartResult.propagateErr();
  }
  SplitNodeResult unwrappedSplitAtStartResult = splitAtStartResult.unwrap();
  if (unwrappedSplitAtStartResult.HasCaretPointSuggestion()) {
    pointToPutCaret = unwrappedSplitAtStartResult.UnwrapCaretPoint();
  }

  MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(),
                unwrappedSplitAtStartResult.GetPreviousContent()->IsText());
  MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(),
                unwrappedSplitAtStartResult.GetNextContent()->IsText());
  MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(),
                unwrappedSplitAtEndResult.GetPreviousContent()->IsText());
  MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(),
                unwrappedSplitAtEndResult.GetNextContent()->IsText());
  // Note that those text nodes are grabbed by unwrappedSplitAtStartResult,
  // unwrappedSplitAtEndResult or the callers.  Therefore, we don't need to make
  // them strong pointer.
  Text* const leftTextNode =
      unwrappedSplitAtStartResult.DidSplit()
          ? unwrappedSplitAtStartResult.GetPreviousContentAs<Text>()
          : nullptr;
  Text* const middleTextNode =
      unwrappedSplitAtStartResult.DidSplit()
          ? unwrappedSplitAtStartResult.GetNextContentAs<Text>()
          : (unwrappedSplitAtEndResult.DidSplit()
                 ? unwrappedSplitAtEndResult.GetPreviousContentAs<Text>()
                 : &aText);
  Text* const rightTextNode =
      unwrappedSplitAtEndResult.DidSplit()
          ? unwrappedSplitAtEndResult.GetNextContentAs<Text>()
          : nullptr;
  if (mAttribute) {
    // Look for siblings that are correct type of node
    nsIContent* sibling = HTMLEditUtils::GetPreviousSibling(
        *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode});
    if (sibling && sibling->IsElement()) {
      OwningNonNull<Element> element(*sibling->AsElement());
      Result<bool, nsresult> result =
          ElementIsGoodContainerForTheStyle(aHTMLEditor, element);
      if (MOZ_UNLIKELY(result.isErr())) {
        NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
        return result.propagateErr();
      }
      if (result.inspect()) {
        // Previous sib is already right kind of inline node; slide this over
        Result<MoveNodeResult, nsresult> moveTextNodeResult =
            aHTMLEditor.MoveNodeToEndWithTransaction(
                MOZ_KnownLive(*middleTextNode), element);
        if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
          NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
          return moveTextNodeResult.propagateErr();
        }
        MoveNodeResult unwrappedMoveTextNodeResult =
            moveTextNodeResult.unwrap();
        unwrappedMoveTextNodeResult.MoveCaretPointTo(
            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
        OnHandled(*middleTextNode);
        return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode,
                                           rightTextNode,
                                           std::move(pointToPutCaret));
      }
    }
    sibling = HTMLEditUtils::GetNextSibling(
        *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode});
    if (sibling && sibling->IsElement()) {
      OwningNonNull<Element> element(*sibling->AsElement());
      Result<bool, nsresult> result =
          ElementIsGoodContainerForTheStyle(aHTMLEditor, element);
      if (MOZ_UNLIKELY(result.isErr())) {
        NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
        return result.propagateErr();
      }
      if (result.inspect()) {
        // Following sib is already right kind of inline node; slide this over
        Result<MoveNodeResult, nsresult> moveTextNodeResult =
            aHTMLEditor.MoveNodeWithTransaction(MOZ_KnownLive(*middleTextNode),
                                                EditorDOMPoint(sibling, 0u));
        if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
          NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
          return moveTextNodeResult.propagateErr();
        }
        MoveNodeResult unwrappedMoveTextNodeResult =
            moveTextNodeResult.unwrap();
        unwrappedMoveTextNodeResult.MoveCaretPointTo(
            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
        OnHandled(*middleTextNode);
        return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode,
                                           rightTextNode,
                                           std::move(pointToPutCaret));
      }
    }
  }

  // Wrap the node inside inline node.
  Result<CaretPoint, nsresult> setStyleResult =
      ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
          aHTMLEditor, MOZ_KnownLive(*middleTextNode));
  if (MOZ_UNLIKELY(setStyleResult.isErr())) {
    NS_WARNING(
        "AutoInlineStyleSetter::"
        "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
    return setStyleResult.propagateErr();
  }
  return SplitRangeOffFromNodeResult(
      leftTextNode, middleTextNode, rightTextNode,
      setStyleResult.unwrap().UnwrapCaretPoint());
}

Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::ApplyStyle(
    HTMLEditor& aHTMLEditor, nsIContent& aContent) {
  // If this is an element that can't be contained in a span, we have to
  // recurse to its children.
  if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span, aContent)) {
    if (!aContent.HasChildren()) {
      return CaretPoint(EditorDOMPoint());
    }

    AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents;
    HTMLEditUtils::CollectChildren(
        aContent, arrayOfContents,
        {CollectChildrenOption::IgnoreNonEditableChildren,
         CollectChildrenOption::IgnoreInvisibleTextNodes});

    // Then loop through the list, set the property on each node.
    EditorDOMPoint pointToPutCaret;
    for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
      // keep it alive.
      Result<CaretPoint, nsresult> setInlinePropertyResult =
          ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
              aHTMLEditor, MOZ_KnownLive(content));
      if (MOZ_UNLIKELY(setInlinePropertyResult.isErr())) {
        NS_WARNING(
            "AutoInlineStyleSetter::"
            "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
        return setInlinePropertyResult;
      }
      setInlinePropertyResult.unwrap().MoveCaretPointTo(
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
    }
    return CaretPoint(std::move(pointToPutCaret));
  }

  // First check if there's an adjacent sibling we can put our node into.
  nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling(
      aContent, {WalkTreeOption::IgnoreNonEditableNode});
  nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling(
      aContent, {WalkTreeOption::IgnoreNonEditableNode});
  if (RefPtr<Element> previousElement =
          Element::FromNodeOrNull(previousSibling)) {
    Result<bool, nsresult> canMoveIntoPreviousSibling =
        ElementIsGoodContainerForTheStyle(aHTMLEditor, *previousElement);
    if (MOZ_UNLIKELY(canMoveIntoPreviousSibling.isErr())) {
      NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
      return canMoveIntoPreviousSibling.propagateErr();
    }
    if (canMoveIntoPreviousSibling.inspect()) {
      Result<MoveNodeResult, nsresult> moveNodeResult =
          aHTMLEditor.MoveNodeToEndWithTransaction(aContent, *previousSibling);
      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
        return moveNodeResult.propagateErr();
      }
      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
      RefPtr<Element> nextElement = Element::FromNodeOrNull(nextSibling);
      if (!nextElement) {
        OnHandled(aContent);
        return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint());
      }
      Result<bool, nsresult> canMoveIntoNextSibling =
          ElementIsGoodContainerForTheStyle(aHTMLEditor, *nextElement);
      if (MOZ_UNLIKELY(canMoveIntoNextSibling.isErr())) {
        NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
        unwrappedMoveNodeResult.IgnoreCaretPointSuggestion();
        return canMoveIntoNextSibling.propagateErr();
      }
      if (!canMoveIntoNextSibling.inspect()) {
        OnHandled(aContent);
        return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint());
      }
      unwrappedMoveNodeResult.IgnoreCaretPointSuggestion();

      // JoinNodesWithTransaction (DoJoinNodes) tries to collapse selection to
      // the joined point and we want to skip updating `Selection` here.
      AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
      Result<JoinNodesResult, nsresult> joinNodesResult =
          aHTMLEditor.JoinNodesWithTransaction(*previousElement, *nextElement);
      if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
        NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
        return joinNodesResult.propagateErr();
      }
      // So, let's take it.
      OnHandled(aContent);
      return CaretPoint(
          joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>());
    }
  }

  if (RefPtr<Element> nextElement = Element::FromNodeOrNull(nextSibling)) {
    Result<bool, nsresult> canMoveIntoNextSibling =
        ElementIsGoodContainerForTheStyle(aHTMLEditor, *nextElement);
    if (MOZ_UNLIKELY(canMoveIntoNextSibling.isErr())) {
      NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
      return canMoveIntoNextSibling.propagateErr();
    }
    if (canMoveIntoNextSibling.inspect()) {
      Result<MoveNodeResult, nsresult> moveNodeResult =
          aHTMLEditor.MoveNodeWithTransaction(aContent,
                                              EditorDOMPoint(nextElement, 0u));
      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
        NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
        return moveNodeResult.propagateErr();
      }
      OnHandled(aContent);
      return CaretPoint(moveNodeResult.unwrap().UnwrapCaretPoint());
    }
  }

  // Don't need to do anything if property already set on node
  if (const RefPtr<Element> element = aContent.GetAsElementOrParentElement()) {
    if (IsCSSSettable(*element)) {
      nsAutoString value(mAttributeValue);
      // MOZ_KnownLive(element) because it's aContent.
      Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
          CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this,
                                                  value);
      if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
        NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
        return isComputedCSSEquivalentToStyleOrError.propagateErr();
      }
      if (isComputedCSSEquivalentToStyleOrError.unwrap()) {
        OnHandled(aContent);
        return CaretPoint(EditorDOMPoint());
      }
    } else if (HTMLEditUtils::IsInlineStyleSetByElement(*element, *this,
                                                        &mAttributeValue)) {
      OnHandled(aContent);
      return CaretPoint(EditorDOMPoint());
    }
  }

  auto ShouldUseCSS = [&]() {
    if (aHTMLEditor.IsCSSEnabled() && aContent.GetAsElementOrParentElement() &&
        IsCSSSettable(*aContent.GetAsElementOrParentElement())) {
      return true;
    }
    // bgcolor is always done using CSS
    if (mAttribute == nsGkAtoms::bgcolor) {
      return true;
    }
    // called for removing parent style, we should use CSS with <span> element.
    if (IsStyleToInvert()) {
      return true;
    }
    // If we set color value, the value may be able to specified only with CSS.
    // In that case, we need to use CSS even in the HTML mode.
    if (mAttribute == nsGkAtoms::color) {
      return mAttributeValue.First() != '#' &&
             !HTMLEditUtils::CanConvertToHTMLColorValue(mAttributeValue);
    }
    return false;
  };

  if (ShouldUseCSS()) {
    // We need special handlings for text-decoration.
    if (IsStyleOfTextDecoration(IgnoreSElement::No)) {
      Result<CaretPoint, nsresult> result =
          ApplyCSSTextDecoration(aHTMLEditor, aContent);
      NS_WARNING_ASSERTION(
          result.isOk(),
          "AutoInlineStyleSetter::ApplyCSSTextDecoration() failed");
      return result;
    }
    EditorDOMPoint pointToPutCaret;
    RefPtr<nsStyledElement> styledElement = [&]() -> nsStyledElement* {
      autoconst styledElement = nsStyledElement::FromNode(&aContent);
      return styledElement && ElementIsGoodContainerToSetStyle(*styledElement)
                 ? styledElement
                 : nullptr;
    }();

    // If we don't have good element to set the style, let's create new <span>.
    if (!styledElement) {
      Result<CreateElementResult, nsresult> wrapInSpanElementResult =
          aHTMLEditor.InsertContainerWithTransaction(aContent,
                                                     *nsGkAtoms::span);
      if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) {
        NS_WARNING(
            "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
            "failed");
        return wrapInSpanElementResult.propagateErr();
      }
      CreateElementResult unwrappedWrapInSpanElementResult =
          wrapInSpanElementResult.unwrap();
      MOZ_ASSERT(unwrappedWrapInSpanElementResult.GetNewNode());
      unwrappedWrapInSpanElementResult.MoveCaretPointTo(
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
      styledElement = nsStyledElement::FromNode(
          unwrappedWrapInSpanElementResult.GetNewNode());
      MOZ_ASSERT(styledElement);
      if (MOZ_UNLIKELY(!styledElement)) {
        // Don't return error to avoid creating new path to throwing error.
        OnHandled(aContent);
        return CaretPoint(pointToPutCaret);
      }
    }

    // Add the CSS styles corresponding to the HTML style request
    if (IsCSSSettable(*styledElement)) {
      Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
          WithTransaction::Yes, aHTMLEditor, *styledElement, *this,
          &mAttributeValue);
      if (MOZ_UNLIKELY(result.isErr())) {
        if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
          return Err(NS_ERROR_EDITOR_DESTROYED);
        }
        NS_WARNING(
            "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored");
      }
    }
    OnHandled(aContent);
    return CaretPoint(pointToPutCaret);
  }

  nsAutoString attributeValue(mAttributeValue);
  if (mAttribute == nsGkAtoms::color && mAttributeValue.First() != '#') {
    // At here, all color values should be able to be parsed as a CSS color
    // value.  Therefore, we need to convert it to normalized HTML color value.
    HTMLEditUtils::ConvertToNormalizedHTMLColorValue(attributeValue,
                                                     attributeValue);
  }

  // is it already the right kind of node, but with wrong attribute?
  if (aContent.IsHTMLElement(&HTMLPropertyRef())) {
    if (NS_WARN_IF(!mAttribute)) {
      return Err(NS_ERROR_INVALID_ARG);
    }
    // Just set the attribute on it.
    nsresult rv = aHTMLEditor.SetAttributeWithTransaction(
        MOZ_KnownLive(*aContent.AsElement()), *mAttribute, attributeValue);
    if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
      return Err(NS_ERROR_EDITOR_DESTROYED);
    }
    if (NS_FAILED(rv)) {
      NS_WARNING("EditorBase::SetAttributeWithTransaction() failed");
      return Err(rv);
    }
    OnHandled(aContent);
    return CaretPoint(EditorDOMPoint());
  }

  // ok, chuck it in its very own container
  Result<CreateElementResult, nsresult> wrapWithNewElementToFormatResult =
      aHTMLEditor.InsertContainerWithTransaction(
          aContent, MOZ_KnownLive(HTMLPropertyRef()),
          !mAttribute ? HTMLEditor::DoNothingForNewElement
                      // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
                      : [&](HTMLEditor& aHTMLEditor, Element& aNewElement,
                            const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
                          nsresult rv =
                              aNewElement.SetAttr(kNameSpaceID_None, mAttribute,
                                                  attributeValue, false);
                          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                                               "Element::SetAttr() failed");
                          return rv;
                        });
  if (MOZ_UNLIKELY(wrapWithNewElementToFormatResult.isErr())) {
    NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
    return wrapWithNewElementToFormatResult.propagateErr();
  }
  OnHandled(aContent);
  MOZ_ASSERT(wrapWithNewElementToFormatResult.inspect().GetNewNode());
  return CaretPoint(
      wrapWithNewElementToFormatResult.unwrap().UnwrapCaretPoint());
}

Result<CaretPoint, nsresult>
HTMLEditor::AutoInlineStyleSetter::ApplyCSSTextDecoration(
    HTMLEditor& aHTMLEditor, nsIContent& aContent) {
  MOZ_ASSERT(IsStyleOfTextDecoration(IgnoreSElement::No));

  EditorDOMPoint pointToPutCaret;
  RefPtr<nsStyledElement> styledElement = nsStyledElement::FromNode(aContent);
  nsAutoString newTextDecorationValue;
  if (&HTMLPropertyRef() == nsGkAtoms::u) {
    newTextDecorationValue.AssignLiteral(u"underline");
  } else if (&HTMLPropertyRef() == nsGkAtoms::s ||
             &HTMLPropertyRef() == nsGkAtoms::strike) {
    newTextDecorationValue.AssignLiteral(u"line-through");
  } else {
    MOZ_ASSERT_UNREACHABLE(
        "Was new value added in "
        "IsStyleOfTextDecoration(IgnoreSElement::No))?");
  }
  if (styledElement && IsCSSSettable(*styledElement) &&
      ElementIsGoodContainerToSetStyle(*styledElement)) {
    nsAutoString textDecorationValue;
    nsresult rv = CSSEditUtils::GetSpecifiedProperty(
        *styledElement, *nsGkAtoms::text_decoration, textDecorationValue);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::text_decoration) "
          "failed");
      return Err(rv);
    }
    // However, if the element is an element to style the text-decoration,
    // replace it with new <span>.
    if (styledElement && styledElement->IsAnyOfHTMLElements(
                             nsGkAtoms::u, nsGkAtoms::s, nsGkAtoms::strike)) {
      Result<CreateElementResult, nsresult> replaceResult =
          aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
              *styledElement, *nsGkAtoms::span);
      if (MOZ_UNLIKELY(replaceResult.isErr())) {
        NS_WARNING(
            "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
            "failed");
        return replaceResult.propagateErr();
      }
      CreateElementResult unwrappedReplaceResult = replaceResult.unwrap();
      MOZ_ASSERT(unwrappedReplaceResult.GetNewNode());
      unwrappedReplaceResult.MoveCaretPointTo(
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
      // The new <span> needs to specify the original element's text-decoration
      // style unless it's specified explicitly.
      if (textDecorationValue.IsEmpty()) {
        if (!newTextDecorationValue.IsEmpty()) {
          newTextDecorationValue.Append(HTMLEditUtils::kSpace);
        }
        if (styledElement->IsHTMLElement(nsGkAtoms::u)) {
          newTextDecorationValue.AppendLiteral(u"underline");
        } else {
          newTextDecorationValue.AppendLiteral(u"line-through");
        }
      }
      styledElement =
          nsStyledElement::FromNode(unwrappedReplaceResult.GetNewNode());
      if (NS_WARN_IF(!styledElement)) {
        OnHandled(aContent);
        return CaretPoint(pointToPutCaret);
      }
    }
    // If the element has default style, we need to keep it after specifying
    // text-decoration.
    else if (textDecorationValue.IsEmpty() &&
             styledElement->IsAnyOfHTMLElements(nsGkAtoms::u, nsGkAtoms::ins)) {
      if (!newTextDecorationValue.IsEmpty()) {
        newTextDecorationValue.Append(HTMLEditUtils::kSpace);
      }
      newTextDecorationValue.AppendLiteral(u"underline");
    } else if (textDecorationValue.IsEmpty() &&
               styledElement->IsAnyOfHTMLElements(
                   nsGkAtoms::s, nsGkAtoms::strike, nsGkAtoms::del)) {
      if (!newTextDecorationValue.IsEmpty()) {
        newTextDecorationValue.Append(HTMLEditUtils::kSpace);
      }
      newTextDecorationValue.AppendLiteral(u"line-through");
    }
  }
  // Otherwise, use new <span> element.
  else {
    Result<CreateElementResult, nsresult> wrapInSpanElementResult =
        aHTMLEditor.InsertContainerWithTransaction(aContent, *nsGkAtoms::span);
    if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) {
      NS_WARNING(
          "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) failed");
      return wrapInSpanElementResult.propagateErr();
    }
    CreateElementResult unwrappedWrapInSpanElementResult =
        wrapInSpanElementResult.unwrap();
    MOZ_ASSERT(unwrappedWrapInSpanElementResult.GetNewNode());
    unwrappedWrapInSpanElementResult.MoveCaretPointTo(
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
    styledElement = nsStyledElement::FromNode(
        unwrappedWrapInSpanElementResult.GetNewNode());
    if (NS_WARN_IF(!styledElement)) {
      OnHandled(aContent);
      return CaretPoint(pointToPutCaret);
    }
  }

  nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction(
      aHTMLEditor, *styledElement, *nsGkAtoms::text_decoration,
      newTextDecorationValue);
  if (NS_FAILED(rv)) {
    NS_WARNING("CSSEditUtils::SetCSSPropertyWithTransaction() failed");
    return Err(rv);
  }
  OnHandled(aContent);
  return CaretPoint(pointToPutCaret);
}

Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::
    ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor& aHTMLEditor,
                                                       nsIContent& aContent) {
  if (NS_WARN_IF(!aContent.GetParentNode())) {
    return Err(NS_ERROR_FAILURE);
  }
  OwningNonNull<nsINode> parent = *aContent.GetParentNode();
  nsCOMPtr<nsIContent> previousSibling = aContent.GetPreviousSibling(),
                       nextSibling = aContent.GetNextSibling();
  EditorDOMPoint pointToPutCaret;
  if (aContent.IsElement()) {
    Result<EditorDOMPoint, nsresult> removeStyleResult =
        aHTMLEditor.RemoveStyleInside(MOZ_KnownLive(*aContent.AsElement()),
                                      *this, SpecifiedStyle::Preserve);
    if (MOZ_UNLIKELY(removeStyleResult.isErr())) {
      NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
      return removeStyleResult.propagateErr();
    }
    if (removeStyleResult.inspect().IsSet()) {
      pointToPutCaret = removeStyleResult.unwrap();
    }
    if (nsStaticAtom* similarElementNameAtom = GetSimilarElementNameAtom()) {
      Result<EditorDOMPoint, nsresult> removeStyleResult =
          aHTMLEditor.RemoveStyleInside(
              MOZ_KnownLive(*aContent.AsElement()),
              EditorInlineStyle(*similarElementNameAtom),
              SpecifiedStyle::Preserve);
      if (MOZ_UNLIKELY(removeStyleResult.isErr())) {
        NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
        return removeStyleResult.propagateErr();
      }
      if (removeStyleResult.inspect().IsSet()) {
        pointToPutCaret = removeStyleResult.unwrap();
      }
    }
  }

  if (aContent.GetParentNode()) {
    // The node is still where it was
    Result<CaretPoint, nsresult> pointToPutCaretOrError =
        ApplyStyle(aHTMLEditor, aContent);
    NS_WARNING_ASSERTION(pointToPutCaretOrError.isOk(),
                         "AutoInlineStyleSetter::ApplyStyle() failed");
    return pointToPutCaretOrError;
  }

  // It's vanished.  Use the old siblings for reference to construct a
  // list.  But first, verify that the previous/next siblings are still
  // where we expect them; otherwise we have to give up.
  if (NS_WARN_IF(previousSibling &&
                 previousSibling->GetParentNode() != parent) ||
      NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parent) ||
      NS_WARN_IF(!parent->IsInComposedDoc())) {
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
  }
  AutoTArray<OwningNonNull<nsIContent>, 24> nodesToSet;
  for (nsIContent* content = previousSibling ? previousSibling->GetNextSibling()
                                             : parent->GetFirstChild();
       content && content != nextSibling; content = content->GetNextSibling()) {
    if (EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
      nodesToSet.AppendElement(*content);
    }
  }

  for (OwningNonNull<nsIContent>& content : nodesToSet) {
    // MOZ_KnownLive because 'nodesToSet' is guaranteed to
    // keep it alive.
    Result<CaretPoint, nsresult> pointToPutCaretOrError =
        ApplyStyle(aHTMLEditor, MOZ_KnownLive(content));
    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
      NS_WARNING("AutoInlineStyleSetter::ApplyStyle() failed");
      return pointToPutCaretOrError;
    }
    pointToPutCaretOrError.unwrap().MoveCaretPointTo(
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
  }

  return CaretPoint(pointToPutCaret);
}

bool HTMLEditor::AutoInlineStyleSetter::ContentIsElementSettingTheStyle(
    const HTMLEditor& aHTMLEditor, nsIContent& aContent) const {
  Element* const element = Element::FromNode(&aContent);
  if (!element) {
    return false;
  }
  if (IsRepresentedBy(*element)) {
    return true;
  }
  Result<bool, nsresult> specified = IsSpecifiedBy(aHTMLEditor, *element);
  NS_WARNING_ASSERTION(specified.isOk(),
                       "EditorInlineStyle::IsSpecified() failed, but ignored");
  return specified.unwrapOr(false);
}

// static
nsIContent* HTMLEditor::AutoInlineStyleSetter::GetNextEditableInlineContent(
    const nsIContent& aContent, const nsINode* aLimiter) {
  autoconst nextContentInRange = [&]() -> nsIContent* {
    for (nsIContent* parent : aContent.InclusiveAncestorsOfType<nsIContent>()) {
      if (parent == aLimiter ||
          !EditorUtils::IsEditableContent(*parent, EditorType::HTML) ||
          (parent->IsElement() &&
           (HTMLEditUtils::IsBlockElement(
                *parent->AsElement(),
                BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
            HTMLEditUtils::IsDisplayInsideFlowRoot(*parent->AsElement())))) {
        return nullptr;
      }
      if (nsIContent* nextSibling = parent->GetNextSibling()) {
        return nextSibling;
      }
    }
    return nullptr;
  }();
  return nextContentInRange &&
                 EditorUtils::IsEditableContent(*nextContentInRange,
                                                EditorType::HTML) &&
                 !HTMLEditUtils::IsBlockElement(
                     *nextContentInRange,
                     BlockInlineCheck::UseComputedDisplayOutsideStyle)
             ? nextContentInRange
--> --------------------

--> maximum size reached

--> --------------------

97%


¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.67Bemerkung:  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.