mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	Currently, the `<br>` element type -- whether normal `<br>` element or padding `<br>` element for empty editor or last line -- is managed by the flags of `nsINode`. Therefore, changing the flag does not cause mutation, so `IMEContentObserver` cannot observe the type changes. However, `ContentEventHandler` treats the padding `<br>` elements as invisible. Therefore, when a `<br>` element becomes a padding one, `IMEContentObserver` needs to notify IME of atext removed notification, and also when a `<br>` element becomes a normal one (i.e., visible), `IMEContentObserver` needs to notify IME of a text added notification. Therefore, this patch makes `EditorBase` disconnect the `<br>` element temporarily to make `IMEContentObserver` observable the type change. Depends on D211698 Differential Revision: https://phabricator.services.mozilla.com/D211699
		
			
				
	
	
		
			12137 lines
		
	
	
	
		
			515 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			12137 lines
		
	
	
	
		
			515 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=2 sw=2 et tw=80: */
 | 
						|
/* 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 "HTMLEditor.h"
 | 
						|
#include "HTMLEditorInlines.h"
 | 
						|
#include "HTMLEditorNestedClasses.h"
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <utility>
 | 
						|
 | 
						|
#include "AutoRangeArray.h"
 | 
						|
#include "CSSEditUtils.h"
 | 
						|
#include "EditAction.h"
 | 
						|
#include "EditorDOMPoint.h"
 | 
						|
#include "EditorUtils.h"
 | 
						|
#include "HTMLEditHelpers.h"
 | 
						|
#include "HTMLEditUtils.h"
 | 
						|
#include "PendingStyles.h"  // for SpecifiedStyle
 | 
						|
#include "WSRunObject.h"
 | 
						|
 | 
						|
#include "ErrorList.h"
 | 
						|
#include "mozilla/Assertions.h"
 | 
						|
#include "mozilla/Attributes.h"
 | 
						|
#include "mozilla/AutoRestore.h"
 | 
						|
#include "mozilla/CheckedInt.h"
 | 
						|
#include "mozilla/ContentIterator.h"
 | 
						|
#include "mozilla/EditorForwards.h"
 | 
						|
#include "mozilla/IntegerRange.h"
 | 
						|
#include "mozilla/InternalMutationEvent.h"
 | 
						|
#include "mozilla/MathAlgorithms.h"
 | 
						|
#include "mozilla/Maybe.h"
 | 
						|
#include "mozilla/OwningNonNull.h"
 | 
						|
#include "mozilla/Preferences.h"
 | 
						|
#include "mozilla/PresShell.h"
 | 
						|
#include "mozilla/RangeUtils.h"
 | 
						|
#include "mozilla/StaticPrefs_editor.h"  // for StaticPrefs::editor_*
 | 
						|
#include "mozilla/TextComposition.h"
 | 
						|
#include "mozilla/UniquePtr.h"
 | 
						|
#include "mozilla/Unused.h"
 | 
						|
#include "mozilla/dom/AncestorIterator.h"
 | 
						|
#include "mozilla/dom/Element.h"
 | 
						|
#include "mozilla/dom/HTMLBRElement.h"
 | 
						|
#include "mozilla/dom/RangeBinding.h"
 | 
						|
#include "mozilla/dom/Selection.h"
 | 
						|
#include "mozilla/dom/StaticRange.h"
 | 
						|
#include "mozilla/mozalloc.h"
 | 
						|
#include "nsAString.h"
 | 
						|
#include "nsAlgorithm.h"
 | 
						|
#include "nsAtom.h"
 | 
						|
#include "nsCRT.h"
 | 
						|
#include "nsCRTGlue.h"
 | 
						|
#include "nsComponentManagerUtils.h"
 | 
						|
#include "nsContentUtils.h"
 | 
						|
#include "nsDebug.h"
 | 
						|
#include "nsError.h"
 | 
						|
#include "nsFrameSelection.h"
 | 
						|
#include "nsGkAtoms.h"
 | 
						|
#include "nsHTMLDocument.h"
 | 
						|
#include "nsIContent.h"
 | 
						|
#include "nsID.h"
 | 
						|
#include "nsIFrame.h"
 | 
						|
#include "nsINode.h"
 | 
						|
#include "nsLiteralString.h"
 | 
						|
#include "nsPrintfCString.h"
 | 
						|
#include "nsRange.h"
 | 
						|
#include "nsReadableUtils.h"
 | 
						|
#include "nsString.h"
 | 
						|
#include "nsStringFwd.h"
 | 
						|
#include "nsStyledElement.h"
 | 
						|
#include "nsTArray.h"
 | 
						|
#include "nsTextNode.h"
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
#include "nsUnicharUtils.h"
 | 
						|
 | 
						|
class nsISupports;
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
using namespace dom;
 | 
						|
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
 | 
						|
using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
 | 
						|
using LeafNodeType = HTMLEditUtils::LeafNodeType;
 | 
						|
using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
 | 
						|
using WalkTextOption = HTMLEditUtils::WalkTextOption;
 | 
						|
using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
 | 
						|
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
 | 
						|
 | 
						|
/********************************************************
 | 
						|
 *  first some helpful functors we will use
 | 
						|
 ********************************************************/
 | 
						|
 | 
						|
static bool IsPendingStyleCachePreservingSubAction(
 | 
						|
    EditSubAction aEditSubAction) {
 | 
						|
  switch (aEditSubAction) {
 | 
						|
    case EditSubAction::eDeleteSelectedContent:
 | 
						|
    case EditSubAction::eInsertLineBreak:
 | 
						|
    case EditSubAction::eInsertParagraphSeparator:
 | 
						|
    case EditSubAction::eCreateOrChangeList:
 | 
						|
    case EditSubAction::eIndent:
 | 
						|
    case EditSubAction::eOutdent:
 | 
						|
    case EditSubAction::eSetOrClearAlignment:
 | 
						|
    case EditSubAction::eCreateOrRemoveBlock:
 | 
						|
    case EditSubAction::eFormatBlockForHTMLCommand:
 | 
						|
    case EditSubAction::eMergeBlockContents:
 | 
						|
    case EditSubAction::eRemoveList:
 | 
						|
    case EditSubAction::eCreateOrChangeDefinitionListItem:
 | 
						|
    case EditSubAction::eInsertElement:
 | 
						|
    case EditSubAction::eInsertQuotation:
 | 
						|
    case EditSubAction::eInsertQuotedText:
 | 
						|
      return true;
 | 
						|
    default:
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
template already_AddRefed<nsRange>
 | 
						|
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorDOMRange& aRange);
 | 
						|
template already_AddRefed<nsRange>
 | 
						|
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorRawDOMRange& aRange);
 | 
						|
template already_AddRefed<nsRange>
 | 
						|
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
 | 
						|
template already_AddRefed<nsRange>
 | 
						|
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorRawDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
 | 
						|
template already_AddRefed<nsRange>
 | 
						|
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
 | 
						|
template already_AddRefed<nsRange>
 | 
						|
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
 | 
						|
 | 
						|
nsresult HTMLEditor::InitEditorContentAndSelection() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  // We should do nothing with the result of GetRoot() if only a part of the
 | 
						|
  // document is editable.
 | 
						|
  if (!EntireDocumentIsEditable()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // If the selection hasn't been set up yet, set it up collapsed to the end of
 | 
						|
  // our editable content.
 | 
						|
  // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe
 | 
						|
  //     removed by the web app and if they call `Selection::AddRange()` without
 | 
						|
  //     checking the range count, it may cause multiple selection ranges.
 | 
						|
  if (!SelectionRef().RangeCount()) {
 | 
						|
    nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument();
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() "
 | 
						|
          "failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsPlaintextMailComposer()) {
 | 
						|
    // XXX Should we do this in HTMLEditor?  It's odd to guarantee that last
 | 
						|
    //     empty line is visible only when it's in the plain text mode.
 | 
						|
    nsresult rv = EnsurePaddingBRElementInMultilineEditor();
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Element* bodyOrDocumentElement = GetRoot();
 | 
						|
  if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!bodyOrDocumentElement) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
 | 
						|
      RawRangeBoundary(bodyOrDocumentElement, 0u),
 | 
						|
      RawRangeBoundary(bodyOrDocumentElement,
 | 
						|
                       bodyOrDocumentElement->GetChildCount()));
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
 | 
						|
      "failed, but ignored");
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
 | 
						|
    EditSubAction aTopLevelEditSubAction,
 | 
						|
    nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!aRv.Failed());
 | 
						|
 | 
						|
  EditorBase::OnStartToHandleTopLevelEditSubAction(
 | 
						|
      aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv);
 | 
						|
 | 
						|
  MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction);
 | 
						|
  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
 | 
						|
             aDirectionOfTopLevelEditSubAction);
 | 
						|
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mInitSucceeded) {
 | 
						|
    return;  // We should do nothing if we're being initialized.
 | 
						|
  }
 | 
						|
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !aRv.Failed(),
 | 
						|
      "EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
 | 
						|
 | 
						|
  // Let's work with the latest layout information after (maybe) dispatching
 | 
						|
  // `beforeinput` event.
 | 
						|
  RefPtr<Document> document = GetDocument();
 | 
						|
  if (NS_WARN_IF(!document)) {
 | 
						|
    aRv.Throw(NS_ERROR_UNEXPECTED);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  document->FlushPendingNotifications(FlushType::Frames);
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Remember where our selection was before edit action took place:
 | 
						|
  const auto atCompositionStart =
 | 
						|
      GetFirstIMESelectionStartPoint<EditorRawDOMPoint>();
 | 
						|
  if (atCompositionStart.IsSet()) {
 | 
						|
    // If there is composition string, let's remember current composition
 | 
						|
    // range.
 | 
						|
    TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
 | 
						|
        atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>());
 | 
						|
  } else {
 | 
						|
    // Get the selection location
 | 
						|
    // XXX This may occur so that I think that we shouldn't throw exception
 | 
						|
    //     in this case.
 | 
						|
    if (NS_WARN_IF(!SelectionRef().RangeCount())) {
 | 
						|
      aRv.Throw(NS_ERROR_UNEXPECTED);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (const nsRange* range = SelectionRef().GetRangeAt(0)) {
 | 
						|
      TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Register with range updater to track this as we perturb the doc
 | 
						|
  RangeUpdaterRef().RegisterRangeItem(
 | 
						|
      *TopLevelEditSubActionDataRef().mSelectedRange);
 | 
						|
 | 
						|
  // Remember current inline styles for deletion and normal insertion ops
 | 
						|
  const bool cacheInlineStyles = [&]() {
 | 
						|
    switch (aTopLevelEditSubAction) {
 | 
						|
      case EditSubAction::eInsertText:
 | 
						|
      case EditSubAction::eInsertTextComingFromIME:
 | 
						|
      case EditSubAction::eDeleteSelectedContent:
 | 
						|
        return true;
 | 
						|
      default:
 | 
						|
        return IsPendingStyleCachePreservingSubAction(aTopLevelEditSubAction);
 | 
						|
    }
 | 
						|
  }();
 | 
						|
  if (cacheInlineStyles) {
 | 
						|
    const RefPtr<Element> editingHost =
 | 
						|
        ComputeEditingHost(LimitInBodyElement::No);
 | 
						|
    if (NS_WARN_IF(!editingHost)) {
 | 
						|
      aRv.Throw(NS_ERROR_FAILURE);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    nsIContent* const startContainer =
 | 
						|
        HTMLEditUtils::GetContentToPreserveInlineStyles(
 | 
						|
            TopLevelEditSubActionDataRef()
 | 
						|
                .mSelectedRange->StartPoint<EditorRawDOMPoint>(),
 | 
						|
            *editingHost);
 | 
						|
    if (NS_WARN_IF(!startContainer)) {
 | 
						|
      aRv.Throw(NS_ERROR_FAILURE);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (const RefPtr<Element> startContainerElement =
 | 
						|
            startContainer->GetAsElementOrParentElement()) {
 | 
						|
      nsresult rv = CacheInlineStyles(*startContainerElement);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
 | 
						|
        aRv.Throw(rv);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Stabilize the document against contenteditable count changes
 | 
						|
  if (document->GetEditingState() == Document::EditingState::eContentEditable) {
 | 
						|
    document->ChangeContentEditableCount(nullptr, +1);
 | 
						|
    TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Check that selection is in subtree defined by body node
 | 
						|
  nsresult rv = EnsureSelectionInBodyOrDocumentElement();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
 | 
						|
                       "failed, but ignored");
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  nsresult rv;
 | 
						|
  while (true) {
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      rv = NS_ERROR_EDITOR_DESTROYED;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!mInitSucceeded) {
 | 
						|
      rv = NS_OK;  // We should do nothing if we're being initialized.
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    // Do all the tricky stuff
 | 
						|
    rv = OnEndHandlingTopLevelEditSubActionInternal();
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
 | 
						|
    // Perhaps, we need to do the following jobs even if the editor has been
 | 
						|
    // destroyed since they adjust some states of HTML document but don't
 | 
						|
    // modify the DOM tree nor Selection.
 | 
						|
 | 
						|
    // Free up selectionState range item
 | 
						|
    if (TopLevelEditSubActionDataRef().mSelectedRange) {
 | 
						|
      RangeUpdaterRef().DropRangeItem(
 | 
						|
          *TopLevelEditSubActionDataRef().mSelectedRange);
 | 
						|
    }
 | 
						|
 | 
						|
    // Reset the contenteditable count to its previous value
 | 
						|
    if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) {
 | 
						|
      Document* document = GetDocument();
 | 
						|
      if (NS_WARN_IF(!document)) {
 | 
						|
        rv = NS_ERROR_FAILURE;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      if (document->GetEditingState() ==
 | 
						|
          Document::EditingState::eContentEditable) {
 | 
						|
        document->ChangeContentEditableCount(nullptr, -1);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    break;
 | 
						|
  }
 | 
						|
  DebugOnly<nsresult> rvIgnored =
 | 
						|
      EditorBase::OnEndHandlingTopLevelEditSubAction();
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored),
 | 
						|
      "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
 | 
						|
  MOZ_ASSERT(!GetTopLevelEditSubAction());
 | 
						|
  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  nsresult rv = EnsureSelectionInBodyOrDocumentElement();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  switch (GetTopLevelEditSubAction()) {
 | 
						|
    case EditSubAction::eReplaceHeadWithHTMLSource:
 | 
						|
    case EditSubAction::eCreatePaddingBRElementForEmptyEditor:
 | 
						|
      return NS_OK;
 | 
						|
    default:
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() &&
 | 
						|
      GetTopLevelEditSubAction() != EditSubAction::eUndo &&
 | 
						|
      GetTopLevelEditSubAction() != EditSubAction::eRedo) {
 | 
						|
    // don't let any txns in here move the selection around behind our back.
 | 
						|
    // Note that this won't prevent explicit selection setting from working.
 | 
						|
    AutoTransactionsConserveSelection dontChangeMySelection(*this);
 | 
						|
 | 
						|
    {
 | 
						|
      EditorDOMRange changedRange(
 | 
						|
          *TopLevelEditSubActionDataRef().mChangedRange);
 | 
						|
      if (changedRange.IsPositioned() &&
 | 
						|
          changedRange.EnsureNotInNativeAnonymousSubtree()) {
 | 
						|
        bool isBlockLevelSubAction = false;
 | 
						|
        switch (GetTopLevelEditSubAction()) {
 | 
						|
          case EditSubAction::eInsertText:
 | 
						|
          case EditSubAction::eInsertTextComingFromIME:
 | 
						|
          case EditSubAction::eInsertLineBreak:
 | 
						|
          case EditSubAction::eInsertParagraphSeparator:
 | 
						|
          case EditSubAction::eDeleteText: {
 | 
						|
            // XXX We should investigate whether this is really needed because
 | 
						|
            //     it seems that the following code does not handle the
 | 
						|
            //     white-spaces.
 | 
						|
            RefPtr<nsRange> extendedChangedRange =
 | 
						|
                CreateRangeIncludingAdjuscentWhiteSpaces(changedRange);
 | 
						|
            if (extendedChangedRange) {
 | 
						|
              MOZ_ASSERT(extendedChangedRange->IsPositioned());
 | 
						|
              // Use extended range temporarily.
 | 
						|
              TopLevelEditSubActionDataRef().mChangedRange =
 | 
						|
                  std::move(extendedChangedRange);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          }
 | 
						|
          case EditSubAction::eCreateOrChangeList:
 | 
						|
          case EditSubAction::eCreateOrChangeDefinitionListItem:
 | 
						|
          case EditSubAction::eRemoveList:
 | 
						|
          case EditSubAction::eFormatBlockForHTMLCommand:
 | 
						|
          case EditSubAction::eCreateOrRemoveBlock:
 | 
						|
          case EditSubAction::eIndent:
 | 
						|
          case EditSubAction::eOutdent:
 | 
						|
          case EditSubAction::eSetOrClearAlignment:
 | 
						|
          case EditSubAction::eSetPositionToAbsolute:
 | 
						|
          case EditSubAction::eSetPositionToStatic:
 | 
						|
          case EditSubAction::eDecreaseZIndex:
 | 
						|
          case EditSubAction::eIncreaseZIndex:
 | 
						|
            isBlockLevelSubAction = true;
 | 
						|
            [[fallthrough]];
 | 
						|
          default: {
 | 
						|
            Element* editingHost = ComputeEditingHost();
 | 
						|
            if (MOZ_UNLIKELY(!editingHost)) {
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            RefPtr<nsRange> extendedChangedRange = AutoRangeArray::
 | 
						|
                CreateRangeWrappingStartAndEndLinesContainingBoundaries(
 | 
						|
                    changedRange, GetTopLevelEditSubAction(),
 | 
						|
                    isBlockLevelSubAction
 | 
						|
                        ? BlockInlineCheck::UseHTMLDefaultStyle
 | 
						|
                        : BlockInlineCheck::UseComputedDisplayOutsideStyle,
 | 
						|
                    *editingHost);
 | 
						|
            if (!extendedChangedRange) {
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            MOZ_ASSERT(extendedChangedRange->IsPositioned());
 | 
						|
            // Use extended range temporarily.
 | 
						|
            TopLevelEditSubActionDataRef().mChangedRange =
 | 
						|
                std::move(extendedChangedRange);
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // if we did a ranged deletion or handling backspace key, make sure we have
 | 
						|
    // a place to put caret.
 | 
						|
    // Note we only want to do this if the overall operation was deletion,
 | 
						|
    // not if deletion was done along the way for
 | 
						|
    // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
 | 
						|
    // That's why this is here rather than DeleteSelectionAsSubAction().
 | 
						|
    // However, we shouldn't insert <br> elements if we've already removed
 | 
						|
    // empty block parents because users may want to disappear the line by
 | 
						|
    // the deletion.
 | 
						|
    // XXX We should make HandleDeleteSelection() store expected container
 | 
						|
    //     for handling this here since we cannot trust current selection is
 | 
						|
    //     collapsed at deleted point.
 | 
						|
    if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent &&
 | 
						|
        TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange &&
 | 
						|
        !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) {
 | 
						|
      const auto newCaretPosition =
 | 
						|
          GetFirstSelectionStartPoint<EditorDOMPoint>();
 | 
						|
      if (!newCaretPosition.IsSet()) {
 | 
						|
        NS_WARNING("There was no selection range");
 | 
						|
        return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
 | 
						|
      }
 | 
						|
      Result<CaretPoint, nsresult> caretPointOrError =
 | 
						|
          InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
 | 
						|
              newCaretPosition);
 | 
						|
      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::"
 | 
						|
            "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
 | 
						|
            "failed");
 | 
						|
        return caretPointOrError.unwrapErr();
 | 
						|
      }
 | 
						|
      nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
 | 
						|
    }
 | 
						|
 | 
						|
    // add in any needed <br>s, and remove any unneeded ones.
 | 
						|
    nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
 | 
						|
        TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(),
 | 
						|
        TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw());
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
 | 
						|
        " failed, but ignored");
 | 
						|
 | 
						|
    // merge any adjacent text nodes
 | 
						|
    switch (GetTopLevelEditSubAction()) {
 | 
						|
      case EditSubAction::eInsertText:
 | 
						|
      case EditSubAction::eInsertTextComingFromIME:
 | 
						|
        break;
 | 
						|
      default: {
 | 
						|
        nsresult rv = CollapseAdjacentTextNodes(
 | 
						|
            MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
 | 
						|
        if (NS_WARN_IF(Destroyed())) {
 | 
						|
          return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
        }
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
 | 
						|
          return rv;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Clean up any empty nodes in the changed range unless they are inserted
 | 
						|
    // intentionally.
 | 
						|
    if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) {
 | 
						|
      nsresult rv = RemoveEmptyNodesIn(
 | 
						|
          EditorDOMRange(*TopLevelEditSubActionDataRef().mChangedRange));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // attempt to transform any unneeded nbsp's into spaces after doing various
 | 
						|
    // operations
 | 
						|
    switch (GetTopLevelEditSubAction()) {
 | 
						|
      case EditSubAction::eDeleteSelectedContent:
 | 
						|
        if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        [[fallthrough]];
 | 
						|
      case EditSubAction::eInsertText:
 | 
						|
      case EditSubAction::eInsertTextComingFromIME:
 | 
						|
      case EditSubAction::eInsertLineBreak:
 | 
						|
      case EditSubAction::eInsertParagraphSeparator:
 | 
						|
      case EditSubAction::ePasteHTMLContent:
 | 
						|
      case EditSubAction::eInsertHTMLSource: {
 | 
						|
        // Due to the replacement of white-spaces in
 | 
						|
        // WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(),
 | 
						|
        // selection ranges may be changed since DOM ranges track the DOM
 | 
						|
        // mutation by themselves.  However, we want to keep selection as-is.
 | 
						|
        // Therefore, we should restore `Selection` after replacing
 | 
						|
        // white-spaces.
 | 
						|
        AutoSelectionRestorer restoreSelection(*this);
 | 
						|
        // TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
 | 
						|
        //       white-spaces with NPSPs and then, we'll replace them with ASCII
 | 
						|
        //       white-spaces here.  We should avoid this overwriting things as
 | 
						|
        //       far as possible because replacing characters in text nodes
 | 
						|
        //       causes running mutation event listeners which are really
 | 
						|
        //       expensive.
 | 
						|
        // Adjust end of composition string if there is composition string.
 | 
						|
        auto pointToAdjust = GetLastIMESelectionEndPoint<EditorDOMPoint>();
 | 
						|
        if (!pointToAdjust.IsInContentNode()) {
 | 
						|
          // Otherwise, adjust current selection start point.
 | 
						|
          pointToAdjust = GetFirstSelectionStartPoint<EditorDOMPoint>();
 | 
						|
          if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) {
 | 
						|
            return NS_ERROR_FAILURE;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if (EditorUtils::IsEditableContent(
 | 
						|
                *pointToAdjust.ContainerAs<nsIContent>(), EditorType::HTML)) {
 | 
						|
          AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
 | 
						|
                                               &pointToAdjust);
 | 
						|
          nsresult rv =
 | 
						|
              WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
 | 
						|
                  *this, pointToAdjust);
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            NS_WARNING(
 | 
						|
                "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
 | 
						|
                "failed");
 | 
						|
            return rv;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        // also do this for original selection endpoints.
 | 
						|
        // XXX Hmm, if `NormalizeVisibleWhiteSpacesAt()` runs mutation event
 | 
						|
        //     listener and that causes changing `mSelectedRange`, what we
 | 
						|
        //     should do?
 | 
						|
        if (NS_WARN_IF(!TopLevelEditSubActionDataRef()
 | 
						|
                            .mSelectedRange->IsPositioned())) {
 | 
						|
          return NS_ERROR_FAILURE;
 | 
						|
        }
 | 
						|
 | 
						|
        EditorDOMPoint atStart =
 | 
						|
            TopLevelEditSubActionDataRef().mSelectedRange->StartPoint();
 | 
						|
        if (atStart != pointToAdjust && atStart.IsInContentNode() &&
 | 
						|
            EditorUtils::IsEditableContent(*atStart.ContainerAs<nsIContent>(),
 | 
						|
                                           EditorType::HTML)) {
 | 
						|
          AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
 | 
						|
                                               &pointToAdjust);
 | 
						|
          AutoTrackDOMPoint trackStartPoint(RangeUpdaterRef(), &atStart);
 | 
						|
          nsresult rv =
 | 
						|
              WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
 | 
						|
                  *this, atStart);
 | 
						|
          if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
            return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
          }
 | 
						|
          NS_WARNING_ASSERTION(
 | 
						|
              NS_SUCCEEDED(rv),
 | 
						|
              "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
 | 
						|
              "failed, but ignored");
 | 
						|
        }
 | 
						|
        // we only need to handle old selection endpoint if it was different
 | 
						|
        // from start
 | 
						|
        EditorDOMPoint atEnd =
 | 
						|
            TopLevelEditSubActionDataRef().mSelectedRange->EndPoint();
 | 
						|
        if (!TopLevelEditSubActionDataRef().mSelectedRange->Collapsed() &&
 | 
						|
            atEnd != pointToAdjust && atEnd != atStart &&
 | 
						|
            atEnd.IsInContentNode() &&
 | 
						|
            EditorUtils::IsEditableContent(*atEnd.ContainerAs<nsIContent>(),
 | 
						|
                                           EditorType::HTML)) {
 | 
						|
          nsresult rv =
 | 
						|
              WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(*this,
 | 
						|
                                                                        atEnd);
 | 
						|
          if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
            return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
          }
 | 
						|
          NS_WARNING_ASSERTION(
 | 
						|
              NS_SUCCEEDED(rv),
 | 
						|
              "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
 | 
						|
              "failed, but ignored");
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      default:
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
    // Adjust selection for insert text, html paste, and delete actions if
 | 
						|
    // we haven't removed new empty blocks.  Note that if empty block parents
 | 
						|
    // are removed, Selection should've been adjusted by the method which
 | 
						|
    // did it.
 | 
						|
    if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks &&
 | 
						|
        SelectionRef().IsCollapsed()) {
 | 
						|
      switch (GetTopLevelEditSubAction()) {
 | 
						|
        case EditSubAction::eInsertText:
 | 
						|
        case EditSubAction::eInsertTextComingFromIME:
 | 
						|
        case EditSubAction::eDeleteSelectedContent:
 | 
						|
        case EditSubAction::eInsertLineBreak:
 | 
						|
        case EditSubAction::eInsertParagraphSeparator:
 | 
						|
        case EditSubAction::ePasteHTMLContent:
 | 
						|
        case EditSubAction::eInsertHTMLSource:
 | 
						|
          // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
 | 
						|
          //     does not create padding `<br>` element for empty editor.
 | 
						|
          //     Investigate which is better that whether this should does it
 | 
						|
          //     or wait MaybeCreatePaddingBRElementForEmptyEditor().
 | 
						|
          rv = AdjustCaretPositionAndEnsurePaddingBRElement(
 | 
						|
              GetDirectionOfTopLevelEditSubAction());
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            NS_WARNING(
 | 
						|
                "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
 | 
						|
                "failed");
 | 
						|
            return rv;
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
          break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // check for any styles which were removed inappropriately
 | 
						|
    bool reapplyCachedStyle;
 | 
						|
    switch (GetTopLevelEditSubAction()) {
 | 
						|
      case EditSubAction::eInsertText:
 | 
						|
      case EditSubAction::eInsertTextComingFromIME:
 | 
						|
      case EditSubAction::eDeleteSelectedContent:
 | 
						|
        reapplyCachedStyle = true;
 | 
						|
        break;
 | 
						|
      default:
 | 
						|
        reapplyCachedStyle =
 | 
						|
            IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction());
 | 
						|
        break;
 | 
						|
    }
 | 
						|
 | 
						|
    // If the selection is in empty inline HTML elements, we should delete
 | 
						|
    // them unless it's inserted intentionally.
 | 
						|
    if (mPlaceholderBatch &&
 | 
						|
        TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements &&
 | 
						|
        SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) {
 | 
						|
      RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr;
 | 
						|
      for (Element* ancestor :
 | 
						|
           SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) {
 | 
						|
        if (!ancestor->IsHTMLElement() ||
 | 
						|
            !HTMLEditUtils::IsRemovableFromParentNode(*ancestor) ||
 | 
						|
            !HTMLEditUtils::IsEmptyInlineContainer(
 | 
						|
                *ancestor, {EmptyCheckOption::TreatSingleBRElementAsVisible},
 | 
						|
                BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        mostDistantEmptyInlineAncestor = ancestor;
 | 
						|
      }
 | 
						|
      if (mostDistantEmptyInlineAncestor) {
 | 
						|
        nsresult rv =
 | 
						|
            DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor);
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING(
 | 
						|
              "EditorBase::DeleteNodeWithTransaction() failed at deleting "
 | 
						|
              "empty inline ancestors");
 | 
						|
          return rv;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // But the cached inline styles should be restored from type-in-state later.
 | 
						|
    if (reapplyCachedStyle) {
 | 
						|
      DebugOnly<nsresult> rvIgnored =
 | 
						|
          mPendingStylesToApplyToNewContent->UpdateSelState(*this);
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rvIgnored),
 | 
						|
          "PendingStyles::UpdateSelState() failed, but ignored");
 | 
						|
      rvIgnored = ReapplyCachedStyles();
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rvIgnored),
 | 
						|
          "HTMLEditor::ReapplyCachedStyles() failed, but ignored");
 | 
						|
      TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  rv = HandleInlineSpellCheck(
 | 
						|
      TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(),
 | 
						|
      TopLevelEditSubActionDataRef().mChangedRange);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // detect empty doc
 | 
						|
  // XXX Need to investigate when the padding <br> element is removed because
 | 
						|
  //     I don't see the <br> element with testing manually.  If it won't be
 | 
						|
  //     used, we can get rid of this cost.
 | 
						|
  rv = MaybeCreatePaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // adjust selection HINT if needed
 | 
						|
  if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
 | 
						|
      SelectionRef().IsCollapsed()) {
 | 
						|
    SetSelectionInterlinePosition();
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::CanHandleHTMLEditSubAction(
 | 
						|
    CheckSelectionInReplacedElement aCheckSelectionInReplacedElement
 | 
						|
    /* = CheckSelectionInReplacedElement::Yes */) const {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
 | 
						|
  // If there is not selection ranges, we should ignore the result.
 | 
						|
  if (!SelectionRef().RangeCount()) {
 | 
						|
    return EditActionResult::CanceledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  const nsRange* range = SelectionRef().GetRangeAt(0);
 | 
						|
  nsINode* selStartNode = range->GetStartContainer();
 | 
						|
  if (NS_WARN_IF(!selStartNode) || NS_WARN_IF(!selStartNode->IsContent())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode)) {
 | 
						|
    return EditActionResult::CanceledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  nsINode* selEndNode = range->GetEndContainer();
 | 
						|
  if (NS_WARN_IF(!selEndNode) || NS_WARN_IF(!selEndNode->IsContent())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (selStartNode == selEndNode) {
 | 
						|
    if (aCheckSelectionInReplacedElement ==
 | 
						|
            CheckSelectionInReplacedElement::Yes &&
 | 
						|
        HTMLEditUtils::IsNonEditableReplacedContent(
 | 
						|
            *selStartNode->AsContent())) {
 | 
						|
      return EditActionResult::CanceledResult();
 | 
						|
    }
 | 
						|
    return EditActionResult::IgnoredResult();
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsNonEditableReplacedContent(*selStartNode->AsContent()) ||
 | 
						|
      HTMLEditUtils::IsNonEditableReplacedContent(*selEndNode->AsContent())) {
 | 
						|
    return EditActionResult::CanceledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) {
 | 
						|
    return EditActionResult::CanceledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  // If anchor node is in an HTML element which has inert attribute, we should
 | 
						|
  // do nothing.
 | 
						|
  // XXX HTMLEditor typically uses first range instead of anchor/focus range.
 | 
						|
  //     Therefore, referring first range here is more reasonable than
 | 
						|
  //     anchor/focus range of Selection.
 | 
						|
  nsIContent* const selAnchorContent = SelectionRef().GetDirection() == eDirNext
 | 
						|
                                           ? nsIContent::FromNode(selStartNode)
 | 
						|
                                           : nsIContent::FromNode(selEndNode);
 | 
						|
  if (selAnchorContent &&
 | 
						|
      HTMLEditUtils::ContentIsInert(*selAnchorContent->AsContent())) {
 | 
						|
    return EditActionResult::CanceledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX What does it mean the common ancestor is editable?  I have no idea.
 | 
						|
  //     It should be in same (active) editing host, and even if it's editable,
 | 
						|
  //     there may be non-editable contents in the range.
 | 
						|
  nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor();
 | 
						|
  if (MOZ_UNLIKELY(!commonAncestor)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
 | 
						|
             ? EditActionResult::IgnoredResult()
 | 
						|
             : EditActionResult::CanceledResult();
 | 
						|
}
 | 
						|
 | 
						|
MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent(
 | 
						|
    nsIContent& aContent) {
 | 
						|
  nsAutoString direction;
 | 
						|
  DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
 | 
						|
      aContent, *nsGkAtoms::direction, direction);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | 
						|
                       "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
 | 
						|
                       " failed, but ignored");
 | 
						|
  return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
 | 
						|
                                        : *nsGkAtoms::marginLeft;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(SelectionRef().IsCollapsed());
 | 
						|
 | 
						|
  // If we are after a padding `<br>` element for empty last line in the same
 | 
						|
  // block, then move selection to be before it
 | 
						|
  const nsRange* firstRange = SelectionRef().GetRangeAt(0);
 | 
						|
  if (NS_WARN_IF(!firstRange)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  EditorRawDOMPoint atSelectionStart(firstRange->StartRef());
 | 
						|
  if (NS_WARN_IF(!atSelectionStart.IsSet())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(atSelectionStart.IsSetAndValid());
 | 
						|
 | 
						|
  if (!atSelectionStart.IsInContentNode()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  Element* editingHost = ComputeEditingHost();
 | 
						|
  if (!editingHost) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() did nothing "
 | 
						|
        "because of no editing host");
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent(
 | 
						|
      atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle,
 | 
						|
      editingHost);
 | 
						|
  if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) ||
 | 
						|
      !previousBRElement->GetParent() ||
 | 
						|
      !EditorUtils::IsEditableContent(*previousBRElement->GetParent(),
 | 
						|
                                      EditorType::HTML) ||
 | 
						|
      !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  const RefPtr<const Element> blockElementAtSelectionStart =
 | 
						|
      HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
          *atSelectionStart.ContainerAs<nsIContent>(),
 | 
						|
          HTMLEditUtils::ClosestBlockElement,
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  const RefPtr<const Element> parentBlockElementOfBRElement =
 | 
						|
      HTMLEditUtils::GetAncestorElement(
 | 
						|
          *previousBRElement, HTMLEditUtils::ClosestBlockElement,
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
 | 
						|
  if (!blockElementAtSelectionStart ||
 | 
						|
      blockElementAtSelectionStart != parentBlockElementOfBRElement) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we are here then the selection is right after a padding <br>
 | 
						|
  // element for empty last line that is in the same block as the
 | 
						|
  // selection.  We need to move the selection start to be before the
 | 
						|
  // padding <br> element.
 | 
						|
  EditorRawDOMPoint atInvisibleBRElement(previousBRElement);
 | 
						|
  nsresult rv = CollapseSelectionTo(atInvisibleBRElement);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::CollapseSelectionTo() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  if (mPaddingBRElementForEmptyEditor) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX I think that we should not insert a <br> element if we're for a web
 | 
						|
  // content.  Probably, this is required only by chrome editors such as
 | 
						|
  // the mail composer of Thunderbird and the composer of SeaMonkey.
 | 
						|
 | 
						|
  const RefPtr<Element> bodyOrDocumentElement = GetRoot();
 | 
						|
  if (!bodyOrDocumentElement) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // Skip adding the padding <br> element for empty editor if body
 | 
						|
  // is read-only.
 | 
						|
  if (!HTMLEditUtils::IsSimplyEditableNode(*bodyOrDocumentElement)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // Now we've got the body element. Iterate over the body element's children,
 | 
						|
  // looking for editable content. If no editable content is found, insert the
 | 
						|
  // padding <br> element.
 | 
						|
  EditorType editorType = GetEditorType();
 | 
						|
  bool isRootEditable =
 | 
						|
      EditorUtils::IsEditableContent(*bodyOrDocumentElement, editorType);
 | 
						|
  for (nsIContent* child = bodyOrDocumentElement->GetFirstChild(); child;
 | 
						|
       child = child->GetNextSibling()) {
 | 
						|
    if (EditorUtils::IsPaddingBRElementForEmptyEditor(*child) ||
 | 
						|
        !isRootEditable || EditorUtils::IsEditableContent(*child, editorType) ||
 | 
						|
        HTMLEditUtils::IsBlockElement(
 | 
						|
            *child, BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor,
 | 
						|
      nsIEditor::eNone, ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return ignoredError.StealNSResult();
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  // Create a br.
 | 
						|
  RefPtr<HTMLBRElement> newBRElement =
 | 
						|
      HTMLBRElement::FromNodeOrNull(RefPtr{CreateHTMLContent(nsGkAtoms::br)});
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
  }
 | 
						|
  if (NS_WARN_IF(!newBRElement)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  mPaddingBRElementForEmptyEditor = newBRElement;
 | 
						|
 | 
						|
  // Give it a special attribute.
 | 
						|
  nsresult rv =
 | 
						|
      UpdateBRElementType(*newBRElement, BRElementType::PaddingForEmptyEditor);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("EditorBase::UpdateBRElementType() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // Put the node in the document.
 | 
						|
  Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
      InsertNodeWithTransaction<Element>(
 | 
						|
          *newBRElement, EditorDOMPoint(bodyOrDocumentElement, 0u));
 | 
						|
  if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
    NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
 | 
						|
    return insertBRElementResult.unwrapErr();
 | 
						|
  }
 | 
						|
 | 
						|
  // Set selection.
 | 
						|
  insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
 | 
						|
  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "EditorBase::CollapseSelectionToStartOf() caused destroying the "
 | 
						|
        "editor");
 | 
						|
    return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  if (!mPaddingBRElementForEmptyEditor) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we're an HTML editor, a mutation event listener may recreate padding
 | 
						|
  // <br> element for empty editor again during the call of
 | 
						|
  // DeleteNodeWithTransaction().  So, move it first.
 | 
						|
  RefPtr<HTMLBRElement> paddingBRElement(
 | 
						|
      std::move(mPaddingBRElementForEmptyEditor));
 | 
						|
  nsresult rv = DeleteNodeWithTransaction(*paddingBRElement);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() {
 | 
						|
  if (NS_WARN_IF(!mRootElement)) {
 | 
						|
    NS_WARNING("Failed to handle padding BR element due to no root element");
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  // The idea here is to see if the magic empty node has suddenly reappeared. If
 | 
						|
  // it has, set our state so we remember it. There is a tradeoff between doing
 | 
						|
  // here and at redo, or doing it everywhere else that might care.  Since undo
 | 
						|
  // and redo are relatively rare, it makes sense to take the (small)
 | 
						|
  // performance hit here.
 | 
						|
  nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent(
 | 
						|
      *mRootElement, {LeafNodeType::OnlyLeafNode});
 | 
						|
  if (firstLeafChild &&
 | 
						|
      EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) {
 | 
						|
    mPaddingBRElementForEmptyEditor =
 | 
						|
        static_cast<HTMLBRElement*>(firstLeafChild);
 | 
						|
  } else {
 | 
						|
    mPaddingBRElementForEmptyEditor = nullptr;
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::PrepareInlineStylesForCaret() {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
  MOZ_ASSERT(SelectionRef().IsCollapsed());
 | 
						|
 | 
						|
  // XXX This method works with the top level edit sub-action, but this
 | 
						|
  //     must be wrong if we are handling nested edit action.
 | 
						|
 | 
						|
  if (TopLevelEditSubActionDataRef().mDidDeleteSelection) {
 | 
						|
    switch (GetTopLevelEditSubAction()) {
 | 
						|
      case EditSubAction::eInsertText:
 | 
						|
      case EditSubAction::eInsertTextComingFromIME:
 | 
						|
      case EditSubAction::eDeleteSelectedContent: {
 | 
						|
        nsresult rv = ReapplyCachedStyles();
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
 | 
						|
          return rv;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      default:
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  // For most actions we want to clear the cached styles, but there are
 | 
						|
  // exceptions
 | 
						|
  if (!IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
 | 
						|
    TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
 | 
						|
    EditSubAction aEditSubAction, const nsAString& aInsertionString,
 | 
						|
    SelectionHandling aSelectionHandling) {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
 | 
						|
             aEditSubAction == EditSubAction::eInsertTextComingFromIME);
 | 
						|
  MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore,
 | 
						|
                aEditSubAction == EditSubAction::eInsertTextComingFromIME);
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  UndefineCaretBidiLevel();
 | 
						|
 | 
						|
  // If the selection isn't collapsed, delete it.  Don't delete existing inline
 | 
						|
  // tags, because we're hopefully going to insert text (bug 787432).
 | 
						|
  if (!SelectionRef().IsCollapsed() &&
 | 
						|
      aSelectionHandling == SelectionHandling::Delete) {
 | 
						|
    nsresult rv =
 | 
						|
        DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
 | 
						|
          "nsIEditor::eNoStrip) failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Document> document = GetDocument();
 | 
						|
  if (NS_WARN_IF(!document)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  const RefPtr<Element> editingHost = ComputeEditingHost(
 | 
						|
      GetDocument()->IsXMLDocument() ? LimitInBodyElement::No
 | 
						|
                                     : LimitInBodyElement::Yes);
 | 
						|
  if (NS_WARN_IF(!editingHost)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  auto pointToInsert = GetFirstSelectionStartPoint<EditorDOMPoint>();
 | 
						|
  if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  // for every property that is set, insert a new inline style node
 | 
						|
  Result<EditorDOMPoint, nsresult> setStyleResult =
 | 
						|
      CreateStyleForInsertText(pointToInsert, *editingHost);
 | 
						|
  if (MOZ_UNLIKELY(setStyleResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
 | 
						|
    return setStyleResult.propagateErr();
 | 
						|
  }
 | 
						|
  if (setStyleResult.inspect().IsSet()) {
 | 
						|
    pointToInsert = setStyleResult.unwrap();
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
 | 
						|
      NS_WARN_IF(!pointToInsert.IsInContentNode())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(pointToInsert.IsSetAndValid());
 | 
						|
 | 
						|
  // If the point is not in an element which can contain text nodes, climb up
 | 
						|
  // the DOM tree.
 | 
						|
  if (!pointToInsert.IsInTextNode()) {
 | 
						|
    while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
 | 
						|
                                          *nsGkAtoms::textTagName)) {
 | 
						|
      if (NS_WARN_IF(pointToInsert.GetContainer() == editingHost) ||
 | 
						|
          NS_WARN_IF(!pointToInsert.GetContainerParentAs<nsIContent>())) {
 | 
						|
        NS_WARNING("Selection start point couldn't have text nodes");
 | 
						|
        return Err(NS_ERROR_FAILURE);
 | 
						|
      }
 | 
						|
      pointToInsert.Set(pointToInsert.ContainerAs<nsIContent>());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (aEditSubAction == EditSubAction::eInsertTextComingFromIME) {
 | 
						|
    auto compositionStartPoint =
 | 
						|
        GetFirstIMESelectionStartPoint<EditorDOMPoint>();
 | 
						|
    if (!compositionStartPoint.IsSet()) {
 | 
						|
      compositionStartPoint = pointToInsert;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aInsertionString.IsEmpty()) {
 | 
						|
      // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
 | 
						|
      // but IME needs the InsertTextWithTransaction() call to still happen
 | 
						|
      // since empty strings are meaningful there.
 | 
						|
      Result<InsertTextResult, nsresult> insertTextResult =
 | 
						|
          InsertTextWithTransaction(*document, aInsertionString,
 | 
						|
                                    compositionStartPoint,
 | 
						|
                                    InsertTextTo::ExistingTextNodeIfAvailable);
 | 
						|
      if (MOZ_UNLIKELY(insertTextResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
 | 
						|
        return insertTextResult.propagateErr();
 | 
						|
      }
 | 
						|
      nsresult rv = insertTextResult.unwrap().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                  SuggestCaret::AndIgnoreTrivialError});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
 | 
						|
    auto compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>();
 | 
						|
    if (!compositionEndPoint.IsSet()) {
 | 
						|
      compositionEndPoint = compositionStartPoint;
 | 
						|
    }
 | 
						|
    Result<InsertTextResult, nsresult> replaceTextResult =
 | 
						|
        WhiteSpaceVisibilityKeeper::ReplaceText(
 | 
						|
            *this, aInsertionString,
 | 
						|
            EditorDOMRange(compositionStartPoint, compositionEndPoint),
 | 
						|
            InsertTextTo::ExistingTextNodeIfAvailable, *editingHost);
 | 
						|
    if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
 | 
						|
      NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
 | 
						|
      return replaceTextResult.propagateErr();
 | 
						|
    }
 | 
						|
    // CompositionTransaction should've set selection so that we should ignore
 | 
						|
    // caret suggestion.
 | 
						|
    replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
    compositionStartPoint = GetFirstIMESelectionStartPoint<EditorDOMPoint>();
 | 
						|
    compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!compositionStartPoint.IsSet()) ||
 | 
						|
        NS_WARN_IF(!compositionEndPoint.IsSet())) {
 | 
						|
      // Mutation event listener has changed the DOM tree...
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
    nsresult rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
 | 
						|
        compositionStartPoint.ToRawRangeBoundary(),
 | 
						|
        compositionEndPoint.ToRawRangeBoundary());
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("nsRange::SetStartAndEnd() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
 | 
						|
 | 
						|
  // find where we are
 | 
						|
  EditorDOMPoint currentPoint(pointToInsert);
 | 
						|
 | 
						|
  // is our text going to be PREformatted?
 | 
						|
  // We remember this so that we know how to handle tabs.
 | 
						|
  const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
 | 
						|
      *pointToInsert.ContainerAs<nsIContent>());
 | 
						|
 | 
						|
  // turn off the edit listener: we know how to
 | 
						|
  // build the "doc changed range" ourselves, and it's
 | 
						|
  // must faster to do it once here than to track all
 | 
						|
  // the changes one at a time.
 | 
						|
  AutoRestore<bool> disableListener(
 | 
						|
      EditSubActionDataRef().mAdjustChangedRangeFromListener);
 | 
						|
  EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
 | 
						|
 | 
						|
  // don't change my selection in subtransactions
 | 
						|
  AutoTransactionsConserveSelection dontChangeMySelection(*this);
 | 
						|
  {
 | 
						|
    AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
 | 
						|
 | 
						|
    const auto GetInsertTextTo = [](int32_t aInclusiveNextLinefeedOffset,
 | 
						|
                                    uint32_t aLineStartOffset) {
 | 
						|
      if (aInclusiveNextLinefeedOffset > 0) {
 | 
						|
        return aLineStartOffset > 0
 | 
						|
                   // If we'll insert a <br> and we're inserting 2nd or later
 | 
						|
                   // line, we should always create new `Text` since it'll be
 | 
						|
                   // between 2 <br> elements.
 | 
						|
                   ? InsertTextTo::AlwaysCreateNewTextNode
 | 
						|
                   // If we'll insert a <br> and we're inserting first line,
 | 
						|
                   // we should append text to preceding text node, but
 | 
						|
                   // we don't want to insert it to a a following text node
 | 
						|
                   // because of avoiding to split the `Text`.
 | 
						|
                   : InsertTextTo::ExistingTextNodeIfAvailableAndNotStart;
 | 
						|
      }
 | 
						|
      // If we're inserting the last line, the text should be inserted to
 | 
						|
      // start of the following `Text` if there is or middle of the `Text`
 | 
						|
      // at insertion position if we're inserting only the line.
 | 
						|
      return InsertTextTo::ExistingTextNodeIfAvailable;
 | 
						|
    };
 | 
						|
 | 
						|
    // for efficiency, break out the pre case separately.  This is because
 | 
						|
    // its a lot cheaper to search the input string for only newlines than
 | 
						|
    // it is to search for both tabs and newlines.
 | 
						|
    if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
 | 
						|
      uint32_t nextOffset = 0;
 | 
						|
      while (nextOffset < aInsertionString.Length()) {
 | 
						|
        const uint32_t lineStartOffset = nextOffset;
 | 
						|
        const int32_t inclusiveNextLinefeedOffset =
 | 
						|
            aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
 | 
						|
        const uint32_t lineLength =
 | 
						|
            inclusiveNextLinefeedOffset != -1
 | 
						|
                ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
 | 
						|
                      lineStartOffset
 | 
						|
                : aInsertionString.Length() - lineStartOffset;
 | 
						|
        if (lineLength) {
 | 
						|
          // lineText does not include the preformatted line break.
 | 
						|
          const nsDependentSubstring lineText(aInsertionString, lineStartOffset,
 | 
						|
                                              lineLength);
 | 
						|
          Result<InsertTextResult, nsresult> insertTextResult =
 | 
						|
              InsertTextWithTransaction(
 | 
						|
                  *document, lineText, currentPoint,
 | 
						|
                  GetInsertTextTo(inclusiveNextLinefeedOffset,
 | 
						|
                                  lineStartOffset));
 | 
						|
          if (MOZ_UNLIKELY(insertTextResult.isErr())) {
 | 
						|
            NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
 | 
						|
            return insertTextResult.propagateErr();
 | 
						|
          }
 | 
						|
          // Ignore the caret suggestion because of `dontChangeMySelection`
 | 
						|
          // above.
 | 
						|
          insertTextResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
          if (insertTextResult.inspect().Handled()) {
 | 
						|
            pointToInsert = currentPoint = insertTextResult.unwrap()
 | 
						|
                                               .EndOfInsertedTextRef()
 | 
						|
                                               .To<EditorDOMPoint>();
 | 
						|
          } else {
 | 
						|
            pointToInsert = currentPoint;
 | 
						|
          }
 | 
						|
          if (inclusiveNextLinefeedOffset < 0) {
 | 
						|
            break;  // We reached the last line
 | 
						|
          }
 | 
						|
        }
 | 
						|
        MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
 | 
						|
        Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
            InsertBRElement(WithTransaction::Yes, currentPoint);
 | 
						|
        if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
          return insertBRElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
            insertBRElementResult.unwrap();
 | 
						|
        // We don't want to update selection here because we've blocked
 | 
						|
        // InsertNodeTransaction updating selection with
 | 
						|
        // dontChangeMySelection.
 | 
						|
        unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
 | 
						|
        MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
 | 
						|
 | 
						|
        nextOffset = inclusiveNextLinefeedOffset + 1;
 | 
						|
        RefPtr<Element> brElement =
 | 
						|
            unwrappedInsertBRElementResult.UnwrapNewNode();
 | 
						|
        if (brElement->GetNextSibling()) {
 | 
						|
          pointToInsert.Set(brElement->GetNextSibling());
 | 
						|
        } else {
 | 
						|
          pointToInsert.SetToEndOf(currentPoint.GetContainer());
 | 
						|
        }
 | 
						|
        // XXX In most cases, pointToInsert and currentPoint are same here.
 | 
						|
        //     But if the <br> element has been moved to different point by
 | 
						|
        //     mutation observer, those points become different.
 | 
						|
        currentPoint.SetAfter(brElement);
 | 
						|
        NS_WARNING_ASSERTION(currentPoint.IsSet(),
 | 
						|
                             "Failed to set after the <br> element");
 | 
						|
        NS_WARNING_ASSERTION(currentPoint == pointToInsert,
 | 
						|
                             "Perhaps, <br> element position has been moved to "
 | 
						|
                             "different point by mutation observer");
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      uint32_t nextOffset = 0;
 | 
						|
      while (nextOffset < aInsertionString.Length()) {
 | 
						|
        const uint32_t lineStartOffset = nextOffset;
 | 
						|
        const int32_t inclusiveNextLinefeedOffset =
 | 
						|
            aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
 | 
						|
        const uint32_t lineLength =
 | 
						|
            inclusiveNextLinefeedOffset != -1
 | 
						|
                ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
 | 
						|
                      lineStartOffset
 | 
						|
                : aInsertionString.Length() - lineStartOffset;
 | 
						|
 | 
						|
        if (lineLength) {
 | 
						|
          auto insertTextResult =
 | 
						|
              [&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> {
 | 
						|
            // lineText does not include the preformatted line break.
 | 
						|
            const nsDependentSubstring lineText(aInsertionString,
 | 
						|
                                                lineStartOffset, lineLength);
 | 
						|
            if (!lineText.Contains(u'\t')) {
 | 
						|
              return WhiteSpaceVisibilityKeeper::InsertText(
 | 
						|
                  *this, lineText, currentPoint,
 | 
						|
                  GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
 | 
						|
                  *editingHost);
 | 
						|
            }
 | 
						|
            nsAutoString formattedLineText(lineText);
 | 
						|
            formattedLineText.ReplaceSubstring(u"\t"_ns, u"    "_ns);
 | 
						|
            return WhiteSpaceVisibilityKeeper::InsertText(
 | 
						|
                *this, formattedLineText, currentPoint,
 | 
						|
                GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
 | 
						|
                *editingHost);
 | 
						|
          }();
 | 
						|
          if (MOZ_UNLIKELY(insertTextResult.isErr())) {
 | 
						|
            NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
 | 
						|
            return insertTextResult.propagateErr();
 | 
						|
          }
 | 
						|
          // Ignore the caret suggestion because of `dontChangeMySelection`
 | 
						|
          // above.
 | 
						|
          insertTextResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
          if (insertTextResult.inspect().Handled()) {
 | 
						|
            pointToInsert = currentPoint = insertTextResult.unwrap()
 | 
						|
                                               .EndOfInsertedTextRef()
 | 
						|
                                               .To<EditorDOMPoint>();
 | 
						|
          } else {
 | 
						|
            pointToInsert = currentPoint;
 | 
						|
          }
 | 
						|
          if (inclusiveNextLinefeedOffset < 0) {
 | 
						|
            break;  // We reached the last line
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
            WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint,
 | 
						|
                                                        *editingHost);
 | 
						|
        if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
          NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
 | 
						|
          return insertBRElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
            insertBRElementResult.unwrap();
 | 
						|
        // TODO: Some methods called for handling non-preformatted text use
 | 
						|
        //       ComputeEditingHost().  Therefore, they depend on the latest
 | 
						|
        //       selection.  So we cannot skip updating selection here.
 | 
						|
        nsresult rv = unwrappedInsertBRElementResult.SuggestCaretPointTo(
 | 
						|
            *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                    SuggestCaret::AndIgnoreTrivialError});
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
        NS_WARNING_ASSERTION(
 | 
						|
            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
            "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
        nextOffset = inclusiveNextLinefeedOffset + 1;
 | 
						|
        RefPtr<Element> newBRElement =
 | 
						|
            unwrappedInsertBRElementResult.UnwrapNewNode();
 | 
						|
        MOZ_DIAGNOSTIC_ASSERT(newBRElement);
 | 
						|
        if (newBRElement->GetNextSibling()) {
 | 
						|
          pointToInsert.Set(newBRElement->GetNextSibling());
 | 
						|
        } else {
 | 
						|
          pointToInsert.SetToEndOf(currentPoint.GetContainer());
 | 
						|
        }
 | 
						|
        currentPoint.SetAfter(newBRElement);
 | 
						|
        NS_WARNING_ASSERTION(currentPoint.IsSet(),
 | 
						|
                             "Failed to set after the new <br> element");
 | 
						|
        // XXX If the newBRElement has been moved or removed by mutation
 | 
						|
        //     observer, we hit this assert.  We need to check if
 | 
						|
        //     newBRElement is in expected point, though, we must have
 | 
						|
        //     a lot of same bugs...
 | 
						|
        NS_WARNING_ASSERTION(
 | 
						|
            currentPoint == pointToInsert,
 | 
						|
            "Perhaps, newBRElement has been moved or removed unexpectedly");
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // After this block, pointToInsert is updated by AutoTrackDOMPoint.
 | 
						|
  }
 | 
						|
 | 
						|
  if (currentPoint.IsSet()) {
 | 
						|
    currentPoint.SetInterlinePosition(InterlinePosition::EndOfLine);
 | 
						|
    nsresult rv = CollapseSelectionTo(currentPoint);
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "Selection::Collapse() failed, but ignored");
 | 
						|
 | 
						|
    // manually update the doc changed range so that AfterEdit will clean up
 | 
						|
    // the correct portion of the document.
 | 
						|
    rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
 | 
						|
        pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary());
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("nsRange::SetStartAndEnd() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  DebugOnly<nsresult> rvIgnored =
 | 
						|
      SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | 
						|
                       "Selection::SetInterlinePosition(InterlinePosition::"
 | 
						|
                       "EndOfLine) failed, but ignored");
 | 
						|
  rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("nsRange::CollapseTo() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::InsertLineBreakAsSubAction() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!mInitSucceeded)) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    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;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX This may be called by execCommand() with "insertLineBreak".
 | 
						|
  //     In such case, naming the transaction "TypingTxnName" is odd.
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
 | 
						|
                                             ScrollSelectionIntoView::Yes,
 | 
						|
                                             __FUNCTION__);
 | 
						|
 | 
						|
  // calling it text insertion to trigger moz br treatment by rules
 | 
						|
  // XXX Why do we use EditSubAction::eInsertText here?  Looks like
 | 
						|
  //     EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
 | 
						|
  //     is better.
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eInsertText, 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");
 | 
						|
 | 
						|
  UndefineCaretBidiLevel();
 | 
						|
 | 
						|
  // If the selection isn't collapsed, delete it.
 | 
						|
  if (!SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv =
 | 
						|
        DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const nsRange* firstRange = SelectionRef().GetRangeAt(0);
 | 
						|
  if (NS_WARN_IF(!firstRange)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint atStartOfSelection(firstRange->StartRef());
 | 
						|
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
 | 
						|
 | 
						|
  RefPtr<Element> editingHost = ComputeEditingHost();
 | 
						|
  if (NS_WARN_IF(!editingHost)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // For backward compatibility, we should not insert a linefeed if
 | 
						|
  // paragraph separator is set to "br" which is Gecko-specific mode.
 | 
						|
  if (GetDefaultParagraphSeparator() == ParagraphSeparator::br ||
 | 
						|
      !HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection,
 | 
						|
                                                    *editingHost)) {
 | 
						|
    Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
        InsertBRElement(WithTransaction::Yes, atStartOfSelection,
 | 
						|
                        nsIEditor::eNext);
 | 
						|
    if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
      return insertBRElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
        insertBRElementResult.unwrap();
 | 
						|
    MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
    unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
    auto pointToPutCaret =
 | 
						|
        EditorDOMPoint::After(*unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
    if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) {
 | 
						|
      NS_WARNING("Inserted <br> was unexpectedly removed");
 | 
						|
      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
    }
 | 
						|
    const WSScanResult backwardScanFromBeforeBRElementResult =
 | 
						|
        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
 | 
						|
            editingHost,
 | 
						|
            EditorDOMPoint(unwrappedInsertBRElementResult.GetNewNode()),
 | 
						|
            BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
    if (MOZ_UNLIKELY(backwardScanFromBeforeBRElementResult.Failed())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() failed");
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
 | 
						|
    const WSScanResult forwardScanFromAfterBRElementResult =
 | 
						|
        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
 | 
						|
            editingHost, pointToPutCaret,
 | 
						|
            BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
    if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
 | 
						|
      NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    const bool brElementIsAfterBlock =
 | 
						|
        backwardScanFromBeforeBRElementResult.ReachedBlockBoundary() ||
 | 
						|
        // FIXME: This is wrong considering because the inline editing host may
 | 
						|
        // be surrounded by visible inline content.  However, WSRunScanner is
 | 
						|
        // not aware of block boundary around it and stopping this change causes
 | 
						|
        // starting to fail some WPT.  Therefore, we need to keep doing this for
 | 
						|
        // now.
 | 
						|
        backwardScanFromBeforeBRElementResult
 | 
						|
            .ReachedInlineEditingHostBoundary();
 | 
						|
    const bool brElementIsBeforeBlock =
 | 
						|
        forwardScanFromAfterBRElementResult.ReachedBlockBoundary() ||
 | 
						|
        // FIXME: See above comment.
 | 
						|
        forwardScanFromAfterBRElementResult.ReachedInlineEditingHostBoundary();
 | 
						|
    const bool isEmptyEditingHost = HTMLEditUtils::IsEmptyNode(
 | 
						|
        *editingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
 | 
						|
    if (brElementIsBeforeBlock &&
 | 
						|
        (isEmptyEditingHost || !brElementIsAfterBlock)) {
 | 
						|
      // Empty last line is invisible if it's immediately before either parent
 | 
						|
      // or another block's boundary so that we need to put invisible <br>
 | 
						|
      // element here for making it visible.
 | 
						|
      Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
 | 
						|
          WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToPutCaret,
 | 
						|
                                                      *editingHost);
 | 
						|
      if (MOZ_UNLIKELY(invisibleAdditionalBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
 | 
						|
        return invisibleAdditionalBRElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedInvisibleAdditionalBRElement =
 | 
						|
          invisibleAdditionalBRElementResult.unwrap();
 | 
						|
      pointToPutCaret.Set(unwrappedInvisibleAdditionalBRElement.GetNewNode());
 | 
						|
      unwrappedInvisibleAdditionalBRElement.IgnoreCaretPointSuggestion();
 | 
						|
    } else if (forwardScanFromAfterBRElementResult
 | 
						|
                   .InVisibleOrCollapsibleCharacters()) {
 | 
						|
      pointToPutCaret = forwardScanFromAfterBRElementResult
 | 
						|
                            .PointAtReachedContent<EditorDOMPoint>();
 | 
						|
    } else if (forwardScanFromAfterBRElementResult.ReachedSpecialContent()) {
 | 
						|
      // Next inserting text should be inserted into styled inline elements if
 | 
						|
      // they have first visible thing in the new line.
 | 
						|
      pointToPutCaret = forwardScanFromAfterBRElementResult
 | 
						|
                            .PointAtReachedContent<EditorDOMPoint>();
 | 
						|
    }
 | 
						|
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  firstRange = SelectionRef().GetRangeAt(0);
 | 
						|
  if (NS_WARN_IF(!firstRange)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  atStartOfSelection = EditorDOMPoint(firstRange->StartRef());
 | 
						|
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
 | 
						|
 | 
						|
  // Do nothing if the node is read-only
 | 
						|
  if (!HTMLEditUtils::IsSimplyEditableNode(
 | 
						|
          *atStartOfSelection.GetContainer())) {
 | 
						|
    return NS_SUCCESS_DOM_NO_OPERATION;
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> insertLineFeedResult =
 | 
						|
      HandleInsertLinefeed(atStartOfSelection, *editingHost);
 | 
						|
  if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
 | 
						|
    return insertLineFeedResult.unwrapErr();
 | 
						|
  }
 | 
						|
  rv = CollapseSelectionTo(insertLineFeedResult.inspect());
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::CollapseSelectionTo() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult>
 | 
						|
HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) {
 | 
						|
  if (NS_WARN_IF(!mInitSucceeded)) {
 | 
						|
    return Err(NS_ERROR_NOT_INITIALIZED);
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(
 | 
						|
        CheckSelectionInReplacedElement::OnlyWhenNotInSameNode);
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX This may be called by execCommand() with "insertParagraph".
 | 
						|
  //     In such case, naming the transaction "TypingTxnName" is odd.
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
 | 
						|
                                             ScrollSelectionIntoView::Yes,
 | 
						|
                                             __FUNCTION__);
 | 
						|
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext,
 | 
						|
      ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(ignoredError.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  UndefineCaretBidiLevel();
 | 
						|
 | 
						|
  // If the selection isn't collapsed, delete it.
 | 
						|
  if (!SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv =
 | 
						|
        DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  AutoRangeArray selectionRanges(SelectionRef());
 | 
						|
  {
 | 
						|
    // If the editing host is the body element, the selection may be outside
 | 
						|
    // aEditingHost.  In the case, we should use the editing host outside the
 | 
						|
    // <body> only here for keeping our traditional behavior for now.
 | 
						|
    // This should be fixed in bug 1634351.
 | 
						|
    const Element* editingHostMaybeOutsideBody = &aEditingHost;
 | 
						|
    if (aEditingHost.IsHTMLElement(nsGkAtoms::body)) {
 | 
						|
      editingHostMaybeOutsideBody = ComputeEditingHost(LimitInBodyElement::No);
 | 
						|
      if (NS_WARN_IF(!editingHostMaybeOutsideBody)) {
 | 
						|
        return Err(NS_ERROR_FAILURE);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    selectionRanges.EnsureOnlyEditableRanges(*editingHostMaybeOutsideBody);
 | 
						|
    if (NS_WARN_IF(selectionRanges.Ranges().IsEmpty())) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  auto pointToInsert =
 | 
						|
      selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
  if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  while (true) {
 | 
						|
    Element* element = pointToInsert.GetContainerOrContainerParentElement();
 | 
						|
    if (MOZ_UNLIKELY(!element)) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    // If the element can have a <br> element (it means that the element or its
 | 
						|
    // container must be able to have <div> or <p> too), we can handle
 | 
						|
    // insertParagraph at the point.
 | 
						|
    if (HTMLEditUtils::CanNodeContain(*element, *nsGkAtoms::br)) {
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    // Otherwise, try to insert paragraph at the parent.
 | 
						|
    pointToInsert = pointToInsert.ParentPoint();
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsMailEditor()) {
 | 
						|
    if (RefPtr<Element> mailCiteElement = GetMostDistantAncestorMailCiteElement(
 | 
						|
            *pointToInsert.ContainerAs<nsIContent>())) {
 | 
						|
      // Split any mailcites in the way.  Should we abort this if we encounter
 | 
						|
      // table cell boundaries?
 | 
						|
      Result<EditorDOMPoint, nsresult> atNewBRElementOrError =
 | 
						|
          HandleInsertParagraphInMailCiteElement(*mailCiteElement,
 | 
						|
                                                 pointToInsert, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(atNewBRElementOrError.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::HandleInsertParagraphInMailCiteElement() failed");
 | 
						|
        return atNewBRElementOrError.propagateErr();
 | 
						|
      }
 | 
						|
      EditorDOMPoint pointToPutCaret = atNewBRElementOrError.unwrap();
 | 
						|
      MOZ_ASSERT(pointToPutCaret.IsSet());
 | 
						|
      pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
 | 
						|
      MOZ_ASSERT(pointToPutCaret.GetChild());
 | 
						|
      MOZ_ASSERT(pointToPutCaret.GetChild()->IsHTMLElement(nsGkAtoms::br));
 | 
						|
      nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If the active editing host is an inline element, or if the active editing
 | 
						|
  // host is the block parent itself and we're configured to use <br> as a
 | 
						|
  // paragraph separator, just append a <br>.
 | 
						|
  // If the editing host parent element is editable, it means that the editing
 | 
						|
  // host must be a <body> element and the selection may be outside the body
 | 
						|
  // element.  If the selection is outside the editing host, we should not
 | 
						|
  // insert new paragraph nor <br> element.
 | 
						|
  // XXX Currently, we don't support editing outside <body> element, but Blink
 | 
						|
  //     does it.
 | 
						|
  if (aEditingHost.GetParentElement() &&
 | 
						|
      HTMLEditUtils::IsSimplyEditableNode(*aEditingHost.GetParentElement()) &&
 | 
						|
      !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
 | 
						|
          pointToInsert.ContainerAs<nsIContent>(), &aEditingHost)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
 | 
						|
  }
 | 
						|
 | 
						|
  auto InsertLineBreakInstead =
 | 
						|
      [](const Element* aEditableBlockElement,
 | 
						|
         const EditorDOMPoint& aCandidatePointToSplit,
 | 
						|
         ParagraphSeparator aDefaultParagraphSeparator,
 | 
						|
         const Element& aEditingHost) {
 | 
						|
        // If there is no block parent in the editing host, i.e., the editing
 | 
						|
        // host itself is also a non-block element, we should insert a line
 | 
						|
        // break.
 | 
						|
        if (!aEditableBlockElement) {
 | 
						|
          // XXX Chromium checks if the CSS box of the editing host is a block.
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
 | 
						|
        // If the editable block element is not splittable, e.g., it's an
 | 
						|
        // editing host, and the default paragraph separator is <br> or the
 | 
						|
        // element cannot contain a <p> element, we should insert a <br>
 | 
						|
        // element.
 | 
						|
        if (!HTMLEditUtils::IsSplittableNode(*aEditableBlockElement)) {
 | 
						|
          return aDefaultParagraphSeparator == ParagraphSeparator::br ||
 | 
						|
                 !HTMLEditUtils::CanElementContainParagraph(
 | 
						|
                     *aEditableBlockElement) ||
 | 
						|
                 (HTMLEditUtils::ShouldInsertLinefeedCharacter(
 | 
						|
                      aCandidatePointToSplit, aEditingHost) &&
 | 
						|
                  HTMLEditUtils::IsDisplayOutsideInline(aEditingHost));
 | 
						|
        }
 | 
						|
 | 
						|
        // If the nearest block parent is a single-line container declared in
 | 
						|
        // the execCommand spec and not the editing host, we should separate the
 | 
						|
        // block even if the default paragraph separator is <br> element.
 | 
						|
        if (HTMLEditUtils::IsSingleLineContainer(*aEditableBlockElement)) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // Otherwise, unless there is no block ancestor which can contain <p>
 | 
						|
        // element, we shouldn't insert a line break here.
 | 
						|
        for (const Element* editableBlockAncestor = aEditableBlockElement;
 | 
						|
             editableBlockAncestor;
 | 
						|
             editableBlockAncestor = HTMLEditUtils::GetAncestorElement(
 | 
						|
                 *editableBlockAncestor,
 | 
						|
                 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
 | 
						|
                 BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
 | 
						|
          if (HTMLEditUtils::CanElementContainParagraph(
 | 
						|
                  *editableBlockAncestor)) {
 | 
						|
            return false;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
      };
 | 
						|
 | 
						|
  // Look for the nearest parent block.  However, don't return error even if
 | 
						|
  // there is no block parent here because in such case, i.e., editing host
 | 
						|
  // is an inline element, we should insert <br> simply.
 | 
						|
  RefPtr<Element> editableBlockElement =
 | 
						|
      HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
          *pointToInsert.ContainerAs<nsIContent>(),
 | 
						|
          HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
 | 
						|
          BlockInlineCheck::UseComputedDisplayOutsideStyle);
 | 
						|
 | 
						|
  // If we cannot insert a <p>/<div> element at the selection, we should insert
 | 
						|
  // a <br> element or a linefeed instead.
 | 
						|
  const ParagraphSeparator separator = GetDefaultParagraphSeparator();
 | 
						|
  if (InsertLineBreakInstead(editableBlockElement, pointToInsert, separator,
 | 
						|
                             aEditingHost)) {
 | 
						|
    // For backward compatibility, we should not insert a linefeed if
 | 
						|
    // paragraph separator is set to "br" which is Gecko-specific mode.
 | 
						|
    if (separator != ParagraphSeparator::br &&
 | 
						|
        HTMLEditUtils::ShouldInsertLinefeedCharacter(pointToInsert,
 | 
						|
                                                     aEditingHost)) {
 | 
						|
      Result<EditorDOMPoint, nsresult> insertLineFeedResult =
 | 
						|
          HandleInsertLinefeed(pointToInsert, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
 | 
						|
        return insertLineFeedResult.propagateErr();
 | 
						|
      }
 | 
						|
      nsresult rv = CollapseSelectionTo(insertLineFeedResult.inspect());
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
 | 
						|
    Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
        HandleInsertBRElement(pointToInsert, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
 | 
						|
      return insertBRElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    nsresult rv =
 | 
						|
        insertBRElementResult.inspect().SuggestCaretPointTo(*this, {});
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  // If somebody wants to restrict caret position in a block element below,
 | 
						|
  // we should guarantee it.  Otherwise, we can put caret to the candidate
 | 
						|
  // point.
 | 
						|
  auto CollapseSelection =
 | 
						|
      [this](const EditorDOMPoint& aCandidatePointToPutCaret,
 | 
						|
             const Element* aBlockElementShouldHaveCaret,
 | 
						|
             const SuggestCaretOptions& aOptions)
 | 
						|
          MOZ_CAN_RUN_SCRIPT -> nsresult {
 | 
						|
    if (!aCandidatePointToPutCaret.IsSet()) {
 | 
						|
      if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) {
 | 
						|
        return NS_OK;
 | 
						|
      }
 | 
						|
      return aOptions.contains(SuggestCaret::AndIgnoreTrivialError)
 | 
						|
                 ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
 | 
						|
                 : NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    EditorDOMPoint pointToPutCaret(aCandidatePointToPutCaret);
 | 
						|
    if (aBlockElementShouldHaveCaret) {
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
 | 
						|
              EditorDOMPoint>(*aBlockElementShouldHaveCaret,
 | 
						|
                              aCandidatePointToPutCaret);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
 | 
						|
            "failed, but ignored");
 | 
						|
      } else if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
    }
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
    if (NS_FAILED(rv) && MOZ_LIKELY(rv != NS_ERROR_EDITOR_DESTROYED) &&
 | 
						|
        aOptions.contains(SuggestCaret::AndIgnoreTrivialError)) {
 | 
						|
      rv = NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR;
 | 
						|
    }
 | 
						|
    return rv;
 | 
						|
  };
 | 
						|
 | 
						|
  RefPtr<Element> blockElementToPutCaret;
 | 
						|
  // If the default paragraph separator is not <br> and selection is not in
 | 
						|
  // a splittable block element, we should wrap selected contents in a new
 | 
						|
  // paragraph, then, split it.
 | 
						|
  if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement) &&
 | 
						|
      separator != ParagraphSeparator::br) {
 | 
						|
    MOZ_ASSERT(separator == ParagraphSeparator::div ||
 | 
						|
               separator == ParagraphSeparator::p);
 | 
						|
    // FIXME: If there is no splittable block element, the other browsers wrap
 | 
						|
    // the right nodes into new paragraph, but keep the left node as-is.
 | 
						|
    // We should follow them to make here simpler and better compatibility.
 | 
						|
    Result<RefPtr<Element>, nsresult> suggestBlockElementToPutCaretOrError =
 | 
						|
        FormatBlockContainerWithTransaction(
 | 
						|
            selectionRanges,
 | 
						|
            MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName(separator)),
 | 
						|
            // For keeping the traditional behavior at insertParagraph command,
 | 
						|
            // let's use the XUL paragraph state command targets even if we're
 | 
						|
            // handling HTML insertParagraph command.
 | 
						|
            FormatBlockMode::XULParagraphStateCommand, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(suggestBlockElementToPutCaretOrError.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed");
 | 
						|
      return suggestBlockElementToPutCaretOrError.propagateErr();
 | 
						|
    }
 | 
						|
    if (selectionRanges.HasSavedRanges()) {
 | 
						|
      selectionRanges.RestoreFromSavedRanges();
 | 
						|
    }
 | 
						|
    pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
    }
 | 
						|
    MOZ_ASSERT(pointToInsert.IsSetAndValid());
 | 
						|
    blockElementToPutCaret = suggestBlockElementToPutCaretOrError.unwrap();
 | 
						|
 | 
						|
    editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
        *pointToInsert.ContainerAs<nsIContent>(),
 | 
						|
        HTMLEditUtils::ClosestEditableBlockElementOrButtonElement,
 | 
						|
        BlockInlineCheck::UseComputedDisplayOutsideStyle);
 | 
						|
    if (NS_WARN_IF(!editableBlockElement)) {
 | 
						|
      return Err(NS_ERROR_UNEXPECTED);
 | 
						|
    }
 | 
						|
    if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*editableBlockElement))) {
 | 
						|
      // Didn't create a new block for some reason, fall back to <br>
 | 
						|
      Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
          HandleInsertBRElement(pointToInsert, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
 | 
						|
        return insertBRElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
          insertBRElementResult.unwrap();
 | 
						|
      EditorDOMPoint pointToPutCaret =
 | 
						|
          unwrappedInsertBRElementResult.UnwrapCaretPoint();
 | 
						|
      if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::HandleInsertBRElement() didn't suggest a point to put "
 | 
						|
            "caret");
 | 
						|
        return Err(NS_ERROR_FAILURE);
 | 
						|
      }
 | 
						|
      nsresult rv =
 | 
						|
          CollapseSelection(pointToPutCaret, blockElementToPutCaret, {});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("CollapseSelection() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
    // We want to collapse selection in the editable block element.
 | 
						|
    blockElementToPutCaret = editableBlockElement;
 | 
						|
  }
 | 
						|
 | 
						|
  // If block is empty, populate with br.  (For example, imagine a div that
 | 
						|
  // contains the word "text".  The user selects "text" and types return.
 | 
						|
  // "Text" is deleted leaving an empty block.  We want to put in one br to
 | 
						|
  // make block have a line.  Then code further below will put in a second br.)
 | 
						|
  RefPtr<Element> insertedPaddingBRElement;
 | 
						|
  if (HTMLEditUtils::IsEmptyBlockElement(
 | 
						|
          *editableBlockElement,
 | 
						|
          {EmptyCheckOption::TreatSingleBRElementAsVisible},
 | 
						|
          BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
 | 
						|
    Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
        InsertBRElement(WithTransaction::Yes,
 | 
						|
                        EditorDOMPoint::AtEndOf(*editableBlockElement));
 | 
						|
    if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
      return insertBRElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
        insertBRElementResult.unwrap();
 | 
						|
    unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
    insertedPaddingBRElement = unwrappedInsertBRElementResult.UnwrapNewNode();
 | 
						|
 | 
						|
    pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Element> maybeNonEditableListItem =
 | 
						|
      HTMLEditUtils::GetClosestAncestorListItemElement(*editableBlockElement,
 | 
						|
                                                       &aEditingHost);
 | 
						|
  if (maybeNonEditableListItem &&
 | 
						|
      HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem)) {
 | 
						|
    Result<InsertParagraphResult, nsresult> insertParagraphInListItemResult =
 | 
						|
        HandleInsertParagraphInListItemElement(*maybeNonEditableListItem,
 | 
						|
                                               pointToInsert, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(insertParagraphInListItemResult.isErr())) {
 | 
						|
      if (NS_WARN_IF(insertParagraphInListItemResult.unwrapErr() ==
 | 
						|
                     NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::HandleInsertParagraphInListItemElement() failed, but "
 | 
						|
          "ignored");
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
    InsertParagraphResult unwrappedInsertParagraphInListItemResult =
 | 
						|
        insertParagraphInListItemResult.unwrap();
 | 
						|
    MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.Handled());
 | 
						|
    MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.GetNewNode());
 | 
						|
    const RefPtr<Element> listItemOrParagraphElement =
 | 
						|
        unwrappedInsertParagraphInListItemResult.UnwrapNewNode();
 | 
						|
    const EditorDOMPoint pointToPutCaret =
 | 
						|
        unwrappedInsertParagraphInListItemResult.UnwrapCaretPoint();
 | 
						|
    nsresult rv = CollapseSelection(pointToPutCaret, listItemOrParagraphElement,
 | 
						|
                                    {SuggestCaret::AndIgnoreTrivialError});
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("CollapseSelection() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
                         "CollapseSelection() failed, but ignored");
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsHeader(*editableBlockElement)) {
 | 
						|
    Result<InsertParagraphResult, nsresult>
 | 
						|
        insertParagraphInHeadingElementResult =
 | 
						|
            HandleInsertParagraphInHeadingElement(*editableBlockElement,
 | 
						|
                                                  pointToInsert);
 | 
						|
    if (MOZ_UNLIKELY(insertParagraphInHeadingElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::HandleInsertParagraphInHeadingElement() failed, but "
 | 
						|
          "ignored");
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
    InsertParagraphResult unwrappedInsertParagraphInHeadingElementResult =
 | 
						|
        insertParagraphInHeadingElementResult.unwrap();
 | 
						|
    if (unwrappedInsertParagraphInHeadingElementResult.Handled()) {
 | 
						|
      MOZ_ASSERT(unwrappedInsertParagraphInHeadingElementResult.GetNewNode());
 | 
						|
      blockElementToPutCaret =
 | 
						|
          unwrappedInsertParagraphInHeadingElementResult.UnwrapNewNode();
 | 
						|
    }
 | 
						|
    const EditorDOMPoint pointToPutCaret =
 | 
						|
        unwrappedInsertParagraphInHeadingElementResult.UnwrapCaretPoint();
 | 
						|
    nsresult rv = CollapseSelection(pointToPutCaret, blockElementToPutCaret,
 | 
						|
                                    {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                                     SuggestCaret::AndIgnoreTrivialError});
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("CollapseSelection() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
                         "CollapseSelection() failed, but ignored");
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX Ideally, we should take same behavior with both <p> container and
 | 
						|
  //     <div> container.  However, we are still using <br> as default
 | 
						|
  //     paragraph separator (non-standard) and we've split only <p> container
 | 
						|
  //     long time.  Therefore, some web apps may depend on this behavior like
 | 
						|
  //     Gmail.  So, let's use traditional odd behavior only when the default
 | 
						|
  //     paragraph separator is <br>.  Otherwise, take consistent behavior
 | 
						|
  //     between <p> container and <div> container.
 | 
						|
  if ((separator == ParagraphSeparator::br &&
 | 
						|
       editableBlockElement->IsHTMLElement(nsGkAtoms::p)) ||
 | 
						|
      (separator != ParagraphSeparator::br &&
 | 
						|
       editableBlockElement->IsAnyOfHTMLElements(nsGkAtoms::p,
 | 
						|
                                                 nsGkAtoms::div))) {
 | 
						|
    // Paragraphs: special rules to look for <br>s
 | 
						|
    Result<SplitNodeResult, nsresult> splitNodeResult =
 | 
						|
        HandleInsertParagraphInParagraph(
 | 
						|
            *editableBlockElement,
 | 
						|
            insertedPaddingBRElement ? EditorDOMPoint(insertedPaddingBRElement)
 | 
						|
                                     : pointToInsert,
 | 
						|
            aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::HandleInsertParagraphInParagraph() failed");
 | 
						|
      return splitNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    if (splitNodeResult.inspect().Handled()) {
 | 
						|
      SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
 | 
						|
      const RefPtr<Element> rightParagraphElement =
 | 
						|
          unwrappedSplitNodeResult.DidSplit()
 | 
						|
              ? unwrappedSplitNodeResult.GetNextContentAs<Element>()
 | 
						|
              : blockElementToPutCaret.get();
 | 
						|
      const EditorDOMPoint pointToPutCaret =
 | 
						|
          unwrappedSplitNodeResult.UnwrapCaretPoint();
 | 
						|
      nsresult rv = CollapseSelection(pointToPutCaret, rightParagraphElement,
 | 
						|
                                      {SuggestCaret::AndIgnoreTrivialError});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("CollapseSelection() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
                           "CollapseSelection() failed, but ignored");
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
    MOZ_ASSERT(!splitNodeResult.inspect().HasCaretPointSuggestion());
 | 
						|
 | 
						|
    // Fall through, if HandleInsertParagraphInParagraph() didn't handle it.
 | 
						|
    MOZ_ASSERT(pointToInsert.IsSetAndValid(),
 | 
						|
               "HTMLEditor::HandleInsertParagraphInParagraph() shouldn't touch "
 | 
						|
               "the DOM tree if it returns not-handled state");
 | 
						|
  }
 | 
						|
 | 
						|
  // If nobody handles this edit action, let's insert new <br> at the selection.
 | 
						|
  Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
      HandleInsertBRElement(pointToInsert, aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
 | 
						|
    return insertBRElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
      insertBRElementResult.unwrap();
 | 
						|
  EditorDOMPoint pointToPutCaret =
 | 
						|
      unwrappedInsertBRElementResult.UnwrapCaretPoint();
 | 
						|
  rv = CollapseSelection(pointToPutCaret, blockElementToPutCaret, {});
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult> HTMLEditor::HandleInsertBRElement(
 | 
						|
    const EditorDOMPoint& aPointToBreak, const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(aPointToBreak.IsSet());
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  const bool editingHostIsEmpty = HTMLEditUtils::IsEmptyNode(
 | 
						|
      aEditingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible});
 | 
						|
  WSRunScanner wsRunScanner(&aEditingHost, aPointToBreak,
 | 
						|
                            BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  const WSScanResult backwardScanResult =
 | 
						|
      wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPointToBreak);
 | 
						|
  if (MOZ_UNLIKELY(backwardScanResult.Failed())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  const bool brElementIsAfterBlock =
 | 
						|
      backwardScanResult.ReachedBlockBoundary() ||
 | 
						|
      // FIXME: This is wrong considering because the inline editing host may
 | 
						|
      // be surrounded by visible inline content.  However, WSRunScanner is
 | 
						|
      // not aware of block boundary around it and stopping this change causes
 | 
						|
      // starting to fail some WPT.  Therefore, we need to keep doing this for
 | 
						|
      // now.
 | 
						|
      backwardScanResult.ReachedInlineEditingHostBoundary();
 | 
						|
  const WSScanResult forwardScanResult =
 | 
						|
      wsRunScanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
 | 
						|
          aPointToBreak);
 | 
						|
  if (MOZ_UNLIKELY(forwardScanResult.Failed())) {
 | 
						|
    NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  const bool brElementIsBeforeBlock =
 | 
						|
      forwardScanResult.ReachedBlockBoundary() ||
 | 
						|
      // FIXME: See above comment
 | 
						|
      forwardScanResult.ReachedInlineEditingHostBoundary();
 | 
						|
 | 
						|
  // First, insert a <br> element.
 | 
						|
  RefPtr<Element> brElement;
 | 
						|
  if (IsPlaintextMailComposer()) {
 | 
						|
    Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
        InsertBRElement(WithTransaction::Yes, aPointToBreak);
 | 
						|
    if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
      return insertBRElementResult;
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
        insertBRElementResult.unwrap();
 | 
						|
    // We'll return with suggesting new caret position and nobody refers
 | 
						|
    // selection after here.  So we don't need to update selection here.
 | 
						|
    unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
    brElement = unwrappedInsertBRElementResult.UnwrapNewNode();
 | 
						|
  } else {
 | 
						|
    EditorDOMPoint pointToBreak(aPointToBreak);
 | 
						|
    // If the container of the break is a link, we need to split it and
 | 
						|
    // insert new <br> between the split links.
 | 
						|
    RefPtr<Element> linkNode =
 | 
						|
        HTMLEditor::GetLinkElement(pointToBreak.GetContainer());
 | 
						|
    if (linkNode) {
 | 
						|
      Result<SplitNodeResult, nsresult> splitLinkNodeResult =
 | 
						|
          SplitNodeDeepWithTransaction(
 | 
						|
              *linkNode, pointToBreak,
 | 
						|
              SplitAtEdges::eDoNotCreateEmptyContainer);
 | 
						|
      if (MOZ_UNLIKELY(splitLinkNodeResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
            "eDoNotCreateEmptyContainer) failed");
 | 
						|
        return splitLinkNodeResult.propagateErr();
 | 
						|
      }
 | 
						|
      // TODO: Some methods called by
 | 
						|
      //       WhiteSpaceVisibilityKeeper::InsertBRElement() use
 | 
						|
      //       ComputeEditingHost() which depends on selection.  Therefore,
 | 
						|
      //       we cannot skip updating selection here.
 | 
						|
      nsresult rv = splitLinkNodeResult.inspect().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      pointToBreak =
 | 
						|
          splitLinkNodeResult.inspect().AtSplitPoint<EditorDOMPoint>();
 | 
						|
    }
 | 
						|
    Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
        WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToBreak,
 | 
						|
                                                    aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
      NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
 | 
						|
      return insertBRElementResult;
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
        insertBRElementResult.unwrap();
 | 
						|
    // We'll return with suggesting new caret position and nobody refers
 | 
						|
    // selection after here.  So we don't need to update selection here.
 | 
						|
    unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    brElement = unwrappedInsertBRElementResult.UnwrapNewNode();
 | 
						|
    MOZ_ASSERT(brElement);
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(!brElement->GetParentNode())) {
 | 
						|
    NS_WARNING("Inserted <br> element was removed by the web app");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
  auto afterBRElement = EditorDOMPoint::After(brElement);
 | 
						|
 | 
						|
  auto InsertAdditionalInvisibleLineBreak =
 | 
						|
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<CreateElementResult, nsresult> {
 | 
						|
    // Empty last line is invisible if it's immediately before either parent or
 | 
						|
    // another block's boundary so that we need to put invisible <br> element
 | 
						|
    // here for making it visible.
 | 
						|
    Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
 | 
						|
        WhiteSpaceVisibilityKeeper::InsertBRElement(*this, afterBRElement,
 | 
						|
                                                    aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(invisibleAdditionalBRElementResult.isErr())) {
 | 
						|
      NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
 | 
						|
      return invisibleAdditionalBRElementResult;
 | 
						|
    }
 | 
						|
    // afterBRElement points after the first <br> with referring an old child.
 | 
						|
    // Therefore, we need to update it with new child which is the new invisible
 | 
						|
    // <br>.
 | 
						|
    afterBRElement.Set(
 | 
						|
        invisibleAdditionalBRElementResult.inspect().GetNewNode());
 | 
						|
    return invisibleAdditionalBRElementResult;
 | 
						|
  };
 | 
						|
 | 
						|
  if (brElementIsAfterBlock && brElementIsBeforeBlock) {
 | 
						|
    // We just placed a <br> between block boundaries.  This is the one case
 | 
						|
    // where we want the selection to be before the br we just placed, as the
 | 
						|
    // br will be on a new line, rather than at end of prior line.
 | 
						|
    // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
 | 
						|
    //     modifying the DOM tree.  So, now, the <br> element may not be
 | 
						|
    //     between blocks.
 | 
						|
    EditorDOMPoint pointToPutCaret;
 | 
						|
    if (editingHostIsEmpty) {
 | 
						|
      Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
 | 
						|
          InsertAdditionalInvisibleLineBreak();
 | 
						|
      if (invisibleAdditionalBRElementResult.isErr()) {
 | 
						|
        return invisibleAdditionalBRElementResult;
 | 
						|
      }
 | 
						|
      invisibleAdditionalBRElementResult.unwrap().IgnoreCaretPointSuggestion();
 | 
						|
      pointToPutCaret = std::move(afterBRElement);
 | 
						|
    } else {
 | 
						|
      pointToPutCaret =
 | 
						|
          EditorDOMPoint(brElement, InterlinePosition::StartOfNextLine);
 | 
						|
    }
 | 
						|
    return CreateElementResult(std::move(brElement),
 | 
						|
                               std::move(pointToPutCaret));
 | 
						|
  }
 | 
						|
 | 
						|
  const WSScanResult forwardScanFromAfterBRElementResult =
 | 
						|
      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
 | 
						|
          &aEditingHost, afterBRElement,
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) {
 | 
						|
    NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  if (forwardScanFromAfterBRElementResult.ReachedBRElement()) {
 | 
						|
    // The next thing after the break we inserted is another break.  Move the
 | 
						|
    // second break to be the first break's sibling.  This will prevent them
 | 
						|
    // from being in different inline nodes, which would break
 | 
						|
    // SetInterlinePosition().  It will also assure that if the user clicks
 | 
						|
    // away and then clicks back on their new blank line, they will still get
 | 
						|
    // the style from the line above.
 | 
						|
    if (brElement->GetNextSibling() !=
 | 
						|
        forwardScanFromAfterBRElementResult.BRElementPtr()) {
 | 
						|
      MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr());
 | 
						|
      Result<MoveNodeResult, nsresult> moveBRElementResult =
 | 
						|
          MoveNodeWithTransaction(
 | 
						|
              MOZ_KnownLive(
 | 
						|
                  *forwardScanFromAfterBRElementResult.BRElementPtr()),
 | 
						|
              afterBRElement);
 | 
						|
      if (MOZ_UNLIKELY(moveBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
        return moveBRElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      nsresult rv = moveBRElementResult.inspect().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                  SuggestCaret::AndIgnoreTrivialError});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
          "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
    }
 | 
						|
  } else if ((forwardScanFromAfterBRElementResult.ReachedBlockBoundary() ||
 | 
						|
              // FIXME: This is wrong considering because the inline editing
 | 
						|
              // host may be surrounded by visible inline content.  However,
 | 
						|
              // WSRunScanner is not aware of block boundary around it and
 | 
						|
              // stopping this change causes starting to fail some WPT.
 | 
						|
              // Therefore, we need to keep doing this for now.
 | 
						|
              forwardScanFromAfterBRElementResult
 | 
						|
                  .ReachedInlineEditingHostBoundary()) &&
 | 
						|
             !brElementIsAfterBlock) {
 | 
						|
    Result<CreateElementResult, nsresult> invisibleAdditionalBRElementResult =
 | 
						|
        InsertAdditionalInvisibleLineBreak();
 | 
						|
    if (invisibleAdditionalBRElementResult.isErr()) {
 | 
						|
      return invisibleAdditionalBRElementResult;
 | 
						|
    }
 | 
						|
    invisibleAdditionalBRElementResult.unwrap().IgnoreCaretPointSuggestion();
 | 
						|
  }
 | 
						|
 | 
						|
  // We want the caret to stick to whatever is past the break.  This is because
 | 
						|
  // the break is on the same line we were on, but the next content will be on
 | 
						|
  // the following line.
 | 
						|
 | 
						|
  // An exception to this is if the break has a next sibling that is a block
 | 
						|
  // node.  Then we stick to the left to avoid an uber caret.
 | 
						|
  nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling();
 | 
						|
  afterBRElement.SetInterlinePosition(
 | 
						|
      nextSiblingOfBRElement && HTMLEditUtils::IsBlockElement(
 | 
						|
                                    *nextSiblingOfBRElement,
 | 
						|
                                    BlockInlineCheck::UseComputedDisplayStyle)
 | 
						|
          ? InterlinePosition::EndOfLine
 | 
						|
          : InterlinePosition::StartOfNextLine);
 | 
						|
  return CreateElementResult(std::move(brElement), afterBRElement);
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::HandleInsertLinefeed(
 | 
						|
    const EditorDOMPoint& aPointToBreak, const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aPointToBreak.IsSet())) {
 | 
						|
    return Err(NS_ERROR_INVALID_ARG);
 | 
						|
  }
 | 
						|
 | 
						|
  const RefPtr<Document> document = GetDocument();
 | 
						|
  MOZ_DIAGNOSTIC_ASSERT(document);
 | 
						|
  if (NS_WARN_IF(!document)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  // TODO: The following code is duplicated from `HandleInsertText`.  They
 | 
						|
  //       should be merged when we fix bug 92921.
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> setStyleResult =
 | 
						|
      CreateStyleForInsertText(aPointToBreak, aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(setStyleResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
 | 
						|
    return setStyleResult.propagateErr();
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint pointToInsert = setStyleResult.inspect().IsSet()
 | 
						|
                                     ? setStyleResult.inspect()
 | 
						|
                                     : aPointToBreak;
 | 
						|
  if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
 | 
						|
      NS_WARN_IF(!pointToInsert.IsInContentNode())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(pointToInsert.IsSetAndValid());
 | 
						|
 | 
						|
  // The node may not be able to have a text node so that we need to check it
 | 
						|
  // here.
 | 
						|
  if (!pointToInsert.IsInTextNode() &&
 | 
						|
      !HTMLEditUtils::CanNodeContain(*pointToInsert.ContainerAs<nsIContent>(),
 | 
						|
                                     *nsGkAtoms::textTagName)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::HandleInsertLinefeed() couldn't insert a linefeed because "
 | 
						|
        "the insertion position couldn't have text nodes");
 | 
						|
    return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
 | 
						|
  }
 | 
						|
 | 
						|
  AutoRestore<bool> disableListener(
 | 
						|
      EditSubActionDataRef().mAdjustChangedRangeFromListener);
 | 
						|
  EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
 | 
						|
 | 
						|
  // 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);
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  {
 | 
						|
    AutoTrackDOMPoint trackingInsertingPosition(RangeUpdaterRef(),
 | 
						|
                                                &pointToInsert);
 | 
						|
    Result<InsertTextResult, nsresult> insertTextResult =
 | 
						|
        InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert,
 | 
						|
                                  InsertTextTo::ExistingTextNodeIfAvailable);
 | 
						|
    if (MOZ_UNLIKELY(insertTextResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
 | 
						|
      return insertTextResult.propagateErr();
 | 
						|
    }
 | 
						|
    // Ignore the caret suggestion because of `dontChangeMySelection` above.
 | 
						|
    insertTextResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
    pointToPutCaret = insertTextResult.inspect().Handled()
 | 
						|
                          ? insertTextResult.unwrap()
 | 
						|
                                .EndOfInsertedTextRef()
 | 
						|
                                .To<EditorDOMPoint>()
 | 
						|
                          : pointToInsert;
 | 
						|
  }
 | 
						|
 | 
						|
  // Insert a padding <br> element at the end of the block element if there is
 | 
						|
  // no content between the inserted linefeed and the following block boundary
 | 
						|
  // to make sure that the last line is visible.
 | 
						|
  // XXX Blink/WebKit inserts another linefeed character in this case.  However,
 | 
						|
  //     for doing it, we need more work, e.g., updating serializer, deleting
 | 
						|
  //     unnecessary padding <br> element at modifying the last line.
 | 
						|
  if (pointToPutCaret.IsInContentNode() && pointToPutCaret.IsEndOfContainer()) {
 | 
						|
    WSRunScanner wsScannerAtCaret(&aEditingHost, pointToPutCaret,
 | 
						|
                                  BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
    if (wsScannerAtCaret.StartsFromPreformattedLineBreak() &&
 | 
						|
        (wsScannerAtCaret.EndsByBlockBoundary() ||
 | 
						|
         wsScannerAtCaret.EndsByInlineEditingHostBoundary()) &&
 | 
						|
        HTMLEditUtils::CanNodeContain(*wsScannerAtCaret.GetEndReasonContent(),
 | 
						|
                                      *nsGkAtoms::br)) {
 | 
						|
      AutoTrackDOMPoint trackingInsertedPosition(RangeUpdaterRef(),
 | 
						|
                                                 &pointToInsert);
 | 
						|
      AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
 | 
						|
                                                 &pointToPutCaret);
 | 
						|
      Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
          InsertBRElement(WithTransaction::Yes, pointToPutCaret);
 | 
						|
      if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
        return insertBRElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      // We're tracking next caret position with newCaretPosition.  Therefore,
 | 
						|
      // we don't need to update selection here.
 | 
						|
      insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
      MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // manually update the doc changed range so that
 | 
						|
  // OnEndHandlingTopLevelEditSubActionInternal will clean up the correct
 | 
						|
  // portion of the document.
 | 
						|
  MOZ_ASSERT(pointToPutCaret.IsSet());
 | 
						|
  if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
 | 
						|
    // XXX Here is odd.  We did mChangedRange->SetStartAndEnd(pointToInsert,
 | 
						|
    //     pointToPutCaret), but it always fails because of the latter is unset.
 | 
						|
    //     Therefore, always returning NS_ERROR_FAILURE from here is the
 | 
						|
    //     traditional behavior...
 | 
						|
    // TODO: Stop updating the interline position of Selection with fixing here
 | 
						|
    //       and returning expected point.
 | 
						|
    DebugOnly<nsresult> rvIgnored =
 | 
						|
        SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | 
						|
                         "Selection::SetInterlinePosition(InterlinePosition::"
 | 
						|
                         "EndOfLine) failed, but ignored");
 | 
						|
    if (NS_FAILED(TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(
 | 
						|
            pointToInsert))) {
 | 
						|
      NS_WARNING("nsRange::CollapseTo() failed");
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    NS_WARNING(
 | 
						|
        "We always return NS_ERROR_FAILURE here because of a failure of "
 | 
						|
        "updating mChangedRange");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_FAILED(TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
 | 
						|
          pointToInsert.ToRawRangeBoundary(),
 | 
						|
          pointToPutCaret.ToRawRangeBoundary()))) {
 | 
						|
    NS_WARNING("nsRange::SetStartAndEnd() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  pointToPutCaret.SetInterlinePosition(InterlinePosition::EndOfLine);
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult>
 | 
						|
HTMLEditor::HandleInsertParagraphInMailCiteElement(
 | 
						|
    Element& aMailCiteElement, const EditorDOMPoint& aPointToSplit,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aPointToSplit.IsSet());
 | 
						|
  NS_ASSERTION(!HTMLEditUtils::IsEmptyNode(
 | 
						|
                   aMailCiteElement,
 | 
						|
                   {EmptyCheckOption::TreatNonEditableContentAsInvisible}),
 | 
						|
               "The mail-cite element will be deleted, does it expected result "
 | 
						|
               "for you?");
 | 
						|
 | 
						|
  auto splitCiteElementResult =
 | 
						|
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
 | 
						|
    EditorDOMPoint pointToSplit(aPointToSplit);
 | 
						|
 | 
						|
    // If our selection is just before a break, nudge it to be just after
 | 
						|
    // it. This does two things for us.  It saves us the trouble of having
 | 
						|
    // to add a break here ourselves to preserve the "blockness" of the
 | 
						|
    // inline span mailquote (in the inline case), and : it means the break
 | 
						|
    // won't end up making an empty line that happens to be inside a
 | 
						|
    // mailquote (in either inline or block case). The latter can confuse a
 | 
						|
    // user if they click there and start typing, because being in the
 | 
						|
    // mailquote may affect wrapping behavior, or font color, etc.
 | 
						|
    const WSScanResult forwardScanFromPointToSplitResult =
 | 
						|
        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
 | 
						|
            &aEditingHost, pointToSplit, BlockInlineCheck::UseHTMLDefaultStyle);
 | 
						|
    if (forwardScanFromPointToSplitResult.Failed()) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    // If selection start point is before a break and it's inside the
 | 
						|
    // mailquote, let's split it after the visible node.
 | 
						|
    if (forwardScanFromPointToSplitResult.ReachedBRElement() &&
 | 
						|
        forwardScanFromPointToSplitResult.BRElementPtr() != &aMailCiteElement &&
 | 
						|
        aMailCiteElement.Contains(
 | 
						|
            forwardScanFromPointToSplitResult.BRElementPtr())) {
 | 
						|
      pointToSplit = forwardScanFromPointToSplitResult
 | 
						|
                         .PointAfterReachedContent<EditorDOMPoint>();
 | 
						|
    }
 | 
						|
 | 
						|
    if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
 | 
						|
    Result<SplitNodeResult, nsresult> splitResult =
 | 
						|
        SplitNodeDeepWithTransaction(aMailCiteElement, pointToSplit,
 | 
						|
                                     SplitAtEdges::eDoNotCreateEmptyContainer);
 | 
						|
    if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::SplitNodeDeepWithTransaction(aMailCiteElement, "
 | 
						|
          "SplitAtEdges::eDoNotCreateEmptyContainer) failed");
 | 
						|
      return splitResult;
 | 
						|
    }
 | 
						|
    nsresult rv = splitResult.inspect().SuggestCaretPointTo(
 | 
						|
        *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return splitResult;
 | 
						|
  }();
 | 
						|
  if (MOZ_UNLIKELY(splitCiteElementResult.isErr())) {
 | 
						|
    NS_WARNING("Failed to split a mail-cite element");
 | 
						|
    return splitCiteElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  SplitNodeResult unwrappedSplitCiteElementResult =
 | 
						|
      splitCiteElementResult.unwrap();
 | 
						|
  // When adding caret suggestion to SplitNodeResult, here didn't change
 | 
						|
  // selection so that just ignore it.
 | 
						|
  unwrappedSplitCiteElementResult.IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  // Add an invisible <br> to the end of left cite node if it was a <span> of
 | 
						|
  // style="display: block".  This is important, since when serializing the cite
 | 
						|
  // to plain text, the span which caused the visual break is discarded.  So the
 | 
						|
  // added <br> will guarantee that the serializer will insert a break where the
 | 
						|
  // user saw one.
 | 
						|
  // FYI: unwrappedSplitCiteElementResult grabs the previous node and the next
 | 
						|
  //      node with nsCOMPtr or EditorDOMPoint.  So, it's safe to access
 | 
						|
  //      leftCiteElement and rightCiteElement even after changing the DOM tree
 | 
						|
  //      and/or selection even though it's raw pointer.
 | 
						|
  auto* const leftCiteElement =
 | 
						|
      unwrappedSplitCiteElementResult.GetPreviousContentAs<Element>();
 | 
						|
  auto* const rightCiteElement =
 | 
						|
      unwrappedSplitCiteElementResult.GetNextContentAs<Element>();
 | 
						|
  if (leftCiteElement && leftCiteElement->IsHTMLElement(nsGkAtoms::span) &&
 | 
						|
      // XXX Oh, this depends on layout information of new element, and it's
 | 
						|
      //     created by the hacky flush in DoSplitNode().  So we need to
 | 
						|
      //     redesign around this for bug 1710784.
 | 
						|
      leftCiteElement->GetPrimaryFrame() &&
 | 
						|
      leftCiteElement->GetPrimaryFrame()->IsBlockFrameOrSubclass()) {
 | 
						|
    nsIContent* lastChild = leftCiteElement->GetLastChild();
 | 
						|
    if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
      Result<CreateElementResult, nsresult> insertInvisibleBRElementResult =
 | 
						|
          InsertBRElement(WithTransaction::Yes,
 | 
						|
                          EditorDOMPoint::AtEndOf(*leftCiteElement));
 | 
						|
      if (MOZ_UNLIKELY(insertInvisibleBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
        return insertInvisibleBRElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      // We don't need to update selection here because we'll do another
 | 
						|
      // InsertBRElement call soon.
 | 
						|
      insertInvisibleBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
      MOZ_ASSERT(insertInvisibleBRElementResult.inspect().GetNewNode());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // In most cases, <br> should be inserted after current cite.  However, if
 | 
						|
  // left cite hasn't been created because the split point was start of the
 | 
						|
  // cite node, <br> should be inserted before the current cite.
 | 
						|
  Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
 | 
						|
      WithTransaction::Yes,
 | 
						|
      unwrappedSplitCiteElementResult.AtSplitPoint<EditorDOMPoint>());
 | 
						|
  if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
    return Err(insertBRElementResult.unwrapErr());
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
      insertBRElementResult.unwrap();
 | 
						|
  // We'll return with suggesting caret position.  Therefore, we don't need
 | 
						|
  // to update selection here.
 | 
						|
  unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
 | 
						|
  MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
 | 
						|
  // if aMailCiteElement wasn't a block, we might also want another break before
 | 
						|
  // it. We need to examine the content both before the br we just added and
 | 
						|
  // also just after it.  If we don't have another br or block boundary
 | 
						|
  // adjacent, then we will need a 2nd br added to achieve blank line that user
 | 
						|
  // expects.
 | 
						|
  if (HTMLEditUtils::IsInlineContent(
 | 
						|
          aMailCiteElement, BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
    nsresult rvOfInsertingBRElement = [&]() MOZ_CAN_RUN_SCRIPT {
 | 
						|
      EditorDOMPoint pointToCreateNewBRElement(
 | 
						|
          unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
 | 
						|
      // XXX Cannot we replace this complicated check with just a call of
 | 
						|
      //     HTMLEditUtils::IsVisibleBRElement with
 | 
						|
      //     resultOfInsertingBRElement.inspect()?
 | 
						|
      const WSScanResult backwardScanFromPointToCreateNewBRElementResult =
 | 
						|
          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
 | 
						|
              &aEditingHost, pointToCreateNewBRElement,
 | 
						|
              BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
      if (MOZ_UNLIKELY(
 | 
						|
              backwardScanFromPointToCreateNewBRElementResult.Failed())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() "
 | 
						|
            "failed");
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
      }
 | 
						|
      if (!backwardScanFromPointToCreateNewBRElementResult
 | 
						|
               .InVisibleOrCollapsibleCharacters() &&
 | 
						|
          !backwardScanFromPointToCreateNewBRElementResult
 | 
						|
               .ReachedSpecialContent()) {
 | 
						|
        return NS_SUCCESS_DOM_NO_OPERATION;
 | 
						|
      }
 | 
						|
      const WSScanResult forwardScanFromPointAfterNewBRElementResult =
 | 
						|
          WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
 | 
						|
              &aEditingHost,
 | 
						|
              EditorRawDOMPoint::After(pointToCreateNewBRElement),
 | 
						|
              BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
      if (MOZ_UNLIKELY(forwardScanFromPointAfterNewBRElementResult.Failed())) {
 | 
						|
        NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
      }
 | 
						|
      if (!forwardScanFromPointAfterNewBRElementResult
 | 
						|
               .InVisibleOrCollapsibleCharacters() &&
 | 
						|
          !forwardScanFromPointAfterNewBRElementResult
 | 
						|
               .ReachedSpecialContent() &&
 | 
						|
          // In case we're at the very end.
 | 
						|
          !forwardScanFromPointAfterNewBRElementResult
 | 
						|
               .ReachedCurrentBlockBoundary()) {
 | 
						|
        return NS_SUCCESS_DOM_NO_OPERATION;
 | 
						|
      }
 | 
						|
      Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
          InsertBRElement(WithTransaction::Yes, pointToCreateNewBRElement);
 | 
						|
      if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
        return insertBRElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
      MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
 | 
						|
      return NS_OK;
 | 
						|
    }();
 | 
						|
 | 
						|
    if (NS_FAILED(rvOfInsertingBRElement)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "Failed to insert additional <br> element before the inline right "
 | 
						|
          "mail-cite element");
 | 
						|
      return Err(rvOfInsertingBRElement);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (leftCiteElement &&
 | 
						|
      HTMLEditUtils::IsEmptyNode(
 | 
						|
          *leftCiteElement,
 | 
						|
          {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
 | 
						|
    // MOZ_KnownLive(leftCiteElement) because it's grabbed by
 | 
						|
    // unwrappedSplitCiteElementResult.
 | 
						|
    nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*leftCiteElement));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (rightCiteElement &&
 | 
						|
      HTMLEditUtils::IsEmptyNode(
 | 
						|
          *rightCiteElement,
 | 
						|
          {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
 | 
						|
    // MOZ_KnownLive(rightCiteElement) because it's grabbed by
 | 
						|
    // unwrappedSplitCiteElementResult.
 | 
						|
    nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*rightCiteElement));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(!unwrappedInsertBRElementResult.GetNewNode()->GetParent())) {
 | 
						|
    NS_WARNING("Inserted <br> shouldn't become an orphan node");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
  return EditorDOMPoint(unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
}
 | 
						|
 | 
						|
HTMLEditor::CharPointData
 | 
						|
HTMLEditor::GetPreviousCharPointDataForNormalizingWhiteSpaces(
 | 
						|
    const EditorDOMPointInText& aPoint) const {
 | 
						|
  MOZ_ASSERT(aPoint.IsSetAndValid());
 | 
						|
 | 
						|
  if (!aPoint.IsStartOfContainer()) {
 | 
						|
    return CharPointData::InSameTextNode(
 | 
						|
        HTMLEditor::GetPreviousCharPointType(aPoint));
 | 
						|
  }
 | 
						|
  const auto previousCharPoint =
 | 
						|
      WSRunScanner::GetPreviousEditableCharPoint<EditorRawDOMPointInText>(
 | 
						|
          ComputeEditingHost(), aPoint,
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  if (!previousCharPoint.IsSet()) {
 | 
						|
    return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
 | 
						|
  }
 | 
						|
  return CharPointData::InDifferentTextNode(
 | 
						|
      HTMLEditor::GetCharPointType(previousCharPoint));
 | 
						|
}
 | 
						|
 | 
						|
HTMLEditor::CharPointData
 | 
						|
HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
 | 
						|
    const EditorDOMPointInText& aPoint) const {
 | 
						|
  MOZ_ASSERT(aPoint.IsSetAndValid());
 | 
						|
 | 
						|
  if (!aPoint.IsEndOfContainer()) {
 | 
						|
    return CharPointData::InSameTextNode(HTMLEditor::GetCharPointType(aPoint));
 | 
						|
  }
 | 
						|
  const auto nextCharPoint =
 | 
						|
      WSRunScanner::GetInclusiveNextEditableCharPoint<EditorRawDOMPointInText>(
 | 
						|
          ComputeEditingHost(), aPoint,
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  if (!nextCharPoint.IsSet()) {
 | 
						|
    return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
 | 
						|
  }
 | 
						|
  return CharPointData::InDifferentTextNode(
 | 
						|
      HTMLEditor::GetCharPointType(nextCharPoint));
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
void HTMLEditor::GenerateWhiteSpaceSequence(
 | 
						|
    nsAString& aResult, uint32_t aLength,
 | 
						|
    const CharPointData& aPreviousCharPointData,
 | 
						|
    const CharPointData& aNextCharPointData) {
 | 
						|
  MOZ_ASSERT(aResult.IsEmpty());
 | 
						|
  MOZ_ASSERT(aLength);
 | 
						|
  // For now, this method does not assume that result will be append to
 | 
						|
  // white-space sequence in the text node.
 | 
						|
  MOZ_ASSERT(aPreviousCharPointData.AcrossTextNodeBoundary() ||
 | 
						|
             !aPreviousCharPointData.IsCollapsibleWhiteSpace());
 | 
						|
  // For now, this method does not assume that the result will be inserted
 | 
						|
  // into white-space sequence nor start of white-space sequence.
 | 
						|
  MOZ_ASSERT(aNextCharPointData.AcrossTextNodeBoundary() ||
 | 
						|
             !aNextCharPointData.IsCollapsibleWhiteSpace());
 | 
						|
 | 
						|
  if (aLength == 1) {
 | 
						|
    // Even if previous/next char is in different text node, we should put
 | 
						|
    // an ASCII white-space between visible characters.
 | 
						|
    // XXX This means that this does not allow to put an NBSP in HTML editor
 | 
						|
    //     without preformatted style.  However, Chrome has same issue too.
 | 
						|
    if (aPreviousCharPointData.Type() == CharPointType::VisibleChar &&
 | 
						|
        aNextCharPointData.Type() == CharPointType::VisibleChar) {
 | 
						|
      aResult.Assign(HTMLEditUtils::kSpace);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // If it's start or end of text, put an NBSP.
 | 
						|
    if (aPreviousCharPointData.Type() == CharPointType::TextEnd ||
 | 
						|
        aNextCharPointData.Type() == CharPointType::TextEnd) {
 | 
						|
      aResult.Assign(HTMLEditUtils::kNBSP);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // If the character is next to a preformatted linefeed, we need to put
 | 
						|
    // an NBSP for avoiding collapsed into the linefeed.
 | 
						|
    if (aPreviousCharPointData.Type() == CharPointType::PreformattedLineBreak ||
 | 
						|
        aNextCharPointData.Type() == CharPointType::PreformattedLineBreak) {
 | 
						|
      aResult.Assign(HTMLEditUtils::kNBSP);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Now, the white-space will be inserted to a white-space sequence, but not
 | 
						|
    // end of text.  We can put an ASCII white-space only when both sides are
 | 
						|
    // not ASCII white-spaces.
 | 
						|
    aResult.Assign(
 | 
						|
        aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
 | 
						|
                aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
 | 
						|
            ? HTMLEditUtils::kNBSP
 | 
						|
            : HTMLEditUtils::kSpace);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Generate pairs of NBSP and ASCII white-space.
 | 
						|
  aResult.SetLength(aLength);
 | 
						|
  bool appendNBSP = true;  // Basically, starts with an NBSP.
 | 
						|
  char16_t* lastChar = aResult.EndWriting() - 1;
 | 
						|
  for (char16_t* iter = aResult.BeginWriting(); iter != lastChar; iter++) {
 | 
						|
    *iter = appendNBSP ? HTMLEditUtils::kNBSP : HTMLEditUtils::kSpace;
 | 
						|
    appendNBSP = !appendNBSP;
 | 
						|
  }
 | 
						|
 | 
						|
  // If the final one is expected to an NBSP, we can put an NBSP simply.
 | 
						|
  if (appendNBSP) {
 | 
						|
    *lastChar = HTMLEditUtils::kNBSP;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // If next char point is end of text node, an ASCII white-space or
 | 
						|
  // preformatted linefeed, we need to put an NBSP.
 | 
						|
  *lastChar =
 | 
						|
      aNextCharPointData.AcrossTextNodeBoundary() ||
 | 
						|
              aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
 | 
						|
              aNextCharPointData.Type() == CharPointType::PreformattedLineBreak
 | 
						|
          ? HTMLEditUtils::kNBSP
 | 
						|
          : HTMLEditUtils::kSpace;
 | 
						|
}
 | 
						|
 | 
						|
void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
 | 
						|
    EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
 | 
						|
    nsAString& aNormalizedWhiteSpacesInStartNode,
 | 
						|
    nsAString& aNormalizedWhiteSpacesInEndNode) const {
 | 
						|
  MOZ_ASSERT(aStartToDelete.IsSetAndValid());
 | 
						|
  MOZ_ASSERT(aEndToDelete.IsSetAndValid());
 | 
						|
  MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
 | 
						|
  MOZ_ASSERT(aNormalizedWhiteSpacesInStartNode.IsEmpty());
 | 
						|
  MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.IsEmpty());
 | 
						|
 | 
						|
  // First, check whether there is surrounding white-spaces or not, and if there
 | 
						|
  // are, check whether they are collapsible or not.  Note that we shouldn't
 | 
						|
  // touch white-spaces in different text nodes for performance, but we need
 | 
						|
  // adjacent text node's first or last character information in some cases.
 | 
						|
  Element* editingHost = ComputeEditingHost();
 | 
						|
  const EditorDOMPointInText precedingCharPoint =
 | 
						|
      WSRunScanner::GetPreviousEditableCharPoint(
 | 
						|
          editingHost, aStartToDelete,
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  const EditorDOMPointInText followingCharPoint =
 | 
						|
      WSRunScanner::GetInclusiveNextEditableCharPoint(
 | 
						|
          editingHost, aEndToDelete, BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  // Blink-compat: Normalize white-spaces in first node only when not removing
 | 
						|
  //               its last character or no text nodes follow the first node.
 | 
						|
  //               If removing last character of first node and there are
 | 
						|
  //               following text nodes, white-spaces in following text node are
 | 
						|
  //               normalized instead.
 | 
						|
  const bool removingLastCharOfStartNode =
 | 
						|
      aStartToDelete.ContainerAs<Text>() != aEndToDelete.ContainerAs<Text>() ||
 | 
						|
      (aEndToDelete.IsEndOfContainer() && followingCharPoint.IsSet());
 | 
						|
  const bool maybeNormalizePrecedingWhiteSpaces =
 | 
						|
      !removingLastCharOfStartNode && precedingCharPoint.IsSet() &&
 | 
						|
      !precedingCharPoint.IsEndOfContainer() &&
 | 
						|
      precedingCharPoint.ContainerAs<Text>() ==
 | 
						|
          aStartToDelete.ContainerAs<Text>() &&
 | 
						|
      precedingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
 | 
						|
  const bool maybeNormalizeFollowingWhiteSpaces =
 | 
						|
      followingCharPoint.IsSet() && !followingCharPoint.IsEndOfContainer() &&
 | 
						|
      (followingCharPoint.ContainerAs<Text>() ==
 | 
						|
           aEndToDelete.ContainerAs<Text>() ||
 | 
						|
       removingLastCharOfStartNode) &&
 | 
						|
      followingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
 | 
						|
 | 
						|
  if (!maybeNormalizePrecedingWhiteSpaces &&
 | 
						|
      !maybeNormalizeFollowingWhiteSpaces) {
 | 
						|
    return;  // There are no white-spaces.
 | 
						|
  }
 | 
						|
 | 
						|
  // Next, consider the range to normalize.
 | 
						|
  EditorDOMPointInText startToNormalize, endToNormalize;
 | 
						|
  if (maybeNormalizePrecedingWhiteSpaces) {
 | 
						|
    Maybe<uint32_t> previousCharOffsetOfWhiteSpaces =
 | 
						|
        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
 | 
						|
            precedingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
 | 
						|
    startToNormalize.Set(precedingCharPoint.ContainerAs<Text>(),
 | 
						|
                         previousCharOffsetOfWhiteSpaces.isSome()
 | 
						|
                             ? previousCharOffsetOfWhiteSpaces.value() + 1
 | 
						|
                             : 0);
 | 
						|
    MOZ_ASSERT(!startToNormalize.IsEndOfContainer());
 | 
						|
  }
 | 
						|
  if (maybeNormalizeFollowingWhiteSpaces) {
 | 
						|
    Maybe<uint32_t> nextCharOffsetOfWhiteSpaces =
 | 
						|
        HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
 | 
						|
            followingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
 | 
						|
    if (nextCharOffsetOfWhiteSpaces.isSome()) {
 | 
						|
      endToNormalize.Set(followingCharPoint.ContainerAs<Text>(),
 | 
						|
                         nextCharOffsetOfWhiteSpaces.value());
 | 
						|
    } else {
 | 
						|
      endToNormalize.SetToEndOf(followingCharPoint.ContainerAs<Text>());
 | 
						|
    }
 | 
						|
    MOZ_ASSERT(!endToNormalize.IsStartOfContainer());
 | 
						|
  }
 | 
						|
 | 
						|
  // Next, retrieve surrounding information of white-space sequence.
 | 
						|
  // If we're removing first text node's last character, we need to
 | 
						|
  // normalize white-spaces starts from another text node.  In this case,
 | 
						|
  // we need to lie for avoiding assertion in GenerateWhiteSpaceSequence().
 | 
						|
  CharPointData previousCharPointData =
 | 
						|
      removingLastCharOfStartNode
 | 
						|
          ? CharPointData::InDifferentTextNode(CharPointType::TextEnd)
 | 
						|
          : GetPreviousCharPointDataForNormalizingWhiteSpaces(
 | 
						|
                startToNormalize.IsSet() ? startToNormalize : aStartToDelete);
 | 
						|
  CharPointData nextCharPointData =
 | 
						|
      GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
 | 
						|
          endToNormalize.IsSet() ? endToNormalize : aEndToDelete);
 | 
						|
 | 
						|
  // Next, compute number of white-spaces in start/end node.
 | 
						|
  uint32_t lengthInStartNode = 0, lengthInEndNode = 0;
 | 
						|
  if (startToNormalize.IsSet()) {
 | 
						|
    MOZ_ASSERT(startToNormalize.ContainerAs<Text>() ==
 | 
						|
               aStartToDelete.ContainerAs<Text>());
 | 
						|
    lengthInStartNode = aStartToDelete.Offset() - startToNormalize.Offset();
 | 
						|
    MOZ_ASSERT(lengthInStartNode);
 | 
						|
  }
 | 
						|
  if (endToNormalize.IsSet()) {
 | 
						|
    lengthInEndNode =
 | 
						|
        endToNormalize.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()
 | 
						|
            ? endToNormalize.Offset() - aEndToDelete.Offset()
 | 
						|
            : endToNormalize.Offset();
 | 
						|
    MOZ_ASSERT(lengthInEndNode);
 | 
						|
    // If we normalize white-spaces in a text node, we can replace all of them
 | 
						|
    // with one ReplaceTextTransaction.
 | 
						|
    if (endToNormalize.ContainerAs<Text>() ==
 | 
						|
        aStartToDelete.ContainerAs<Text>()) {
 | 
						|
      lengthInStartNode += lengthInEndNode;
 | 
						|
      lengthInEndNode = 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(lengthInStartNode + lengthInEndNode);
 | 
						|
 | 
						|
  // Next, generate normalized white-spaces.
 | 
						|
  if (!lengthInEndNode) {
 | 
						|
    HTMLEditor::GenerateWhiteSpaceSequence(
 | 
						|
        aNormalizedWhiteSpacesInStartNode, lengthInStartNode,
 | 
						|
        previousCharPointData, nextCharPointData);
 | 
						|
  } else if (!lengthInStartNode) {
 | 
						|
    HTMLEditor::GenerateWhiteSpaceSequence(
 | 
						|
        aNormalizedWhiteSpacesInEndNode, lengthInEndNode, previousCharPointData,
 | 
						|
        nextCharPointData);
 | 
						|
  } else {
 | 
						|
    // For making `GenerateWhiteSpaceSequence()` simpler, we should create
 | 
						|
    // whole white-space sequence first, then, copy to the out params.
 | 
						|
    nsAutoString whiteSpaces;
 | 
						|
    HTMLEditor::GenerateWhiteSpaceSequence(
 | 
						|
        whiteSpaces, lengthInStartNode + lengthInEndNode, previousCharPointData,
 | 
						|
        nextCharPointData);
 | 
						|
    aNormalizedWhiteSpacesInStartNode =
 | 
						|
        Substring(whiteSpaces, 0, lengthInStartNode);
 | 
						|
    aNormalizedWhiteSpacesInEndNode = Substring(whiteSpaces, lengthInStartNode);
 | 
						|
    MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.Length() == lengthInEndNode);
 | 
						|
  }
 | 
						|
 | 
						|
  // TODO: Shrink the replacing range and string as far as possible because
 | 
						|
  //       this may run a lot, i.e., HTMLEditor creates ReplaceTextTransaction
 | 
						|
  //       a lot for normalizing white-spaces.  Then, each transaction shouldn't
 | 
						|
  //       have all white-spaces every time because once it's normalized, we
 | 
						|
  //       don't need to normalize all of the sequence again, but currently
 | 
						|
  //       we do.
 | 
						|
 | 
						|
  // Finally, extend the range.
 | 
						|
  if (startToNormalize.IsSet()) {
 | 
						|
    aStartToDelete = startToNormalize;
 | 
						|
  }
 | 
						|
  if (endToNormalize.IsSet()) {
 | 
						|
    aEndToDelete = endToNormalize;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Result<CaretPoint, nsresult>
 | 
						|
HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
 | 
						|
    const EditorDOMPointInText& aStartToDelete,
 | 
						|
    const EditorDOMPointInText& aEndToDelete,
 | 
						|
    TreatEmptyTextNodes aTreatEmptyTextNodes,
 | 
						|
    DeleteDirection aDeleteDirection) {
 | 
						|
  MOZ_ASSERT(aStartToDelete.IsSetAndValid());
 | 
						|
  MOZ_ASSERT(aEndToDelete.IsSetAndValid());
 | 
						|
  MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
 | 
						|
 | 
						|
  // Use nsString for these replacing string because we should avoid to copy
 | 
						|
  // the buffer from auto storange to ReplaceTextTransaction.
 | 
						|
  nsString normalizedWhiteSpacesInFirstNode, normalizedWhiteSpacesInLastNode;
 | 
						|
 | 
						|
  // First, check whether we need to normalize white-spaces after deleting
 | 
						|
  // the given range.
 | 
						|
  EditorDOMPointInText startToDelete(aStartToDelete);
 | 
						|
  EditorDOMPointInText endToDelete(aEndToDelete);
 | 
						|
  ExtendRangeToDeleteWithNormalizingWhiteSpaces(
 | 
						|
      startToDelete, endToDelete, normalizedWhiteSpacesInFirstNode,
 | 
						|
      normalizedWhiteSpacesInLastNode);
 | 
						|
 | 
						|
  // If extended range is still collapsed, i.e., the caller just wants to
 | 
						|
  // normalize white-space sequence, but there is no white-spaces which need to
 | 
						|
  // be replaced, we need to do nothing here.
 | 
						|
  if (startToDelete == endToDelete) {
 | 
						|
    return CaretPoint(aStartToDelete.To<EditorDOMPoint>());
 | 
						|
  }
 | 
						|
 | 
						|
  // Note that the container text node of startToDelete may be removed from
 | 
						|
  // the tree if it becomes empty.  Therefore, we need to track the point.
 | 
						|
  EditorDOMPoint newCaretPosition;
 | 
						|
  if (aStartToDelete.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()) {
 | 
						|
    newCaretPosition = aEndToDelete.To<EditorDOMPoint>();
 | 
						|
  } else if (aDeleteDirection == DeleteDirection::Forward) {
 | 
						|
    newCaretPosition.SetToEndOf(aStartToDelete.ContainerAs<Text>());
 | 
						|
  } else {
 | 
						|
    newCaretPosition.Set(aEndToDelete.ContainerAs<Text>(), 0u);
 | 
						|
  }
 | 
						|
 | 
						|
  // Then, modify the text nodes in the range.
 | 
						|
  while (true) {
 | 
						|
    AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
 | 
						|
                                               &newCaretPosition);
 | 
						|
    // Use ReplaceTextTransaction if we need to normalize white-spaces in
 | 
						|
    // the first text node.
 | 
						|
    if (!normalizedWhiteSpacesInFirstNode.IsEmpty()) {
 | 
						|
      EditorDOMPoint trackingEndToDelete(endToDelete.ContainerAs<Text>(),
 | 
						|
                                         endToDelete.Offset());
 | 
						|
      {
 | 
						|
        AutoTrackDOMPoint trackEndToDelete(RangeUpdaterRef(),
 | 
						|
                                           &trackingEndToDelete);
 | 
						|
        uint32_t lengthToReplaceInFirstTextNode =
 | 
						|
            startToDelete.ContainerAs<Text>() ==
 | 
						|
                    trackingEndToDelete.ContainerAs<Text>()
 | 
						|
                ? trackingEndToDelete.Offset() - startToDelete.Offset()
 | 
						|
                : startToDelete.ContainerAs<Text>()->TextLength() -
 | 
						|
                      startToDelete.Offset();
 | 
						|
        Result<InsertTextResult, nsresult> replaceTextResult =
 | 
						|
            ReplaceTextWithTransaction(
 | 
						|
                MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
 | 
						|
                startToDelete.Offset(), lengthToReplaceInFirstTextNode,
 | 
						|
                normalizedWhiteSpacesInFirstNode);
 | 
						|
        if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
 | 
						|
          NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
 | 
						|
          return replaceTextResult.propagateErr();
 | 
						|
        }
 | 
						|
        // We'll return computed caret point, newCaretPosition, below.
 | 
						|
        replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
 | 
						|
        if (startToDelete.ContainerAs<Text>() ==
 | 
						|
            trackingEndToDelete.ContainerAs<Text>()) {
 | 
						|
          MOZ_ASSERT(normalizedWhiteSpacesInLastNode.IsEmpty());
 | 
						|
          break;  // There is no more text which we need to delete.
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if (MayHaveMutationEventListeners(
 | 
						|
              NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
 | 
						|
          (NS_WARN_IF(!trackingEndToDelete.IsSetAndValid()) ||
 | 
						|
           NS_WARN_IF(!trackingEndToDelete.IsInTextNode()))) {
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
      MOZ_ASSERT(trackingEndToDelete.IsInTextNode());
 | 
						|
      endToDelete.Set(trackingEndToDelete.ContainerAs<Text>(),
 | 
						|
                      trackingEndToDelete.Offset());
 | 
						|
      // If the remaining range was modified by mutation event listener,
 | 
						|
      // we should stop handling the deletion.
 | 
						|
      startToDelete =
 | 
						|
          EditorDOMPointInText::AtEndOf(*startToDelete.ContainerAs<Text>());
 | 
						|
      if (MayHaveMutationEventListeners(
 | 
						|
              NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
 | 
						|
          NS_WARN_IF(!startToDelete.IsBefore(endToDelete))) {
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    // Delete ASCII whiteSpaces in the range simpley if there are some text
 | 
						|
    // nodes which we don't need to replace their text.
 | 
						|
    if (normalizedWhiteSpacesInLastNode.IsEmpty() ||
 | 
						|
        startToDelete.ContainerAs<Text>() != endToDelete.ContainerAs<Text>()) {
 | 
						|
      // If we need to replace text in the last text node, we should
 | 
						|
      // delete text before its previous text node.
 | 
						|
      EditorDOMPointInText endToDeleteExceptReplaceRange =
 | 
						|
          normalizedWhiteSpacesInLastNode.IsEmpty()
 | 
						|
              ? endToDelete
 | 
						|
              : EditorDOMPointInText(endToDelete.ContainerAs<Text>(), 0);
 | 
						|
      if (startToDelete != endToDeleteExceptReplaceRange) {
 | 
						|
        Result<CaretPoint, nsresult> caretPointOrError =
 | 
						|
            DeleteTextAndTextNodesWithTransaction(startToDelete,
 | 
						|
                                                  endToDeleteExceptReplaceRange,
 | 
						|
                                                  aTreatEmptyTextNodes);
 | 
						|
        if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
 | 
						|
          return caretPointOrError.propagateErr();
 | 
						|
        }
 | 
						|
        nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
 | 
						|
            *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                    SuggestCaret::AndIgnoreTrivialError});
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
        NS_WARNING_ASSERTION(
 | 
						|
            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
            "CaretPoint::SuggestCaretPointTo() failed, but ignored");
 | 
						|
        if (normalizedWhiteSpacesInLastNode.IsEmpty()) {
 | 
						|
          break;  // There is no more text which we need to delete.
 | 
						|
        }
 | 
						|
        if (MayHaveMutationEventListeners(
 | 
						|
                NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED |
 | 
						|
                NS_EVENT_BITS_MUTATION_NODEREMOVED |
 | 
						|
                NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT |
 | 
						|
                NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED) &&
 | 
						|
            (NS_WARN_IF(!endToDeleteExceptReplaceRange.IsSetAndValid()) ||
 | 
						|
             NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
 | 
						|
             NS_WARN_IF(endToDelete.IsStartOfContainer()))) {
 | 
						|
          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
        }
 | 
						|
        // Then, replace the text in the last text node.
 | 
						|
        startToDelete = endToDeleteExceptReplaceRange;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Replace ASCII whiteSpaces in the range and following character in the
 | 
						|
    // last text node.
 | 
						|
    MOZ_ASSERT(!normalizedWhiteSpacesInLastNode.IsEmpty());
 | 
						|
    MOZ_ASSERT(startToDelete.ContainerAs<Text>() ==
 | 
						|
               endToDelete.ContainerAs<Text>());
 | 
						|
    Result<InsertTextResult, nsresult> replaceTextResult =
 | 
						|
        ReplaceTextWithTransaction(
 | 
						|
            MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
 | 
						|
            startToDelete.Offset(),
 | 
						|
            endToDelete.Offset() - startToDelete.Offset(),
 | 
						|
            normalizedWhiteSpacesInLastNode);
 | 
						|
    if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
 | 
						|
      return replaceTextResult.propagateErr();
 | 
						|
    }
 | 
						|
    // We'll return computed caret point, newCaretPosition, below.
 | 
						|
    replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
 | 
						|
    break;
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!newCaretPosition.IsSetAndValid()) ||
 | 
						|
      NS_WARN_IF(!newCaretPosition.GetContainer()->IsInComposedDoc())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  // Look for leaf node to put caret if we remove some empty inline ancestors
 | 
						|
  // at new caret position.
 | 
						|
  if (!newCaretPosition.IsInTextNode()) {
 | 
						|
    if (const Element* editableBlockElementOrInlineEditingHost =
 | 
						|
            HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
                *newCaretPosition.ContainerAs<nsIContent>(),
 | 
						|
                HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
 | 
						|
                BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
      Element* editingHost = ComputeEditingHost();
 | 
						|
      // Try to put caret next to immediately after previous editable leaf.
 | 
						|
      nsIContent* previousContent =
 | 
						|
          HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
 | 
						|
              newCaretPosition, *editableBlockElementOrInlineEditingHost,
 | 
						|
              {LeafNodeType::LeafNodeOrNonEditableNode},
 | 
						|
              BlockInlineCheck::UseComputedDisplayStyle, editingHost);
 | 
						|
      if (previousContent &&
 | 
						|
          !HTMLEditUtils::IsBlockElement(
 | 
						|
              *previousContent, BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
        newCaretPosition =
 | 
						|
            previousContent->IsText() ||
 | 
						|
                    HTMLEditUtils::IsContainerNode(*previousContent)
 | 
						|
                ? EditorDOMPoint::AtEndOf(*previousContent)
 | 
						|
                : EditorDOMPoint::After(*previousContent);
 | 
						|
      }
 | 
						|
      // But if the point is very first of a block element or immediately after
 | 
						|
      // a child block, look for next editable leaf instead.
 | 
						|
      else if (nsIContent* nextContent =
 | 
						|
                   HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
 | 
						|
                       newCaretPosition,
 | 
						|
                       *editableBlockElementOrInlineEditingHost,
 | 
						|
                       {LeafNodeType::LeafNodeOrNonEditableNode},
 | 
						|
                       BlockInlineCheck::UseComputedDisplayStyle,
 | 
						|
                       editingHost)) {
 | 
						|
        newCaretPosition = nextContent->IsText() ||
 | 
						|
                                   HTMLEditUtils::IsContainerNode(*nextContent)
 | 
						|
                               ? EditorDOMPoint(nextContent, 0)
 | 
						|
                               : EditorDOMPoint(nextContent);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // For compatibility with Blink, we should move caret to end of previous
 | 
						|
  // text node if it's direct previous sibling of the first text node in the
 | 
						|
  // range.
 | 
						|
  if (newCaretPosition.IsStartOfContainer() &&
 | 
						|
      newCaretPosition.IsInTextNode() &&
 | 
						|
      newCaretPosition.GetContainer()->GetPreviousSibling() &&
 | 
						|
      newCaretPosition.GetContainer()->GetPreviousSibling()->IsText()) {
 | 
						|
    newCaretPosition.SetToEndOf(
 | 
						|
        newCaretPosition.GetContainer()->GetPreviousSibling()->AsText());
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
 | 
						|
                                               &newCaretPosition);
 | 
						|
    Result<CaretPoint, nsresult> caretPointOrError =
 | 
						|
        InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
 | 
						|
            newCaretPosition);
 | 
						|
    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::"
 | 
						|
          "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() failed");
 | 
						|
      return caretPointOrError;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (!newCaretPosition.IsSetAndValid()) {
 | 
						|
    NS_WARNING("Inserting <br> element caused unexpected DOM tree");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
  return CaretPoint(std::move(newCaretPosition));
 | 
						|
}
 | 
						|
 | 
						|
Result<CaretPoint, nsresult>
 | 
						|
HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
 | 
						|
    const EditorDOMPoint& aPointToInsert) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aPointToInsert.IsSet());
 | 
						|
 | 
						|
  if (!aPointToInsert.IsInContentNode()) {
 | 
						|
    return CaretPoint(EditorDOMPoint());
 | 
						|
  }
 | 
						|
 | 
						|
  // If container of the point is not in a block, we don't need to put a
 | 
						|
  // `<br>` element here.
 | 
						|
  if (!HTMLEditUtils::IsBlockElement(
 | 
						|
          *aPointToInsert.ContainerAs<nsIContent>(),
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
    return CaretPoint(EditorDOMPoint());
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
 | 
						|
          *aPointToInsert.ContainerAs<nsIContent>()))) {
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  WSRunScanner wsRunScanner(ComputeEditingHost(), aPointToInsert,
 | 
						|
                            BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  // If the point is not start of a hard line, we don't need to put a `<br>`
 | 
						|
  // element here.
 | 
						|
  if (!wsRunScanner.StartsFromHardLineBreak() &&
 | 
						|
      !wsRunScanner.StartsFromInlineEditingHostBoundary()) {
 | 
						|
    return CaretPoint(EditorDOMPoint());
 | 
						|
  }
 | 
						|
  // If the point is not end of a hard line or the hard line does not end with
 | 
						|
  // block boundary, we don't need to put a `<br>` element here.
 | 
						|
  if (!wsRunScanner.EndsByBlockBoundary() &&
 | 
						|
      !wsRunScanner.EndsByInlineEditingHostBoundary()) {
 | 
						|
    return CaretPoint(EditorDOMPoint());
 | 
						|
  }
 | 
						|
 | 
						|
  // If we cannot insert a `<br>` element here, do nothing.
 | 
						|
  if (!HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
 | 
						|
                                     *nsGkAtoms::br)) {
 | 
						|
    return CaretPoint(EditorDOMPoint());
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
 | 
						|
      WithTransaction::Yes, aPointToInsert, nsIEditor::ePrevious);
 | 
						|
  if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::InsertBRElement(WithTransaction::Yes, ePrevious) failed");
 | 
						|
    return insertBRElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  return CaretPoint(insertBRElementResult.unwrap().UnwrapCaretPoint());
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult>
 | 
						|
HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
 | 
						|
    const nsStaticAtom& aListElementOrListItemElementTagName,
 | 
						|
    const nsAString& aBulletType,
 | 
						|
    SelectAllOfCurrentList aSelectAllOfCurrentList) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
 | 
						|
             &aListElementOrListItemElementTagName == nsGkAtoms::ol ||
 | 
						|
             &aListElementOrListItemElementTagName == nsGkAtoms::dl ||
 | 
						|
             &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
 | 
						|
             &aListElementOrListItemElementTagName == nsGkAtoms::dt);
 | 
						|
 | 
						|
  if (NS_WARN_IF(!mInitSucceeded)) {
 | 
						|
    return Err(NS_ERROR_NOT_INITIALIZED);
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Some selection containers are not content node, but ignored");
 | 
						|
    return EditActionResult::IgnoredResult();
 | 
						|
  }
 | 
						|
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(
 | 
						|
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
 | 
						|
 | 
						|
  // XXX EditSubAction::eCreateOrChangeDefinitionListItem and
 | 
						|
  //     EditSubAction::eCreateOrChangeList are treated differently in
 | 
						|
  //     HTMLEditor::MaybeSplitElementsAtEveryBRElement().  Only when
 | 
						|
  //     EditSubAction::eCreateOrChangeList, it splits inline nodes.
 | 
						|
  //     Currently, it shouldn't be done when we called for formatting
 | 
						|
  //     `<dd>` or `<dt>` by
 | 
						|
  //     HTMLEditor::MakeDefinitionListItemWithTransaction().  But this
 | 
						|
  //     difference may be a bug.  We should investigate this later.
 | 
						|
  IgnoredErrorResult error;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this,
 | 
						|
      &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
 | 
						|
              &aListElementOrListItemElementTagName == nsGkAtoms::dt
 | 
						|
          ? EditSubAction::eCreateOrChangeDefinitionListItem
 | 
						|
          : EditSubAction::eCreateOrChangeList,
 | 
						|
      nsIEditor::eNext, error);
 | 
						|
  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(error.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !error.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const nsStaticAtom* listTagName = nullptr;
 | 
						|
  const nsStaticAtom* listItemTagName = nullptr;
 | 
						|
  if (&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
 | 
						|
      &aListElementOrListItemElementTagName == nsGkAtoms::ol) {
 | 
						|
    listTagName = &aListElementOrListItemElementTagName;
 | 
						|
    listItemTagName = nsGkAtoms::li;
 | 
						|
  } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dl) {
 | 
						|
    listTagName = &aListElementOrListItemElementTagName;
 | 
						|
    listItemTagName = nsGkAtoms::dd;
 | 
						|
  } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dd ||
 | 
						|
             &aListElementOrListItemElementTagName == nsGkAtoms::dt) {
 | 
						|
    listTagName = nsGkAtoms::dl;
 | 
						|
    listItemTagName = &aListElementOrListItemElementTagName;
 | 
						|
  } else {
 | 
						|
    NS_WARNING(
 | 
						|
        "aListElementOrListItemElementTagName was neither list element name "
 | 
						|
        "nor "
 | 
						|
        "definition listitem element name");
 | 
						|
    return Err(NS_ERROR_INVALID_ARG);
 | 
						|
  }
 | 
						|
 | 
						|
  const RefPtr<Element> editingHost = ComputeEditingHost();
 | 
						|
  if (MOZ_UNLIKELY(!editingHost)) {
 | 
						|
    return EditActionResult::CanceledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  // Expands selection range to include the immediate block parent, and then
 | 
						|
  // further expands to include any ancestors whose children are all in the
 | 
						|
  // range.
 | 
						|
  // XXX Why do we do this only when there is only one selection range?
 | 
						|
  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            SelectionRef().GetRangeAt(0u), *editingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.propagateErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we
 | 
						|
    // cannot use Selection::SetStartAndEndInLimit() here.
 | 
						|
    error.SuppressException();
 | 
						|
    SelectionRef().SetBaseAndExtentInLimiter(
 | 
						|
        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
 | 
						|
        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    if (MOZ_UNLIKELY(error.Failed())) {
 | 
						|
      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
 | 
						|
      return Err(error.StealNSResult());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  AutoListElementCreator listCreator(*listTagName, *listItemTagName,
 | 
						|
                                     aBulletType);
 | 
						|
  AutoRangeArray selectionRanges(SelectionRef());
 | 
						|
  Result<EditActionResult, nsresult> result = listCreator.Run(
 | 
						|
      *this, selectionRanges, aSelectAllOfCurrentList, *editingHost);
 | 
						|
  if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::ConvertContentAroundRangesToList() failed");
 | 
						|
    // XXX Should we try to restore selection ranges in this case?
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  rv = selectionRanges.ApplyTo(SelectionRef());
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("AutoRangeArray::ApplyTo() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return result.inspect().Ignored() ? EditActionResult::CanceledResult()
 | 
						|
                                    : EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::AutoListElementCreator::Run(
 | 
						|
    HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
 | 
						|
    SelectAllOfCurrentList aSelectAllOfCurrentList,
 | 
						|
    const Element& aEditingHost) const {
 | 
						|
  MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!aHTMLEditor.IsSelectionRangeContainerNotContent());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(aHTMLEditor))) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  AutoContentNodeArray arrayOfContents;
 | 
						|
  nsresult rv = SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
 | 
						|
      aHTMLEditor, aRanges, aSelectAllOfCurrentList, aEditingHost,
 | 
						|
      arrayOfContents);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AutoListElementCreator::"
 | 
						|
        "SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  // check if all our nodes are <br>s, or empty inlines
 | 
						|
  // if no nodes, we make empty list.  Ditto if the user tried to make a list
 | 
						|
  // of some # of breaks.
 | 
						|
  if (AutoListElementCreator::
 | 
						|
          IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
 | 
						|
              arrayOfContents)) {
 | 
						|
    Result<RefPtr<Element>, nsresult> newListItemElementOrError =
 | 
						|
        ReplaceContentNodesWithEmptyNewList(aHTMLEditor, aRanges,
 | 
						|
                                            arrayOfContents, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoListElementCreator::ReplaceContentNodesWithEmptyNewList() "
 | 
						|
          "failed");
 | 
						|
      return newListItemElementOrError.propagateErr();
 | 
						|
    }
 | 
						|
    if (MOZ_UNLIKELY(!newListItemElementOrError.inspect())) {
 | 
						|
      aRanges.RestoreFromSavedRanges();
 | 
						|
      return EditActionResult::CanceledResult();
 | 
						|
    }
 | 
						|
    aRanges.ClearSavedRanges();
 | 
						|
    nsresult rv = aRanges.Collapse(
 | 
						|
        EditorRawDOMPoint(newListItemElementOrError.inspect(), 0u));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoRangeArray::Collapse() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return EditActionResult::IgnoredResult();
 | 
						|
  }
 | 
						|
 | 
						|
  Result<RefPtr<Element>, nsresult> listItemOrListToPutCaretOrError =
 | 
						|
      WrapContentNodesIntoNewListElements(aHTMLEditor, aRanges, arrayOfContents,
 | 
						|
                                          aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(listItemOrListToPutCaretOrError.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AutoListElementCreator::WrapContentNodesIntoNewListElements() failed");
 | 
						|
    return listItemOrListToPutCaretOrError.propagateErr();
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(aRanges.HasSavedRanges());
 | 
						|
  aRanges.RestoreFromSavedRanges();
 | 
						|
 | 
						|
  // If selection will be collapsed but not in listItemOrListToPutCaret, we need
 | 
						|
  // to adjust the caret position into it.
 | 
						|
  if (listItemOrListToPutCaretOrError.inspect()) {
 | 
						|
    DebugOnly<nsresult> rvIgnored =
 | 
						|
        EnsureCollapsedRangeIsInListItemOrListElement(
 | 
						|
            *listItemOrListToPutCaretOrError.inspect(), aRanges);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rvIgnored),
 | 
						|
        "AutoListElementCreator::"
 | 
						|
        "EnsureCollapsedRangeIsInListItemOrListElement() failed, but ignored");
 | 
						|
  }
 | 
						|
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::
 | 
						|
    SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
 | 
						|
        HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
 | 
						|
        SelectAllOfCurrentList aSelectAllOfCurrentList,
 | 
						|
        const Element& aEditingHost,
 | 
						|
        ContentNodeArray& aOutArrayOfContents) const {
 | 
						|
  MOZ_ASSERT(aOutArrayOfContents.IsEmpty());
 | 
						|
 | 
						|
  if (aSelectAllOfCurrentList == SelectAllOfCurrentList::Yes) {
 | 
						|
    if (Element* parentListElementOfRanges =
 | 
						|
            aRanges.GetClosestAncestorAnyListElementOfRange()) {
 | 
						|
      aOutArrayOfContents.AppendElement(
 | 
						|
          OwningNonNull<nsIContent>(*parentListElementOfRanges));
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  AutoRangeArray extendedRanges(aRanges);
 | 
						|
 | 
						|
  // 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(aHTMLEditor);
 | 
						|
 | 
						|
  extendedRanges.ExtendRangesToWrapLines(EditSubAction::eCreateOrChangeList,
 | 
						|
                                         BlockInlineCheck::UseHTMLDefaultStyle,
 | 
						|
                                         aEditingHost);
 | 
						|
  Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
      extendedRanges.SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
 | 
						|
          aHTMLEditor, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AutoRangeArray::"
 | 
						|
        "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
 | 
						|
    return splitResult.unwrapErr();
 | 
						|
  }
 | 
						|
  nsresult rv = extendedRanges.CollectEditTargetNodes(
 | 
						|
      aHTMLEditor, aOutArrayOfContents, EditSubAction::eCreateOrChangeList,
 | 
						|
      AutoRangeArray::CollectNonEditableNodes::No);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
 | 
						|
        "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
      aHTMLEditor.MaybeSplitElementsAtEveryBRElement(
 | 
						|
          aOutArrayOfContents, EditSubAction::eCreateOrChangeList);
 | 
						|
  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
 | 
						|
        "eCreateOrChangeList) failed");
 | 
						|
    return splitAtBRElementsResult.unwrapErr();
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
bool HTMLEditor::AutoListElementCreator::
 | 
						|
    IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
 | 
						|
        const ContentNodeArray& aArrayOfContents) {
 | 
						|
  for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
 | 
						|
    // if content is not a <br> or empty inline, we're done
 | 
						|
    // XXX Should we handle line breaks in preformatted text node?
 | 
						|
    if (!content->IsHTMLElement(nsGkAtoms::br) &&
 | 
						|
        !HTMLEditUtils::IsEmptyInlineContainer(
 | 
						|
            content,
 | 
						|
            {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
             EmptyCheckOption::TreatNonEditableContentAsInvisible},
 | 
						|
            BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
Result<RefPtr<Element>, nsresult>
 | 
						|
HTMLEditor::AutoListElementCreator::ReplaceContentNodesWithEmptyNewList(
 | 
						|
    HTMLEditor& aHTMLEditor, const AutoRangeArray& aRanges,
 | 
						|
    const AutoContentNodeArray& aArrayOfContents,
 | 
						|
    const Element& aEditingHost) const {
 | 
						|
  // if only breaks, delete them
 | 
						|
  for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
 | 
						|
    // MOZ_KnownLive because of bug 1620312
 | 
						|
    nsresult rv =
 | 
						|
        aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*content));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const auto firstRangeStartPoint =
 | 
						|
      aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
  if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  // Make sure we can put a list here.
 | 
						|
  if (!HTMLEditUtils::CanNodeContain(*firstRangeStartPoint.GetContainer(),
 | 
						|
                                     mListTagName)) {
 | 
						|
    return RefPtr<Element>();
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Element> newListItemElement;
 | 
						|
  Result<CreateElementResult, nsresult> createNewListElementResult =
 | 
						|
      aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
          mListTagName, firstRangeStartPoint, BRElementNextToSplitPoint::Keep,
 | 
						|
          aEditingHost,
 | 
						|
          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
 | 
						|
          [&](HTMLEditor& aHTMLEditor, Element& aListElement,
 | 
						|
              const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
 | 
						|
            AutoHandlingState dummyState;
 | 
						|
            Result<CreateElementResult, nsresult> createListItemElementResult =
 | 
						|
                AppendListItemElement(aHTMLEditor, aListElement, dummyState);
 | 
						|
            if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
 | 
						|
              NS_WARNING(
 | 
						|
                  "AutoListElementCreator::AppendListItemElement() failed");
 | 
						|
              return createListItemElementResult.unwrapErr();
 | 
						|
            }
 | 
						|
            CreateElementResult unwrappedResult =
 | 
						|
                createListItemElementResult.unwrap();
 | 
						|
            // There is AutoSelectionRestorer in this method so that it'll
 | 
						|
            // be restored or updated with making it abort.  Therefore,
 | 
						|
            // we don't need to update selection here.
 | 
						|
            // XXX I'd like to check aRanges.HasSavedRanges() here, but it
 | 
						|
            //     requires ifdefs to avoid bustage of opt builds caused
 | 
						|
            //     by unused warning...
 | 
						|
            unwrappedResult.IgnoreCaretPointSuggestion();
 | 
						|
            newListItemElement = unwrappedResult.UnwrapNewNode();
 | 
						|
            MOZ_ASSERT(newListItemElement);
 | 
						|
            return NS_OK;
 | 
						|
          });
 | 
						|
  if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        nsPrintfCString(
 | 
						|
            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
            "%s) failed",
 | 
						|
            nsAtomCString(&mListTagName).get())
 | 
						|
            .get());
 | 
						|
    return createNewListElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(createNewListElementResult.inspect().GetNewNode());
 | 
						|
 | 
						|
  // Put selection in new list item and don't restore the Selection.
 | 
						|
  createNewListElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  return newListItemElement;
 | 
						|
}
 | 
						|
 | 
						|
Result<RefPtr<Element>, nsresult>
 | 
						|
HTMLEditor::AutoListElementCreator::WrapContentNodesIntoNewListElements(
 | 
						|
    HTMLEditor& aHTMLEditor, AutoRangeArray& aRanges,
 | 
						|
    AutoContentNodeArray& aArrayOfContents, const Element& aEditingHost) const {
 | 
						|
  // if there is only one node in the array, and it is a list, div, or
 | 
						|
  // blockquote, then look inside of it until we find inner list or content.
 | 
						|
  if (aArrayOfContents.Length() == 1) {
 | 
						|
    if (Element* deepestDivBlockquoteOrListElement =
 | 
						|
            HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
 | 
						|
                aArrayOfContents[0], {WalkTreeOption::IgnoreNonEditableNode},
 | 
						|
                BlockInlineCheck::UseHTMLDefaultStyle, nsGkAtoms::div,
 | 
						|
                nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol,
 | 
						|
                nsGkAtoms::dl)) {
 | 
						|
      if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
 | 
						|
              nsGkAtoms::div, nsGkAtoms::blockquote)) {
 | 
						|
        aArrayOfContents.Clear();
 | 
						|
        HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement,
 | 
						|
                                       aArrayOfContents, 0, {});
 | 
						|
      } else {
 | 
						|
        aArrayOfContents.ReplaceElementAt(
 | 
						|
            0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Ok, now go through all the nodes and put then in the list,
 | 
						|
  // or whatever is appropriate.  Wohoo!
 | 
						|
  AutoHandlingState handlingState;
 | 
						|
  for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
 | 
						|
    // MOZ_KnownLive because of bug 1620312
 | 
						|
    nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
 | 
						|
                                     handlingState, aEditingHost);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return std::move(handlingState.mListOrListItemElementToPutCaret);
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::HandleChildContent(
 | 
						|
    HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
 | 
						|
    AutoHandlingState& aState, const Element& aEditingHost) const {
 | 
						|
  // make sure we don't assemble content that is in different table cells
 | 
						|
  // into the same list.  respect table cell boundaries when listifying.
 | 
						|
  if (aState.mCurrentListElement &&
 | 
						|
      HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
 | 
						|
          *aState.mCurrentListElement) !=
 | 
						|
          HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
 | 
						|
              aHandlingContent)) {
 | 
						|
    aState.mCurrentListElement = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // If current node is a `<br>` element, delete it and forget previous
 | 
						|
  // list item element.
 | 
						|
  // If current node is an empty inline node, just delete it.
 | 
						|
  if (EditorUtils::IsEditableContent(aHandlingContent, EditorType::HTML) &&
 | 
						|
      (aHandlingContent.IsHTMLElement(nsGkAtoms::br) ||
 | 
						|
       HTMLEditUtils::IsEmptyInlineContainer(
 | 
						|
           aHandlingContent,
 | 
						|
           {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
            EmptyCheckOption::TreatNonEditableContentAsInvisible},
 | 
						|
           BlockInlineCheck::UseHTMLDefaultStyle))) {
 | 
						|
    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aHandlingContent);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    if (aHandlingContent.IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
      aState.mPreviousListItemElement = nullptr;
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we meet a list, we can reuse it or convert it to the expected type list.
 | 
						|
  if (HTMLEditUtils::IsAnyListElement(&aHandlingContent)) {
 | 
						|
    nsresult rv = HandleChildListElement(
 | 
						|
        aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "AutoListElementCreator::HandleChildListElement() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // We cannot handle nodes if not in element node.
 | 
						|
  if (NS_WARN_IF(!aHandlingContent.GetParentElement())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we meet a list item, we can just move it to current list element or new
 | 
						|
  // list element.
 | 
						|
  if (HTMLEditUtils::IsListItem(&aHandlingContent)) {
 | 
						|
    nsresult rv = HandleChildListItemElement(
 | 
						|
        aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "AutoListElementCreator::HandleChildListItemElement() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we meet a <div> or a <p>, we want only its children to wrapping into
 | 
						|
  // list element.  Therefore, this call will call this recursively.
 | 
						|
  if (aHandlingContent.IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::p)) {
 | 
						|
    nsresult rv = HandleChildDivOrParagraphElement(
 | 
						|
        aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState,
 | 
						|
        aEditingHost);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "AutoListElementCreator::HandleChildDivOrParagraphElement() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we've not met a list element, create a list element and make it
 | 
						|
  // current list element.
 | 
						|
  if (!aState.mCurrentListElement) {
 | 
						|
    nsresult rv = CreateAndUpdateCurrentListElement(
 | 
						|
        aHTMLEditor, EditorDOMPoint(&aHandlingContent),
 | 
						|
        EmptyListItem::NotCreate, aState, aEditingHost);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoListElementCreator::HandleChildInlineElement() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If we meet an inline content, we want to move it to previously used list
 | 
						|
  // item element or new list item element.
 | 
						|
  if (HTMLEditUtils::IsInlineContent(aHandlingContent,
 | 
						|
                                     BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
    nsresult rv =
 | 
						|
        HandleChildInlineContent(aHTMLEditor, aHandlingContent, aState);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "AutoListElementCreator::HandleChildInlineElement() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // Otherwise, we should wrap it into new list item element.
 | 
						|
  nsresult rv =
 | 
						|
      WrapContentIntoNewListItemElement(aHTMLEditor, aHandlingContent, aState);
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::HandleChildListElement(
 | 
						|
    HTMLEditor& aHTMLEditor, Element& aHandlingListElement,
 | 
						|
    AutoHandlingState& aState) const {
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aHandlingListElement));
 | 
						|
 | 
						|
  // If we met a list element and current list element is not a descendant
 | 
						|
  // of the list, append current node to end of the current list element.
 | 
						|
  // Then, wrap it with list item element and delete the old container.
 | 
						|
  if (aState.mCurrentListElement &&
 | 
						|
      !EditorUtils::IsDescendantOf(aHandlingListElement,
 | 
						|
                                   *aState.mCurrentListElement)) {
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        aHTMLEditor.MoveNodeToEndWithTransaction(
 | 
						|
            aHandlingListElement, MOZ_KnownLive(*aState.mCurrentListElement));
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
      return moveNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    moveNodeResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
    Result<CreateElementResult, nsresult> convertListTypeResult =
 | 
						|
        aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
 | 
						|
                                          mListItemTagName);
 | 
						|
    if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::ChangeListElementType() failed");
 | 
						|
      return convertListTypeResult.propagateErr();
 | 
						|
    }
 | 
						|
    convertListTypeResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
    Result<EditorDOMPoint, nsresult> unwrapNewListElementResult =
 | 
						|
        aHTMLEditor.RemoveBlockContainerWithTransaction(
 | 
						|
            MOZ_KnownLive(*convertListTypeResult.inspect().GetNewNode()));
 | 
						|
    if (MOZ_UNLIKELY(unwrapNewListElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
      return unwrapNewListElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    aState.mPreviousListItemElement = nullptr;
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // If current list element is in found list element or we've not met a
 | 
						|
  // list element, convert current list element to proper type.
 | 
						|
  Result<CreateElementResult, nsresult> convertListTypeResult =
 | 
						|
      aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
 | 
						|
                                        mListItemTagName);
 | 
						|
  if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::ChangeListElementType() failed");
 | 
						|
    return convertListTypeResult.propagateErr();
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedConvertListTypeResult =
 | 
						|
      convertListTypeResult.unwrap();
 | 
						|
  unwrappedConvertListTypeResult.IgnoreCaretPointSuggestion();
 | 
						|
  MOZ_ASSERT(unwrappedConvertListTypeResult.GetNewNode());
 | 
						|
  aState.mCurrentListElement = unwrappedConvertListTypeResult.UnwrapNewNode();
 | 
						|
  aState.mPreviousListItemElement = nullptr;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult
 | 
						|
HTMLEditor::AutoListElementCreator::HandleChildListItemInDifferentTypeList(
 | 
						|
    HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
 | 
						|
    AutoHandlingState& aState) const {
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
 | 
						|
  MOZ_ASSERT(
 | 
						|
      !aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
 | 
						|
 | 
						|
  // If we've not met a list element or current node is not in current list
 | 
						|
  // element, insert a list element at current node and set current list element
 | 
						|
  // to the new one.
 | 
						|
  if (!aState.mCurrentListElement ||
 | 
						|
      aHandlingListItemElement.IsInclusiveDescendantOf(
 | 
						|
          aState.mCurrentListElement)) {
 | 
						|
    EditorDOMPoint atListItem(&aHandlingListItemElement);
 | 
						|
    MOZ_ASSERT(atListItem.IsInContentNode());
 | 
						|
 | 
						|
    Result<SplitNodeResult, nsresult> splitListItemParentResult =
 | 
						|
        aHTMLEditor.SplitNodeWithTransaction(atListItem);
 | 
						|
    if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
 | 
						|
      return splitListItemParentResult.propagateErr();
 | 
						|
    }
 | 
						|
    SplitNodeResult unwrappedSplitListItemParentResult =
 | 
						|
        splitListItemParentResult.unwrap();
 | 
						|
    MOZ_ASSERT(unwrappedSplitListItemParentResult.DidSplit());
 | 
						|
    unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
    Result<CreateElementResult, nsresult> createNewListElementResult =
 | 
						|
        aHTMLEditor.CreateAndInsertElement(
 | 
						|
            WithTransaction::Yes, mListTagName,
 | 
						|
            unwrappedSplitListItemParentResult.AtNextContent<EditorDOMPoint>());
 | 
						|
    if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
 | 
						|
          "failed");
 | 
						|
      return createNewListElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrapCreateNewListElementResult =
 | 
						|
        createNewListElementResult.unwrap();
 | 
						|
    unwrapCreateNewListElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    MOZ_ASSERT(unwrapCreateNewListElementResult.GetNewNode());
 | 
						|
    aState.mCurrentListElement =
 | 
						|
        unwrapCreateNewListElementResult.UnwrapNewNode();
 | 
						|
  }
 | 
						|
 | 
						|
  // Then, move current node into current list element.
 | 
						|
  Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
      aHTMLEditor.MoveNodeToEndWithTransaction(
 | 
						|
          aHandlingListItemElement, MOZ_KnownLive(*aState.mCurrentListElement));
 | 
						|
  if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
    return moveNodeResult.propagateErr();
 | 
						|
  }
 | 
						|
  moveNodeResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  // Convert list item type if current node is different list item type.
 | 
						|
  if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  Result<CreateElementResult, nsresult> newListItemElementOrError =
 | 
						|
      aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
 | 
						|
          aHandlingListItemElement, mListItemTagName);
 | 
						|
  if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
 | 
						|
    return newListItemElementOrError.propagateErr();
 | 
						|
  }
 | 
						|
  newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemElement(
 | 
						|
    HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
 | 
						|
    AutoHandlingState& aState) const {
 | 
						|
  MOZ_ASSERT(aHandlingListItemElement.GetParentNode());
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
 | 
						|
 | 
						|
  // If current list item element is not in proper list element, we need
 | 
						|
  // to convert the list element.
 | 
						|
  // XXX This check is not enough,
 | 
						|
  if (!aHandlingListItemElement.GetParentNode()->IsHTMLElement(&mListTagName)) {
 | 
						|
    nsresult rv = HandleChildListItemInDifferentTypeList(
 | 
						|
        aHTMLEditor, aHandlingListItemElement, aState);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoListElementCreator::HandleChildListItemInDifferentTypeList() "
 | 
						|
          "failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    nsresult rv = HandleChildListItemInSameTypeList(
 | 
						|
        aHTMLEditor, aHandlingListItemElement, aState);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoListElementCreator::HandleChildListItemInSameTypeList() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If bullet type is specified, set list type attribute.
 | 
						|
  // XXX Cannot we set type attribute before inserting the list item
 | 
						|
  //     element into the DOM tree?
 | 
						|
  if (!mBulletType.IsEmpty()) {
 | 
						|
    nsresult rv = aHTMLEditor.SetAttributeWithTransaction(
 | 
						|
        aHandlingListItemElement, *nsGkAtoms::type, mBulletType);
 | 
						|
    if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // Otherwise, remove list type attribute if there is.
 | 
						|
  if (!aHandlingListItemElement.HasAttr(nsGkAtoms::type)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  nsresult rv = aHTMLEditor.RemoveAttributeWithTransaction(
 | 
						|
      aHandlingListItemElement, *nsGkAtoms::type);
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemInSameTypeList(
 | 
						|
    HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
 | 
						|
    AutoHandlingState& aState) const {
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsListItem(&aHandlingListItemElement));
 | 
						|
  MOZ_ASSERT(
 | 
						|
      aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
 | 
						|
 | 
						|
  EditorDOMPoint atListItem(&aHandlingListItemElement);
 | 
						|
  MOZ_ASSERT(atListItem.IsInContentNode());
 | 
						|
 | 
						|
  // If we've not met a list element, set current list element to the
 | 
						|
  // parent of current list item element.
 | 
						|
  if (!aState.mCurrentListElement) {
 | 
						|
    aState.mCurrentListElement = atListItem.GetContainerAs<Element>();
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        HTMLEditUtils::IsAnyListElement(aState.mCurrentListElement),
 | 
						|
        "Current list item parent is not a list element");
 | 
						|
  }
 | 
						|
  // If current list item element is not a child of current list element,
 | 
						|
  // move it into current list item.
 | 
						|
  else if (atListItem.GetContainer() != aState.mCurrentListElement) {
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        aHTMLEditor.MoveNodeToEndWithTransaction(
 | 
						|
            aHandlingListItemElement,
 | 
						|
            MOZ_KnownLive(*aState.mCurrentListElement));
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
      return moveNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    moveNodeResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  }
 | 
						|
 | 
						|
  // Then, if current list item element is not proper type for current
 | 
						|
  // list element, convert list item element to proper element.
 | 
						|
  if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  // FIXME: Manage attribute cloning
 | 
						|
  Result<CreateElementResult, nsresult> newListItemElementOrError =
 | 
						|
      aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
 | 
						|
          aHandlingListItemElement, mListItemTagName);
 | 
						|
  if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
 | 
						|
        "failed");
 | 
						|
    return newListItemElementOrError.propagateErr();
 | 
						|
  }
 | 
						|
  newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::HandleChildDivOrParagraphElement(
 | 
						|
    HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement,
 | 
						|
    AutoHandlingState& aState, const Element& aEditingHost) const {
 | 
						|
  MOZ_ASSERT(aHandlingDivOrParagraphElement.IsAnyOfHTMLElements(nsGkAtoms::div,
 | 
						|
                                                                nsGkAtoms::p));
 | 
						|
 | 
						|
  AutoRestore<RefPtr<Element>> previouslyReplacingBlockElement(
 | 
						|
      aState.mReplacingBlockElement);
 | 
						|
  aState.mReplacingBlockElement = &aHandlingDivOrParagraphElement;
 | 
						|
  AutoRestore<bool> previouslyReplacingBlockElementIdCopied(
 | 
						|
      aState.mMaybeCopiedReplacingBlockElementId);
 | 
						|
  aState.mMaybeCopiedReplacingBlockElementId = false;
 | 
						|
 | 
						|
  // If the <div> or <p> is empty, we should replace it with a list element
 | 
						|
  // and/or a list item element.
 | 
						|
  if (HTMLEditUtils::IsEmptyNode(aHandlingDivOrParagraphElement,
 | 
						|
                                 {EmptyCheckOption::TreatListItemAsVisible,
 | 
						|
                                  EmptyCheckOption::TreatTableCellAsVisible})) {
 | 
						|
    if (!aState.mCurrentListElement) {
 | 
						|
      nsresult rv = CreateAndUpdateCurrentListElement(
 | 
						|
          aHTMLEditor, EditorDOMPoint(&aHandlingDivOrParagraphElement),
 | 
						|
          EmptyListItem::Create, aState, aEditingHost);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING(
 | 
						|
            "AutoListElementCreator::CreateAndUpdateCurrentListElement("
 | 
						|
            "EmptyListItem::Create) failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      Result<CreateElementResult, nsresult> createListItemElementResult =
 | 
						|
          AppendListItemElement(
 | 
						|
              aHTMLEditor, MOZ_KnownLive(*aState.mCurrentListElement), aState);
 | 
						|
      if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
 | 
						|
        NS_WARNING("AutoListElementCreator::AppendListItemElement() failed");
 | 
						|
        return createListItemElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedResult =
 | 
						|
          createListItemElementResult.unwrap();
 | 
						|
      unwrappedResult.IgnoreCaretPointSuggestion();
 | 
						|
      aState.mListOrListItemElementToPutCaret = unwrappedResult.UnwrapNewNode();
 | 
						|
    }
 | 
						|
    nsresult rv =
 | 
						|
        aHTMLEditor.DeleteNodeWithTransaction(aHandlingDivOrParagraphElement);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    // We don't want new inline contents inserted into the new list item element
 | 
						|
    // because we want to keep the line break at end of
 | 
						|
    // aHandlingDivOrParagraphElement.
 | 
						|
    aState.mPreviousListItemElement = nullptr;
 | 
						|
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // If current node is a <div> element, replace it with its children and handle
 | 
						|
  // them as same as topmost children in the range.
 | 
						|
  AutoContentNodeArray arrayOfContentsInDiv;
 | 
						|
  HTMLEditUtils::CollectChildren(aHandlingDivOrParagraphElement,
 | 
						|
                                 arrayOfContentsInDiv, 0,
 | 
						|
                                 {CollectChildrenOption::CollectListChildren,
 | 
						|
                                  CollectChildrenOption::CollectTableChildren});
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
 | 
						|
      aHTMLEditor.RemoveContainerWithTransaction(
 | 
						|
          aHandlingDivOrParagraphElement);
 | 
						|
  if (MOZ_UNLIKELY(unwrapDivElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
 | 
						|
    return unwrapDivElementResult.unwrapErr();
 | 
						|
  }
 | 
						|
 | 
						|
  for (const OwningNonNull<nsIContent>& content : arrayOfContentsInDiv) {
 | 
						|
    // MOZ_KnownLive because of bug 1620312
 | 
						|
    nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
 | 
						|
                                     aState, aEditingHost);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // We don't want new inline contents inserted into the new list item element
 | 
						|
  // because we want to keep the line break at end of
 | 
						|
  // aHandlingDivOrParagraphElement.
 | 
						|
  aState.mPreviousListItemElement = nullptr;
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::CreateAndUpdateCurrentListElement(
 | 
						|
    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
 | 
						|
    EmptyListItem aEmptyListItem, AutoHandlingState& aState,
 | 
						|
    const Element& aEditingHost) const {
 | 
						|
  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
 | 
						|
 | 
						|
  aState.mPreviousListItemElement = nullptr;
 | 
						|
  RefPtr<Element> newListItemElement;
 | 
						|
  auto initializer =
 | 
						|
      // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
 | 
						|
      [&](HTMLEditor&, Element& aListElement, const EditorDOMPoint&)
 | 
						|
          MOZ_CAN_RUN_SCRIPT_BOUNDARY {
 | 
						|
            // If the replacing element has `dir` attribute, the new list
 | 
						|
            // element should take it to correct its list marker position.
 | 
						|
            if (aState.mReplacingBlockElement) {
 | 
						|
              nsString dirValue;
 | 
						|
              if (aState.mReplacingBlockElement->GetAttr(nsGkAtoms::dir,
 | 
						|
                                                         dirValue) &&
 | 
						|
                  !dirValue.IsEmpty()) {
 | 
						|
                // We don't need to use transaction to set `dir` attribute here
 | 
						|
                // because the element will be stored with the `dir` attribute
 | 
						|
                // in InsertNodeTransaction.  Therefore, undo should work.
 | 
						|
                IgnoredErrorResult ignoredError;
 | 
						|
                aListElement.SetAttr(nsGkAtoms::dir, dirValue, ignoredError);
 | 
						|
                NS_WARNING_ASSERTION(
 | 
						|
                    !ignoredError.Failed(),
 | 
						|
                    "Element::SetAttr(nsGkAtoms::dir) failed, but ignored");
 | 
						|
              }
 | 
						|
            }
 | 
						|
            if (aEmptyListItem == EmptyListItem::Create) {
 | 
						|
              Result<CreateElementResult, nsresult> createNewListItemResult =
 | 
						|
                  AppendListItemElement(aHTMLEditor, aListElement, aState);
 | 
						|
              if (MOZ_UNLIKELY(createNewListItemResult.isErr())) {
 | 
						|
                NS_WARNING(
 | 
						|
                    "HTMLEditor::AppendNewElementToInsertingElement()"
 | 
						|
                    " failed");
 | 
						|
                return createNewListItemResult.unwrapErr();
 | 
						|
              }
 | 
						|
              CreateElementResult unwrappedResult =
 | 
						|
                  createNewListItemResult.unwrap();
 | 
						|
              unwrappedResult.IgnoreCaretPointSuggestion();
 | 
						|
              newListItemElement = unwrappedResult.UnwrapNewNode();
 | 
						|
            }
 | 
						|
            return NS_OK;
 | 
						|
          };
 | 
						|
  Result<CreateElementResult, nsresult> createNewListElementResult =
 | 
						|
      aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
          mListTagName, aPointToInsert, BRElementNextToSplitPoint::Keep,
 | 
						|
          aEditingHost, initializer);
 | 
						|
  if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        nsPrintfCString(
 | 
						|
            "HTMLEditor::"
 | 
						|
            "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
 | 
						|
            nsAtomCString(&mListTagName).get())
 | 
						|
            .get());
 | 
						|
    return createNewListElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedCreateNewListElementResult =
 | 
						|
      createNewListElementResult.unwrap();
 | 
						|
  unwrappedCreateNewListElementResult.IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
 | 
						|
  aState.mListOrListItemElementToPutCaret =
 | 
						|
      newListItemElement ? newListItemElement.get()
 | 
						|
                         : unwrappedCreateNewListElementResult.GetNewNode();
 | 
						|
  aState.mCurrentListElement =
 | 
						|
      unwrappedCreateNewListElementResult.UnwrapNewNode();
 | 
						|
  aState.mPreviousListItemElement = std::move(newListItemElement);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::MaybeCloneAttributesToNewListItem(
 | 
						|
    HTMLEditor& aHTMLEditor, Element& aListItemElement,
 | 
						|
    AutoHandlingState& aState) {
 | 
						|
  if (!aState.mReplacingBlockElement) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  // If we're replacing a block element, the list items should have attributes
 | 
						|
  // of the replacing element. However, we don't want to copy `dir` attribute
 | 
						|
  // because it does not affect content in list item element and setting
 | 
						|
  // opposite direction from the parent list causes the marker invisible.
 | 
						|
  // Therefore, we don't want to take it. Finally, we don't need to use
 | 
						|
  // transaction to copy the attributes here because the element will be stored
 | 
						|
  // with the attributes in InsertNodeTransaction.  Therefore, undo should work.
 | 
						|
  nsresult rv = aHTMLEditor.CopyAttributes(
 | 
						|
      WithTransaction::No, aListItemElement,
 | 
						|
      MOZ_KnownLive(*aState.mReplacingBlockElement),
 | 
						|
      aState.mMaybeCopiedReplacingBlockElementId
 | 
						|
          ? HTMLEditor::CopyAllAttributesExceptIdAndDir
 | 
						|
          : HTMLEditor::CopyAllAttributesExceptDir);
 | 
						|
  aState.mMaybeCopiedReplacingBlockElementId = true;
 | 
						|
  if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
 | 
						|
    return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "HTMLEditor::CopyAttributes(WithTransaction::No) failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult>
 | 
						|
HTMLEditor::AutoListElementCreator::AppendListItemElement(
 | 
						|
    HTMLEditor& aHTMLEditor, const Element& aListElement,
 | 
						|
    AutoHandlingState& aState) const {
 | 
						|
  const WithTransaction withTransaction = aListElement.IsInComposedDoc()
 | 
						|
                                              ? WithTransaction::Yes
 | 
						|
                                              : WithTransaction::No;
 | 
						|
  Result<CreateElementResult, nsresult> createNewListItemResult =
 | 
						|
      aHTMLEditor.CreateAndInsertElement(
 | 
						|
          withTransaction, mListItemTagName,
 | 
						|
          EditorDOMPoint::AtEndOf(aListElement),
 | 
						|
          !aState.mReplacingBlockElement
 | 
						|
              ? HTMLEditor::DoNothingForNewElement
 | 
						|
              // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
 | 
						|
              : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
 | 
						|
                          const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
 | 
						|
                  nsresult rv =
 | 
						|
                      AutoListElementCreator::MaybeCloneAttributesToNewListItem(
 | 
						|
                          aHTMLEditor, aListItemElement, aState);
 | 
						|
                  NS_WARNING_ASSERTION(
 | 
						|
                      NS_SUCCEEDED(rv),
 | 
						|
                      "AutoListElementCreator::"
 | 
						|
                      "MaybeCloneAttributesToNewListItem() failed");
 | 
						|
                  return rv;
 | 
						|
                });
 | 
						|
  NS_WARNING_ASSERTION(createNewListItemResult.isOk(),
 | 
						|
                       "HTMLEditor::CreateAndInsertElement() failed");
 | 
						|
  return createNewListItemResult;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::HandleChildInlineContent(
 | 
						|
    HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent,
 | 
						|
    AutoHandlingState& aState) const {
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsInlineContent(
 | 
						|
      aHandlingInlineContent, BlockInlineCheck::UseHTMLDefaultStyle));
 | 
						|
 | 
						|
  // If we're currently handling contents of a list item and current node
 | 
						|
  // is not a block element, move current node into the list item.
 | 
						|
  if (!aState.mPreviousListItemElement) {
 | 
						|
    nsresult rv = WrapContentIntoNewListItemElement(
 | 
						|
        aHTMLEditor, aHandlingInlineContent, aState);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  Result<MoveNodeResult, nsresult> moveInlineElementResult =
 | 
						|
      aHTMLEditor.MoveNodeToEndWithTransaction(
 | 
						|
          aHandlingInlineContent,
 | 
						|
          MOZ_KnownLive(*aState.mPreviousListItemElement));
 | 
						|
  if (MOZ_UNLIKELY(moveInlineElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
    return moveInlineElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  moveInlineElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::WrapContentIntoNewListItemElement(
 | 
						|
    HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
 | 
						|
    AutoHandlingState& aState) const {
 | 
						|
  // If current node is not a paragraph, wrap current node with new list
 | 
						|
  // item element and move it into current list element.
 | 
						|
  Result<CreateElementResult, nsresult> wrapContentInListItemElementResult =
 | 
						|
      aHTMLEditor.InsertContainerWithTransaction(
 | 
						|
          aHandlingContent, mListItemTagName,
 | 
						|
          !aState.mReplacingBlockElement
 | 
						|
              ? HTMLEditor::DoNothingForNewElement
 | 
						|
              // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
 | 
						|
              : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
 | 
						|
                          const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
 | 
						|
                  nsresult rv =
 | 
						|
                      AutoListElementCreator::MaybeCloneAttributesToNewListItem(
 | 
						|
                          aHTMLEditor, aListItemElement, aState);
 | 
						|
                  NS_WARNING_ASSERTION(
 | 
						|
                      NS_SUCCEEDED(rv),
 | 
						|
                      "AutoListElementCreator::"
 | 
						|
                      "MaybeCloneAttributesToNewListItem() failed");
 | 
						|
                  return rv;
 | 
						|
                });
 | 
						|
  if (MOZ_UNLIKELY(wrapContentInListItemElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
 | 
						|
    return wrapContentInListItemElementResult.unwrapErr();
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedWrapContentInListItemElementResult =
 | 
						|
      wrapContentInListItemElementResult.unwrap();
 | 
						|
  unwrappedWrapContentInListItemElementResult.IgnoreCaretPointSuggestion();
 | 
						|
  MOZ_ASSERT(unwrappedWrapContentInListItemElementResult.GetNewNode());
 | 
						|
 | 
						|
  // MOZ_KnownLive(unwrappedWrapContentInListItemElementResult.GetNewNode()):
 | 
						|
  // The result is grabbed by unwrappedWrapContentInListItemElementResult.
 | 
						|
  Result<MoveNodeResult, nsresult> moveListItemElementResult =
 | 
						|
      aHTMLEditor.MoveNodeToEndWithTransaction(
 | 
						|
          MOZ_KnownLive(
 | 
						|
              *unwrappedWrapContentInListItemElementResult.GetNewNode()),
 | 
						|
          MOZ_KnownLive(*aState.mCurrentListElement));
 | 
						|
  if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
    return moveListItemElementResult.unwrapErr();
 | 
						|
  }
 | 
						|
  moveListItemElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  // If current node is not a block element, new list item should have
 | 
						|
  // following inline nodes too.
 | 
						|
  if (HTMLEditUtils::IsInlineContent(aHandlingContent,
 | 
						|
                                     BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
    aState.mPreviousListItemElement =
 | 
						|
        unwrappedWrapContentInListItemElementResult.UnwrapNewNode();
 | 
						|
  } else {
 | 
						|
    aState.mPreviousListItemElement = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX Why don't we set `type` attribute here??
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AutoListElementCreator::
 | 
						|
    EnsureCollapsedRangeIsInListItemOrListElement(
 | 
						|
        Element& aListItemOrListToPutCaret, AutoRangeArray& aRanges) const {
 | 
						|
  if (!aRanges.IsCollapsed() || aRanges.Ranges().IsEmpty()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  const auto firstRangeStartPoint =
 | 
						|
      aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
 | 
						|
  if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
      HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
 | 
						|
          EditorRawDOMPoint>(aListItemOrListToPutCaret, firstRangeStartPoint);
 | 
						|
  if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditUtils::ComputePointToPutCaretInElementIfOutside()");
 | 
						|
    return pointToPutCaretOrError.unwrapErr();
 | 
						|
  }
 | 
						|
  if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
    nsresult rv = aRanges.Collapse(pointToPutCaretOrError.inspect());
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoRangeArray::Collapse() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::RemoveListAtSelectionAsSubAction(
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  {
 | 
						|
    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 error;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eRemoveList, nsIEditor::eNext, error);
 | 
						|
  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return error.StealNSResult();
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !error.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  // XXX Why do we do this only when there is only one selection range?
 | 
						|
  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            SelectionRef().GetRangeAt(0u), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.unwrapErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we
 | 
						|
    // cannot use Selection::SetStartAndEndInLimit() here.
 | 
						|
    error.SuppressException();
 | 
						|
    SelectionRef().SetBaseAndExtentInLimiter(
 | 
						|
        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
 | 
						|
        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    if (error.Failed()) {
 | 
						|
      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
 | 
						|
      return error.StealNSResult();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  AutoSelectionRestorer restoreSelectionLater(*this);
 | 
						|
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
 | 
						|
  {
 | 
						|
    // 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);
 | 
						|
 | 
						|
    {
 | 
						|
      AutoRangeArray extendedSelectionRanges(SelectionRef());
 | 
						|
      extendedSelectionRanges.ExtendRangesToWrapLines(
 | 
						|
          EditSubAction::eCreateOrChangeList,
 | 
						|
          BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
      Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
          extendedSelectionRanges
 | 
						|
              .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
 | 
						|
                  *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "AutoRangeArray::"
 | 
						|
            "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
 | 
						|
            "failed");
 | 
						|
        return splitResult.unwrapErr();
 | 
						|
      }
 | 
						|
      nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
 | 
						|
          *this, arrayOfContents, EditSubAction::eCreateOrChangeList,
 | 
						|
          AutoRangeArray::CollectNonEditableNodes::No);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING(
 | 
						|
            "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
 | 
						|
            "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    const Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
        MaybeSplitElementsAtEveryBRElement(arrayOfContents,
 | 
						|
                                           EditSubAction::eCreateOrChangeList);
 | 
						|
    if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
 | 
						|
          "eCreateOrChangeList) failed");
 | 
						|
      return splitAtBRElementsResult.inspectErr();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Remove all non-editable nodes.  Leave them be.
 | 
						|
  // XXX CollectEditTargetNodes() should return only editable contents when it's
 | 
						|
  //     called with CollectNonEditableNodes::No, but checking it here, looks
 | 
						|
  //     like just wasting the runtime cost.
 | 
						|
  for (int32_t i = arrayOfContents.Length() - 1; i >= 0; i--) {
 | 
						|
    OwningNonNull<nsIContent>& content = arrayOfContents[i];
 | 
						|
    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
 | 
						|
      arrayOfContents.RemoveElementAt(i);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Only act on lists or list items in the array
 | 
						|
  for (auto& content : arrayOfContents) {
 | 
						|
    // here's where we actually figure out what to do
 | 
						|
    if (HTMLEditUtils::IsListItem(content)) {
 | 
						|
      // unlist this listitem
 | 
						|
      nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
 | 
						|
                                          LiftUpFromAllParentListElements::Yes);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
 | 
						|
            ":Yes) failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(content)) {
 | 
						|
      // node is a list, move list items out
 | 
						|
      nsresult rv =
 | 
						|
          DestroyListStructureRecursively(MOZ_KnownLive(*content->AsElement()));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Result<RefPtr<Element>, nsresult>
 | 
						|
HTMLEditor::FormatBlockContainerWithTransaction(
 | 
						|
    AutoRangeArray& aSelectionRanges, const nsStaticAtom& aNewFormatTagName,
 | 
						|
    FormatBlockMode aFormatBlockMode, const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  // XXX Why do we do this only when there is only one selection range?
 | 
						|
  if (!aSelectionRanges.IsCollapsed() &&
 | 
						|
      aSelectionRanges.Ranges().Length() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            aSelectionRanges.FirstRangeRef(), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.propagateErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we
 | 
						|
    // cannot use AutoRangeArray::SetStartAndEnd() here.
 | 
						|
    if (NS_FAILED(aSelectionRanges.SetBaseAndExtent(
 | 
						|
            extendedRange.inspect().StartRef(),
 | 
						|
            extendedRange.inspect().EndRef()))) {
 | 
						|
      NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ALWAYS_TRUE(aSelectionRanges.SaveAndTrackRanges(*this));
 | 
						|
 | 
						|
  // 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);
 | 
						|
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
 | 
						|
  aSelectionRanges.ExtendRangesToWrapLines(
 | 
						|
      aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
 | 
						|
          ? EditSubAction::eFormatBlockForHTMLCommand
 | 
						|
          : EditSubAction::eCreateOrRemoveBlock,
 | 
						|
      BlockInlineCheck::UseComputedDisplayOutsideStyle, aEditingHost);
 | 
						|
  Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
      aSelectionRanges
 | 
						|
          .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
 | 
						|
              *this, BlockInlineCheck::UseComputedDisplayOutsideStyle,
 | 
						|
              aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AutoRangeArray::"
 | 
						|
        "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
 | 
						|
    return splitResult.propagateErr();
 | 
						|
  }
 | 
						|
  nsresult rv = aSelectionRanges.CollectEditTargetNodes(
 | 
						|
      *this, arrayOfContents,
 | 
						|
      aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
 | 
						|
          ? EditSubAction::eFormatBlockForHTMLCommand
 | 
						|
          : EditSubAction::eCreateOrRemoveBlock,
 | 
						|
      AutoRangeArray::CollectNonEditableNodes::Yes);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AutoRangeArray::CollectEditTargetNodes(CollectNonEditableNodes::No) "
 | 
						|
        "failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
      MaybeSplitElementsAtEveryBRElement(
 | 
						|
          arrayOfContents,
 | 
						|
          aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
 | 
						|
              ? EditSubAction::eFormatBlockForHTMLCommand
 | 
						|
              : EditSubAction::eCreateOrRemoveBlock);
 | 
						|
  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed");
 | 
						|
    return splitAtBRElementsResult.propagateErr();
 | 
						|
  }
 | 
						|
 | 
						|
  // If there is no visible and editable nodes in the edit targets, make an
 | 
						|
  // empty block.
 | 
						|
  // XXX Isn't this odd if there are only non-editable visible nodes?
 | 
						|
  if (HTMLEditUtils::IsEmptyOneHardLine(
 | 
						|
          arrayOfContents, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
 | 
						|
    if (NS_WARN_IF(aSelectionRanges.Ranges().IsEmpty())) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
 | 
						|
    auto pointToInsertBlock =
 | 
						|
        aSelectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
    if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand &&
 | 
						|
        (&aNewFormatTagName == nsGkAtoms::normal ||
 | 
						|
         &aNewFormatTagName == nsGkAtoms::_empty)) {
 | 
						|
      if (!pointToInsertBlock.IsInContentNode()) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
 | 
						|
            "block parent because container of the point is not content");
 | 
						|
        return Err(NS_ERROR_FAILURE);
 | 
						|
      }
 | 
						|
      // We are removing blocks (going to "body text")
 | 
						|
      const RefPtr<Element> editableBlockElement =
 | 
						|
          HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
              *pointToInsertBlock.ContainerAs<nsIContent>(),
 | 
						|
              HTMLEditUtils::ClosestEditableBlockElement,
 | 
						|
              BlockInlineCheck::UseComputedDisplayOutsideStyle);
 | 
						|
      if (!editableBlockElement) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
 | 
						|
            "block parent");
 | 
						|
        return Err(NS_ERROR_FAILURE);
 | 
						|
      }
 | 
						|
      if (editableBlockElement->IsAnyOfHTMLElements(
 | 
						|
              nsGkAtoms::dd, nsGkAtoms::dl, nsGkAtoms::dt) ||
 | 
						|
          !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
 | 
						|
              *editableBlockElement)) {
 | 
						|
        return RefPtr<Element>();
 | 
						|
      }
 | 
						|
 | 
						|
      // If the first editable node after selection is a br, consume it.
 | 
						|
      // Otherwise it gets pushed into a following block after the split,
 | 
						|
      // which is visually bad.
 | 
						|
      if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent(
 | 
						|
              pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode},
 | 
						|
              BlockInlineCheck::UseComputedDisplayOutsideStyle,
 | 
						|
              &aEditingHost)) {
 | 
						|
        if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
          AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
 | 
						|
          nsresult rv = DeleteNodeWithTransaction(*brContent);
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
            return Err(rv);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
      // Do the splits!
 | 
						|
      Result<SplitNodeResult, nsresult> splitNodeResult =
 | 
						|
          SplitNodeDeepWithTransaction(
 | 
						|
              *editableBlockElement, pointToInsertBlock,
 | 
						|
              SplitAtEdges::eDoNotCreateEmptyContainer);
 | 
						|
      if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
 | 
						|
        return splitNodeResult.propagateErr();
 | 
						|
      }
 | 
						|
      SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
 | 
						|
      unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
 | 
						|
      // Put a <br> element at the split point
 | 
						|
      Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
          InsertBRElement(
 | 
						|
              WithTransaction::Yes,
 | 
						|
              unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>());
 | 
						|
      if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
        return insertBRElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
 | 
						|
      aSelectionRanges.ClearSavedRanges();
 | 
						|
      nsresult rv = aSelectionRanges.Collapse(
 | 
						|
          EditorRawDOMPoint(insertBRElementResult.inspect().GetNewNode()));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("AutoRangeArray::Collapse() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      return RefPtr<Element>();
 | 
						|
    }
 | 
						|
 | 
						|
    // We are making a block.  Consume a br, if needed.
 | 
						|
    if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
 | 
						|
            pointToInsertBlock,
 | 
						|
            {WalkTreeOption::IgnoreNonEditableNode,
 | 
						|
             WalkTreeOption::StopAtBlockBoundary},
 | 
						|
            BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
 | 
						|
      if (maybeBRContent->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
        AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
 | 
						|
        nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
        // We don't need to act on this node any more
 | 
						|
        arrayOfContents.RemoveElement(maybeBRContent);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    // Make sure we can put a block here.
 | 
						|
    Result<CreateElementResult, nsresult> createNewBlockElementResult =
 | 
						|
        InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
            aNewFormatTagName, pointToInsertBlock,
 | 
						|
            BRElementNextToSplitPoint::Keep, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          nsPrintfCString(
 | 
						|
              "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
              "%s) failed",
 | 
						|
              nsAtomCString(&aNewFormatTagName).get())
 | 
						|
              .get());
 | 
						|
      return createNewBlockElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedCreateNewBlockElementResult =
 | 
						|
        createNewBlockElementResult.unwrap();
 | 
						|
    unwrappedCreateNewBlockElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
 | 
						|
 | 
						|
    // Delete anything that was in the list of nodes
 | 
						|
    while (!arrayOfContents.IsEmpty()) {
 | 
						|
      OwningNonNull<nsIContent>& content = arrayOfContents[0];
 | 
						|
      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
 | 
						|
      // keep it alive.
 | 
						|
      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      arrayOfContents.RemoveElementAt(0);
 | 
						|
    }
 | 
						|
    // Put selection in new block
 | 
						|
    aSelectionRanges.ClearSavedRanges();
 | 
						|
    nsresult rv = aSelectionRanges.Collapse(EditorRawDOMPoint(
 | 
						|
        unwrappedCreateNewBlockElementResult.GetNewNode(), 0u));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoRangeArray::Collapse() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return unwrappedCreateNewBlockElementResult.UnwrapNewNode();
 | 
						|
  }
 | 
						|
 | 
						|
  if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
 | 
						|
    // Okay, now go through all the nodes and make the right kind of blocks, or
 | 
						|
    // whatever is appropriate.
 | 
						|
    // Note: blockquote is handled a little differently.
 | 
						|
    if (&aNewFormatTagName == nsGkAtoms::blockquote) {
 | 
						|
      Result<CreateElementResult, nsresult>
 | 
						|
          wrapContentsInBlockquoteElementsResult =
 | 
						|
              WrapContentsInBlockquoteElementsWithTransaction(arrayOfContents,
 | 
						|
                                                              aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(wrapContentsInBlockquoteElementsResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
 | 
						|
            "failed");
 | 
						|
        return wrapContentsInBlockquoteElementsResult.propagateErr();
 | 
						|
      }
 | 
						|
      wrapContentsInBlockquoteElementsResult.inspect()
 | 
						|
          .IgnoreCaretPointSuggestion();
 | 
						|
      return wrapContentsInBlockquoteElementsResult.unwrap().UnwrapNewNode();
 | 
						|
    }
 | 
						|
    if (&aNewFormatTagName == nsGkAtoms::normal ||
 | 
						|
        &aNewFormatTagName == nsGkAtoms::_empty) {
 | 
						|
      Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
 | 
						|
          RemoveBlockContainerElementsWithTransaction(
 | 
						|
              arrayOfContents, FormatBlockMode::XULParagraphStateCommand,
 | 
						|
              BlockInlineCheck::UseComputedDisplayOutsideStyle);
 | 
						|
      if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
 | 
						|
        return removeBlockContainerElementsResult.propagateErr();
 | 
						|
      }
 | 
						|
      return RefPtr<Element>();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> wrapContentsInBlockElementResult =
 | 
						|
      CreateOrChangeFormatContainerElement(arrayOfContents, aNewFormatTagName,
 | 
						|
                                           aFormatBlockMode, aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(wrapContentsInBlockElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::CreateOrChangeFormatContainerElement() failed");
 | 
						|
    return wrapContentsInBlockElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  wrapContentsInBlockElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  return wrapContentsInBlockElementResult.unwrap().UnwrapNewNode();
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
 | 
						|
 | 
						|
  if (!SelectionRef().IsCollapsed()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  const nsRange* firstRange = SelectionRef().GetRangeAt(0);
 | 
						|
  if (NS_WARN_IF(!firstRange)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  const RangeBoundary& atStartOfSelection = firstRange->StartRef();
 | 
						|
  if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  if (!atStartOfSelection.Container()->IsElement()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  OwningNonNull<Element> startContainerElement =
 | 
						|
      *atStartOfSelection.Container()->AsElement();
 | 
						|
  nsresult rv =
 | 
						|
      InsertPaddingBRElementForEmptyLastLineIfNeeded(startContainerElement);
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::IndentAsSubAction(
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(
 | 
						|
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eIndent, nsIEditor::eNext, ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(ignoredError.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Some selection containers are not content node, but ignored");
 | 
						|
    return EditActionResult::IgnoredResult();
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditActionResult, nsresult> result =
 | 
						|
      HandleIndentAtSelection(aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::HandleIndentAtSelection() failed");
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
  if (result.inspect().Canceled()) {
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Mutation event listener might have changed selection");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  // TODO: Investigate when we need to put a `<br>` element after indenting
 | 
						|
  //       ranges.  Then, we could stop calling this here, or maybe we need to
 | 
						|
  //       do it while moving content nodes.
 | 
						|
  nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction(
 | 
						|
    RefPtr<Element>* aSubListElement, const EditorDOMPoint& aPointInListElement,
 | 
						|
    nsIContent& aContentMovingToSubList, const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(
 | 
						|
      HTMLEditUtils::IsAnyListElement(aPointInListElement.GetContainer()),
 | 
						|
      "unexpected container");
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  // some logic for putting list items into nested lists...
 | 
						|
 | 
						|
  // If aContentMovingToSubList is followed by a sub-list element whose tag is
 | 
						|
  // same as the parent list element's tag, we can move it to start of the
 | 
						|
  // sub-list.
 | 
						|
  if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
 | 
						|
          aContentMovingToSubList, {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
 | 
						|
                                    WalkTreeOption::IgnoreNonEditableNode})) {
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(nextEditableSibling) &&
 | 
						|
        aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
 | 
						|
            nextEditableSibling->NodeInfo()->NameAtom() &&
 | 
						|
        aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
 | 
						|
            nextEditableSibling->NodeInfo()->NamespaceID()) {
 | 
						|
      Result<MoveNodeResult, nsresult> moveListElementResult =
 | 
						|
          MoveNodeWithTransaction(aContentMovingToSubList,
 | 
						|
                                  EditorDOMPoint(nextEditableSibling, 0u));
 | 
						|
      if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
        return moveListElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      return moveListElementResult.unwrap().UnwrapCaretPoint();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If aContentMovingToSubList follows a sub-list element whose tag is same
 | 
						|
  // as the parent list element's tag, we can move it to end of the sub-list.
 | 
						|
  if (nsCOMPtr<nsIContent> previousEditableSibling =
 | 
						|
          HTMLEditUtils::GetPreviousSibling(
 | 
						|
              aContentMovingToSubList,
 | 
						|
              {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
 | 
						|
               WalkTreeOption::IgnoreNonEditableNode})) {
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(previousEditableSibling) &&
 | 
						|
        aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
 | 
						|
            previousEditableSibling->NodeInfo()->NameAtom() &&
 | 
						|
        aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
 | 
						|
            previousEditableSibling->NodeInfo()->NamespaceID()) {
 | 
						|
      Result<MoveNodeResult, nsresult> moveListElementResult =
 | 
						|
          MoveNodeToEndWithTransaction(aContentMovingToSubList,
 | 
						|
                                       *previousEditableSibling);
 | 
						|
      if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
        return moveListElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      return moveListElementResult.unwrap().UnwrapCaretPoint();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If aContentMovingToSubList does not follow aSubListElement, we need
 | 
						|
  // to create new sub-list element.
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  nsIContent* previousEditableSibling =
 | 
						|
      *aSubListElement ? HTMLEditUtils::GetPreviousSibling(
 | 
						|
                             aContentMovingToSubList,
 | 
						|
                             {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
 | 
						|
                              WalkTreeOption::IgnoreNonEditableNode})
 | 
						|
                       : nullptr;
 | 
						|
  if (!*aSubListElement || (previousEditableSibling &&
 | 
						|
                            previousEditableSibling != *aSubListElement)) {
 | 
						|
    nsAtom* containerName =
 | 
						|
        aPointInListElement.GetContainer()->NodeInfo()->NameAtom();
 | 
						|
    // Create a new nested list of correct type.
 | 
						|
    Result<CreateElementResult, nsresult> createNewListElementResult =
 | 
						|
        InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
            MOZ_KnownLive(*containerName), aPointInListElement,
 | 
						|
            BRElementNextToSplitPoint::Keep, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          nsPrintfCString(
 | 
						|
              "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
              "%s) failed",
 | 
						|
              nsAtomCString(containerName).get())
 | 
						|
              .get());
 | 
						|
      return createNewListElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedCreateNewListElementResult =
 | 
						|
        createNewListElementResult.unwrap();
 | 
						|
    MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
 | 
						|
    pointToPutCaret = unwrappedCreateNewListElementResult.UnwrapCaretPoint();
 | 
						|
    *aSubListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
 | 
						|
  }
 | 
						|
 | 
						|
  // Finally, we should move aContentMovingToSubList into aSubListElement.
 | 
						|
  const RefPtr<Element> subListElement = *aSubListElement;
 | 
						|
  Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
      MoveNodeToEndWithTransaction(aContentMovingToSubList, *subListElement);
 | 
						|
  if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
    return moveNodeResult.propagateErr();
 | 
						|
  }
 | 
						|
  MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
  if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
 | 
						|
    pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
 | 
						|
  }
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::HandleIndentAtSelection(
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  AutoRangeArray selectionRanges(SelectionRef());
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(!selectionRanges.IsInContent())) {
 | 
						|
    NS_WARNING("Mutation event listener might have changed the selection");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsCSSEnabled()) {
 | 
						|
    nsresult rv = HandleCSSIndentAroundRanges(selectionRanges, aEditingHost);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("HTMLEditor::HandleCSSIndentAroundRanges() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    nsresult rv = HandleHTMLIndentAroundRanges(selectionRanges, aEditingHost);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("HTMLEditor::HandleHTMLIndentAroundRanges() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  rv = selectionRanges.ApplyTo(SelectionRef());
 | 
						|
  if (MOZ_UNLIKELY(Destroyed())) {
 | 
						|
    NS_WARNING("AutoRangeArray::ApplyTo() caused destroying the editor");
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("AutoRangeArray::ApplyTo() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::HandleCSSIndentAroundRanges(AutoRangeArray& aRanges,
 | 
						|
                                                 const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
 | 
						|
  MOZ_ASSERT(aRanges.IsInContent());
 | 
						|
 | 
						|
  if (aRanges.Ranges().IsEmpty()) {
 | 
						|
    NS_WARNING("There is no selection range");
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX Why do we do this only when there is only one selection range?
 | 
						|
  if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            aRanges.FirstRangeRef(), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.unwrapErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we
 | 
						|
    // cannot use SetStartAndEnd() here.
 | 
						|
    nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
 | 
						|
                                           extendedRange.inspect().EndRef());
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
 | 
						|
 | 
						|
  // short circuit: detect case of collapsed selection inside an <li>.
 | 
						|
  // just sublist that <li>.  This prevents bug 97797.
 | 
						|
 | 
						|
  if (aRanges.IsCollapsed()) {
 | 
						|
    const auto atCaret = aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!atCaret.IsSet())) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    MOZ_ASSERT(atCaret.IsInContentNode());
 | 
						|
    Element* const editableBlockElement =
 | 
						|
        HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
            *atCaret.ContainerAs<nsIContent>(),
 | 
						|
            HTMLEditUtils::ClosestEditableBlockElement,
 | 
						|
            BlockInlineCheck::UseHTMLDefaultStyle);
 | 
						|
    if (editableBlockElement &&
 | 
						|
        HTMLEditUtils::IsListItem(editableBlockElement)) {
 | 
						|
      arrayOfContents.AppendElement(*editableBlockElement);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  if (arrayOfContents.IsEmpty()) {
 | 
						|
    {
 | 
						|
      AutoRangeArray extendedRanges(aRanges);
 | 
						|
      extendedRanges.ExtendRangesToWrapLines(
 | 
						|
          EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
 | 
						|
          aEditingHost);
 | 
						|
      Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
          extendedRanges
 | 
						|
              .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
 | 
						|
                  *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "AutoRangeArray::"
 | 
						|
            "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
 | 
						|
            "failed");
 | 
						|
        return splitResult.unwrapErr();
 | 
						|
      }
 | 
						|
      if (splitResult.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = splitResult.unwrap();
 | 
						|
      }
 | 
						|
      nsresult rv = extendedRanges.CollectEditTargetNodes(
 | 
						|
          *this, arrayOfContents, EditSubAction::eIndent,
 | 
						|
          AutoRangeArray::CollectNonEditableNodes::Yes);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING(
 | 
						|
            "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eIndent, "
 | 
						|
            "CollectNonEditableNodes::Yes) failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
        MaybeSplitElementsAtEveryBRElement(arrayOfContents,
 | 
						|
                                           EditSubAction::eIndent);
 | 
						|
    if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
 | 
						|
          "eIndent) failed");
 | 
						|
      return splitAtBRElementsResult.inspectErr();
 | 
						|
    }
 | 
						|
    if (splitAtBRElementsResult.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = splitAtBRElementsResult.unwrap();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If there is no visible and editable nodes in the edit targets, make an
 | 
						|
  // empty block.
 | 
						|
  // XXX Isn't this odd if there are only non-editable visible nodes?
 | 
						|
  if (HTMLEditUtils::IsEmptyOneHardLine(
 | 
						|
          arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
    const EditorDOMPoint pointToInsertDivElement =
 | 
						|
        pointToPutCaret.IsSet()
 | 
						|
            ? std::move(pointToPutCaret)
 | 
						|
            : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!pointToInsertDivElement.IsSet())) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    // make sure we can put a block here
 | 
						|
    Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
        InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
            *nsGkAtoms::div, pointToInsertDivElement,
 | 
						|
            BRElementNextToSplitPoint::Keep, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
          "nsGkAtoms::div) failed");
 | 
						|
      return createNewDivElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedCreateNewDivElementResult =
 | 
						|
        createNewDivElementResult.unwrap();
 | 
						|
    // We'll collapse ranges below, so we don't need to touch the ranges here.
 | 
						|
    unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    const RefPtr<Element> newDivElement =
 | 
						|
        unwrappedCreateNewDivElementResult.UnwrapNewNode();
 | 
						|
    MOZ_ASSERT(newDivElement);
 | 
						|
    const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
        ChangeMarginStart(*newDivElement, ChangeMargin::Increase, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
      if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
 | 
						|
                     NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
      }
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
 | 
						|
          "ignored");
 | 
						|
    }
 | 
						|
    // delete anything that was in the list of nodes
 | 
						|
    // XXX We don't need to remove the nodes from the array for performance.
 | 
						|
    for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
 | 
						|
      // MOZ_KnownLive(content) due to bug 1622253
 | 
						|
      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(content));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    aRanges.ClearSavedRanges();
 | 
						|
    nsresult rv = aRanges.Collapse(EditorDOMPoint(newDivElement, 0u));
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Element> latestNewBlockElement;
 | 
						|
  auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
 | 
						|
      [&]() -> nsresult {
 | 
						|
    MOZ_ASSERT(aRanges.HasSavedRanges());
 | 
						|
    aRanges.RestoreFromSavedRanges();
 | 
						|
 | 
						|
    if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
 | 
						|
        aRanges.Ranges().IsEmpty()) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    const auto firstRangeStartRawPoint =
 | 
						|
        aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
 | 
						|
    if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
    Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
 | 
						|
        HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
 | 
						|
            EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
 | 
						|
    if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
 | 
						|
          "but ignored");
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
    if (!pointInNewBlockElementOrError.inspect().IsSet()) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
    return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
 | 
						|
  };
 | 
						|
 | 
						|
  // Ok, now go through all the nodes and put them into sub-list element
 | 
						|
  // elements and new <div> elements which have start margin.
 | 
						|
  RefPtr<Element> subListElement, divElement;
 | 
						|
  for (OwningNonNull<nsIContent>& content : arrayOfContents) {
 | 
						|
    // Here's where we actually figure out what to do.
 | 
						|
    EditorDOMPoint atContent(content);
 | 
						|
    if (NS_WARN_IF(!atContent.IsSet())) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Ignore all non-editable nodes.  Leave them be.
 | 
						|
    // XXX We ignore non-editable nodes here, but not so in the above block.
 | 
						|
    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
 | 
						|
      const RefPtr<Element> oldSubListElement = subListElement;
 | 
						|
      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
 | 
						|
      // keep it alive.
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          IndentListChildWithTransaction(&subListElement, atContent,
 | 
						|
                                         MOZ_KnownLive(content), aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
 | 
						|
        return pointToPutCaretOrError.unwrapErr();
 | 
						|
      }
 | 
						|
      if (subListElement != oldSubListElement) {
 | 
						|
        // New list element is created, so we should put caret into the new list
 | 
						|
        // element.
 | 
						|
        latestNewBlockElement = subListElement;
 | 
						|
      }
 | 
						|
      if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Not a list item.
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsBlockElement(content,
 | 
						|
                                      BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
 | 
						|
                            ChangeMargin::Increase, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
 | 
						|
                         NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
 | 
						|
          return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
        }
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
 | 
						|
            "ignored");
 | 
						|
      } else if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
      divElement = nullptr;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!divElement) {
 | 
						|
      // First, check that our element can contain a div.
 | 
						|
      if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
 | 
						|
                                         *nsGkAtoms::div)) {
 | 
						|
        // XXX This is odd, why do we stop indenting remaining content nodes?
 | 
						|
        //     Perhaps, `continue` is better.
 | 
						|
        nsresult rv =
 | 
						|
            RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
 | 
						|
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                             "RestoreSavedRangesAndCollapseInLatestBlockElement"
 | 
						|
                             "IfOutside() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
 | 
						|
      Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
          InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
              *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
              aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
            "nsGkAtoms::div) failed");
 | 
						|
        return createNewDivElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedCreateNewDivElementResult =
 | 
						|
          createNewDivElementResult.unwrap();
 | 
						|
      pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
 | 
						|
 | 
						|
      MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
 | 
						|
      divElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          ChangeMarginStart(*divElement, ChangeMargin::Increase, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
 | 
						|
                         NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
 | 
						|
          return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
        }
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
 | 
						|
            "ignored");
 | 
						|
      } else if (AllowsTransactionsToChangeSelection() &&
 | 
						|
                 pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
 | 
						|
      latestNewBlockElement = divElement;
 | 
						|
    }
 | 
						|
 | 
						|
    // Move the content into the <div> which has start margin.
 | 
						|
    // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
 | 
						|
    // keep it alive.
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *divElement);
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
      return moveNodeResult.unwrapErr();
 | 
						|
    }
 | 
						|
    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
    if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
 | 
						|
      pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::HandleHTMLIndentAroundRanges(AutoRangeArray& aRanges,
 | 
						|
                                                  const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
 | 
						|
  MOZ_ASSERT(aRanges.IsInContent());
 | 
						|
 | 
						|
  // XXX Why do we do this only when there is only one range?
 | 
						|
  if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            aRanges.FirstRangeRef(), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.unwrapErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we cannot use
 | 
						|
    // SetStartAndEnd() here.
 | 
						|
    nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
 | 
						|
                                           extendedRange.inspect().EndRef());
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("AutoRangeArray::SetBaseAndExtent() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
 | 
						|
  // convert the selection ranges into "promoted" selection ranges:
 | 
						|
  // this basically just expands the range to include the immediate
 | 
						|
  // block parent, and then further expands to include any ancestors
 | 
						|
  // whose children are all in the range
 | 
						|
 | 
						|
  // use these ranges to construct a list of nodes to act on.
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
 | 
						|
  {
 | 
						|
    AutoRangeArray extendedRanges(aRanges);
 | 
						|
    extendedRanges.ExtendRangesToWrapLines(
 | 
						|
        EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
 | 
						|
        aEditingHost);
 | 
						|
    Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
        extendedRanges
 | 
						|
            .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
 | 
						|
                *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoRangeArray::"
 | 
						|
          "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
 | 
						|
          "failed");
 | 
						|
      return splitResult.unwrapErr();
 | 
						|
    }
 | 
						|
    if (splitResult.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = splitResult.unwrap();
 | 
						|
    }
 | 
						|
    nsresult rv = extendedRanges.CollectEditTargetNodes(
 | 
						|
        *this, arrayOfContents, EditSubAction::eIndent,
 | 
						|
        AutoRangeArray::CollectNonEditableNodes::Yes);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eIndent, "
 | 
						|
          "CollectNonEditableNodes::Yes) failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // FIXME: Split ancestors when we consider to indent the range.
 | 
						|
  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
      MaybeSplitElementsAtEveryBRElement(arrayOfContents,
 | 
						|
                                         EditSubAction::eIndent);
 | 
						|
  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::eIndent)"
 | 
						|
        " failed");
 | 
						|
    return splitAtBRElementsResult.inspectErr();
 | 
						|
  }
 | 
						|
  if (splitAtBRElementsResult.inspect().IsSet()) {
 | 
						|
    pointToPutCaret = splitAtBRElementsResult.unwrap();
 | 
						|
  }
 | 
						|
 | 
						|
  // If there is no visible and editable nodes in the edit targets, make an
 | 
						|
  // empty block.
 | 
						|
  // XXX Isn't this odd if there are only non-editable visible nodes?
 | 
						|
  if (HTMLEditUtils::IsEmptyOneHardLine(
 | 
						|
          arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
    const EditorDOMPoint pointToInsertBlockquoteElement =
 | 
						|
        pointToPutCaret.IsSet()
 | 
						|
            ? std::move(pointToPutCaret)
 | 
						|
            : EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!pointToInsertBlockquoteElement.IsSet())) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    // If there is no element which can have <blockquote>, abort.
 | 
						|
    if (NS_WARN_IF(!HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
 | 
						|
                        *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
 | 
						|
                        &aEditingHost)
 | 
						|
                        .IsSet())) {
 | 
						|
      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
 | 
						|
    }
 | 
						|
 | 
						|
    // Make sure we can put a block here.
 | 
						|
    // XXX Unfortunately, this calls
 | 
						|
    // MaybeSplitAncestorsForInsertWithTransaction() then,
 | 
						|
    // HTMLEditUtils::GetInsertionPointInInclusiveAncestor() is called again.
 | 
						|
    Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
 | 
						|
        InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
            *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
 | 
						|
            BRElementNextToSplitPoint::Keep, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
          "nsGkAtoms::blockquote) failed");
 | 
						|
      return createNewBlockquoteElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedCreateNewBlockquoteElementResult =
 | 
						|
        createNewBlockquoteElementResult.unwrap();
 | 
						|
    unwrappedCreateNewBlockquoteElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    RefPtr<Element> newBlockquoteElement =
 | 
						|
        unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
 | 
						|
    MOZ_ASSERT(newBlockquoteElement);
 | 
						|
    // delete anything that was in the list of nodes
 | 
						|
    // XXX We don't need to remove the nodes from the array for performance.
 | 
						|
    for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
 | 
						|
      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
 | 
						|
      // keep it alive.
 | 
						|
      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    aRanges.ClearSavedRanges();
 | 
						|
    nsresult rv = aRanges.Collapse(EditorRawDOMPoint(newBlockquoteElement, 0u));
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "EditorBase::CollapseSelectionToStartOf() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Element> latestNewBlockElement;
 | 
						|
  auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
 | 
						|
      [&]() -> nsresult {
 | 
						|
    MOZ_ASSERT(aRanges.HasSavedRanges());
 | 
						|
    aRanges.RestoreFromSavedRanges();
 | 
						|
 | 
						|
    if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
 | 
						|
        aRanges.Ranges().IsEmpty()) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    const auto firstRangeStartRawPoint =
 | 
						|
        aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
 | 
						|
    if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
    Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
 | 
						|
        HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
 | 
						|
            EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
 | 
						|
    if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
 | 
						|
          "but ignored");
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
    if (!pointInNewBlockElementOrError.inspect().IsSet()) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
    return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
 | 
						|
  };
 | 
						|
 | 
						|
  // Ok, now go through all the nodes and put them in a blockquote,
 | 
						|
  // or whatever is appropriate.  Wohoo!
 | 
						|
  RefPtr<Element> subListElement, blockquoteElement, indentedListItemElement;
 | 
						|
  for (OwningNonNull<nsIContent>& content : arrayOfContents) {
 | 
						|
    // Here's where we actually figure out what to do.
 | 
						|
    EditorDOMPoint atContent(content);
 | 
						|
    if (NS_WARN_IF(!atContent.IsSet())) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Ignore all non-editable nodes.  Leave them be.
 | 
						|
    // XXX We ignore non-editable nodes here, but not so in the above block.
 | 
						|
    if (!EditorUtils::IsEditableContent(content, EditorType::HTML) ||
 | 
						|
        !HTMLEditUtils::IsRemovableNode(content)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If the content has been moved to different place, ignore it.
 | 
						|
    if (MOZ_UNLIKELY(!content->IsInclusiveDescendantOf(&aEditingHost))) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
 | 
						|
      const RefPtr<Element> oldSubListElement = subListElement;
 | 
						|
      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
 | 
						|
      // keep it alive.
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          IndentListChildWithTransaction(&subListElement, atContent,
 | 
						|
                                         MOZ_KnownLive(content), aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
 | 
						|
        return pointToPutCaretOrError.unwrapErr();
 | 
						|
      }
 | 
						|
      if (oldSubListElement != subListElement) {
 | 
						|
        // New list element is created, so we should put caret into the new list
 | 
						|
        // element.
 | 
						|
        latestNewBlockElement = subListElement;
 | 
						|
      }
 | 
						|
      if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
      blockquoteElement = nullptr;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Not a list item, use blockquote?
 | 
						|
 | 
						|
    // if we are inside a list item, we don't want to blockquote, we want
 | 
						|
    // to sublist the list item.  We may have several nodes listed in the
 | 
						|
    // array of nodes to act on, that are in the same list item.  Since
 | 
						|
    // we only want to indent that li once, we must keep track of the most
 | 
						|
    // recent indented list item, and not indent it if we find another node
 | 
						|
    // to act on that is still inside the same li.
 | 
						|
    if (RefPtr<Element> listItem =
 | 
						|
            HTMLEditUtils::GetClosestAncestorListItemElement(content,
 | 
						|
                                                             &aEditingHost)) {
 | 
						|
      if (indentedListItemElement == listItem) {
 | 
						|
        // already indented this list item
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      // check to see if subListElement is still appropriate.  Which it is if
 | 
						|
      // content is still right after it in the same list.
 | 
						|
      nsIContent* previousEditableSibling =
 | 
						|
          subListElement
 | 
						|
              ? HTMLEditUtils::GetPreviousSibling(
 | 
						|
                    *listItem, {WalkTreeOption::IgnoreNonEditableNode})
 | 
						|
              : nullptr;
 | 
						|
      if (!subListElement || (previousEditableSibling &&
 | 
						|
                              previousEditableSibling != subListElement)) {
 | 
						|
        EditorDOMPoint atListItem(listItem);
 | 
						|
        if (NS_WARN_IF(!listItem)) {
 | 
						|
          return NS_ERROR_FAILURE;
 | 
						|
        }
 | 
						|
        nsAtom* containerName =
 | 
						|
            atListItem.GetContainer()->NodeInfo()->NameAtom();
 | 
						|
        // Create a new nested list of correct type.
 | 
						|
        Result<CreateElementResult, nsresult> createNewListElementResult =
 | 
						|
            InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
                MOZ_KnownLive(*containerName), atListItem,
 | 
						|
                BRElementNextToSplitPoint::Keep, aEditingHost);
 | 
						|
        if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
 | 
						|
          NS_WARNING(nsPrintfCString("HTMLEditor::"
 | 
						|
                                     "InsertElementWithSplittingAncestorsWithTr"
 | 
						|
                                     "ansaction(%s) failed",
 | 
						|
                                     nsAtomCString(containerName).get())
 | 
						|
                         .get());
 | 
						|
          return createNewListElementResult.unwrapErr();
 | 
						|
        }
 | 
						|
        CreateElementResult unwrappedCreateNewListElementResult =
 | 
						|
            createNewListElementResult.unwrap();
 | 
						|
        if (unwrappedCreateNewListElementResult.HasCaretPointSuggestion()) {
 | 
						|
          pointToPutCaret =
 | 
						|
              unwrappedCreateNewListElementResult.UnwrapCaretPoint();
 | 
						|
        }
 | 
						|
        MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
 | 
						|
        subListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
 | 
						|
      }
 | 
						|
 | 
						|
      Result<MoveNodeResult, nsresult> moveListItemElementResult =
 | 
						|
          MoveNodeToEndWithTransaction(*listItem, *subListElement);
 | 
						|
      if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
        return moveListItemElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      MoveNodeResult unwrappedMoveListItemElementResult =
 | 
						|
          moveListItemElementResult.unwrap();
 | 
						|
      if (unwrappedMoveListItemElementResult.HasCaretPointSuggestion()) {
 | 
						|
        pointToPutCaret = unwrappedMoveListItemElementResult.UnwrapCaretPoint();
 | 
						|
      }
 | 
						|
 | 
						|
      // Remember the list item element which we indented now for ignoring its
 | 
						|
      // children to avoid using <blockquote> in it.
 | 
						|
      indentedListItemElement = std::move(listItem);
 | 
						|
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // need to make a blockquote to put things in if we haven't already,
 | 
						|
    // or if this node doesn't go in blockquote we used earlier.
 | 
						|
    // One reason it might not go in prio blockquote is if we are now
 | 
						|
    // in a different table cell.
 | 
						|
    if (blockquoteElement &&
 | 
						|
        HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
 | 
						|
            *blockquoteElement) !=
 | 
						|
            HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content)) {
 | 
						|
      blockquoteElement = nullptr;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!blockquoteElement) {
 | 
						|
      // First, check that our element can contain a blockquote.
 | 
						|
      if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
 | 
						|
                                         *nsGkAtoms::blockquote)) {
 | 
						|
        // XXX This is odd, why do we stop indenting remaining content nodes?
 | 
						|
        //     Perhaps, `continue` is better.
 | 
						|
        nsresult rv =
 | 
						|
            RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
 | 
						|
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                             "RestoreSavedRangesAndCollapseInLatestBlockElement"
 | 
						|
                             "IfOutside() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
 | 
						|
      Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
 | 
						|
          InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
              *nsGkAtoms::blockquote, atContent,
 | 
						|
              BRElementNextToSplitPoint::Keep, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
            "nsGkAtoms::blockquote) failed");
 | 
						|
        return createNewBlockquoteElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedCreateNewBlockquoteElementResult =
 | 
						|
          createNewBlockquoteElementResult.unwrap();
 | 
						|
      if (unwrappedCreateNewBlockquoteElementResult.HasCaretPointSuggestion()) {
 | 
						|
        pointToPutCaret =
 | 
						|
            unwrappedCreateNewBlockquoteElementResult.UnwrapCaretPoint();
 | 
						|
      }
 | 
						|
 | 
						|
      MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
 | 
						|
      blockquoteElement =
 | 
						|
          unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
 | 
						|
      latestNewBlockElement = blockquoteElement;
 | 
						|
    }
 | 
						|
 | 
						|
    // tuck the node into the end of the active blockquote
 | 
						|
    // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
 | 
						|
    // keep it alive.
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
 | 
						|
                                     *blockquoteElement);
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
      return moveNodeResult.unwrapErr();
 | 
						|
    }
 | 
						|
    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
    if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
 | 
						|
      pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
 | 
						|
    }
 | 
						|
    subListElement = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rv),
 | 
						|
      "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::OutdentAsSubAction(
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(
 | 
						|
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eOutdent, nsIEditor::eNext, ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(ignoredError.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Some selection containers are not content node, but ignored");
 | 
						|
    return EditActionResult::IgnoredResult();
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditActionResult, nsresult> result =
 | 
						|
      HandleOutdentAtSelection(aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::HandleOutdentAtSelection() failed");
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
  if (result.inspect().Canceled()) {
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Mutation event listener might have changed the selection");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
 | 
						|
        "failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::HandleOutdentAtSelection(
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
 | 
						|
 | 
						|
  // XXX Why do we do this only when there is only one selection range?
 | 
						|
  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            SelectionRef().GetRangeAt(0u), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.propagateErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we
 | 
						|
    // cannot use Selection::SetStartAndEndInLimit() here.
 | 
						|
    IgnoredErrorResult error;
 | 
						|
    SelectionRef().SetBaseAndExtentInLimiter(
 | 
						|
        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
 | 
						|
        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    if (MOZ_UNLIKELY(error.Failed())) {
 | 
						|
      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
 | 
						|
      return Err(error.StealNSResult());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // HandleOutdentAtSelectionInternal() creates AutoSelectionRestorer.
 | 
						|
  // Therefore, even if it returns NS_OK, the editor might have been destroyed
 | 
						|
  // at restoring Selection.
 | 
						|
  Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
 | 
						|
      HandleOutdentAtSelectionInternal(aEditingHost);
 | 
						|
  MOZ_ASSERT_IF(outdentResult.isOk(),
 | 
						|
                !outdentResult.inspect().HasCaretPointSuggestion());
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  if (MOZ_UNLIKELY(outdentResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::HandleOutdentAtSelectionInternal() failed");
 | 
						|
    return outdentResult.propagateErr();
 | 
						|
  }
 | 
						|
  SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
 | 
						|
 | 
						|
  // Make sure selection didn't stick to last piece of content in old bq (only
 | 
						|
  // a problem for collapsed selections)
 | 
						|
  if (!unwrappedOutdentResult.GetLeftContent() &&
 | 
						|
      !unwrappedOutdentResult.GetRightContent()) {
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  if (!SelectionRef().IsCollapsed()) {
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  // Push selection past end of left element of last split indented element.
 | 
						|
  if (unwrappedOutdentResult.GetLeftContent()) {
 | 
						|
    const nsRange* firstRange = SelectionRef().GetRangeAt(0);
 | 
						|
    if (NS_WARN_IF(!firstRange)) {
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
    const RangeBoundary& atStartOfSelection = firstRange->StartRef();
 | 
						|
    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    if (atStartOfSelection.Container() ==
 | 
						|
            unwrappedOutdentResult.GetLeftContent() ||
 | 
						|
        EditorUtils::IsDescendantOf(*atStartOfSelection.Container(),
 | 
						|
                                    *unwrappedOutdentResult.GetLeftContent())) {
 | 
						|
      // Selection is inside the left node - push it past it.
 | 
						|
      EditorRawDOMPoint afterRememberedLeftBQ(
 | 
						|
          EditorRawDOMPoint::After(*unwrappedOutdentResult.GetLeftContent()));
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          afterRememberedLeftBQ.IsSet(),
 | 
						|
          "Failed to set after remembered left blockquote element");
 | 
						|
      nsresult rv = CollapseSelectionTo(afterRememberedLeftBQ);
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "EditorBase::CollapseSelectionTo() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
  // And pull selection before beginning of right element of last split
 | 
						|
  // indented element.
 | 
						|
  if (unwrappedOutdentResult.GetRightContent()) {
 | 
						|
    const nsRange* firstRange = SelectionRef().GetRangeAt(0);
 | 
						|
    if (NS_WARN_IF(!firstRange)) {
 | 
						|
      return EditActionResult::HandledResult();
 | 
						|
    }
 | 
						|
    const RangeBoundary& atStartOfSelection = firstRange->StartRef();
 | 
						|
    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    if (atStartOfSelection.Container() ==
 | 
						|
            unwrappedOutdentResult.GetRightContent() ||
 | 
						|
        EditorUtils::IsDescendantOf(
 | 
						|
            *atStartOfSelection.Container(),
 | 
						|
            *unwrappedOutdentResult.GetRightContent())) {
 | 
						|
      // Selection is inside the right element - push it before it.
 | 
						|
      EditorRawDOMPoint atRememberedRightBQ(
 | 
						|
          unwrappedOutdentResult.GetRightContent());
 | 
						|
      nsresult rv = CollapseSelectionTo(atRememberedRightBQ);
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "EditorBase::CollapseSelectionTo() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
Result<SplitRangeOffFromNodeResult, nsresult>
 | 
						|
HTMLEditor::HandleOutdentAtSelectionInternal(const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  AutoSelectionRestorer restoreSelectionLater(*this);
 | 
						|
 | 
						|
  bool useCSS = IsCSSEnabled();
 | 
						|
 | 
						|
  // Convert the selection ranges into "promoted" selection ranges: this
 | 
						|
  // basically just expands the range to include the immediate block parent,
 | 
						|
  // and then further expands to include any ancestors whose children are all
 | 
						|
  // in the range
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
 | 
						|
  {
 | 
						|
    AutoRangeArray extendedSelectionRanges(SelectionRef());
 | 
						|
    extendedSelectionRanges.ExtendRangesToWrapLines(
 | 
						|
        EditSubAction::eOutdent, BlockInlineCheck::UseHTMLDefaultStyle,
 | 
						|
        aEditingHost);
 | 
						|
    nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
 | 
						|
        *this, arrayOfContents, EditSubAction::eOutdent,
 | 
						|
        AutoRangeArray::CollectNonEditableNodes::Yes);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoRangeArray::CollectEditTargetNodes(EditSubAction::eOutdent, "
 | 
						|
          "CollectNonEditableNodes::Yes) failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
        MaybeSplitElementsAtEveryBRElement(arrayOfContents,
 | 
						|
                                           EditSubAction::eOutdent);
 | 
						|
    if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
 | 
						|
          "eOutdent) failed");
 | 
						|
      return splitAtBRElementsResult.propagateErr();
 | 
						|
    }
 | 
						|
    if (AllowsTransactionsToChangeSelection() &&
 | 
						|
        splitAtBRElementsResult.inspect().IsSet()) {
 | 
						|
      nsresult rv = CollapseSelectionTo(splitAtBRElementsResult.inspect());
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIContent> leftContentOfLastOutdented;
 | 
						|
  nsCOMPtr<nsIContent> middleContentOfLastOutdented;
 | 
						|
  nsCOMPtr<nsIContent> rightContentOfLastOutdented;
 | 
						|
  RefPtr<Element> indentedParentElement;
 | 
						|
  nsCOMPtr<nsIContent> firstContentToBeOutdented, lastContentToBeOutdented;
 | 
						|
  BlockIndentedWith indentedParentIndentedWith = BlockIndentedWith::HTML;
 | 
						|
  for (OwningNonNull<nsIContent>& content : arrayOfContents) {
 | 
						|
    // Here's where we actually figure out what to do
 | 
						|
    EditorDOMPoint atContent(content);
 | 
						|
    if (!atContent.IsSet()) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If it's a `<blockquote>`, remove it to outdent its children.
 | 
						|
    if (content->IsHTMLElement(nsGkAtoms::blockquote)) {
 | 
						|
      // If we've already found an ancestor block element indented, we need to
 | 
						|
      // split it and remove the block element first.
 | 
						|
      if (indentedParentElement) {
 | 
						|
        NS_WARNING_ASSERTION(indentedParentElement == content,
 | 
						|
                             "Indented parent element is not the <blockquote>");
 | 
						|
        Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
 | 
						|
            OutdentPartOfBlock(*indentedParentElement,
 | 
						|
                               *firstContentToBeOutdented,
 | 
						|
                               *lastContentToBeOutdented,
 | 
						|
                               indentedParentIndentedWith, aEditingHost);
 | 
						|
        if (MOZ_UNLIKELY(outdentResult.isErr())) {
 | 
						|
          NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
 | 
						|
          return outdentResult;
 | 
						|
        }
 | 
						|
        SplitRangeOffFromNodeResult unwrappedOutdentResult =
 | 
						|
            outdentResult.unwrap();
 | 
						|
        unwrappedOutdentResult.IgnoreCaretPointSuggestion();
 | 
						|
        leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
 | 
						|
        middleContentOfLastOutdented =
 | 
						|
            unwrappedOutdentResult.UnwrapMiddleContent();
 | 
						|
        rightContentOfLastOutdented =
 | 
						|
            unwrappedOutdentResult.UnwrapRightContent();
 | 
						|
        indentedParentElement = nullptr;
 | 
						|
        firstContentToBeOutdented = nullptr;
 | 
						|
        lastContentToBeOutdented = nullptr;
 | 
						|
        indentedParentIndentedWith = BlockIndentedWith::HTML;
 | 
						|
      }
 | 
						|
      Result<EditorDOMPoint, nsresult> unwrapBlockquoteElementResult =
 | 
						|
          RemoveBlockContainerWithTransaction(
 | 
						|
              MOZ_KnownLive(*content->AsElement()));
 | 
						|
      if (MOZ_UNLIKELY(unwrapBlockquoteElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
        return unwrapBlockquoteElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      const EditorDOMPoint& pointToPutCaret =
 | 
						|
          unwrapBlockquoteElementResult.inspect();
 | 
						|
      if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
 | 
						|
        nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If we're using CSS and the node is a block element, check its start
 | 
						|
    // margin whether it's indented with CSS.
 | 
						|
    if (useCSS && HTMLEditUtils::IsBlockElement(
 | 
						|
                      content, BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
      nsStaticAtom& marginProperty =
 | 
						|
          MarginPropertyAtomForIndent(MOZ_KnownLive(content));
 | 
						|
      if (NS_WARN_IF(Destroyed())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      nsAutoString value;
 | 
						|
      DebugOnly<nsresult> rvIgnored =
 | 
						|
          CSSEditUtils::GetSpecifiedProperty(content, marginProperty, value);
 | 
						|
      if (NS_WARN_IF(Destroyed())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rvIgnored),
 | 
						|
          "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
 | 
						|
      float startMargin = 0;
 | 
						|
      RefPtr<nsAtom> unit;
 | 
						|
      CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
 | 
						|
      // If indented with CSS, we should decrease the start margin.
 | 
						|
      if (startMargin > 0) {
 | 
						|
        const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
            ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
 | 
						|
                              ChangeMargin::Decrease, aEditingHost);
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
          if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
 | 
						|
                         NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
            return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
          }
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
 | 
						|
              "but ignored");
 | 
						|
        } else if (AllowsTransactionsToChangeSelection() &&
 | 
						|
                   pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
          nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
            return Err(rv);
 | 
						|
          }
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // If it's a list item, we should treat as that it "indents" its children.
 | 
						|
    if (HTMLEditUtils::IsListItem(content)) {
 | 
						|
      // If it is a list item, that means we are not outdenting whole list.
 | 
						|
      // XXX I don't understand this sentence...  We may meet parent list
 | 
						|
      //     element, no?
 | 
						|
      if (indentedParentElement) {
 | 
						|
        Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
 | 
						|
            OutdentPartOfBlock(*indentedParentElement,
 | 
						|
                               *firstContentToBeOutdented,
 | 
						|
                               *lastContentToBeOutdented,
 | 
						|
                               indentedParentIndentedWith, aEditingHost);
 | 
						|
        if (MOZ_UNLIKELY(outdentResult.isErr())) {
 | 
						|
          NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
 | 
						|
          return outdentResult;
 | 
						|
        }
 | 
						|
        SplitRangeOffFromNodeResult unwrappedOutdentResult =
 | 
						|
            outdentResult.unwrap();
 | 
						|
        unwrappedOutdentResult.IgnoreCaretPointSuggestion();
 | 
						|
        leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
 | 
						|
        middleContentOfLastOutdented =
 | 
						|
            unwrappedOutdentResult.UnwrapMiddleContent();
 | 
						|
        rightContentOfLastOutdented =
 | 
						|
            unwrappedOutdentResult.UnwrapRightContent();
 | 
						|
        indentedParentElement = nullptr;
 | 
						|
        firstContentToBeOutdented = nullptr;
 | 
						|
        lastContentToBeOutdented = nullptr;
 | 
						|
        indentedParentIndentedWith = BlockIndentedWith::HTML;
 | 
						|
      }
 | 
						|
      // XXX `content` could become different element since
 | 
						|
      //     `OutdentPartOfBlock()` may run mutation event listeners.
 | 
						|
      nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
 | 
						|
                                          LiftUpFromAllParentListElements::No);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
 | 
						|
            ":No) failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If we've found an ancestor block element which indents its children
 | 
						|
    // and the current node is NOT a descendant of it, we should remove it to
 | 
						|
    // outdent its children.  Otherwise, i.e., current node is a descendant of
 | 
						|
    // it, we meet new node which should be outdented when the indented parent
 | 
						|
    // is removed.
 | 
						|
    if (indentedParentElement) {
 | 
						|
      if (EditorUtils::IsDescendantOf(*content, *indentedParentElement)) {
 | 
						|
        // Extend the range to be outdented at removing the
 | 
						|
        // indentedParentElement.
 | 
						|
        lastContentToBeOutdented = content;
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
 | 
						|
          OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
 | 
						|
                             *lastContentToBeOutdented,
 | 
						|
                             indentedParentIndentedWith, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(outdentResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
 | 
						|
        return outdentResult;
 | 
						|
      }
 | 
						|
      SplitRangeOffFromNodeResult unwrappedOutdentResult =
 | 
						|
          outdentResult.unwrap();
 | 
						|
      unwrappedOutdentResult.IgnoreCaretPointSuggestion();
 | 
						|
      leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
 | 
						|
      middleContentOfLastOutdented =
 | 
						|
          unwrappedOutdentResult.UnwrapMiddleContent();
 | 
						|
      rightContentOfLastOutdented = unwrappedOutdentResult.UnwrapRightContent();
 | 
						|
      indentedParentElement = nullptr;
 | 
						|
      firstContentToBeOutdented = nullptr;
 | 
						|
      lastContentToBeOutdented = nullptr;
 | 
						|
      // curBlockIndentedWith = HTMLEditor::BlockIndentedWith::HTML;
 | 
						|
 | 
						|
      // Then, we need to look for next indentedParentElement.
 | 
						|
    }
 | 
						|
 | 
						|
    indentedParentIndentedWith = BlockIndentedWith::HTML;
 | 
						|
    for (nsCOMPtr<nsIContent> parentContent = content->GetParent();
 | 
						|
         parentContent && !parentContent->IsHTMLElement(nsGkAtoms::body) &&
 | 
						|
         parentContent != &aEditingHost &&
 | 
						|
         (parentContent->IsHTMLElement(nsGkAtoms::table) ||
 | 
						|
          !HTMLEditUtils::IsAnyTableElement(parentContent));
 | 
						|
         parentContent = parentContent->GetParent()) {
 | 
						|
      if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*parentContent))) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      // If we reach a `<blockquote>` ancestor, it should be split at next
 | 
						|
      // time at least for outdenting current node.
 | 
						|
      if (parentContent->IsHTMLElement(nsGkAtoms::blockquote)) {
 | 
						|
        indentedParentElement = parentContent->AsElement();
 | 
						|
        firstContentToBeOutdented = content;
 | 
						|
        lastContentToBeOutdented = content;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!useCSS) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      nsCOMPtr<nsINode> grandParentNode = parentContent->GetParentNode();
 | 
						|
      nsStaticAtom& marginProperty =
 | 
						|
          MarginPropertyAtomForIndent(MOZ_KnownLive(content));
 | 
						|
      if (NS_WARN_IF(Destroyed())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      if (NS_WARN_IF(grandParentNode != parentContent->GetParentNode())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
      nsAutoString value;
 | 
						|
      DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty(
 | 
						|
          *parentContent, marginProperty, value);
 | 
						|
      if (NS_WARN_IF(Destroyed())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rvIgnored),
 | 
						|
          "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
 | 
						|
      // XXX Now, editing host may become different element.  If so, shouldn't
 | 
						|
      //     we stop this handling?
 | 
						|
      float startMargin;
 | 
						|
      RefPtr<nsAtom> unit;
 | 
						|
      CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
 | 
						|
      // If we reach a block element which indents its children with start
 | 
						|
      // margin, we should remove it at next time.
 | 
						|
      if (startMargin > 0 &&
 | 
						|
          !(HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) &&
 | 
						|
            HTMLEditUtils::IsAnyListElement(content))) {
 | 
						|
        indentedParentElement = parentContent->AsElement();
 | 
						|
        firstContentToBeOutdented = content;
 | 
						|
        lastContentToBeOutdented = content;
 | 
						|
        indentedParentIndentedWith = BlockIndentedWith::CSS;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (indentedParentElement) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If we don't have any block elements which indents current node and
 | 
						|
    // both current node and its parent are list element, remove current
 | 
						|
    // node to move all its children to the parent list.
 | 
						|
    // XXX This is buggy.  When both lists' item types are different,
 | 
						|
    //     we create invalid tree.  E.g., `<ul>` may have `<dd>` as its
 | 
						|
    //     list item element.
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
 | 
						|
      if (!HTMLEditUtils::IsAnyListElement(content)) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      // Just unwrap this sublist
 | 
						|
      Result<EditorDOMPoint, nsresult> unwrapSubListElementResult =
 | 
						|
          RemoveBlockContainerWithTransaction(
 | 
						|
              MOZ_KnownLive(*content->AsElement()));
 | 
						|
      if (MOZ_UNLIKELY(unwrapSubListElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
        return unwrapSubListElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      const EditorDOMPoint& pointToPutCaret =
 | 
						|
          unwrapSubListElementResult.inspect();
 | 
						|
      if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If current content is a list element but its parent is not a list
 | 
						|
    // element, move children to where it is and remove it from the tree.
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(content)) {
 | 
						|
      // XXX If mutation event listener appends new children forever, this
 | 
						|
      //     becomes an infinite loop so that we should set limitation from
 | 
						|
      //     first child count.
 | 
						|
      for (nsCOMPtr<nsIContent> lastChildContent = content->GetLastChild();
 | 
						|
           lastChildContent; lastChildContent = content->GetLastChild()) {
 | 
						|
        if (HTMLEditUtils::IsListItem(lastChildContent)) {
 | 
						|
          nsresult rv = LiftUpListItemElement(
 | 
						|
              MOZ_KnownLive(*lastChildContent->AsElement()),
 | 
						|
              LiftUpFromAllParentListElements::No);
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            NS_WARNING(
 | 
						|
                "HTMLEditor::LiftUpListItemElement("
 | 
						|
                "LiftUpFromAllParentListElements::No) failed");
 | 
						|
            return Err(rv);
 | 
						|
          }
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        if (HTMLEditUtils::IsAnyListElement(lastChildContent)) {
 | 
						|
          // We have an embedded list, so move it out from under the parent
 | 
						|
          // list. Be sure to put it after the parent list because this
 | 
						|
          // loop iterates backwards through the parent's list of children.
 | 
						|
          EditorDOMPoint afterCurrentList(EditorDOMPoint::After(atContent));
 | 
						|
          NS_WARNING_ASSERTION(
 | 
						|
              afterCurrentList.IsSet(),
 | 
						|
              "Failed to set it to after current list element");
 | 
						|
          Result<MoveNodeResult, nsresult> moveListElementResult =
 | 
						|
              MoveNodeWithTransaction(*lastChildContent, afterCurrentList);
 | 
						|
          if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
 | 
						|
            NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
            return moveListElementResult.propagateErr();
 | 
						|
          }
 | 
						|
          nsresult rv = moveListElementResult.inspect().SuggestCaretPointTo(
 | 
						|
              *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                      SuggestCaret::AndIgnoreTrivialError});
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
 | 
						|
            return Err(rv);
 | 
						|
          }
 | 
						|
          NS_WARNING_ASSERTION(
 | 
						|
              rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
              "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        // Delete any non-list items for now
 | 
						|
        // XXX Chrome moves it from the list element.  We should follow it.
 | 
						|
        nsresult rv = DeleteNodeWithTransaction(*lastChildContent);
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      // Delete the now-empty list
 | 
						|
      Result<EditorDOMPoint, nsresult> unwrapListElementResult =
 | 
						|
          RemoveBlockContainerWithTransaction(
 | 
						|
              MOZ_KnownLive(*content->AsElement()));
 | 
						|
      if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
        return unwrapListElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
 | 
						|
      if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (useCSS) {
 | 
						|
      if (RefPtr<Element> element = content->GetAsElementOrParentElement()) {
 | 
						|
        const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
            ChangeMarginStart(*element, ChangeMargin::Decrease, aEditingHost);
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
          if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
 | 
						|
                         NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
            return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
          }
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
 | 
						|
              "but ignored");
 | 
						|
        } else if (AllowsTransactionsToChangeSelection() &&
 | 
						|
                   pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
          nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
 | 
						|
          if (NS_FAILED(rv)) {
 | 
						|
            NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
            return Err(rv);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!indentedParentElement) {
 | 
						|
    return SplitRangeOffFromNodeResult(leftContentOfLastOutdented,
 | 
						|
                                       middleContentOfLastOutdented,
 | 
						|
                                       rightContentOfLastOutdented);
 | 
						|
  }
 | 
						|
 | 
						|
  // We have a <blockquote> we haven't finished handling.
 | 
						|
  Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
 | 
						|
      OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
 | 
						|
                         *lastContentToBeOutdented, indentedParentIndentedWith,
 | 
						|
                         aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(outdentResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
 | 
						|
    return outdentResult;
 | 
						|
  }
 | 
						|
  // We will restore selection soon.  Therefore, callers do not need to restore
 | 
						|
  // the selection.
 | 
						|
  SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
 | 
						|
  unwrappedOutdentResult.ForgetCaretPointSuggestion();
 | 
						|
  return unwrappedOutdentResult;
 | 
						|
}
 | 
						|
 | 
						|
Result<SplitRangeOffFromNodeResult, nsresult>
 | 
						|
HTMLEditor::RemoveBlockContainerElementWithTransactionBetween(
 | 
						|
    Element& aBlockContainerElement, nsIContent& aStartOfRange,
 | 
						|
    nsIContent& aEndOfRange, BlockInlineCheck aBlockInlineCheck) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
 | 
						|
      SplitRangeOffFromElement(aBlockContainerElement, aStartOfRange,
 | 
						|
                               aEndOfRange);
 | 
						|
  if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
    if (splitResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
 | 
						|
      NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
 | 
						|
      return splitResult;
 | 
						|
    }
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SplitRangeOffFromElement() failed, but might be ignored");
 | 
						|
    return SplitRangeOffFromNodeResult(nullptr, nullptr, nullptr);
 | 
						|
  }
 | 
						|
  SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
 | 
						|
  unwrappedSplitResult.MoveCaretPointTo(pointToPutCaret,
 | 
						|
                                        {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
 | 
						|
  // Even if either split aBlockContainerElement or did not split it, we should
 | 
						|
  // unwrap the right most element which is split from aBlockContainerElement
 | 
						|
  // (or aBlockContainerElement itself if it was not split without errors).
 | 
						|
  Element* rightmostElement =
 | 
						|
      unwrappedSplitResult.GetRightmostContentAs<Element>();
 | 
						|
  MOZ_ASSERT(rightmostElement);
 | 
						|
  if (NS_WARN_IF(!rightmostElement)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    // MOZ_KnownLive(rightmostElement) because it's grabbed by
 | 
						|
    // unwrappedSplitResult.
 | 
						|
    Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
 | 
						|
        RemoveBlockContainerWithTransaction(MOZ_KnownLive(*rightmostElement));
 | 
						|
    if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
      return unwrapBlockElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    if (unwrapBlockElementResult.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = unwrapBlockElementResult.unwrap();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return SplitRangeOffFromNodeResult(
 | 
						|
      unwrappedSplitResult.GetLeftContent(), nullptr,
 | 
						|
      unwrappedSplitResult.GetRightContent(), std::move(pointToPutCaret));
 | 
						|
}
 | 
						|
 | 
						|
Result<SplitRangeOffFromNodeResult, nsresult>
 | 
						|
HTMLEditor::SplitRangeOffFromElement(Element& aElementToSplit,
 | 
						|
                                     nsIContent& aStartOfMiddleElement,
 | 
						|
                                     nsIContent& aEndOfMiddleElement) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive
 | 
						|
  // descendants of aElementToSplit.
 | 
						|
  MOZ_ASSERT(
 | 
						|
      EditorUtils::IsDescendantOf(aStartOfMiddleElement, aElementToSplit));
 | 
						|
  MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aElementToSplit));
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  // Split at the start.
 | 
						|
  Result<SplitNodeResult, nsresult> splitAtStartResult =
 | 
						|
      SplitNodeDeepWithTransaction(aElementToSplit,
 | 
						|
                                   EditorDOMPoint(&aStartOfMiddleElement),
 | 
						|
                                   SplitAtEdges::eDoNotCreateEmptyContainer);
 | 
						|
  if (MOZ_UNLIKELY(splitAtStartResult.isErr())) {
 | 
						|
    if (splitAtStartResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
 | 
						|
      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed (at left)");
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
        "eDoNotCreateEmptyContainer) at start of middle element failed");
 | 
						|
  } else {
 | 
						|
    splitAtStartResult.inspect().CopyCaretPointTo(
 | 
						|
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
  }
 | 
						|
 | 
						|
  // Split at after the end
 | 
						|
  auto atAfterEnd = EditorDOMPoint::After(aEndOfMiddleElement);
 | 
						|
  Element* rightElement =
 | 
						|
      splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()
 | 
						|
          ? splitAtStartResult.inspect().GetNextContentAs<Element>()
 | 
						|
          : &aElementToSplit;
 | 
						|
  // MOZ_KnownLive(rightElement) because it's grabbed by splitAtStartResult or
 | 
						|
  // aElementToSplit whose lifetime is guaranteed by the caller.
 | 
						|
  Result<SplitNodeResult, nsresult> splitAtEndResult =
 | 
						|
      SplitNodeDeepWithTransaction(MOZ_KnownLive(*rightElement), atAfterEnd,
 | 
						|
                                   SplitAtEdges::eDoNotCreateEmptyContainer);
 | 
						|
  if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
 | 
						|
    if (splitAtEndResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::SplitNodeDeepWithTransaction() failed (at right)");
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
        "eDoNotCreateEmptyContainer) after end of middle element failed");
 | 
						|
  } else {
 | 
						|
    splitAtEndResult.inspect().CopyCaretPointTo(
 | 
						|
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
  }
 | 
						|
 | 
						|
  if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit() &&
 | 
						|
      splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
 | 
						|
    // Note that the middle node can be computed only with the latter split
 | 
						|
    // result.
 | 
						|
    return SplitRangeOffFromNodeResult(
 | 
						|
        splitAtStartResult.inspect().GetPreviousContent(),
 | 
						|
        splitAtEndResult.inspect().GetPreviousContent(),
 | 
						|
        splitAtEndResult.inspect().GetNextContent(),
 | 
						|
        std::move(pointToPutCaret));
 | 
						|
  }
 | 
						|
  if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()) {
 | 
						|
    return SplitRangeOffFromNodeResult(
 | 
						|
        splitAtStartResult.inspect().GetPreviousContent(),
 | 
						|
        splitAtStartResult.inspect().GetNextContent(), nullptr,
 | 
						|
        std::move(pointToPutCaret));
 | 
						|
  }
 | 
						|
  if (splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
 | 
						|
    return SplitRangeOffFromNodeResult(
 | 
						|
        nullptr, splitAtEndResult.inspect().GetPreviousContent(),
 | 
						|
        splitAtEndResult.inspect().GetNextContent(),
 | 
						|
        std::move(pointToPutCaret));
 | 
						|
  }
 | 
						|
  return SplitRangeOffFromNodeResult(nullptr, &aElementToSplit, nullptr,
 | 
						|
                                     std::move(pointToPutCaret));
 | 
						|
}
 | 
						|
 | 
						|
Result<SplitRangeOffFromNodeResult, nsresult> HTMLEditor::OutdentPartOfBlock(
 | 
						|
    Element& aBlockElement, nsIContent& aStartOfOutdent,
 | 
						|
    nsIContent& aEndOfOutdent, BlockIndentedWith aBlockIndentedWith,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
 | 
						|
      SplitRangeOffFromElement(aBlockElement, aStartOfOutdent, aEndOfOutdent);
 | 
						|
  if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
 | 
						|
    return splitResult;
 | 
						|
  }
 | 
						|
 | 
						|
  SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
 | 
						|
  Element* middleElement = unwrappedSplitResult.GetMiddleContentAs<Element>();
 | 
						|
  if (MOZ_UNLIKELY(!middleElement)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SplitRangeOffFromElement() didn't return middle content");
 | 
						|
    unwrappedSplitResult.IgnoreCaretPointSuggestion();
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleElement))) {
 | 
						|
    unwrappedSplitResult.IgnoreCaretPointSuggestion();
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = unwrappedSplitResult.SuggestCaretPointTo(
 | 
						|
      *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
              SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
              SuggestCaret::AndIgnoreTrivialError});
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("SplitRangeOffFromNodeResult::SuggestCaretPointTo() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "SplitRangeOffFromNodeResult::SuggestCaretPointTo() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (aBlockIndentedWith == BlockIndentedWith::HTML) {
 | 
						|
    // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
 | 
						|
    Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
 | 
						|
        RemoveBlockContainerWithTransaction(MOZ_KnownLive(*middleElement));
 | 
						|
    if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
      return unwrapBlockElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    const EditorDOMPoint& pointToPutCaret = unwrapBlockElementResult.inspect();
 | 
						|
    if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
 | 
						|
      nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return SplitRangeOffFromNodeResult(unwrappedSplitResult.GetLeftContent(),
 | 
						|
                                       nullptr,
 | 
						|
                                       unwrappedSplitResult.GetRightContent());
 | 
						|
  }
 | 
						|
 | 
						|
  // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
 | 
						|
  Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = ChangeMarginStart(
 | 
						|
      MOZ_KnownLive(*middleElement), ChangeMargin::Decrease, aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed");
 | 
						|
    return pointToPutCaretOrError.propagateErr();
 | 
						|
  }
 | 
						|
  if (AllowsTransactionsToChangeSelection() &&
 | 
						|
      pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return unwrappedSplitResult;
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult> HTMLEditor::ChangeListElementType(
 | 
						|
    Element& aListElement, nsAtom& aNewListTag, nsAtom& aNewListItemTag) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 32> listElementChildren;
 | 
						|
  HTMLEditUtils::CollectAllChildren(aListElement, listElementChildren);
 | 
						|
 | 
						|
  for (const OwningNonNull<nsIContent>& childContent : listElementChildren) {
 | 
						|
    if (!childContent->IsElement()) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    Element* childElement = childContent->AsElement();
 | 
						|
    if (HTMLEditUtils::IsListItem(childElement) &&
 | 
						|
        !childContent->IsHTMLElement(&aNewListItemTag)) {
 | 
						|
      // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
 | 
						|
      // listElementChildren.
 | 
						|
      Result<CreateElementResult, nsresult>
 | 
						|
          replaceWithNewListItemElementResult =
 | 
						|
              ReplaceContainerAndCloneAttributesWithTransaction(
 | 
						|
                  MOZ_KnownLive(*childElement), aNewListItemTag);
 | 
						|
      if (MOZ_UNLIKELY(replaceWithNewListItemElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
 | 
						|
            "failed");
 | 
						|
        return replaceWithNewListItemElementResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedReplaceWithNewListItemElementResult =
 | 
						|
          replaceWithNewListItemElementResult.unwrap();
 | 
						|
      unwrappedReplaceWithNewListItemElementResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(childElement) &&
 | 
						|
        !childElement->IsHTMLElement(&aNewListTag)) {
 | 
						|
      // XXX List elements shouldn't have other list elements as their
 | 
						|
      //     child.  Why do we handle such invalid tree?
 | 
						|
      //     -> Maybe, for bug 525888.
 | 
						|
      // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
 | 
						|
      // listElementChildren.
 | 
						|
      Result<CreateElementResult, nsresult> convertListTypeResult =
 | 
						|
          ChangeListElementType(MOZ_KnownLive(*childElement), aNewListTag,
 | 
						|
                                aNewListItemTag);
 | 
						|
      if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::ChangeListElementType() failed");
 | 
						|
        return convertListTypeResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedConvertListTypeResult =
 | 
						|
          convertListTypeResult.unwrap();
 | 
						|
      unwrappedConvertListTypeResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (aListElement.IsHTMLElement(&aNewListTag)) {
 | 
						|
    return CreateElementResult(&aListElement, std::move(pointToPutCaret));
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX If we replace the list element, shouldn't we create it first and then,
 | 
						|
  //     move children into it before inserting the new list element into the
 | 
						|
  //     DOM tree? Then, we could reduce the cost of dispatching DOM mutation
 | 
						|
  //     events.
 | 
						|
  Result<CreateElementResult, nsresult> replaceWithNewListElementResult =
 | 
						|
      ReplaceContainerWithTransaction(aListElement, aNewListTag);
 | 
						|
  if (MOZ_UNLIKELY(replaceWithNewListElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
 | 
						|
    return replaceWithNewListElementResult;
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedReplaceWithNewListElementResult =
 | 
						|
      replaceWithNewListElementResult.unwrap();
 | 
						|
  unwrappedReplaceWithNewListElementResult.MoveCaretPointTo(
 | 
						|
      pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
  return CreateElementResult(
 | 
						|
      unwrappedReplaceWithNewListElementResult.UnwrapNewNode(),
 | 
						|
      std::move(pointToPutCaret));
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
 | 
						|
    const EditorDOMPoint& aPointToInsertText, const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aPointToInsertText.IsSetAndValid());
 | 
						|
  MOZ_ASSERT(mPendingStylesToApplyToNewContent);
 | 
						|
 | 
						|
  const RefPtr<Element> documentRootElement = GetDocument()->GetRootElement();
 | 
						|
  if (NS_WARN_IF(!documentRootElement)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  // process clearing any styles first
 | 
						|
  UniquePtr<PendingStyle> pendingStyle =
 | 
						|
      mPendingStylesToApplyToNewContent->TakeClearingStyle();
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret(aPointToInsertText);
 | 
						|
  {
 | 
						|
    // Transactions may set selection, but we will set selection if necessary.
 | 
						|
    AutoTransactionsConserveSelection dontChangeMySelection(*this);
 | 
						|
 | 
						|
    while (pendingStyle &&
 | 
						|
           pointToPutCaret.GetContainer() != documentRootElement) {
 | 
						|
      // MOZ_KnownLive because we own pendingStyle which guarantees the lifetime
 | 
						|
      // of its members.
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          ClearStyleAt(pointToPutCaret, pendingStyle->ToInlineStyle(),
 | 
						|
                       pendingStyle->GetSpecifiedStyle());
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::ClearStyleAt() failed");
 | 
						|
        return pointToPutCaretOrError;
 | 
						|
      }
 | 
						|
      pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
      pendingStyle = mPendingStylesToApplyToNewContent->TakeClearingStyle();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // then process setting any styles
 | 
						|
  const int32_t relFontSize =
 | 
						|
      mPendingStylesToApplyToNewContent->TakeRelativeFontSize();
 | 
						|
  AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
 | 
						|
  mPendingStylesToApplyToNewContent->TakeAllPreservedStyles(stylesToSet);
 | 
						|
  if (stylesToSet.IsEmpty() && !relFontSize) {
 | 
						|
    return pointToPutCaret;
 | 
						|
  }
 | 
						|
 | 
						|
  // We're in chrome, e.g., the email composer of Thunderbird, and there is
 | 
						|
  // relative font size changes, we need to keep using legacy path until we port
 | 
						|
  // IncrementOrDecrementFontSizeAsSubAction() to work with
 | 
						|
  // AutoInlineStyleSetter.
 | 
						|
  if (relFontSize) {
 | 
						|
    // we have at least one style to add; make a new text node to insert style
 | 
						|
    // nodes above.
 | 
						|
    EditorDOMPoint pointToInsertTextNode(pointToPutCaret);
 | 
						|
    if (pointToInsertTextNode.IsInTextNode()) {
 | 
						|
      // if we are in a text node, split it
 | 
						|
      Result<SplitNodeResult, nsresult> splitTextNodeResult =
 | 
						|
          SplitNodeDeepWithTransaction(
 | 
						|
              MOZ_KnownLive(*pointToInsertTextNode.ContainerAs<Text>()),
 | 
						|
              pointToInsertTextNode,
 | 
						|
              SplitAtEdges::eAllowToCreateEmptyContainer);
 | 
						|
      if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
            "eAllowToCreateEmptyContainer) failed");
 | 
						|
        return splitTextNodeResult.propagateErr();
 | 
						|
      }
 | 
						|
      SplitNodeResult unwrappedSplitTextNodeResult =
 | 
						|
          splitTextNodeResult.unwrap();
 | 
						|
      unwrappedSplitTextNodeResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, *this,
 | 
						|
          {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
           SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
      pointToInsertTextNode =
 | 
						|
          unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
 | 
						|
    }
 | 
						|
    if (!pointToInsertTextNode.IsInContentNode() ||
 | 
						|
        !HTMLEditUtils::IsContainerNode(
 | 
						|
            *pointToInsertTextNode.ContainerAs<nsIContent>())) {
 | 
						|
      return pointToPutCaret;
 | 
						|
    }
 | 
						|
    RefPtr<Text> newEmptyTextNode = CreateTextNode(u""_ns);
 | 
						|
    if (!newEmptyTextNode) {
 | 
						|
      NS_WARNING("EditorBase::CreateTextNode() failed");
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    Result<CreateTextResult, nsresult> insertNewTextNodeResult =
 | 
						|
        InsertNodeWithTransaction<Text>(*newEmptyTextNode,
 | 
						|
                                        pointToInsertTextNode);
 | 
						|
    if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) {
 | 
						|
      NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
 | 
						|
      return insertNewTextNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
    pointToPutCaret.Set(newEmptyTextNode, 0u);
 | 
						|
 | 
						|
    // FIXME: If the stylesToSet have background-color style, it may
 | 
						|
    // be applied shorter because outer <span> element height is not
 | 
						|
    // computed with inner element's height.
 | 
						|
    HTMLEditor::FontSize incrementOrDecrement =
 | 
						|
        relFontSize > 0 ? HTMLEditor::FontSize::incr
 | 
						|
                        : HTMLEditor::FontSize::decr;
 | 
						|
    for ([[maybe_unused]] uint32_t j : IntegerRange(Abs(relFontSize))) {
 | 
						|
      Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult =
 | 
						|
          SetFontSizeOnTextNode(*newEmptyTextNode, 0, UINT32_MAX,
 | 
						|
                                incrementOrDecrement);
 | 
						|
      if (MOZ_UNLIKELY(wrapTextInBigOrSmallElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
 | 
						|
        return wrapTextInBigOrSmallElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      // We don't need to update here because we'll suggest caret position
 | 
						|
      // which is computed above.
 | 
						|
      MOZ_ASSERT(pointToPutCaret.IsSet());
 | 
						|
      wrapTextInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
    }
 | 
						|
 | 
						|
    for (const EditorInlineStyleAndValue& styleToSet : stylesToSet) {
 | 
						|
      AutoInlineStyleSetter inlineStyleSetter(styleToSet);
 | 
						|
      // MOZ_KnownLive(...ContainerAs<nsIContent>()) because pointToPutCaret
 | 
						|
      // grabs the result.
 | 
						|
      Result<CaretPoint, nsresult> setStyleResult =
 | 
						|
          inlineStyleSetter.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
 | 
						|
              *this, MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()));
 | 
						|
      if (MOZ_UNLIKELY(setStyleResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
 | 
						|
        return setStyleResult.propagateErr();
 | 
						|
      }
 | 
						|
      // We don't need to update here because we'll suggest caret position which
 | 
						|
      // is computed above.
 | 
						|
      MOZ_ASSERT(pointToPutCaret.IsSet());
 | 
						|
      setStyleResult.unwrap().IgnoreCaretPointSuggestion();
 | 
						|
    }
 | 
						|
    return pointToPutCaret;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we have preserved commands except relative font style changes, we can
 | 
						|
  // use inline style setting code which reuse ancestors better.
 | 
						|
  AutoRangeArray ranges(pointToPutCaret);
 | 
						|
  if (MOZ_UNLIKELY(ranges.Ranges().IsEmpty())) {
 | 
						|
    NS_WARNING("AutoRangeArray::AutoRangeArray() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  nsresult rv =
 | 
						|
      SetInlinePropertiesAroundRanges(ranges, stylesToSet, aEditingHost);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  if (NS_WARN_IF(ranges.Ranges().IsEmpty())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  // Now `ranges` selects new styled contents and the range may not be
 | 
						|
  // collapsed.  We should use the deepest editable start point of the range
 | 
						|
  // to insert text.
 | 
						|
  nsINode* container = ranges.FirstRangeRef()->GetStartContainer();
 | 
						|
  if (MOZ_UNLIKELY(!container->IsContent())) {
 | 
						|
    container = ranges.FirstRangeRef()->GetChildAtStartOffset();
 | 
						|
    if (MOZ_UNLIKELY(!container)) {
 | 
						|
      NS_WARNING("How did we get lost insertion point?");
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  pointToPutCaret =
 | 
						|
      HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
 | 
						|
          *container->AsContent());
 | 
						|
  if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::AlignAsSubAction(
 | 
						|
    const nsAString& aAlignType, const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(
 | 
						|
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext,
 | 
						|
      ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(ignoredError.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Some selection containers are not content node, but ignored");
 | 
						|
    return EditActionResult::IgnoredResult();
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Mutation event listener might have changed the selection");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  AutoRangeArray selectionRanges(SelectionRef());
 | 
						|
 | 
						|
  // XXX Why do we do this only when there is only one selection range?
 | 
						|
  if (!selectionRanges.IsCollapsed() &&
 | 
						|
      selectionRanges.Ranges().Length() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            selectionRanges.FirstRangeRef(), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.propagateErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we
 | 
						|
    // cannot use etStartAndEnd() here.
 | 
						|
    nsresult rv = selectionRanges.SetBaseAndExtent(
 | 
						|
        extendedRange.inspect().StartRef(), extendedRange.inspect().EndRef());
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  rv = AlignContentsAtRanges(selectionRanges, aAlignType, aEditingHost);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("HTMLEditor::AlignContentsAtSelection() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  rv = selectionRanges.ApplyTo(SelectionRef());
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("AutoRangeArray::ApplyTo() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
 | 
						|
    NS_WARNING("Mutation event listener might have changed the selection");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
 | 
						|
        "failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AlignContentsAtRanges(AutoRangeArray& aRanges,
 | 
						|
                                           const nsAString& aAlignType,
 | 
						|
                                           const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aRanges.IsInContent());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
 | 
						|
  // Convert the selection ranges into "promoted" selection ranges: This
 | 
						|
  // basically just expands the range to include the immediate block parent,
 | 
						|
  // and then further expands to include any ancestors whose children are all
 | 
						|
  // in the range
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
 | 
						|
  {
 | 
						|
    AutoRangeArray extendedRanges(aRanges);
 | 
						|
    extendedRanges.ExtendRangesToWrapLines(
 | 
						|
        EditSubAction::eSetOrClearAlignment,
 | 
						|
        BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
    Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
        extendedRanges
 | 
						|
            .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
 | 
						|
                *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoRangeArray::"
 | 
						|
          "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
 | 
						|
          "failed");
 | 
						|
      return splitResult.unwrapErr();
 | 
						|
    }
 | 
						|
    if (splitResult.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = splitResult.unwrap();
 | 
						|
    }
 | 
						|
    nsresult rv = extendedRanges.CollectEditTargetNodes(
 | 
						|
        *this, arrayOfContents, EditSubAction::eSetOrClearAlignment,
 | 
						|
        AutoRangeArray::CollectNonEditableNodes::Yes);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
 | 
						|
          "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
      MaybeSplitElementsAtEveryBRElement(arrayOfContents,
 | 
						|
                                         EditSubAction::eSetOrClearAlignment);
 | 
						|
  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
 | 
						|
        "eSetOrClearAlignment) failed");
 | 
						|
    return splitAtBRElementsResult.inspectErr();
 | 
						|
  }
 | 
						|
  if (splitAtBRElementsResult.inspect().IsSet()) {
 | 
						|
    pointToPutCaret = splitAtBRElementsResult.unwrap();
 | 
						|
  }
 | 
						|
 | 
						|
  // If we don't have any nodes, or we have only a single br, then we are
 | 
						|
  // creating an empty alignment div.  We have to do some different things for
 | 
						|
  // these.
 | 
						|
  bool createEmptyDivElement = arrayOfContents.IsEmpty();
 | 
						|
  if (arrayOfContents.Length() == 1) {
 | 
						|
    OwningNonNull<nsIContent>& content = arrayOfContents[0];
 | 
						|
 | 
						|
    if (HTMLEditUtils::SupportsAlignAttr(content) &&
 | 
						|
        HTMLEditUtils::IsBlockElement(content,
 | 
						|
                                      BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
      // The node is a table element, an hr, a paragraph, a div or a section
 | 
						|
      // header; in HTML 4, it can directly carry the ALIGN attribute and we
 | 
						|
      // don't need to make a div! If we are in CSS mode, all the work is done
 | 
						|
      // in SetBlockElementAlign().
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
 | 
						|
                               EditTarget::OnlyDescendantsExceptTable);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::SetBlockElementAlign() failed");
 | 
						|
        return pointToPutCaretOrError.unwrapErr();
 | 
						|
      }
 | 
						|
      if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (content->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
      // The special case createEmptyDivElement code (below) that consumes
 | 
						|
      // `<br>` elements can cause tables to split if the start node of the
 | 
						|
      // selection is not in a table cell or caption, for example parent is a
 | 
						|
      // `<tr>`.  Avoid this unnecessary splitting if possible by leaving
 | 
						|
      // createEmptyDivElement false so that we fall through to the normal case
 | 
						|
      // alignment code.
 | 
						|
      //
 | 
						|
      // XXX: It seems a little error prone for the createEmptyDivElement
 | 
						|
      //      special case code to assume that the start node of the selection
 | 
						|
      //      is the parent of the single node in the arrayOfContents, as the
 | 
						|
      //      paragraph above points out. Do we rely on the selection start
 | 
						|
      //      node because of the fact that arrayOfContents can be empty?  We
 | 
						|
      //      should probably revisit this issue. - kin
 | 
						|
 | 
						|
      const EditorDOMPoint firstRangeStartPoint =
 | 
						|
          pointToPutCaret.IsSet()
 | 
						|
              ? pointToPutCaret
 | 
						|
              : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
      if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
      }
 | 
						|
      nsINode* parent = firstRangeStartPoint.GetContainer();
 | 
						|
      createEmptyDivElement = !HTMLEditUtils::IsAnyTableElement(parent) ||
 | 
						|
                              HTMLEditUtils::IsTableCellOrCaption(*parent);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (createEmptyDivElement) {
 | 
						|
    if (MOZ_UNLIKELY(!pointToPutCaret.IsSet() && !aRanges.IsInContent())) {
 | 
						|
      NS_WARNING("Mutation event listener might have changed the selection");
 | 
						|
      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
 | 
						|
    }
 | 
						|
    const EditorDOMPoint pointToInsertDivElement =
 | 
						|
        pointToPutCaret.IsSet()
 | 
						|
            ? pointToPutCaret
 | 
						|
            : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
 | 
						|
    Result<CreateElementResult, nsresult> insertNewDivElementResult =
 | 
						|
        InsertDivElementToAlignContents(pointToInsertDivElement, aAlignType,
 | 
						|
                                        aEditingHost);
 | 
						|
    if (insertNewDivElementResult.isErr()) {
 | 
						|
      NS_WARNING("HTMLEditor::InsertDivElementToAlignContents() failed");
 | 
						|
      return insertNewDivElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedInsertNewDivElementResult =
 | 
						|
        insertNewDivElementResult.unwrap();
 | 
						|
    aRanges.ClearSavedRanges();
 | 
						|
    EditorDOMPoint pointToPutCaret =
 | 
						|
        unwrappedInsertNewDivElementResult.UnwrapCaretPoint();
 | 
						|
    nsresult rv = aRanges.Collapse(pointToPutCaret);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::Collapse() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> maybeCreateDivElementResult =
 | 
						|
      AlignNodesAndDescendants(arrayOfContents, aAlignType, aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(maybeCreateDivElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::AlignNodesAndDescendants() failed");
 | 
						|
    return maybeCreateDivElementResult.unwrapErr();
 | 
						|
  }
 | 
						|
  maybeCreateDivElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  MOZ_ASSERT(aRanges.HasSavedRanges());
 | 
						|
  aRanges.RestoreFromSavedRanges();
 | 
						|
  // If restored range is collapsed outside the latest cased <div> element,
 | 
						|
  // we should move caret into the <div>.
 | 
						|
  if (maybeCreateDivElementResult.inspect().GetNewNode() &&
 | 
						|
      aRanges.IsCollapsed() && !aRanges.Ranges().IsEmpty()) {
 | 
						|
    const auto firstRangeStartRawPoint =
 | 
						|
        aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
 | 
						|
    if (MOZ_LIKELY(firstRangeStartRawPoint.IsSet())) {
 | 
						|
      Result<EditorRawDOMPoint, nsresult> pointInNewDivOrError =
 | 
						|
          HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
 | 
						|
              EditorRawDOMPoint>(
 | 
						|
              *maybeCreateDivElementResult.inspect().GetNewNode(),
 | 
						|
              firstRangeStartRawPoint);
 | 
						|
      if (MOZ_UNLIKELY(pointInNewDivOrError.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
 | 
						|
            "but ignored");
 | 
						|
      } else if (pointInNewDivOrError.inspect().IsSet()) {
 | 
						|
        nsresult rv = aRanges.Collapse(pointInNewDivOrError.unwrap());
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("AutoRangeArray::Collapse() failed");
 | 
						|
          return rv;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult>
 | 
						|
HTMLEditor::InsertDivElementToAlignContents(
 | 
						|
    const EditorDOMPoint& aPointToInsert, const nsAString& aAlignType,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
 | 
						|
  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aPointToInsert.IsSet())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
      InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
          *nsGkAtoms::div, aPointToInsert, BRElementNextToSplitPoint::Delete,
 | 
						|
          aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
        "nsGkAtoms::div, BRElementNextToSplitPoint::Delete) failed");
 | 
						|
    return createNewDivElementResult;
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedCreateNewDivElementResult =
 | 
						|
      createNewDivElementResult.unwrap();
 | 
						|
  // We'll suggest start of the new <div>, so we don't need the suggested
 | 
						|
  // position.
 | 
						|
  unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
 | 
						|
  RefPtr<Element> newDivElement =
 | 
						|
      unwrappedCreateNewDivElementResult.UnwrapNewNode();
 | 
						|
  // Set up the alignment on the div, using HTML or CSS
 | 
						|
  Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
      SetBlockElementAlign(*newDivElement, aAlignType,
 | 
						|
                           EditTarget::OnlyDescendantsExceptTable);
 | 
						|
  if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SetBlockElementAlign(EditTarget::"
 | 
						|
        "OnlyDescendantsExceptTable) failed");
 | 
						|
    return pointToPutCaretOrError.propagateErr();
 | 
						|
  }
 | 
						|
  // We don't need the new suggested position too.
 | 
						|
 | 
						|
  // Put in a padding <br> element for empty last line so that it won't get
 | 
						|
  // deleted.
 | 
						|
  {
 | 
						|
    Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
        InsertPaddingBRElementForEmptyLastLineWithTransaction(
 | 
						|
            EditorDOMPoint(newDivElement, 0u));
 | 
						|
    if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
 | 
						|
          "failed");
 | 
						|
      return insertPaddingBRElementResult;
 | 
						|
    }
 | 
						|
    insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  }
 | 
						|
 | 
						|
  return CreateElementResult(std::move(newDivElement),
 | 
						|
                             EditorDOMPoint(newDivElement, 0u));
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult> HTMLEditor::AlignNodesAndDescendants(
 | 
						|
    nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
 | 
						|
    const nsAString& aAlignType, const Element& aEditingHost) {
 | 
						|
  // Detect all the transitions in the array, where a transition means that
 | 
						|
  // adjacent nodes in the array don't have the same parent.
 | 
						|
  AutoTArray<bool, 64> transitionList;
 | 
						|
  HTMLEditor::MakeTransitionList(aArrayOfContents, transitionList);
 | 
						|
 | 
						|
  RefPtr<Element> latestCreatedDivElement;
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
 | 
						|
  // Okay, now go through all the nodes and give them an align attrib or put
 | 
						|
  // them in a div, or whatever is appropriate.  Woohoo!
 | 
						|
 | 
						|
  RefPtr<Element> createdDivElement;
 | 
						|
  const bool useCSS = IsCSSEnabled();
 | 
						|
  int32_t indexOfTransitionList = -1;
 | 
						|
  for (OwningNonNull<nsIContent>& content : aArrayOfContents) {
 | 
						|
    ++indexOfTransitionList;
 | 
						|
 | 
						|
    // Ignore all non-editable nodes.  Leave them be.
 | 
						|
    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // The node is a table element, an hr, a paragraph, a div or a section
 | 
						|
    // header; in HTML 4, it can directly carry the ALIGN attribute and we
 | 
						|
    // don't need to nest it, just set the alignment.  In CSS, assign the
 | 
						|
    // corresponding CSS styles in SetBlockElementAlign().
 | 
						|
    if (HTMLEditUtils::SupportsAlignAttr(content)) {
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
 | 
						|
                               EditTarget::NodeAndDescendantsExceptTable);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SetBlockElementAlign(EditTarget::"
 | 
						|
            "NodeAndDescendantsExceptTable) failed");
 | 
						|
        return pointToPutCaretOrError.propagateErr();
 | 
						|
      }
 | 
						|
      if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
      // Clear out createdDivElement so that we don't put nodes after this one
 | 
						|
      // into it
 | 
						|
      createdDivElement = nullptr;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    EditorDOMPoint atContent(content);
 | 
						|
    if (NS_WARN_IF(!atContent.IsSet())) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Skip insignificant formatting text nodes to prevent unnecessary
 | 
						|
    // structure splitting!
 | 
						|
    if (content->IsText() &&
 | 
						|
        ((HTMLEditUtils::IsAnyTableElement(atContent.GetContainer()) &&
 | 
						|
          !HTMLEditUtils::IsTableCellOrCaption(*atContent.GetContainer())) ||
 | 
						|
         HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) ||
 | 
						|
         HTMLEditUtils::IsEmptyNode(
 | 
						|
             *content,
 | 
						|
             {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
              EmptyCheckOption::TreatNonEditableContentAsInvisible}))) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If it's a list item, or a list inside a list, forget any "current" div,
 | 
						|
    // and instead put divs inside the appropriate block (td, li, etc.)
 | 
						|
    if (HTMLEditUtils::IsListItem(content) ||
 | 
						|
        HTMLEditUtils::IsAnyListElement(content)) {
 | 
						|
      Element* listOrListItemElement = content->AsElement();
 | 
						|
      {
 | 
						|
        AutoEditorDOMPointOffsetInvalidator lockChild(atContent);
 | 
						|
        // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
 | 
						|
        // which is array of OwningNonNull.
 | 
						|
        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
            RemoveAlignFromDescendants(MOZ_KnownLive(*listOrListItemElement),
 | 
						|
                                       aAlignType,
 | 
						|
                                       EditTarget::OnlyDescendantsExceptTable);
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
 | 
						|
              "OnlyDescendantsExceptTable) failed");
 | 
						|
          return pointToPutCaretOrError.propagateErr();
 | 
						|
        }
 | 
						|
        if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
          pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if (NS_WARN_IF(!atContent.IsSetAndValid())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
 | 
						|
      if (useCSS) {
 | 
						|
        nsStyledElement* styledListOrListItemElement =
 | 
						|
            nsStyledElement::FromNode(listOrListItemElement);
 | 
						|
        if (styledListOrListItemElement &&
 | 
						|
            EditorElementStyle::Align().IsCSSSettable(
 | 
						|
                *styledListOrListItemElement)) {
 | 
						|
          // MOZ_KnownLive(*styledListOrListItemElement): An element of
 | 
						|
          // aArrayOfContents which is array of OwningNonNull.
 | 
						|
          Result<size_t, nsresult> result =
 | 
						|
              CSSEditUtils::SetCSSEquivalentToStyle(
 | 
						|
                  WithTransaction::Yes, *this,
 | 
						|
                  MOZ_KnownLive(*styledListOrListItemElement),
 | 
						|
                  EditorElementStyle::Align(), &aAlignType);
 | 
						|
          if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
            if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
              return result.propagateErr();
 | 
						|
            }
 | 
						|
            NS_WARNING(
 | 
						|
                "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
 | 
						|
                "Align()) failed, but ignored");
 | 
						|
          }
 | 
						|
        }
 | 
						|
        createdDivElement = nullptr;
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
 | 
						|
        // If we don't use CSS, add a content to list element: they have to
 | 
						|
        // be inside another list, i.e., >= second level of nesting.
 | 
						|
        // XXX AlignContentsInAllTableCellsAndListItems() handles only list
 | 
						|
        //     item elements and table cells.  Is it intentional?  Why don't
 | 
						|
        //     we need to align contents in other type blocks?
 | 
						|
        // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
 | 
						|
        // which is array of OwningNonNull.
 | 
						|
        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
            AlignContentsInAllTableCellsAndListItems(
 | 
						|
                MOZ_KnownLive(*listOrListItemElement), aAlignType);
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed");
 | 
						|
          return pointToPutCaretOrError.propagateErr();
 | 
						|
        }
 | 
						|
        if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
          pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
        }
 | 
						|
        createdDivElement = nullptr;
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      // Clear out createdDivElement so that we don't put nodes after this one
 | 
						|
      // into it
 | 
						|
    }
 | 
						|
 | 
						|
    // Need to make a div to put things in if we haven't already, or if this
 | 
						|
    // node doesn't go in div we used earlier.
 | 
						|
    if (!createdDivElement || transitionList[indexOfTransitionList]) {
 | 
						|
      // First, check that our element can contain a div.
 | 
						|
      if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
 | 
						|
                                         *nsGkAtoms::div)) {
 | 
						|
        // XXX Why do we return "OK" here rather than returning error or
 | 
						|
        //     doing continue?
 | 
						|
        return latestCreatedDivElement
 | 
						|
                   ? CreateElementResult(std::move(latestCreatedDivElement),
 | 
						|
                                         std::move(pointToPutCaret))
 | 
						|
                   : CreateElementResult::NotHandled(
 | 
						|
                         std::move(pointToPutCaret));
 | 
						|
      }
 | 
						|
 | 
						|
      Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
          InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
              *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
              aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
            "nsGkAtoms::div) failed");
 | 
						|
        return createNewDivElementResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedCreateNewDivElementResult =
 | 
						|
          createNewDivElementResult.unwrap();
 | 
						|
      if (unwrappedCreateNewDivElementResult.HasCaretPointSuggestion()) {
 | 
						|
        pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
 | 
						|
      }
 | 
						|
 | 
						|
      MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
 | 
						|
      createdDivElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
 | 
						|
      // Set up the alignment on the div
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          SetBlockElementAlign(*createdDivElement, aAlignType,
 | 
						|
                               EditTarget::OnlyDescendantsExceptTable);
 | 
						|
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
        if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
 | 
						|
                       NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
          return pointToPutCaretOrError.propagateErr();
 | 
						|
        }
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SetBlockElementAlign(EditTarget::"
 | 
						|
            "OnlyDescendantsExceptTable) failed, but ignored");
 | 
						|
      } else if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
      latestCreatedDivElement = createdDivElement;
 | 
						|
    }
 | 
						|
 | 
						|
    // Tuck the node into the end of the active div
 | 
						|
    //
 | 
						|
    // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it alive.
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
 | 
						|
                                     *createdDivElement);
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
      return moveNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
    if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
 | 
						|
      pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return latestCreatedDivElement
 | 
						|
             ? CreateElementResult(std::move(latestCreatedDivElement),
 | 
						|
                                   std::move(pointToPutCaret))
 | 
						|
             : CreateElementResult::NotHandled(std::move(pointToPutCaret));
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult>
 | 
						|
HTMLEditor::AlignContentsInAllTableCellsAndListItems(
 | 
						|
    Element& aElement, const nsAString& aAlignType) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  // Gather list of table cells or list items
 | 
						|
  AutoTArray<OwningNonNull<Element>, 64> arrayOfTableCellsAndListItems;
 | 
						|
  DOMIterator iter(aElement);
 | 
						|
  iter.AppendNodesToArray(
 | 
						|
      +[](nsINode& aNode, void*) -> bool {
 | 
						|
        MOZ_ASSERT(Element::FromNode(&aNode));
 | 
						|
        return HTMLEditUtils::IsTableCell(&aNode) ||
 | 
						|
               HTMLEditUtils::IsListItem(&aNode);
 | 
						|
      },
 | 
						|
      arrayOfTableCellsAndListItems);
 | 
						|
 | 
						|
  // Now that we have the list, align their contents as requested
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  for (auto& tableCellOrListItemElement : arrayOfTableCellsAndListItems) {
 | 
						|
    // MOZ_KnownLive because 'arrayOfTableCellsAndListItems' is guaranteed to
 | 
						|
    // keep it alive.
 | 
						|
    Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
        AlignBlockContentsWithDivElement(
 | 
						|
            MOZ_KnownLive(tableCellOrListItemElement), aAlignType);
 | 
						|
    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed");
 | 
						|
      return pointToPutCaretOrError;
 | 
						|
    }
 | 
						|
    if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::AlignBlockContentsWithDivElement(
 | 
						|
    Element& aBlockElement, const nsAString& aAlignType) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  // XXX I don't understand why we should NOT align non-editable children
 | 
						|
  //     with modifying EDITABLE `<div>` element.
 | 
						|
  nsCOMPtr<nsIContent> firstEditableContent = HTMLEditUtils::GetFirstChild(
 | 
						|
      aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (!firstEditableContent) {
 | 
						|
    // This block has no editable content, nothing to align.
 | 
						|
    return EditorDOMPoint();
 | 
						|
  }
 | 
						|
 | 
						|
  // If there is only one editable content and it's a `<div>` element,
 | 
						|
  // just set `align` attribute of it.
 | 
						|
  nsCOMPtr<nsIContent> lastEditableContent = HTMLEditUtils::GetLastChild(
 | 
						|
      aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (firstEditableContent == lastEditableContent &&
 | 
						|
      firstEditableContent->IsHTMLElement(nsGkAtoms::div)) {
 | 
						|
    nsresult rv = SetAttributeOrEquivalent(
 | 
						|
        MOZ_KnownLive(firstEditableContent->AsElement()), nsGkAtoms::align,
 | 
						|
        aAlignType, false);
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) caused "
 | 
						|
          "destroying the editor");
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return EditorDOMPoint();
 | 
						|
  }
 | 
						|
 | 
						|
  // Otherwise, we need to insert a `<div>` element to set `align` attribute.
 | 
						|
  // XXX Don't insert the new `<div>` element until we set `align` attribute
 | 
						|
  //     for avoiding running mutation event listeners.
 | 
						|
  Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
      CreateAndInsertElement(
 | 
						|
          WithTransaction::Yes, *nsGkAtoms::div,
 | 
						|
          EditorDOMPoint(&aBlockElement, 0u),
 | 
						|
          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
 | 
						|
          [&aAlignType](HTMLEditor& aHTMLEditor, Element& aDivElement,
 | 
						|
                        const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
 | 
						|
            MOZ_ASSERT(!aDivElement.IsInComposedDoc());
 | 
						|
            // If aDivElement has not been connected yet, we do not need
 | 
						|
            // transaction of setting align attribute here.
 | 
						|
            nsresult rv = aHTMLEditor.SetAttributeOrEquivalent(
 | 
						|
                &aDivElement, nsGkAtoms::align, aAlignType, false);
 | 
						|
            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                                 "EditorBase::SetAttributeOrEquivalent("
 | 
						|
                                 "nsGkAtoms::align, \"...\", false) failed");
 | 
						|
            return rv;
 | 
						|
          });
 | 
						|
  if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes, "
 | 
						|
        "nsGkAtoms::div) failed");
 | 
						|
    return createNewDivElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedCreateNewDivElementResult =
 | 
						|
      createNewDivElementResult.unwrap();
 | 
						|
  EditorDOMPoint pointToPutCaret =
 | 
						|
      unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
 | 
						|
  RefPtr<Element> newDivElement =
 | 
						|
      unwrappedCreateNewDivElementResult.UnwrapNewNode();
 | 
						|
  MOZ_ASSERT(newDivElement);
 | 
						|
  // XXX This is tricky and does not work with mutation event listeners.
 | 
						|
  //     But I'm not sure what we should do if new content is inserted.
 | 
						|
  //     Anyway, I don't think that we should move editable contents
 | 
						|
  //     over non-editable contents.  Chrome does no do that.
 | 
						|
  while (lastEditableContent && (lastEditableContent != newDivElement)) {
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult = MoveNodeWithTransaction(
 | 
						|
        *lastEditableContent, EditorDOMPoint(newDivElement, 0u));
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
      return moveNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
    if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
 | 
						|
      pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
 | 
						|
    }
 | 
						|
    lastEditableContent = HTMLEditUtils::GetLastChild(
 | 
						|
        aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  }
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorRawDOMRange, nsresult>
 | 
						|
HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
    const nsRange* aRange, const Element& aEditingHost) const {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  // This tweaks selections to be more "natural".
 | 
						|
  // Idea here is to adjust edges of selection ranges so that they do not cross
 | 
						|
  // breaks or block boundaries unless something editable beyond that boundary
 | 
						|
  // is also selected.  This adjustment makes it much easier for the various
 | 
						|
  // block operations to determine what nodes to act on.
 | 
						|
  if (NS_WARN_IF(!aRange) || NS_WARN_IF(!aRange->IsPositioned())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  const EditorRawDOMPoint startPoint(aRange->StartRef());
 | 
						|
  if (NS_WARN_IF(!startPoint.IsSet())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  const EditorRawDOMPoint endPoint(aRange->EndRef());
 | 
						|
  if (NS_WARN_IF(!endPoint.IsSet())) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  // adjusted values default to original values
 | 
						|
  EditorRawDOMRange newRange(startPoint, endPoint);
 | 
						|
 | 
						|
  // Is there any intervening visible white-space?  If so we can't push
 | 
						|
  // selection past that, it would visibly change meaning of users selection.
 | 
						|
  WSRunScanner wsScannerAtEnd(
 | 
						|
      &aEditingHost, endPoint,
 | 
						|
      // We should refer only the default style of HTML because we need to wrap
 | 
						|
      // any elements with a specific HTML element.  So we should not refer
 | 
						|
      // actual style.  For example, we want to reformat parent HTML block
 | 
						|
      // element even if selected in a blocked phrase element or
 | 
						|
      // non-HTMLelement.
 | 
						|
      BlockInlineCheck::UseHTMLDefaultStyle);
 | 
						|
  const WSScanResult scanResultAtEnd =
 | 
						|
      wsScannerAtEnd.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint);
 | 
						|
  if (scanResultAtEnd.Failed()) {
 | 
						|
    NS_WARNING(
 | 
						|
        "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  if (scanResultAtEnd.ReachedSomethingNonTextContent()) {
 | 
						|
    // eThisBlock and eOtherBlock conveniently distinguish cases
 | 
						|
    // of going "down" into a block and "up" out of a block.
 | 
						|
    if (wsScannerAtEnd.StartsFromOtherBlockElement()) {
 | 
						|
      // endpoint is just after the close of a block.
 | 
						|
      if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
 | 
						|
              *wsScannerAtEnd.StartReasonOtherBlockElementPtr(),
 | 
						|
              {LeafNodeType::LeafNodeOrChildBlock},
 | 
						|
              BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
        newRange.SetEnd(EditorRawDOMPoint::After(*child));
 | 
						|
      }
 | 
						|
      // else block is empty - we can leave selection alone here, i think.
 | 
						|
    } else if (wsScannerAtEnd.StartsFromCurrentBlockBoundary() ||
 | 
						|
               wsScannerAtEnd.StartsFromInlineEditingHostBoundary()) {
 | 
						|
      // endpoint is just after start of this block
 | 
						|
      if (nsIContent* child = HTMLEditUtils::GetPreviousContent(
 | 
						|
              endPoint, {WalkTreeOption::IgnoreNonEditableNode},
 | 
						|
              BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
 | 
						|
        newRange.SetEnd(EditorRawDOMPoint::After(*child));
 | 
						|
      }
 | 
						|
      // else block is empty - we can leave selection alone here, i think.
 | 
						|
    } else if (wsScannerAtEnd.StartsFromBRElement()) {
 | 
						|
      // endpoint is just after break.  lets adjust it to before it.
 | 
						|
      newRange.SetEnd(
 | 
						|
          EditorRawDOMPoint(wsScannerAtEnd.StartReasonBRElementPtr()));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Is there any intervening visible white-space?  If so we can't push
 | 
						|
  // selection past that, it would visibly change meaning of users selection.
 | 
						|
  WSRunScanner wsScannerAtStart(&aEditingHost, startPoint,
 | 
						|
                                BlockInlineCheck::UseHTMLDefaultStyle);
 | 
						|
  const WSScanResult scanResultAtStart =
 | 
						|
      wsScannerAtStart.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
 | 
						|
          startPoint);
 | 
						|
  if (scanResultAtStart.Failed()) {
 | 
						|
    NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  if (scanResultAtStart.ReachedSomethingNonTextContent()) {
 | 
						|
    // eThisBlock and eOtherBlock conveniently distinguish cases
 | 
						|
    // of going "down" into a block and "up" out of a block.
 | 
						|
    if (wsScannerAtStart.EndsByOtherBlockElement()) {
 | 
						|
      // startpoint is just before the start of a block.
 | 
						|
      if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
 | 
						|
              *wsScannerAtStart.EndReasonOtherBlockElementPtr(),
 | 
						|
              {LeafNodeType::LeafNodeOrChildBlock},
 | 
						|
              BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
        newRange.SetStart(EditorRawDOMPoint(child));
 | 
						|
      }
 | 
						|
      // else block is empty - we can leave selection alone here, i think.
 | 
						|
    } else if (wsScannerAtStart.EndsByCurrentBlockBoundary() ||
 | 
						|
               wsScannerAtStart.EndsByInlineEditingHostBoundary()) {
 | 
						|
      // startpoint is just before end of this block
 | 
						|
      if (nsIContent* child = HTMLEditUtils::GetNextContent(
 | 
						|
              startPoint, {WalkTreeOption::IgnoreNonEditableNode},
 | 
						|
              BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
 | 
						|
        newRange.SetStart(EditorRawDOMPoint(child));
 | 
						|
      }
 | 
						|
      // else block is empty - we can leave selection alone here, i think.
 | 
						|
    } else if (wsScannerAtStart.EndsByBRElement()) {
 | 
						|
      // startpoint is just before a break.  lets adjust it to after it.
 | 
						|
      newRange.SetStart(
 | 
						|
          EditorRawDOMPoint::After(*wsScannerAtStart.EndReasonBRElementPtr()));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // There is a demented possibility we have to check for.  We might have a very
 | 
						|
  // strange selection that is not collapsed and yet does not contain any
 | 
						|
  // editable content, and satisfies some of the above conditions that cause
 | 
						|
  // tweaking.  In this case we don't want to tweak the selection into a block
 | 
						|
  // it was never in, etc.  There are a variety of strategies one might use to
 | 
						|
  // try to detect these cases, but I think the most straightforward is to see
 | 
						|
  // if the adjusted locations "cross" the old values: i.e., new end before old
 | 
						|
  // start, or new start after old end.  If so then just leave things alone.
 | 
						|
 | 
						|
  Maybe<int32_t> comp = nsContentUtils::ComparePoints(
 | 
						|
      startPoint.ToRawRangeBoundary(), newRange.EndRef().ToRawRangeBoundary());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!comp)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (*comp == 1) {
 | 
						|
    return EditorRawDOMRange();  // New end before old start.
 | 
						|
  }
 | 
						|
 | 
						|
  comp = nsContentUtils::ComparePoints(newRange.StartRef().ToRawRangeBoundary(),
 | 
						|
                                       endPoint.ToRawRangeBoundary());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!comp)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (*comp == 1) {
 | 
						|
    return EditorRawDOMRange();  // New start after old end.
 | 
						|
  }
 | 
						|
 | 
						|
  return newRange;
 | 
						|
}
 | 
						|
 | 
						|
template <typename EditorDOMRangeType>
 | 
						|
already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorDOMRangeType& aRange) {
 | 
						|
  MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned());
 | 
						|
  return CreateRangeIncludingAdjuscentWhiteSpaces(aRange.StartRef(),
 | 
						|
                                                  aRange.EndRef());
 | 
						|
}
 | 
						|
 | 
						|
template <typename EditorDOMPointType1, typename EditorDOMPointType2>
 | 
						|
already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
 | 
						|
    const EditorDOMPointType1& aStartPoint,
 | 
						|
    const EditorDOMPointType2& aEndPoint) {
 | 
						|
  MOZ_DIAGNOSTIC_ASSERT(!aStartPoint.IsInNativeAnonymousSubtree());
 | 
						|
  MOZ_DIAGNOSTIC_ASSERT(!aEndPoint.IsInNativeAnonymousSubtree());
 | 
						|
 | 
						|
  if (!aStartPoint.IsInContentNode() || !aEndPoint.IsInContentNode()) {
 | 
						|
    NS_WARNING_ASSERTION(aStartPoint.IsSet(), "aStartPoint was not set");
 | 
						|
    NS_WARNING_ASSERTION(aEndPoint.IsSet(), "aEndPoint was not set");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  const Element* const editingHost = ComputeEditingHost();
 | 
						|
  if (NS_WARN_IF(!editingHost)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint startPoint = aStartPoint.template To<EditorDOMPoint>();
 | 
						|
  EditorDOMPoint endPoint = aEndPoint.template To<EditorDOMPoint>();
 | 
						|
  AutoRangeArray::UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
 | 
						|
      startPoint, endPoint, *editingHost);
 | 
						|
 | 
						|
  if (NS_WARN_IF(!startPoint.IsInContentNode()) ||
 | 
						|
      NS_WARN_IF(!endPoint.IsInContentNode())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "AutoRangeArray::"
 | 
						|
        "UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement() "
 | 
						|
        "failed");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // For text actions, we want to look backwards (or forwards, as
 | 
						|
  // appropriate) for additional white-space or nbsp's.  We may have to act
 | 
						|
  // on these later even though they are outside of the initial selection.
 | 
						|
  // Even if they are in another node!
 | 
						|
  // XXX Those scanners do not treat siblings of the text nodes.  Perhaps,
 | 
						|
  //     we should use `WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo()`
 | 
						|
  //     and `WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces()` instead.
 | 
						|
  if (startPoint.IsInTextNode()) {
 | 
						|
    while (!startPoint.IsStartOfContainer()) {
 | 
						|
      if (!startPoint.IsPreviousCharASCIISpaceOrNBSP()) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      MOZ_ALWAYS_TRUE(startPoint.RewindOffset());
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (!startPoint.GetChildOrContainerIfDataNode() ||
 | 
						|
      !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
 | 
						|
          editingHost)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  if (endPoint.IsInTextNode()) {
 | 
						|
    while (!endPoint.IsEndOfContainer()) {
 | 
						|
      if (!endPoint.IsCharASCIISpaceOrNBSP()) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset());
 | 
						|
    }
 | 
						|
  }
 | 
						|
  EditorDOMPoint lastRawPoint(endPoint);
 | 
						|
  if (!lastRawPoint.IsStartOfContainer()) {
 | 
						|
    lastRawPoint.RewindOffset();
 | 
						|
  }
 | 
						|
  if (!lastRawPoint.GetChildOrContainerIfDataNode() ||
 | 
						|
      !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
 | 
						|
          editingHost)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<nsRange> range =
 | 
						|
      nsRange::Create(startPoint.ToRawRangeBoundary(),
 | 
						|
                      endPoint.ToRawRangeBoundary(), IgnoreErrors());
 | 
						|
  NS_WARNING_ASSERTION(range, "nsRange::Create() failed");
 | 
						|
  return range.forget();
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::MaybeSplitElementsAtEveryBRElement(
 | 
						|
    nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
 | 
						|
    EditSubAction aEditSubAction) {
 | 
						|
  // Post-process the list to break up inline containers that contain br's, but
 | 
						|
  // only for operations that might care, like making lists or paragraphs
 | 
						|
  switch (aEditSubAction) {
 | 
						|
    case EditSubAction::eCreateOrRemoveBlock:
 | 
						|
    case EditSubAction::eFormatBlockForHTMLCommand:
 | 
						|
    case EditSubAction::eMergeBlockContents:
 | 
						|
    case EditSubAction::eCreateOrChangeList:
 | 
						|
    case EditSubAction::eSetOrClearAlignment:
 | 
						|
    case EditSubAction::eSetPositionToAbsolute:
 | 
						|
    case EditSubAction::eIndent:
 | 
						|
    case EditSubAction::eOutdent: {
 | 
						|
      EditorDOMPoint pointToPutCaret;
 | 
						|
      for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) {
 | 
						|
        OwningNonNull<nsIContent>& content = aArrayOfContents[index];
 | 
						|
        if (HTMLEditUtils::IsInlineContent(
 | 
						|
                content, BlockInlineCheck::UseHTMLDefaultStyle) &&
 | 
						|
            HTMLEditUtils::IsContainerNode(content) && !content->IsText()) {
 | 
						|
          AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfInlineContents;
 | 
						|
          // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
 | 
						|
          // alive.
 | 
						|
          Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
              SplitElementsAtEveryBRElement(MOZ_KnownLive(content),
 | 
						|
                                            arrayOfInlineContents);
 | 
						|
          if (splitResult.isErr()) {
 | 
						|
            NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed");
 | 
						|
            return splitResult;
 | 
						|
          }
 | 
						|
          if (splitResult.inspect().IsSet()) {
 | 
						|
            pointToPutCaret = splitResult.unwrap();
 | 
						|
          }
 | 
						|
          // Put these nodes in aArrayOfContents, replacing the current node
 | 
						|
          aArrayOfContents.RemoveElementAt(index);
 | 
						|
          aArrayOfContents.InsertElementsAt(index, arrayOfInlineContents);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return pointToPutCaret;
 | 
						|
    }
 | 
						|
    default:
 | 
						|
      return EditorDOMPoint();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult>
 | 
						|
HTMLEditor::SplitInlineAncestorsAtRangeBoundaries(
 | 
						|
    RangeItem& aRangeItem, BlockInlineCheck aBlockInlineCheck,
 | 
						|
    const Element& aEditingHost,
 | 
						|
    const nsIContent* aAncestorLimiter /* = nullptr */) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  if (!aRangeItem.Collapsed() && aRangeItem.mEndContainer &&
 | 
						|
      aRangeItem.mEndContainer->IsContent()) {
 | 
						|
    nsCOMPtr<nsIContent> mostAncestorInlineContentAtEnd =
 | 
						|
        HTMLEditUtils::GetMostDistantAncestorInlineElement(
 | 
						|
            *aRangeItem.mEndContainer->AsContent(), aBlockInlineCheck,
 | 
						|
            &aEditingHost, aAncestorLimiter);
 | 
						|
 | 
						|
    if (mostAncestorInlineContentAtEnd) {
 | 
						|
      Result<SplitNodeResult, nsresult> splitEndInlineResult =
 | 
						|
          SplitNodeDeepWithTransaction(
 | 
						|
              *mostAncestorInlineContentAtEnd, aRangeItem.EndPoint(),
 | 
						|
              SplitAtEdges::eDoNotCreateEmptyContainer);
 | 
						|
      if (MOZ_UNLIKELY(splitEndInlineResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
            "eDoNotCreateEmptyContainer) failed");
 | 
						|
        return splitEndInlineResult.propagateErr();
 | 
						|
      }
 | 
						|
      SplitNodeResult unwrappedSplitEndInlineResult =
 | 
						|
          splitEndInlineResult.unwrap();
 | 
						|
      unwrappedSplitEndInlineResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      if (pointToPutCaret.IsInContentNode() &&
 | 
						|
          MOZ_UNLIKELY(
 | 
						|
              &aEditingHost !=
 | 
						|
              ComputeEditingHost(*pointToPutCaret.ContainerAs<nsIContent>()))) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
            "eDoNotCreateEmptyContainer) caused changing editing host");
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
      const auto splitPointAtEnd =
 | 
						|
          unwrappedSplitEndInlineResult.AtSplitPoint<EditorRawDOMPoint>();
 | 
						|
      if (MOZ_UNLIKELY(!splitPointAtEnd.IsSet())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
            "eDoNotCreateEmptyContainer) didn't return split point");
 | 
						|
        return Err(NS_ERROR_FAILURE);
 | 
						|
      }
 | 
						|
      aRangeItem.mEndContainer = splitPointAtEnd.GetContainer();
 | 
						|
      aRangeItem.mEndOffset = splitPointAtEnd.Offset();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!aRangeItem.mStartContainer || !aRangeItem.mStartContainer->IsContent()) {
 | 
						|
    return pointToPutCaret;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIContent> mostAncestorInlineContentAtStart =
 | 
						|
      HTMLEditUtils::GetMostDistantAncestorInlineElement(
 | 
						|
          *aRangeItem.mStartContainer->AsContent(), aBlockInlineCheck,
 | 
						|
          &aEditingHost, aAncestorLimiter);
 | 
						|
 | 
						|
  if (mostAncestorInlineContentAtStart) {
 | 
						|
    Result<SplitNodeResult, nsresult> splitStartInlineResult =
 | 
						|
        SplitNodeDeepWithTransaction(*mostAncestorInlineContentAtStart,
 | 
						|
                                     aRangeItem.StartPoint(),
 | 
						|
                                     SplitAtEdges::eDoNotCreateEmptyContainer);
 | 
						|
    if (MOZ_UNLIKELY(splitStartInlineResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
          "eDoNotCreateEmptyContainer) failed");
 | 
						|
      return splitStartInlineResult.propagateErr();
 | 
						|
    }
 | 
						|
    SplitNodeResult unwrappedSplitStartInlineResult =
 | 
						|
        splitStartInlineResult.unwrap();
 | 
						|
    // XXX Why don't we check editing host like above??
 | 
						|
    unwrappedSplitStartInlineResult.MoveCaretPointTo(
 | 
						|
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
    // XXX If we split only here because of collapsed range, we're modifying
 | 
						|
    //     only start point of aRangeItem.  Shouldn't we modify end point here
 | 
						|
    //     if it's collapsed?
 | 
						|
    const auto splitPointAtStart =
 | 
						|
        unwrappedSplitStartInlineResult.AtSplitPoint<EditorRawDOMPoint>();
 | 
						|
    if (MOZ_UNLIKELY(!splitPointAtStart.IsSet())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
          "eDoNotCreateEmptyContainer) didn't return split point");
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    aRangeItem.mStartContainer = splitPointAtStart.GetContainer();
 | 
						|
    aRangeItem.mStartOffset = splitPointAtStart.Offset();
 | 
						|
  }
 | 
						|
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::SplitElementsAtEveryBRElement(
 | 
						|
    nsIContent& aMostAncestorToBeSplit,
 | 
						|
    nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  // First build up a list of all the break nodes inside the inline container.
 | 
						|
  AutoTArray<OwningNonNull<HTMLBRElement>, 24> arrayOfBRElements;
 | 
						|
  DOMIterator iter(aMostAncestorToBeSplit);
 | 
						|
  iter.AppendAllNodesToArray(arrayOfBRElements);
 | 
						|
 | 
						|
  // If there aren't any breaks, just put inNode itself in the array
 | 
						|
  if (arrayOfBRElements.IsEmpty()) {
 | 
						|
    aOutArrayOfContents.AppendElement(aMostAncestorToBeSplit);
 | 
						|
    return EditorDOMPoint();
 | 
						|
  }
 | 
						|
 | 
						|
  // Else we need to bust up aMostAncestorToBeSplit along all the breaks
 | 
						|
  nsCOMPtr<nsIContent> nextContent = &aMostAncestorToBeSplit;
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  for (OwningNonNull<HTMLBRElement>& brElement : arrayOfBRElements) {
 | 
						|
    EditorDOMPoint atBRNode(brElement);
 | 
						|
    if (NS_WARN_IF(!atBRNode.IsSet())) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    Result<SplitNodeResult, nsresult> splitNodeResult =
 | 
						|
        SplitNodeDeepWithTransaction(
 | 
						|
            *nextContent, atBRNode, SplitAtEdges::eAllowToCreateEmptyContainer);
 | 
						|
    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
 | 
						|
      return splitNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
 | 
						|
    unwrappedSplitNodeResult.MoveCaretPointTo(
 | 
						|
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
    // Put previous node at the split point.
 | 
						|
    if (nsIContent* previousContent =
 | 
						|
            unwrappedSplitNodeResult.GetPreviousContent()) {
 | 
						|
      // Might not be a left node.  A break might have been at the very
 | 
						|
      // beginning of inline container, in which case
 | 
						|
      // SplitNodeDeepWithTransaction() would not actually split anything.
 | 
						|
      aOutArrayOfContents.AppendElement(*previousContent);
 | 
						|
    }
 | 
						|
 | 
						|
    // Move break outside of container and also put in node list
 | 
						|
    // MOZ_KnownLive because 'arrayOfBRElements' is guaranteed to keep it alive.
 | 
						|
    Result<MoveNodeResult, nsresult> moveBRElementResult =
 | 
						|
        MoveNodeWithTransaction(
 | 
						|
            MOZ_KnownLive(brElement),
 | 
						|
            unwrappedSplitNodeResult.AtNextContent<EditorDOMPoint>());
 | 
						|
    if (MOZ_UNLIKELY(moveBRElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
      return moveBRElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    MoveNodeResult unwrappedMoveBRElementResult = moveBRElementResult.unwrap();
 | 
						|
    unwrappedMoveBRElementResult.MoveCaretPointTo(
 | 
						|
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
    aOutArrayOfContents.AppendElement(brElement);
 | 
						|
 | 
						|
    nextContent = unwrappedSplitNodeResult.GetNextContent();
 | 
						|
  }
 | 
						|
 | 
						|
  // Now tack on remaining next node.
 | 
						|
  aOutArrayOfContents.AppendElement(*nextContent);
 | 
						|
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
void HTMLEditor::MakeTransitionList(
 | 
						|
    const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
 | 
						|
    nsTArray<bool>& aTransitionArray) {
 | 
						|
  nsINode* prevParent = nullptr;
 | 
						|
  aTransitionArray.EnsureLengthAtLeast(aArrayOfContents.Length());
 | 
						|
  for (uint32_t i = 0; i < aArrayOfContents.Length(); i++) {
 | 
						|
    aTransitionArray[i] = aArrayOfContents[i]->GetParentNode() != prevParent;
 | 
						|
    prevParent = aArrayOfContents[i]->GetParentNode();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Result<InsertParagraphResult, nsresult>
 | 
						|
HTMLEditor::HandleInsertParagraphInHeadingElement(
 | 
						|
    Element& aHeadingElement, const EditorDOMPoint& aPointToSplit) {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  auto splitHeadingResult =
 | 
						|
      [this, &aPointToSplit, &aHeadingElement]()
 | 
						|
          MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
 | 
						|
    // Normalize collapsible white-spaces around the split point to keep
 | 
						|
    // them visible after the split.  Note that this does not touch
 | 
						|
    // selection because of using AutoTransactionsConserveSelection in
 | 
						|
    // WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes().
 | 
						|
    Result<EditorDOMPoint, nsresult> preparationResult =
 | 
						|
        WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
 | 
						|
            *this, aPointToSplit, aHeadingElement);
 | 
						|
    if (MOZ_UNLIKELY(preparationResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
 | 
						|
          "failed");
 | 
						|
      return preparationResult.propagateErr();
 | 
						|
    }
 | 
						|
    EditorDOMPoint pointToSplit = preparationResult.unwrap();
 | 
						|
    MOZ_ASSERT(pointToSplit.IsInContentNode());
 | 
						|
 | 
						|
    // Split the header
 | 
						|
    Result<SplitNodeResult, nsresult> splitResult =
 | 
						|
        SplitNodeDeepWithTransaction(
 | 
						|
            aHeadingElement, pointToSplit,
 | 
						|
            SplitAtEdges::eAllowToCreateEmptyContainer);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        splitResult.isOk(),
 | 
						|
        "HTMLEditor::SplitNodeDeepWithTransaction(aHeadingElement, "
 | 
						|
        "SplitAtEdges::eAllowToCreateEmptyContainer) failed");
 | 
						|
    return splitResult;
 | 
						|
  }();
 | 
						|
  if (MOZ_UNLIKELY(splitHeadingResult.isErr())) {
 | 
						|
    NS_WARNING("Failed to splitting aHeadingElement");
 | 
						|
    return splitHeadingResult.propagateErr();
 | 
						|
  }
 | 
						|
  SplitNodeResult unwrappedSplitHeadingResult = splitHeadingResult.unwrap();
 | 
						|
  unwrappedSplitHeadingResult.IgnoreCaretPointSuggestion();
 | 
						|
  if (MOZ_UNLIKELY(!unwrappedSplitHeadingResult.DidSplit())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
        "eAllowToCreateEmptyContainer) didn't split aHeadingElement");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  // If the left heading element is empty, put a padding <br> element for empty
 | 
						|
  // last line into it.
 | 
						|
  // FYI: leftHeadingElement is grabbed by unwrappedSplitHeadingResult so that
 | 
						|
  //      it's safe to access anytime.
 | 
						|
  auto* const leftHeadingElement =
 | 
						|
      unwrappedSplitHeadingResult.GetPreviousContentAs<Element>();
 | 
						|
  MOZ_ASSERT(leftHeadingElement,
 | 
						|
             "SplitNodeResult::GetPreviousContent() should return something if "
 | 
						|
             "DidSplit() returns true");
 | 
						|
  MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsHeader(*leftHeadingElement));
 | 
						|
  if (HTMLEditUtils::IsEmptyNode(
 | 
						|
          *leftHeadingElement,
 | 
						|
          {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
           EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
 | 
						|
    Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
        InsertPaddingBRElementForEmptyLastLineWithTransaction(
 | 
						|
            EditorDOMPoint(leftHeadingElement, 0u));
 | 
						|
    if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
 | 
						|
          ") failed");
 | 
						|
      return insertPaddingBRElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
  }
 | 
						|
 | 
						|
  // Put caret at start of the right head element if it's not empty.
 | 
						|
  auto* const rightHeadingElement =
 | 
						|
      unwrappedSplitHeadingResult.GetNextContentAs<Element>();
 | 
						|
  MOZ_ASSERT(rightHeadingElement,
 | 
						|
             "SplitNodeResult::GetNextContent() should return something if "
 | 
						|
             "DidSplit() returns true");
 | 
						|
  if (!HTMLEditUtils::IsEmptyBlockElement(
 | 
						|
          *rightHeadingElement,
 | 
						|
          {EmptyCheckOption::TreatNonEditableContentAsInvisible},
 | 
						|
          BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
 | 
						|
    return InsertParagraphResult(rightHeadingElement,
 | 
						|
                                 EditorDOMPoint(rightHeadingElement, 0u));
 | 
						|
  }
 | 
						|
 | 
						|
  // If the right heading element is empty, delete it.
 | 
						|
  // TODO: If we know the new heading element becomes empty, we stop spliting
 | 
						|
  //       the heading element.
 | 
						|
  // MOZ_KnownLive(rightHeadingElement) because it's grabbed by
 | 
						|
  // unwrappedSplitHeadingResult.
 | 
						|
  nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*rightHeadingElement));
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  // Layout tells the caret to blink in a weird place if we don't place a
 | 
						|
  // break after the header.
 | 
						|
  // XXX This block is dead code unless the removed right heading element is
 | 
						|
  //     reconnected by a mutation event listener.  This is a regression of
 | 
						|
  //     bug 1405751:
 | 
						|
  //     https://searchfox.org/mozilla-central/diff/879f3317d1331818718e18776caa47be7f426a22/editor/libeditor/HTMLEditRules.cpp#6389
 | 
						|
  //     However, the traditional behavior is different from the other browsers.
 | 
						|
  //     Chrome creates new paragraph in this case.  Therefore, we should just
 | 
						|
  //     drop this block in a follow up bug.
 | 
						|
  if (rightHeadingElement->GetNextSibling()) {
 | 
						|
    // XXX Ignoring non-editable <br> element here is odd because non-editable
 | 
						|
    //     <br> elements also work as <br> from point of view of layout.
 | 
						|
    nsIContent* nextEditableSibling =
 | 
						|
        HTMLEditUtils::GetNextSibling(*rightHeadingElement->GetNextSibling(),
 | 
						|
                                      {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
    if (nextEditableSibling &&
 | 
						|
        nextEditableSibling->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
      auto afterEditableBRElement = EditorDOMPoint::After(*nextEditableSibling);
 | 
						|
      if (NS_WARN_IF(!afterEditableBRElement.IsSet())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
      // Put caret at the <br> element.
 | 
						|
      return InsertParagraphResult::NotHandled(
 | 
						|
          std::move(afterEditableBRElement));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(!leftHeadingElement->IsInComposedDoc())) {
 | 
						|
    NS_WARNING("The left heading element was unexpectedly removed");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
 | 
						|
  mPendingStylesToApplyToNewContent->ClearAllStyles();
 | 
						|
 | 
						|
  // Create a paragraph if the right heading element is not followed by an
 | 
						|
  // editable <br> element.
 | 
						|
  nsStaticAtom& newParagraphTagName =
 | 
						|
      &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
 | 
						|
          ? *nsGkAtoms::p
 | 
						|
          : DefaultParagraphSeparatorTagName();
 | 
						|
  // We want a wrapper element even if we separate with a <br>.
 | 
						|
  // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
 | 
						|
  Result<CreateElementResult, nsresult> createNewParagraphElementResult =
 | 
						|
      CreateAndInsertElement(WithTransaction::Yes,
 | 
						|
                             MOZ_KnownLive(newParagraphTagName),
 | 
						|
                             EditorDOMPoint::After(*leftHeadingElement),
 | 
						|
                             HTMLEditor::InsertNewBRElement);
 | 
						|
  if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
 | 
						|
    return createNewParagraphElementResult.propagateErr();
 | 
						|
  }
 | 
						|
  CreateElementResult unwrappedCreateNewParagraphElementResult =
 | 
						|
      createNewParagraphElementResult.unwrap();
 | 
						|
  // Put caret at the <br> element in the following paragraph.
 | 
						|
  unwrappedCreateNewParagraphElementResult.IgnoreCaretPointSuggestion();
 | 
						|
  MOZ_ASSERT(unwrappedCreateNewParagraphElementResult.GetNewNode());
 | 
						|
  EditorDOMPoint pointToPutCaret(
 | 
						|
      unwrappedCreateNewParagraphElementResult.GetNewNode(), 0u);
 | 
						|
  return InsertParagraphResult(
 | 
						|
      unwrappedCreateNewParagraphElementResult.UnwrapNewNode(),
 | 
						|
      std::move(pointToPutCaret));
 | 
						|
}
 | 
						|
 | 
						|
Result<SplitNodeResult, nsresult> HTMLEditor::HandleInsertParagraphInParagraph(
 | 
						|
    Element& aParentDivOrP, const EditorDOMPoint& aCandidatePointToSplit,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aCandidatePointToSplit.IsSetAndValid());
 | 
						|
 | 
						|
  // First, get a better split point to avoid to create a new empty link in the
 | 
						|
  // right paragraph.
 | 
						|
  EditorDOMPoint pointToSplit = [&]() {
 | 
						|
    // We shouldn't create new anchor element which has non-empty href unless
 | 
						|
    // splitting middle of it because we assume that users don't want to create
 | 
						|
    // *same* anchor element across two or more paragraphs in most cases.
 | 
						|
    // So, adjust selection start if it's edge of anchor element(s).
 | 
						|
    // XXX We don't support white-space collapsing in these cases since it needs
 | 
						|
    //     some additional work with WhiteSpaceVisibilityKeeper but it's not
 | 
						|
    //     usual case. E.g., |<a href="foo"><b>foo []</b> </a>|
 | 
						|
    if (aCandidatePointToSplit.IsStartOfContainer()) {
 | 
						|
      EditorDOMPoint candidatePoint(aCandidatePointToSplit);
 | 
						|
      for (nsIContent* container =
 | 
						|
               aCandidatePointToSplit.GetContainerAs<nsIContent>();
 | 
						|
           container && container != &aParentDivOrP;
 | 
						|
           container = container->GetParent()) {
 | 
						|
        if (HTMLEditUtils::IsLink(container)) {
 | 
						|
          // Found link should be only in right node.  So, we shouldn't split
 | 
						|
          // it.
 | 
						|
          candidatePoint.Set(container);
 | 
						|
          // Even if we found an anchor element, don't break because DOM API
 | 
						|
          // allows to nest anchor elements.
 | 
						|
        }
 | 
						|
        // If the container is middle of its parent, stop adjusting split point.
 | 
						|
        if (container->GetPreviousSibling()) {
 | 
						|
          // XXX Should we check if previous sibling is visible content?
 | 
						|
          //     E.g., should we ignore comment node, invisible <br> element?
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return candidatePoint;
 | 
						|
    }
 | 
						|
 | 
						|
    // We also need to check if selection is at invisible <br> element at end
 | 
						|
    // of an <a href="foo"> element because editor inserts a <br> element when
 | 
						|
    // user types Enter key after a white-space which is at middle of
 | 
						|
    // <a href="foo"> element and when setting selection at end of the element,
 | 
						|
    // selection becomes referring the <br> element.  We may need to change this
 | 
						|
    // behavior later if it'd be standardized.
 | 
						|
    if (aCandidatePointToSplit.IsEndOfContainer() ||
 | 
						|
        aCandidatePointToSplit.IsBRElementAtEndOfContainer()) {
 | 
						|
      // If there are 2 <br> elements, the first <br> element is visible.  E.g.,
 | 
						|
      // |<a href="foo"><b>boo[]<br></b><br></a>|, we should split the <a>
 | 
						|
      // element.  Otherwise, E.g., |<a href="foo"><b>boo[]<br></b></a>|,
 | 
						|
      // we should not split the <a> element and ignore inline elements in it.
 | 
						|
      bool foundBRElement =
 | 
						|
          aCandidatePointToSplit.IsBRElementAtEndOfContainer();
 | 
						|
      EditorDOMPoint candidatePoint(aCandidatePointToSplit);
 | 
						|
      for (nsIContent* container =
 | 
						|
               aCandidatePointToSplit.GetContainerAs<nsIContent>();
 | 
						|
           container && container != &aParentDivOrP;
 | 
						|
           container = container->GetParent()) {
 | 
						|
        if (HTMLEditUtils::IsLink(container)) {
 | 
						|
          // Found link should be only in left node.  So, we shouldn't split it.
 | 
						|
          candidatePoint.SetAfter(container);
 | 
						|
          // Even if we found an anchor element, don't break because DOM API
 | 
						|
          // allows to nest anchor elements.
 | 
						|
        }
 | 
						|
        // If the container is middle of its parent, stop adjusting split point.
 | 
						|
        if (nsIContent* nextSibling = container->GetNextSibling()) {
 | 
						|
          if (foundBRElement) {
 | 
						|
            // If we've already found a <br> element, we assume found node is
 | 
						|
            // visible <br> or something other node.
 | 
						|
            // XXX Should we check if non-text data node like comment?
 | 
						|
            break;
 | 
						|
          }
 | 
						|
 | 
						|
          // XXX Should we check if non-text data node like comment?
 | 
						|
          if (!nextSibling->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
            break;
 | 
						|
          }
 | 
						|
          foundBRElement = true;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return candidatePoint;
 | 
						|
    }
 | 
						|
    return aCandidatePointToSplit;
 | 
						|
  }();
 | 
						|
 | 
						|
  const bool createNewParagraph = GetReturnInParagraphCreatesNewParagraph();
 | 
						|
  RefPtr<HTMLBRElement> brElement;
 | 
						|
  if (createNewParagraph && pointToSplit.GetContainer() == &aParentDivOrP) {
 | 
						|
    // We are try to split only the current paragraph.  Therefore, we don't need
 | 
						|
    // to create new <br> elements around it (if left and/or right paragraph
 | 
						|
    // becomes empty, it'll be treated by SplitParagraphWithTransaction().
 | 
						|
    brElement = nullptr;
 | 
						|
  } else if (pointToSplit.IsInTextNode()) {
 | 
						|
    if (pointToSplit.IsStartOfContainer()) {
 | 
						|
      // If we're splitting the paragraph at start of a text node and there is
 | 
						|
      // no preceding visible <br> element, we need to create a <br> element to
 | 
						|
      // keep the inline elements containing this text node.
 | 
						|
      // TODO: If the parent of the text node is the splitting paragraph,
 | 
						|
      //       obviously we don't need to do this because empty paragraphs will
 | 
						|
      //       be treated by SplitParagraphWithTransaction().  In this case, we
 | 
						|
      //       just need to update pointToSplit for using the same path as the
 | 
						|
      //       previous `if` block.
 | 
						|
      brElement =
 | 
						|
          HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousSibling(
 | 
						|
              *pointToSplit.ContainerAs<Text>(),
 | 
						|
              {WalkTreeOption::IgnoreNonEditableNode}));
 | 
						|
      if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
 | 
						|
          EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
 | 
						|
        // If insertParagraph does not create a new paragraph, default to
 | 
						|
        // insertLineBreak.
 | 
						|
        if (!createNewParagraph) {
 | 
						|
          return SplitNodeResult::NotHandled(pointToSplit);
 | 
						|
        }
 | 
						|
        const EditorDOMPoint pointToInsertBR = pointToSplit.ParentPoint();
 | 
						|
        MOZ_ASSERT(pointToInsertBR.IsSet());
 | 
						|
        Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
            InsertBRElement(WithTransaction::Yes, pointToInsertBR);
 | 
						|
        if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
          return insertBRElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        // We'll collapse `Selection` to the place suggested by
 | 
						|
        // SplitParagraphWithTransaction.
 | 
						|
        insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        brElement = HTMLBRElement::FromNodeOrNull(
 | 
						|
            insertBRElementResult.inspect().GetNewNode());
 | 
						|
      }
 | 
						|
    } else if (pointToSplit.IsEndOfContainer()) {
 | 
						|
      // If we're splitting the paragraph at end of a text node and there is not
 | 
						|
      // following visible <br> element, we need to create a <br> element after
 | 
						|
      // the text node to make current style specified by parent inline elements
 | 
						|
      // keep in the right paragraph.
 | 
						|
      // TODO: Same as above, we don't need to do this if the text node is a
 | 
						|
      //       direct child of the paragraph.  For using the simplest path, we
 | 
						|
      //       just need to update `pointToSplit` in the case.
 | 
						|
      brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextSibling(
 | 
						|
          *pointToSplit.ContainerAs<Text>(),
 | 
						|
          {WalkTreeOption::IgnoreNonEditableNode}));
 | 
						|
      if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
 | 
						|
          EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
 | 
						|
        // If insertParagraph does not create a new paragraph, default to
 | 
						|
        // insertLineBreak.
 | 
						|
        if (!createNewParagraph) {
 | 
						|
          return SplitNodeResult::NotHandled(pointToSplit);
 | 
						|
        }
 | 
						|
        const auto pointToInsertBR =
 | 
						|
            EditorDOMPoint::After(*pointToSplit.ContainerAs<Text>());
 | 
						|
        MOZ_ASSERT(pointToInsertBR.IsSet());
 | 
						|
        Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
            InsertBRElement(WithTransaction::Yes, pointToInsertBR);
 | 
						|
        if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
          return insertBRElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        // We'll collapse `Selection` to the place suggested by
 | 
						|
        // SplitParagraphWithTransaction.
 | 
						|
        insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        brElement = HTMLBRElement::FromNodeOrNull(
 | 
						|
            insertBRElementResult.inspect().GetNewNode());
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      // If insertParagraph does not create a new paragraph, default to
 | 
						|
      // insertLineBreak.
 | 
						|
      if (!createNewParagraph) {
 | 
						|
        return SplitNodeResult::NotHandled(pointToSplit);
 | 
						|
      }
 | 
						|
 | 
						|
      // If we're splitting the paragraph at middle of a text node, we should
 | 
						|
      // split the text node here and put a <br> element next to the left text
 | 
						|
      // node.
 | 
						|
      // XXX Why? I think that this should be handled in
 | 
						|
      //     SplitParagraphWithTransaction() directly because I don't find
 | 
						|
      //     the necessary case of the <br> element.
 | 
						|
 | 
						|
      // XXX We split a text node here if caret is middle of it to insert
 | 
						|
      //     <br> element **before** splitting aParentDivOrP.  Then, if
 | 
						|
      //     the <br> element becomes unnecessary, it'll be removed again.
 | 
						|
      //     So this does much more complicated things than what we want to
 | 
						|
      //     do here.  We should handle this case separately to make the code
 | 
						|
      //     much simpler.
 | 
						|
 | 
						|
      // Normalize collapsible white-spaces around the split point to keep
 | 
						|
      // them visible after the split.  Note that this does not touch
 | 
						|
      // selection because of using AutoTransactionsConserveSelection in
 | 
						|
      // WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes().
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToSplitOrError =
 | 
						|
          WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
 | 
						|
              *this, pointToSplit, aParentDivOrP);
 | 
						|
      if (NS_WARN_IF(Destroyed())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() "
 | 
						|
            "failed");
 | 
						|
        return pointToSplitOrError.propagateErr();
 | 
						|
      }
 | 
						|
      MOZ_ASSERT(pointToSplitOrError.inspect().IsSetAndValid());
 | 
						|
      if (pointToSplitOrError.inspect().IsSet()) {
 | 
						|
        pointToSplit = pointToSplitOrError.unwrap();
 | 
						|
      }
 | 
						|
      Result<SplitNodeResult, nsresult> splitParentDivOrPResult =
 | 
						|
          SplitNodeWithTransaction(pointToSplit);
 | 
						|
      if (MOZ_UNLIKELY(splitParentDivOrPResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
 | 
						|
        return splitParentDivOrPResult;
 | 
						|
      }
 | 
						|
      // We'll collapse `Selection` to the place suggested by
 | 
						|
      // SplitParagraphWithTransaction.
 | 
						|
      splitParentDivOrPResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
      pointToSplit.SetToEndOf(
 | 
						|
          splitParentDivOrPResult.inspect().GetPreviousContent());
 | 
						|
      if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
 | 
						|
      // We need to put new <br> after the left node if given node was split
 | 
						|
      // above.
 | 
						|
      const auto pointToInsertBR =
 | 
						|
          EditorDOMPoint::After(*pointToSplit.ContainerAs<nsIContent>());
 | 
						|
      MOZ_ASSERT(pointToInsertBR.IsSet());
 | 
						|
      Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
          InsertBRElement(WithTransaction::Yes, pointToInsertBR);
 | 
						|
      if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
        return insertBRElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      // We'll collapse `Selection` to the place suggested by
 | 
						|
      // SplitParagraphWithTransaction.
 | 
						|
      insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
      brElement = HTMLBRElement::FromNodeOrNull(
 | 
						|
          insertBRElementResult.inspect().GetNewNode());
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // If we're splitting in a child element of the paragraph, and there is no
 | 
						|
    // <br> element around it, we should insert a <br> element at the split
 | 
						|
    // point and keep splitting the paragraph after the new <br> element.
 | 
						|
    // XXX Why? We probably need to do this if we're splitting in an inline
 | 
						|
    //     element which and whose parents provide some styles, we should put
 | 
						|
    //     the <br> element for making a placeholder in the left paragraph for
 | 
						|
    //     moving to the caret, but I think that this could be handled in fewer
 | 
						|
    //     cases than this.
 | 
						|
    brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousContent(
 | 
						|
        pointToSplit, {WalkTreeOption::IgnoreNonEditableNode},
 | 
						|
        BlockInlineCheck::Unused, &aEditingHost));
 | 
						|
    if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
 | 
						|
        EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
 | 
						|
      // is there a BR after it?
 | 
						|
      brElement = HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextContent(
 | 
						|
          pointToSplit, {WalkTreeOption::IgnoreNonEditableNode},
 | 
						|
          BlockInlineCheck::Unused, &aEditingHost));
 | 
						|
      if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
 | 
						|
          EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
 | 
						|
        // If insertParagraph does not create a new paragraph, default to
 | 
						|
        // insertLineBreak.
 | 
						|
        if (!createNewParagraph) {
 | 
						|
          return SplitNodeResult::NotHandled(pointToSplit);
 | 
						|
        }
 | 
						|
        Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
            InsertBRElement(WithTransaction::Yes, pointToSplit);
 | 
						|
        if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
          return insertBRElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        // We'll collapse `Selection` to the place suggested by
 | 
						|
        // SplitParagraphWithTransaction.
 | 
						|
        insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        brElement = HTMLBRElement::FromNodeOrNull(
 | 
						|
            insertBRElementResult.inspect().GetNewNode());
 | 
						|
        // We split the parent after the <br>.
 | 
						|
        pointToSplit.SetAfter(brElement);
 | 
						|
        if (NS_WARN_IF(!pointToSplit.IsSet())) {
 | 
						|
          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Result<SplitNodeResult, nsresult> splitParagraphResult =
 | 
						|
      SplitParagraphWithTransaction(aParentDivOrP, pointToSplit, brElement);
 | 
						|
  if (MOZ_UNLIKELY(splitParagraphResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::SplitParagraphWithTransaction() failed");
 | 
						|
    return splitParagraphResult;
 | 
						|
  }
 | 
						|
  if (MOZ_UNLIKELY(!splitParagraphResult.inspect().DidSplit())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SplitParagraphWithTransaction() didn't split the "
 | 
						|
        "paragraph");
 | 
						|
    splitParagraphResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(splitParagraphResult.inspect().Handled());
 | 
						|
  return splitParagraphResult;
 | 
						|
}
 | 
						|
 | 
						|
Result<SplitNodeResult, nsresult> HTMLEditor::SplitParagraphWithTransaction(
 | 
						|
    Element& aParentDivOrP, const EditorDOMPoint& aStartOfRightNode,
 | 
						|
    HTMLBRElement* aMayBecomeVisibleBRElement) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> preparationResult =
 | 
						|
      WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
 | 
						|
          *this, aStartOfRightNode, aParentDivOrP);
 | 
						|
  if (MOZ_UNLIKELY(preparationResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
 | 
						|
    return preparationResult.propagateErr();
 | 
						|
  }
 | 
						|
  EditorDOMPoint pointToSplit = preparationResult.unwrap();
 | 
						|
  MOZ_ASSERT(pointToSplit.IsInContentNode());
 | 
						|
 | 
						|
  // Split the paragraph.
 | 
						|
  Result<SplitNodeResult, nsresult> splitDivOrPResult =
 | 
						|
      SplitNodeDeepWithTransaction(aParentDivOrP, pointToSplit,
 | 
						|
                                   SplitAtEdges::eAllowToCreateEmptyContainer);
 | 
						|
  if (MOZ_UNLIKELY(splitDivOrPResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
 | 
						|
    return splitDivOrPResult;
 | 
						|
  }
 | 
						|
  SplitNodeResult unwrappedSplitDivOrPResult = splitDivOrPResult.unwrap();
 | 
						|
  if (MOZ_UNLIKELY(!unwrappedSplitDivOrPResult.DidSplit())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes");
 | 
						|
    return unwrappedSplitDivOrPResult;
 | 
						|
  }
 | 
						|
 | 
						|
  // We'll compute caret suggestion later.  So the simple result is not needed.
 | 
						|
  unwrappedSplitDivOrPResult.IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  auto* const leftDivOrParagraphElement =
 | 
						|
      unwrappedSplitDivOrPResult.GetPreviousContentAs<Element>();
 | 
						|
  MOZ_ASSERT(leftDivOrParagraphElement,
 | 
						|
             "SplitNodeResult::GetPreviousContent() should return something if "
 | 
						|
             "DidSplit() returns true");
 | 
						|
  auto* const rightDivOrParagraphElement =
 | 
						|
      unwrappedSplitDivOrPResult.GetNextContentAs<Element>();
 | 
						|
  MOZ_ASSERT(rightDivOrParagraphElement,
 | 
						|
             "SplitNodeResult::GetNextContent() should return something if "
 | 
						|
             "DidSplit() returns true");
 | 
						|
 | 
						|
  // Get rid of the break, if it is visible (otherwise it may be needed to
 | 
						|
  // prevent an empty p).
 | 
						|
  if (aMayBecomeVisibleBRElement &&
 | 
						|
      HTMLEditUtils::IsVisibleBRElement(*aMayBecomeVisibleBRElement)) {
 | 
						|
    nsresult rv = DeleteNodeWithTransaction(*aMayBecomeVisibleBRElement);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Remove ID attribute on the paragraph from the right node.
 | 
						|
  // MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by
 | 
						|
  // unwrappedSplitDivOrPResult.
 | 
						|
  nsresult rv = RemoveAttributeWithTransaction(
 | 
						|
      MOZ_KnownLive(*rightDivOrParagraphElement), *nsGkAtoms::id);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  // We need to ensure to both paragraphs visible even if they are empty.
 | 
						|
  // However, padding <br> element for empty last line isn't useful in this
 | 
						|
  // case because it'll be ignored by PlaintextSerializer.  Additionally,
 | 
						|
  // it'll be exposed as <br> with Element.innerHTML.  Therefore, we can use
 | 
						|
  // normal <br> elements for placeholder in this case.  Note that Chromium
 | 
						|
  // also behaves so.
 | 
						|
  auto InsertBRElementIfEmptyBlockElement =
 | 
						|
      [&](Element& aElement) MOZ_CAN_RUN_SCRIPT {
 | 
						|
        if (!HTMLEditUtils::IsBlockElement(
 | 
						|
                aElement, BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
          return NS_OK;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!HTMLEditUtils::IsEmptyNode(
 | 
						|
                aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
 | 
						|
          return NS_OK;
 | 
						|
        }
 | 
						|
 | 
						|
        // XXX: Probably, we should use
 | 
						|
        //      InsertPaddingBRElementForEmptyLastLineWithTransaction here, and
 | 
						|
        //      if there are some empty inline container, we should put the <br>
 | 
						|
        //      into the last one.
 | 
						|
        Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
            InsertBRElement(WithTransaction::Yes,
 | 
						|
                            EditorDOMPoint(&aElement, 0u));
 | 
						|
        if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
          return insertBRElementResult.unwrapErr();
 | 
						|
        }
 | 
						|
        // After this is called twice, we'll compute new caret position.
 | 
						|
        // Therefore, we don't need to update selection here.
 | 
						|
        insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        return NS_OK;
 | 
						|
      };
 | 
						|
 | 
						|
  // MOZ_KnownLive(leftDivOrParagraphElement) because it's grabbed by
 | 
						|
  // splitDivOrResult.
 | 
						|
  rv = InsertBRElementIfEmptyBlockElement(
 | 
						|
      MOZ_KnownLive(*leftDivOrParagraphElement));
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "InsertBRElementIfEmptyBlockElement(leftDivOrParagraphElement) failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsEmptyNode(*rightDivOrParagraphElement)) {
 | 
						|
    // If the right paragraph is empty, it might have an empty inline element
 | 
						|
    // (which may contain other empty inline containers) and optionally a <br>
 | 
						|
    // element which may not be in the deepest inline element.
 | 
						|
    const RefPtr<Element> deepestInlineContainerElement =
 | 
						|
        [](const Element& aBlockElement) {
 | 
						|
          Element* result = nullptr;
 | 
						|
          for (Element* maybeDeepestInlineContainer =
 | 
						|
                   Element::FromNodeOrNull(aBlockElement.GetFirstChild());
 | 
						|
               maybeDeepestInlineContainer &&
 | 
						|
               HTMLEditUtils::IsInlineContent(
 | 
						|
                   *maybeDeepestInlineContainer,
 | 
						|
                   BlockInlineCheck::UseComputedDisplayStyle) &&
 | 
						|
               HTMLEditUtils::IsContainerNode(*maybeDeepestInlineContainer);
 | 
						|
               maybeDeepestInlineContainer =
 | 
						|
                   maybeDeepestInlineContainer->GetFirstElementChild()) {
 | 
						|
            result = maybeDeepestInlineContainer;
 | 
						|
          }
 | 
						|
          return result;
 | 
						|
        }(*rightDivOrParagraphElement);
 | 
						|
    if (deepestInlineContainerElement) {
 | 
						|
      RefPtr<HTMLBRElement> brElement =
 | 
						|
          HTMLEditUtils::GetFirstBRElement(*rightDivOrParagraphElement);
 | 
						|
      // If there is a <br> element and it is in the deepest inline container,
 | 
						|
      // we need to do nothing anymore. Let's suggest caret position as at the
 | 
						|
      // <br>.
 | 
						|
      if (brElement &&
 | 
						|
          brElement->GetParentNode() == deepestInlineContainerElement) {
 | 
						|
        nsresult rv = UpdateBRElementType(
 | 
						|
            *brElement, BRElementType::PaddingForEmptyLastLine);
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("EditorBase::UpdateBRElementType() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
        return SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
 | 
						|
                               EditorDOMPoint(brElement));
 | 
						|
      }
 | 
						|
      // Otherwise, we should put a padding <br> element into the deepest inline
 | 
						|
      // container and then, existing <br> element (if there is) becomes
 | 
						|
      // unnecessary.
 | 
						|
      if (brElement) {
 | 
						|
        nsresult rv = DeleteNodeWithTransaction(*brElement);
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
          InsertPaddingBRElementForEmptyLastLineWithTransaction(
 | 
						|
              EditorDOMPoint::AtEndOf(deepestInlineContainerElement));
 | 
						|
      if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::"
 | 
						|
            "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
 | 
						|
        return insertPaddingBRElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
      return SplitNodeResult(
 | 
						|
          std::move(unwrappedSplitDivOrPResult),
 | 
						|
          EditorDOMPoint(insertPaddingBRElementResult.inspect().GetNewNode()));
 | 
						|
    }
 | 
						|
 | 
						|
    // If there is no inline container elements, we just need to make the
 | 
						|
    // right paragraph visible.
 | 
						|
    nsresult rv = InsertBRElementIfEmptyBlockElement(
 | 
						|
        MOZ_KnownLive(*rightDivOrParagraphElement));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "InsertBRElementIfEmptyBlockElement(rightDivOrParagraphElement) "
 | 
						|
          "failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Let's put caret at start of the first leaf container.
 | 
						|
  nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
 | 
						|
      *rightDivOrParagraphElement, {LeafNodeType::LeafNodeOrChildBlock},
 | 
						|
      BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  if (MOZ_UNLIKELY(!child)) {
 | 
						|
    return SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
 | 
						|
                           EditorDOMPoint(rightDivOrParagraphElement, 0u));
 | 
						|
  }
 | 
						|
  return child->IsText() || HTMLEditUtils::IsContainerNode(*child)
 | 
						|
             ? SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
 | 
						|
                               EditorDOMPoint(child, 0u))
 | 
						|
             : SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
 | 
						|
                               EditorDOMPoint(child));
 | 
						|
}
 | 
						|
 | 
						|
Result<InsertParagraphResult, nsresult>
 | 
						|
HTMLEditor::HandleInsertParagraphInListItemElement(
 | 
						|
    Element& aListItemElement, const EditorDOMPoint& aPointToSplit,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItemElement));
 | 
						|
 | 
						|
  // If aListItemElement is empty, then we want to outdent its content.
 | 
						|
  if (&aEditingHost != aListItemElement.GetParentElement() &&
 | 
						|
      HTMLEditUtils::IsEmptyBlockElement(
 | 
						|
          aListItemElement,
 | 
						|
          {EmptyCheckOption::TreatNonEditableContentAsInvisible},
 | 
						|
          BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
 | 
						|
    RefPtr<Element> leftListElement = aListItemElement.GetParentElement();
 | 
						|
    // If the given list item element is not the last list item element of
 | 
						|
    // its parent nor not followed by sub list elements, split the parent
 | 
						|
    // before it.
 | 
						|
    if (!HTMLEditUtils::IsLastChild(aListItemElement,
 | 
						|
                                    {WalkTreeOption::IgnoreNonEditableNode})) {
 | 
						|
      Result<SplitNodeResult, nsresult> splitListItemParentResult =
 | 
						|
          SplitNodeWithTransaction(EditorDOMPoint(&aListItemElement));
 | 
						|
      if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
 | 
						|
        return splitListItemParentResult.propagateErr();
 | 
						|
      }
 | 
						|
      SplitNodeResult unwrappedSplitListItemParentResult =
 | 
						|
          splitListItemParentResult.unwrap();
 | 
						|
      if (MOZ_UNLIKELY(!unwrappedSplitListItemParentResult.DidSplit())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::SplitNodeWithTransaction() didn't split the parent of "
 | 
						|
            "aListItemElement");
 | 
						|
        MOZ_ASSERT(
 | 
						|
            !unwrappedSplitListItemParentResult.HasCaretPointSuggestion());
 | 
						|
        return Err(NS_ERROR_FAILURE);
 | 
						|
      }
 | 
						|
      unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion();
 | 
						|
      leftListElement =
 | 
						|
          unwrappedSplitListItemParentResult.GetPreviousContentAs<Element>();
 | 
						|
      MOZ_DIAGNOSTIC_ASSERT(leftListElement);
 | 
						|
    }
 | 
						|
 | 
						|
    auto afterLeftListElement = EditorDOMPoint::After(leftListElement);
 | 
						|
    if (MOZ_UNLIKELY(!afterLeftListElement.IsSet())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
    }
 | 
						|
 | 
						|
    // If aListItemElement is in an invalid sub-list element, move it into
 | 
						|
    // the grand parent list element in order to outdent.
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(afterLeftListElement.GetContainer())) {
 | 
						|
      Result<MoveNodeResult, nsresult> moveListItemElementResult =
 | 
						|
          MoveNodeWithTransaction(aListItemElement, afterLeftListElement);
 | 
						|
      if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
        return moveListItemElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      moveListItemElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
      return InsertParagraphResult(&aListItemElement,
 | 
						|
                                   EditorDOMPoint(&aListItemElement, 0u));
 | 
						|
    }
 | 
						|
 | 
						|
    // Otherwise, replace the empty aListItemElement with a new paragraph.
 | 
						|
    nsresult rv = DeleteNodeWithTransaction(aListItemElement);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    nsStaticAtom& newParagraphTagName =
 | 
						|
        &DefaultParagraphSeparatorTagName() == nsGkAtoms::br
 | 
						|
            ? *nsGkAtoms::p
 | 
						|
            : DefaultParagraphSeparatorTagName();
 | 
						|
    // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown.
 | 
						|
    Result<CreateElementResult, nsresult> createNewParagraphElementResult =
 | 
						|
        CreateAndInsertElement(
 | 
						|
            WithTransaction::Yes, MOZ_KnownLive(newParagraphTagName),
 | 
						|
            afterLeftListElement, HTMLEditor::InsertNewBRElement);
 | 
						|
    if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
 | 
						|
      return createNewParagraphElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    createNewParagraphElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
    MOZ_ASSERT(createNewParagraphElementResult.inspect().GetNewNode());
 | 
						|
    EditorDOMPoint pointToPutCaret(
 | 
						|
        createNewParagraphElementResult.inspect().GetNewNode(), 0u);
 | 
						|
    return InsertParagraphResult(
 | 
						|
        createNewParagraphElementResult.inspect().GetNewNode(),
 | 
						|
        std::move(pointToPutCaret));
 | 
						|
  }
 | 
						|
 | 
						|
  // If aListItemElement has some content or aListItemElement is empty but it's
 | 
						|
  // a child of editing host, we want a new list item at the same list level.
 | 
						|
  // First, sort out white-spaces.
 | 
						|
  Result<EditorDOMPoint, nsresult> preparationResult =
 | 
						|
      WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
 | 
						|
          *this, aPointToSplit, aListItemElement);
 | 
						|
  if (preparationResult.isErr()) {
 | 
						|
    NS_WARNING(
 | 
						|
        "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
 | 
						|
    return Err(preparationResult.unwrapErr());
 | 
						|
  }
 | 
						|
  EditorDOMPoint pointToSplit = preparationResult.unwrap();
 | 
						|
  MOZ_ASSERT(pointToSplit.IsInContentNode());
 | 
						|
 | 
						|
  // Now split the list item.
 | 
						|
  Result<SplitNodeResult, nsresult> splitListItemResult =
 | 
						|
      SplitNodeDeepWithTransaction(aListItemElement, pointToSplit,
 | 
						|
                                   SplitAtEdges::eAllowToCreateEmptyContainer);
 | 
						|
  if (MOZ_UNLIKELY(splitListItemResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
 | 
						|
    return splitListItemResult.propagateErr();
 | 
						|
  }
 | 
						|
  SplitNodeResult unwrappedSplitListItemElement = splitListItemResult.unwrap();
 | 
						|
  unwrappedSplitListItemElement.IgnoreCaretPointSuggestion();
 | 
						|
  if (MOZ_UNLIKELY(!aListItemElement.GetParent())) {
 | 
						|
    NS_WARNING("Somebody disconnected the target listitem from the parent");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  // If aListItemElement is not replaced, we should not do anything anymore.
 | 
						|
  if (MOZ_UNLIKELY(!unwrappedSplitListItemElement.DidSplit()) ||
 | 
						|
      NS_WARN_IF(!unwrappedSplitListItemElement.GetNewContentAs<Element>()) ||
 | 
						|
      NS_WARN_IF(
 | 
						|
          !unwrappedSplitListItemElement.GetOriginalContentAs<Element>())) {
 | 
						|
    NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() didn't split");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  // FYI: They are grabbed by unwrappedSplitListItemElement so that they are
 | 
						|
  // known live
 | 
						|
  //      things.
 | 
						|
  auto& leftListItemElement =
 | 
						|
      *unwrappedSplitListItemElement.GetPreviousContentAs<Element>();
 | 
						|
  auto& rightListItemElement =
 | 
						|
      *unwrappedSplitListItemElement.GetNextContentAs<Element>();
 | 
						|
 | 
						|
  // Hack: until I can change the damaged doc range code back to being
 | 
						|
  // extra-inclusive, I have to manually detect certain list items that may be
 | 
						|
  // left empty.
 | 
						|
  if (HTMLEditUtils::IsEmptyNode(
 | 
						|
          leftListItemElement,
 | 
						|
          {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
           EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
 | 
						|
    Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
        InsertPaddingBRElementForEmptyLastLineWithTransaction(
 | 
						|
            EditorDOMPoint(&leftListItemElement, 0u));
 | 
						|
    if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
 | 
						|
          ") failed");
 | 
						|
      return insertPaddingBRElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    // We're returning a candidate point to put caret so that we don't need to
 | 
						|
    // update now.
 | 
						|
    insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
    return InsertParagraphResult(&rightListItemElement,
 | 
						|
                                 EditorDOMPoint(&rightListItemElement, 0u));
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsEmptyNode(
 | 
						|
          rightListItemElement,
 | 
						|
          {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
 | 
						|
    // If aListItemElement is a <dd> or a <dt> and the right list item is empty
 | 
						|
    // or a direct child of the editing host, replace it a new list item element
 | 
						|
    // whose type is the other one.
 | 
						|
    if (aListItemElement.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt)) {
 | 
						|
      nsStaticAtom& nextDefinitionListItemTagName =
 | 
						|
          aListItemElement.IsHTMLElement(nsGkAtoms::dt) ? *nsGkAtoms::dd
 | 
						|
                                                        : *nsGkAtoms::dt;
 | 
						|
      // MOZ_KnownLive(nextDefinitionListItemTagName) because it's available
 | 
						|
      // until shutdown.
 | 
						|
      Result<CreateElementResult, nsresult> createNewListItemElementResult =
 | 
						|
          CreateAndInsertElement(WithTransaction::Yes,
 | 
						|
                                 MOZ_KnownLive(nextDefinitionListItemTagName),
 | 
						|
                                 EditorDOMPoint::After(rightListItemElement));
 | 
						|
      if (MOZ_UNLIKELY(createNewListItemElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
 | 
						|
        return createNewListItemElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedCreateNewListItemElementResult =
 | 
						|
          createNewListItemElementResult.unwrap();
 | 
						|
      unwrappedCreateNewListItemElementResult.IgnoreCaretPointSuggestion();
 | 
						|
      RefPtr<Element> newListItemElement =
 | 
						|
          unwrappedCreateNewListItemElementResult.UnwrapNewNode();
 | 
						|
      MOZ_ASSERT(newListItemElement);
 | 
						|
      // MOZ_KnownLive(rightListItemElement) because it's grabbed by
 | 
						|
      // unwrappedSplitListItemElement.
 | 
						|
      nsresult rv =
 | 
						|
          DeleteNodeWithTransaction(MOZ_KnownLive(rightListItemElement));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      EditorDOMPoint pointToPutCaret(newListItemElement, 0u);
 | 
						|
      return InsertParagraphResult(std::move(newListItemElement),
 | 
						|
                                   std::move(pointToPutCaret));
 | 
						|
    }
 | 
						|
 | 
						|
    // If aListItemElement is a <li> and the right list item becomes empty or a
 | 
						|
    // direct child of the editing host, copy all inline elements affecting to
 | 
						|
    // the style at end of the left list item element to the right list item
 | 
						|
    // element.
 | 
						|
    // MOZ_KnownLive(leftListItemElement) and
 | 
						|
    // MOZ_KnownLive(rightListItemElement) because they are grabbed by
 | 
						|
    // unwrappedSplitListItemElement.
 | 
						|
    Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
        CopyLastEditableChildStylesWithTransaction(
 | 
						|
            MOZ_KnownLive(leftListItemElement),
 | 
						|
            MOZ_KnownLive(rightListItemElement), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::CopyLastEditableChildStylesWithTransaction() failed");
 | 
						|
      return pointToPutCaretOrError.propagateErr();
 | 
						|
    }
 | 
						|
    return InsertParagraphResult(&rightListItemElement,
 | 
						|
                                 pointToPutCaretOrError.unwrap());
 | 
						|
  }
 | 
						|
 | 
						|
  // If the right list item element is not empty, we need to consider where to
 | 
						|
  // put caret in it. If it has non-container inline elements, <br> or <hr>, at
 | 
						|
  // the element is proper position.
 | 
						|
  const WSScanResult forwardScanFromStartOfListItemResult =
 | 
						|
      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
 | 
						|
          &aEditingHost, EditorRawDOMPoint(&rightListItemElement, 0u),
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
  if (MOZ_UNLIKELY(forwardScanFromStartOfListItemResult.Failed())) {
 | 
						|
    NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  if (forwardScanFromStartOfListItemResult.ReachedSpecialContent() ||
 | 
						|
      forwardScanFromStartOfListItemResult.ReachedBRElement() ||
 | 
						|
      forwardScanFromStartOfListItemResult.ReachedHRElement()) {
 | 
						|
    auto atFoundElement = forwardScanFromStartOfListItemResult
 | 
						|
                              .PointAtReachedContent<EditorDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!atFoundElement.IsSetAndValid())) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
    return InsertParagraphResult(&rightListItemElement,
 | 
						|
                                 std::move(atFoundElement));
 | 
						|
  }
 | 
						|
 | 
						|
  // If we reached a block boundary (end of the list item or a child block),
 | 
						|
  // let's put deepest start of the list item or the child block.
 | 
						|
  if (forwardScanFromStartOfListItemResult.ReachedBlockBoundary() ||
 | 
						|
      // FIXME: This is wrong considering because the inline editing host may
 | 
						|
      // be surrounded by visible inline content.  However, WSRunScanner is
 | 
						|
      // not aware of block boundary around it and stopping this change causes
 | 
						|
      // starting to fail some WPT.  Therefore, we need to keep doing this for
 | 
						|
      // now.
 | 
						|
      forwardScanFromStartOfListItemResult.ReachedInlineEditingHostBoundary()) {
 | 
						|
    return InsertParagraphResult(
 | 
						|
        &rightListItemElement,
 | 
						|
        HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
 | 
						|
            forwardScanFromStartOfListItemResult.GetContent()
 | 
						|
                ? *forwardScanFromStartOfListItemResult.GetContent()
 | 
						|
                : rightListItemElement));
 | 
						|
  }
 | 
						|
 | 
						|
  // Otherwise, return the point at first visible thing.
 | 
						|
  // XXX This may be not meaningful position if it reached block element
 | 
						|
  //     in aListItemElement.
 | 
						|
  return InsertParagraphResult(&rightListItemElement,
 | 
						|
                               forwardScanFromStartOfListItemResult
 | 
						|
                                   .PointAtReachedContent<EditorDOMPoint>());
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult>
 | 
						|
HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction(
 | 
						|
    const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  // The idea here is to put the nodes into a minimal number of blockquotes.
 | 
						|
  // When the user blockquotes something, they expect one blockquote.  That
 | 
						|
  // may not be possible (for instance, if they have two table cells selected,
 | 
						|
  // you need two blockquotes inside the cells).
 | 
						|
  RefPtr<Element> curBlock, blockElementToPutCaret;
 | 
						|
  nsCOMPtr<nsINode> prevParent;
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  for (auto& content : aArrayOfContents) {
 | 
						|
    // If the node is a table element or list item, dive inside
 | 
						|
    if (HTMLEditUtils::IsAnyTableElementButNotTable(content) ||
 | 
						|
        HTMLEditUtils::IsListItem(content)) {
 | 
						|
      // Forget any previous block
 | 
						|
      curBlock = nullptr;
 | 
						|
      // Recursion time
 | 
						|
      AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
 | 
						|
      HTMLEditUtils::CollectAllChildren(*content, childContents);
 | 
						|
      Result<CreateElementResult, nsresult>
 | 
						|
          wrapChildrenInAnotherBlockquoteResult =
 | 
						|
              WrapContentsInBlockquoteElementsWithTransaction(childContents,
 | 
						|
                                                              aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(wrapChildrenInAnotherBlockquoteResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
 | 
						|
            "failed");
 | 
						|
        return wrapChildrenInAnotherBlockquoteResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedWrapChildrenInAnotherBlockquoteResult =
 | 
						|
          wrapChildrenInAnotherBlockquoteResult.unwrap();
 | 
						|
      unwrappedWrapChildrenInAnotherBlockquoteResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      if (unwrappedWrapChildrenInAnotherBlockquoteResult.GetNewNode()) {
 | 
						|
        blockElementToPutCaret =
 | 
						|
            unwrappedWrapChildrenInAnotherBlockquoteResult.UnwrapNewNode();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // If the node has different parent than previous node, further nodes in a
 | 
						|
    // new parent
 | 
						|
    if (prevParent) {
 | 
						|
      if (prevParent != content->GetParentNode()) {
 | 
						|
        // Forget any previous blockquote node we were using
 | 
						|
        curBlock = nullptr;
 | 
						|
        prevParent = content->GetParentNode();
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      prevParent = content->GetParentNode();
 | 
						|
    }
 | 
						|
 | 
						|
    // If no curBlock, make one
 | 
						|
    if (!curBlock) {
 | 
						|
      Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
 | 
						|
          InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
              *nsGkAtoms::blockquote, EditorDOMPoint(content),
 | 
						|
              BRElementNextToSplitPoint::Keep, aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
            "nsGkAtoms::blockquote) failed");
 | 
						|
        return createNewBlockquoteElementResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedCreateNewBlockquoteElementResult =
 | 
						|
          createNewBlockquoteElementResult.unwrap();
 | 
						|
      unwrappedCreateNewBlockquoteElementResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
 | 
						|
      blockElementToPutCaret =
 | 
						|
          unwrappedCreateNewBlockquoteElementResult.GetNewNode();
 | 
						|
      curBlock = unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
 | 
						|
    }
 | 
						|
 | 
						|
    // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to/ keep it alive.
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
      return moveNodeResult.propagateErr();
 | 
						|
    }
 | 
						|
    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
    unwrappedMoveNodeResult.MoveCaretPointTo(
 | 
						|
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
  }
 | 
						|
  return blockElementToPutCaret
 | 
						|
             ? CreateElementResult(std::move(blockElementToPutCaret),
 | 
						|
                                   std::move(pointToPutCaret))
 | 
						|
             : CreateElementResult::NotHandled(std::move(pointToPutCaret));
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult>
 | 
						|
HTMLEditor::RemoveBlockContainerElementsWithTransaction(
 | 
						|
    const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
 | 
						|
    FormatBlockMode aFormatBlockMode, BlockInlineCheck aBlockInlineCheck) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand);
 | 
						|
 | 
						|
  // Intent of this routine is to be used for converting to/from headers,
 | 
						|
  // paragraphs, pre, and address.  Those blocks that pretty much just contain
 | 
						|
  // inline things...
 | 
						|
  RefPtr<Element> blockElement;
 | 
						|
  nsCOMPtr<nsIContent> firstContent, lastContent;
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  for (const auto& content : aArrayOfContents) {
 | 
						|
    // If the current node is a format element, remove it.
 | 
						|
    if (HTMLEditUtils::IsFormatElementForParagraphStateCommand(content)) {
 | 
						|
      // Process any partial progress saved
 | 
						|
      if (blockElement) {
 | 
						|
        Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
 | 
						|
            RemoveBlockContainerElementWithTransactionBetween(
 | 
						|
                *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
 | 
						|
        if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
 | 
						|
              "failed");
 | 
						|
          return unwrapBlockElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        unwrapBlockElementResult.unwrap().MoveCaretPointTo(
 | 
						|
            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
        firstContent = lastContent = blockElement = nullptr;
 | 
						|
      }
 | 
						|
      if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      // Remove current block
 | 
						|
      Result<EditorDOMPoint, nsresult> unwrapFormatBlockResult =
 | 
						|
          RemoveBlockContainerWithTransaction(
 | 
						|
              MOZ_KnownLive(*content->AsElement()));
 | 
						|
      if (MOZ_UNLIKELY(unwrapFormatBlockResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
        return unwrapFormatBlockResult;
 | 
						|
      }
 | 
						|
      if (unwrapFormatBlockResult.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = unwrapFormatBlockResult.unwrap();
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>?
 | 
						|
    if (content->IsAnyOfHTMLElements(
 | 
						|
            nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody, nsGkAtoms::td,
 | 
						|
            nsGkAtoms::li, nsGkAtoms::blockquote, nsGkAtoms::div) ||
 | 
						|
        HTMLEditUtils::IsAnyListElement(content)) {
 | 
						|
      // Process any partial progress saved
 | 
						|
      if (blockElement) {
 | 
						|
        Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
 | 
						|
            RemoveBlockContainerElementWithTransactionBetween(
 | 
						|
                *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
 | 
						|
        if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
 | 
						|
              "failed");
 | 
						|
          return unwrapBlockElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        unwrapBlockElementResult.unwrap().MoveCaretPointTo(
 | 
						|
            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
        firstContent = lastContent = blockElement = nullptr;
 | 
						|
      }
 | 
						|
      if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      // Recursion time
 | 
						|
      AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
 | 
						|
      HTMLEditUtils::CollectAllChildren(*content, childContents);
 | 
						|
      Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
 | 
						|
          RemoveBlockContainerElementsWithTransaction(
 | 
						|
              childContents, aFormatBlockMode, aBlockInlineCheck);
 | 
						|
      if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
 | 
						|
        return removeBlockContainerElementsResult;
 | 
						|
      }
 | 
						|
      if (removeBlockContainerElementsResult.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = removeBlockContainerElementsResult.unwrap();
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsInlineContent(content, aBlockInlineCheck)) {
 | 
						|
      if (blockElement) {
 | 
						|
        // If so, is this node a descendant?
 | 
						|
        if (EditorUtils::IsDescendantOf(*content, *blockElement)) {
 | 
						|
          // Then we don't need to do anything different for this node
 | 
						|
          lastContent = content;
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
        // Otherwise, we have progressed beyond end of blockElement, so let's
 | 
						|
        // handle it now.  We need to remove the portion of blockElement that
 | 
						|
        // contains [firstContent - lastContent].
 | 
						|
        Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
 | 
						|
            RemoveBlockContainerElementWithTransactionBetween(
 | 
						|
                *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
 | 
						|
        if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
 | 
						|
              "failed");
 | 
						|
          return unwrapBlockElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        unwrapBlockElementResult.unwrap().MoveCaretPointTo(
 | 
						|
            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
        firstContent = lastContent = blockElement = nullptr;
 | 
						|
        // Fall out and handle content
 | 
						|
      }
 | 
						|
      blockElement = HTMLEditUtils::GetAncestorElement(
 | 
						|
          content, HTMLEditUtils::ClosestEditableBlockElement,
 | 
						|
          aBlockInlineCheck);
 | 
						|
      if (!blockElement ||
 | 
						|
          !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
 | 
						|
              *blockElement) ||
 | 
						|
          !HTMLEditUtils::IsRemovableNode(*blockElement)) {
 | 
						|
        // Not a block kind that we care about.
 | 
						|
        blockElement = nullptr;
 | 
						|
      } else {
 | 
						|
        firstContent = lastContent = content;
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (blockElement) {
 | 
						|
      // Some node that is already sans block style.  Skip over it and process
 | 
						|
      // any partial progress saved.
 | 
						|
      Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
 | 
						|
          RemoveBlockContainerElementWithTransactionBetween(
 | 
						|
              *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
 | 
						|
      if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
 | 
						|
            "failed");
 | 
						|
        return unwrapBlockElementResult.propagateErr();
 | 
						|
      }
 | 
						|
      unwrapBlockElementResult.unwrap().MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      firstContent = lastContent = blockElement = nullptr;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  // Process any partial progress saved
 | 
						|
  if (blockElement) {
 | 
						|
    Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
 | 
						|
        RemoveBlockContainerElementWithTransactionBetween(
 | 
						|
            *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
 | 
						|
    if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
 | 
						|
          "failed");
 | 
						|
      return unwrapBlockElementResult.propagateErr();
 | 
						|
    }
 | 
						|
    unwrapBlockElementResult.unwrap().MoveCaretPointTo(
 | 
						|
        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
    firstContent = lastContent = blockElement = nullptr;
 | 
						|
  }
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult>
 | 
						|
HTMLEditor::CreateOrChangeFormatContainerElement(
 | 
						|
    nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
 | 
						|
    const nsStaticAtom& aNewFormatTagName, FormatBlockMode aFormatBlockMode,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  // Intent of this routine is to be used for converting to/from headers,
 | 
						|
  // paragraphs, pre, and address.  Those blocks that pretty much just contain
 | 
						|
  // inline things...
 | 
						|
  RefPtr<Element> newBlock, curBlock, blockElementToPutCaret;
 | 
						|
  // If we found a <br> element which should be moved into curBlock, this keeps
 | 
						|
  // storing the <br> element after removing it from the tree.
 | 
						|
  RefPtr<Element> pendingBRElementToMoveCurBlock;
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  for (auto& content : aArrayOfContents) {
 | 
						|
    EditorDOMPoint atContent(content);
 | 
						|
    if (NS_WARN_IF(!atContent.IsInContentNode())) {
 | 
						|
      // If given node has been removed from the document, let's ignore it
 | 
						|
      // since the following code may need its parent replace it with new
 | 
						|
      // block.
 | 
						|
      curBlock = nullptr;
 | 
						|
      newBlock = nullptr;
 | 
						|
      pendingBRElementToMoveCurBlock = nullptr;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Is it already the right kind of block, or an uneditable block?
 | 
						|
    if (content->IsHTMLElement(&aNewFormatTagName) ||
 | 
						|
        (!EditorUtils::IsEditableContent(content, EditorType::HTML) &&
 | 
						|
         HTMLEditUtils::IsBlockElement(
 | 
						|
             content, BlockInlineCheck::UseHTMLDefaultStyle))) {
 | 
						|
      // Forget any previous block used for previous inline nodes
 | 
						|
      curBlock = nullptr;
 | 
						|
      pendingBRElementToMoveCurBlock = nullptr;
 | 
						|
      // Do nothing to this block
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If content is a format element, replace it with a new block of correct
 | 
						|
    // type.
 | 
						|
    // XXX: pre can't hold everything the others can
 | 
						|
    if (HTMLEditUtils::IsMozDiv(content) ||
 | 
						|
        HTMLEditor::IsFormatElement(aFormatBlockMode, content)) {
 | 
						|
      // Forget any previous block used for previous inline nodes
 | 
						|
      curBlock = nullptr;
 | 
						|
      pendingBRElementToMoveCurBlock = nullptr;
 | 
						|
      RefPtr<Element> expectedContainerOfNewBlock =
 | 
						|
          atContent.IsContainerHTMLElement(nsGkAtoms::dl) &&
 | 
						|
                  HTMLEditUtils::IsSplittableNode(
 | 
						|
                      *atContent.ContainerAs<Element>())
 | 
						|
              ? atContent.GetContainerParentAs<Element>()
 | 
						|
              : atContent.GetContainerAs<Element>();
 | 
						|
      Result<CreateElementResult, nsresult> replaceWithNewBlockElementResult =
 | 
						|
          ReplaceContainerAndCloneAttributesWithTransaction(
 | 
						|
              MOZ_KnownLive(*content->AsElement()), aNewFormatTagName);
 | 
						|
      if (MOZ_UNLIKELY(replaceWithNewBlockElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
 | 
						|
            "failed");
 | 
						|
        return replaceWithNewBlockElementResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedReplaceWithNewBlockElementResult =
 | 
						|
          replaceWithNewBlockElementResult.unwrap();
 | 
						|
      // If the new block element was moved to different element or removed by
 | 
						|
      // the web app via mutation event listener, we should stop handling this
 | 
						|
      // action since we cannot handle each of a lot of edge cases.
 | 
						|
      if (NS_WARN_IF(unwrappedReplaceWithNewBlockElementResult.GetNewNode()
 | 
						|
                         ->GetParentNode() != expectedContainerOfNewBlock)) {
 | 
						|
        unwrappedReplaceWithNewBlockElementResult.IgnoreCaretPointSuggestion();
 | 
						|
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
      }
 | 
						|
      unwrappedReplaceWithNewBlockElementResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      newBlock = unwrappedReplaceWithNewBlockElementResult.UnwrapNewNode();
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsTable(content) ||
 | 
						|
        HTMLEditUtils::IsAnyListElement(content) ||
 | 
						|
        content->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::tr,
 | 
						|
                                     nsGkAtoms::td, nsGkAtoms::li,
 | 
						|
                                     nsGkAtoms::blockquote, nsGkAtoms::div)) {
 | 
						|
      // Forget any previous block used for previous inline nodes
 | 
						|
      curBlock = nullptr;
 | 
						|
      pendingBRElementToMoveCurBlock = nullptr;
 | 
						|
      // Recursion time
 | 
						|
      AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
 | 
						|
      HTMLEditUtils::CollectAllChildren(*content, childContents);
 | 
						|
      if (!childContents.IsEmpty()) {
 | 
						|
        Result<CreateElementResult, nsresult> wrapChildrenInBlockElementResult =
 | 
						|
            CreateOrChangeFormatContainerElement(
 | 
						|
                childContents, aNewFormatTagName, aFormatBlockMode,
 | 
						|
                aEditingHost);
 | 
						|
        if (MOZ_UNLIKELY(wrapChildrenInBlockElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::CreateOrChangeFormatContainerElement() failed");
 | 
						|
          return wrapChildrenInBlockElementResult;
 | 
						|
        }
 | 
						|
        CreateElementResult unwrappedWrapChildrenInBlockElementResult =
 | 
						|
            wrapChildrenInBlockElementResult.unwrap();
 | 
						|
        unwrappedWrapChildrenInBlockElementResult.MoveCaretPointTo(
 | 
						|
            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
        if (unwrappedWrapChildrenInBlockElementResult.GetNewNode()) {
 | 
						|
          blockElementToPutCaret =
 | 
						|
              unwrappedWrapChildrenInBlockElementResult.UnwrapNewNode();
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      // Make sure we can put a block here
 | 
						|
      Result<CreateElementResult, nsresult> createNewBlockElementResult =
 | 
						|
          InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
              aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
              aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            nsPrintfCString(
 | 
						|
                "HTMLEditor::"
 | 
						|
                "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
 | 
						|
                nsAtomCString(&aNewFormatTagName).get())
 | 
						|
                .get());
 | 
						|
        return createNewBlockElementResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedCreateNewBlockElementResult =
 | 
						|
          createNewBlockElementResult.unwrap();
 | 
						|
      unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
 | 
						|
      blockElementToPutCaret =
 | 
						|
          unwrappedCreateNewBlockElementResult.UnwrapNewNode();
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (content->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
      if (curBlock) {
 | 
						|
        if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
 | 
						|
          // If the node is a break, we honor it by putting further nodes in a
 | 
						|
          // new parent.
 | 
						|
 | 
						|
          // Forget any previous block used for previous inline nodes.
 | 
						|
          curBlock = nullptr;
 | 
						|
          pendingBRElementToMoveCurBlock = nullptr;
 | 
						|
        } else {
 | 
						|
          // If the node is a break, we need to move it into end of the curBlock
 | 
						|
          // if we'll move following content into curBlock.
 | 
						|
          pendingBRElementToMoveCurBlock = content->AsElement();
 | 
						|
        }
 | 
						|
        // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
 | 
						|
        // alive.
 | 
						|
        nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      // The break is the first (or even only) node we encountered.  Create a
 | 
						|
      // block for it.
 | 
						|
      Result<CreateElementResult, nsresult> createNewBlockElementResult =
 | 
						|
          InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
              aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
              aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
 | 
						|
        NS_WARNING(nsPrintfCString("HTMLEditor::"
 | 
						|
                                   "InsertElementWithSplittingAncestorsWith"
 | 
						|
                                   "Transaction(%s) failed",
 | 
						|
                                   nsAtomCString(&aNewFormatTagName).get())
 | 
						|
                       .get());
 | 
						|
        return createNewBlockElementResult;
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedCreateNewBlockElementResult =
 | 
						|
          createNewBlockElementResult.unwrap();
 | 
						|
      unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      RefPtr<Element> newBlockElement =
 | 
						|
          unwrappedCreateNewBlockElementResult.UnwrapNewNode();
 | 
						|
      MOZ_ASSERT(newBlockElement);
 | 
						|
      blockElementToPutCaret = newBlockElement;
 | 
						|
      // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
 | 
						|
      // alive.
 | 
						|
      Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
          MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
 | 
						|
                                       *newBlockElement);
 | 
						|
      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
        return moveNodeResult.propagateErr();
 | 
						|
      }
 | 
						|
      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
      unwrappedMoveNodeResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
      curBlock = std::move(newBlockElement);
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsInlineContent(content,
 | 
						|
                                       BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
      // If content is inline, pull it into curBlock.  Note: it's assumed that
 | 
						|
      // consecutive inline nodes in aNodeArray are actually members of the
 | 
						|
      // same block parent.  This happens to be true now as a side effect of
 | 
						|
      // how aNodeArray is constructed, but some additional logic should be
 | 
						|
      // added here if that should change
 | 
						|
      //
 | 
						|
      // If content is a non editable, drop it if we are going to <pre>.
 | 
						|
      if (&aNewFormatTagName == nsGkAtoms::pre &&
 | 
						|
          !EditorUtils::IsEditableContent(content, EditorType::HTML)) {
 | 
						|
        // Do nothing to this block
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      // If no curBlock, make one
 | 
						|
      if (!curBlock) {
 | 
						|
        Result<CreateElementResult, nsresult> createNewBlockElementResult =
 | 
						|
            InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
                aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
                aEditingHost);
 | 
						|
        if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
 | 
						|
          NS_WARNING(nsPrintfCString("HTMLEditor::"
 | 
						|
                                     "InsertElementWithSplittingAncestorsWith"
 | 
						|
                                     "Transaction(%s) failed",
 | 
						|
                                     nsAtomCString(&aNewFormatTagName).get())
 | 
						|
                         .get());
 | 
						|
          return createNewBlockElementResult;
 | 
						|
        }
 | 
						|
        CreateElementResult unwrappedCreateNewBlockElementResult =
 | 
						|
            createNewBlockElementResult.unwrap();
 | 
						|
        unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
 | 
						|
            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
        MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
 | 
						|
        blockElementToPutCaret =
 | 
						|
            unwrappedCreateNewBlockElementResult.GetNewNode();
 | 
						|
        curBlock = unwrappedCreateNewBlockElementResult.UnwrapNewNode();
 | 
						|
 | 
						|
        // Update container of content.
 | 
						|
        atContent.Set(content);
 | 
						|
        if (NS_WARN_IF(!atContent.IsSet())) {
 | 
						|
          // This is possible due to mutation events, let's not assert
 | 
						|
          return Err(NS_ERROR_UNEXPECTED);
 | 
						|
        }
 | 
						|
      } else if (pendingBRElementToMoveCurBlock) {
 | 
						|
        Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
            InsertNodeWithTransaction<Element>(
 | 
						|
                *pendingBRElementToMoveCurBlock,
 | 
						|
                EditorDOMPoint::AtEndOf(*curBlock));
 | 
						|
        if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
          NS_WARNING("EditorBase::InsertNodeWithTransaction<Element>() failed");
 | 
						|
          return insertBRElementResult.propagateErr();
 | 
						|
        }
 | 
						|
        insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        pendingBRElementToMoveCurBlock = nullptr;
 | 
						|
      }
 | 
						|
 | 
						|
      // XXX If content is a br, replace it with a return if going to <pre>
 | 
						|
 | 
						|
      // This is a continuation of some inline nodes that belong together in
 | 
						|
      // the same block item.  Use curBlock.
 | 
						|
      //
 | 
						|
      // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
 | 
						|
      // alive.  We could try to make that a rvalue ref and create a const array
 | 
						|
      // on the stack here, but callers are passing in auto arrays, and we don't
 | 
						|
      // want to introduce copies..
 | 
						|
      Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
          MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
 | 
						|
      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
        return moveNodeResult.propagateErr();
 | 
						|
      }
 | 
						|
      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
 | 
						|
      unwrappedMoveNodeResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return blockElementToPutCaret
 | 
						|
             ? CreateElementResult(std::move(blockElementToPutCaret),
 | 
						|
                                   std::move(pointToPutCaret))
 | 
						|
             : CreateElementResult::NotHandled(std::move(pointToPutCaret));
 | 
						|
}
 | 
						|
 | 
						|
Result<SplitNodeResult, nsresult>
 | 
						|
HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(
 | 
						|
    const nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aEditingHost.IsInComposedDoc())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) {
 | 
						|
    return Err(NS_ERROR_INVALID_ARG);
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid());
 | 
						|
 | 
						|
  // The point must be descendant of editing host.
 | 
						|
  // XXX Isn't it a valid case if it points a direct child of aEditingHost?
 | 
						|
  if (NS_WARN_IF(
 | 
						|
          !aStartOfDeepestRightNode.GetContainer()->IsInclusiveDescendantOf(
 | 
						|
              &aEditingHost))) {
 | 
						|
    return Err(NS_ERROR_INVALID_ARG);
 | 
						|
  }
 | 
						|
 | 
						|
  // Look for a node that can legally contain the tag.
 | 
						|
  const EditorDOMPoint pointToInsert =
 | 
						|
      HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
 | 
						|
          aTag, aStartOfDeepestRightNode, &aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached "
 | 
						|
        "editing host");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  // If the point itself can contain the tag, we don't need to split any
 | 
						|
  // ancestor nodes.  In this case, we should return the given split point
 | 
						|
  // as is.
 | 
						|
  if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) {
 | 
						|
    return SplitNodeResult::NotHandled(aStartOfDeepestRightNode);
 | 
						|
  }
 | 
						|
 | 
						|
  Result<SplitNodeResult, nsresult> splitNodeResult =
 | 
						|
      SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()),
 | 
						|
                                   aStartOfDeepestRightNode,
 | 
						|
                                   SplitAtEdges::eAllowToCreateEmptyContainer);
 | 
						|
  NS_WARNING_ASSERTION(splitNodeResult.isOk(),
 | 
						|
                       "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
 | 
						|
                       "eAllowToCreateEmptyContainer) failed");
 | 
						|
  return splitNodeResult;
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult>
 | 
						|
HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
    const nsAtom& aTagName, const EditorDOMPoint& aPointToInsert,
 | 
						|
    BRElementNextToSplitPoint aBRElementNextToSplitPoint,
 | 
						|
    const Element& aEditingHost,
 | 
						|
    const InitializeInsertingElement& aInitializer) {
 | 
						|
  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
 | 
						|
 | 
						|
  const nsCOMPtr<nsIContent> childAtPointToInsert = aPointToInsert.GetChild();
 | 
						|
  Result<SplitNodeResult, nsresult> splitNodeResult =
 | 
						|
      MaybeSplitAncestorsForInsertWithTransaction(aTagName, aPointToInsert,
 | 
						|
                                                  aEditingHost);
 | 
						|
  if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
 | 
						|
    return splitNodeResult.propagateErr();
 | 
						|
  }
 | 
						|
  SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
 | 
						|
  DebugOnly<bool> wasCaretPositionSuggestedAtSplit =
 | 
						|
      unwrappedSplitNodeResult.HasCaretPointSuggestion();
 | 
						|
  // We'll update selection below, and nobody touches selection until then.
 | 
						|
  // Therefore, we don't need to touch selection here.
 | 
						|
  unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
 | 
						|
 | 
						|
  // If current handling node has been moved from the container by a
 | 
						|
  // mutation event listener when we need to do something more for it,
 | 
						|
  // we should stop handling this action since we cannot handle each
 | 
						|
  // edge case.
 | 
						|
  if (childAtPointToInsert &&
 | 
						|
      NS_WARN_IF(!childAtPointToInsert->IsInclusiveDescendantOf(
 | 
						|
          unwrappedSplitNodeResult.DidSplit()
 | 
						|
              ? unwrappedSplitNodeResult.GetNextContent()
 | 
						|
              : aPointToInsert.GetContainer()))) {
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  auto splitPoint = unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>();
 | 
						|
  if (aBRElementNextToSplitPoint == BRElementNextToSplitPoint::Delete) {
 | 
						|
    // Consume a trailing br, if any.  This is to keep an alignment from
 | 
						|
    // creating extra lines, if possible.
 | 
						|
    if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
 | 
						|
            splitPoint,
 | 
						|
            {WalkTreeOption::IgnoreNonEditableNode,
 | 
						|
             WalkTreeOption::StopAtBlockBoundary},
 | 
						|
            BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
 | 
						|
      if (maybeBRContent->IsHTMLElement(nsGkAtoms::br) &&
 | 
						|
          splitPoint.GetChild()) {
 | 
						|
        // Making use of html structure... if next node after where we are
 | 
						|
        // putting our div is not a block, then the br we found is in same
 | 
						|
        // block we are, so it's safe to consume it.
 | 
						|
        if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
 | 
						|
                *splitPoint.GetChild(),
 | 
						|
                {WalkTreeOption::IgnoreNonEditableNode})) {
 | 
						|
          if (!HTMLEditUtils::IsBlockElement(
 | 
						|
                  *nextEditableSibling,
 | 
						|
                  BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
 | 
						|
            AutoEditorDOMPointChildInvalidator lockOffset(splitPoint);
 | 
						|
            nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
 | 
						|
            if (NS_FAILED(rv)) {
 | 
						|
              NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
              return Err(rv);
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> createNewElementResult =
 | 
						|
      CreateAndInsertElement(WithTransaction::Yes, aTagName, splitPoint,
 | 
						|
                             aInitializer);
 | 
						|
  if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
 | 
						|
    return createNewElementResult;
 | 
						|
  }
 | 
						|
  MOZ_ASSERT_IF(wasCaretPositionSuggestedAtSplit,
 | 
						|
                createNewElementResult.inspect().HasCaretPointSuggestion());
 | 
						|
  MOZ_ASSERT(createNewElementResult.inspect().GetNewNode());
 | 
						|
 | 
						|
  // If the new block element was moved to different element or removed by
 | 
						|
  // the web app via mutation event listener, we should stop handling this
 | 
						|
  // action since we cannot handle each of a lot of edge cases.
 | 
						|
  if (NS_WARN_IF(
 | 
						|
          createNewElementResult.inspect().GetNewNode()->GetParentNode() !=
 | 
						|
          splitPoint.GetContainer())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  return createNewElementResult;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction(
 | 
						|
    nsIContent& aNodeLeft, nsIContent& aNodeRight,
 | 
						|
    EditorDOMPoint* aNewFirstChildOfRightNode) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aNewFirstChildOfRightNode);
 | 
						|
 | 
						|
  // Caller responsible for left and right node being the same type
 | 
						|
  if (NS_WARN_IF(!aNodeLeft.GetParentNode())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  // If they don't have the same parent, first move the right node to after
 | 
						|
  // the left one
 | 
						|
  if (aNodeLeft.GetParentNode() != aNodeRight.GetParentNode()) {
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        MoveNodeWithTransaction(aNodeRight, EditorDOMPoint(&aNodeLeft));
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
      return moveNodeResult.unwrapErr();
 | 
						|
    }
 | 
						|
    nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
 | 
						|
        *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                SuggestCaret::AndIgnoreTrivialError});
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
        "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
  }
 | 
						|
 | 
						|
  // Separate join rules for differing blocks
 | 
						|
  if (HTMLEditUtils::IsAnyListElement(&aNodeLeft) || aNodeLeft.IsText()) {
 | 
						|
    // For lists, merge shallow (wouldn't want to combine list items)
 | 
						|
    Result<JoinNodesResult, nsresult> joinNodesResult =
 | 
						|
        JoinNodesWithTransaction(aNodeLeft, aNodeRight);
 | 
						|
    if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::JoinNodesWithTransaction failed");
 | 
						|
      return joinNodesResult.unwrapErr();
 | 
						|
    }
 | 
						|
    *aNewFirstChildOfRightNode =
 | 
						|
        joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // Remember the last left child, and first right child
 | 
						|
  nsCOMPtr<nsIContent> lastEditableChildOfLeftContent =
 | 
						|
      HTMLEditUtils::GetLastChild(aNodeLeft,
 | 
						|
                                  {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent))) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  nsCOMPtr<nsIContent> firstEditableChildOfRightContent =
 | 
						|
      HTMLEditUtils::GetFirstChild(aNodeRight,
 | 
						|
                                   {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (NS_WARN_IF(!firstEditableChildOfRightContent)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // For list items, divs, etc., merge smart
 | 
						|
  Result<JoinNodesResult, nsresult> joinNodesResult =
 | 
						|
      JoinNodesWithTransaction(aNodeLeft, aNodeRight);
 | 
						|
  if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
 | 
						|
    return joinNodesResult.unwrapErr();
 | 
						|
  }
 | 
						|
 | 
						|
  if ((lastEditableChildOfLeftContent->IsText() ||
 | 
						|
       lastEditableChildOfLeftContent->IsElement()) &&
 | 
						|
      HTMLEditUtils::CanContentsBeJoined(*lastEditableChildOfLeftContent,
 | 
						|
                                         *firstEditableChildOfRightContent)) {
 | 
						|
    nsresult rv = JoinNearestEditableNodesWithTransaction(
 | 
						|
        *lastEditableChildOfLeftContent, *firstEditableChildOfRightContent,
 | 
						|
        aNewFirstChildOfRightNode);
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
  *aNewFirstChildOfRightNode =
 | 
						|
      joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Element* HTMLEditor::GetMostDistantAncestorMailCiteElement(
 | 
						|
    const nsINode& aNode) const {
 | 
						|
  Element* mailCiteElement = nullptr;
 | 
						|
  const bool isPlaintextEditor = IsPlaintextMailComposer();
 | 
						|
  for (Element* element : aNode.InclusiveAncestorsOfType<Element>()) {
 | 
						|
    if ((isPlaintextEditor && element->IsHTMLElement(nsGkAtoms::pre)) ||
 | 
						|
        HTMLEditUtils::IsMailCite(*element)) {
 | 
						|
      mailCiteElement = element;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    if (element->IsHTMLElement(nsGkAtoms::body)) {
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return mailCiteElement;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::CacheInlineStyles(Element& Element) {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  nsresult rv = GetInlineStyles(
 | 
						|
      Element, *TopLevelEditSubActionDataRef().mCachedPendingStyles);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "HTMLEditor::GetInlineStyles() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::GetInlineStyles(
 | 
						|
    Element& aElement, AutoPendingStyleCacheArray& aPendingStyleCacheArray) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aPendingStyleCacheArray.IsEmpty());
 | 
						|
 | 
						|
  if (!IsCSSEnabled()) {
 | 
						|
    // In the HTML styling mode, we should preserve the order of inline styles
 | 
						|
    // specified with HTML elements, then, we can keep same order as original
 | 
						|
    // one when we create new elements to apply the styles at new place.
 | 
						|
    // XXX Currently, we don't preserve all inline parents, therefore, we cannot
 | 
						|
    //     restore all inline elements as-is.  Perhaps, we should store all
 | 
						|
    //     inline elements with more details (e.g., all attributes), and store
 | 
						|
    //     same elements.  For example, web apps may give style as:
 | 
						|
    //     em {
 | 
						|
    //       font-style: italic;
 | 
						|
    //     }
 | 
						|
    //     em em {
 | 
						|
    //       font-style: normal;
 | 
						|
    //       font-weight: bold;
 | 
						|
    //     }
 | 
						|
    //     but we cannot restore the style as-is.
 | 
						|
    nsString value;
 | 
						|
    const bool givenElementIsEditable =
 | 
						|
        HTMLEditUtils::IsSimplyEditableNode(aElement);
 | 
						|
    auto NeedToAppend = [&](nsStaticAtom& aTagName, nsStaticAtom* aAttribute) {
 | 
						|
      if (mPendingStylesToApplyToNewContent->GetStyleState(
 | 
						|
              aTagName, aAttribute) != PendingStyleState::NotUpdated) {
 | 
						|
        return false;  // The style has already been changed.
 | 
						|
      }
 | 
						|
      if (aPendingStyleCacheArray.Contains(aTagName, aAttribute)) {
 | 
						|
        return false;  // Already preserved
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    };
 | 
						|
    for (Element* const inclusiveAncestor :
 | 
						|
         aElement.InclusiveAncestorsOfType<Element>()) {
 | 
						|
      if (HTMLEditUtils::IsBlockElement(
 | 
						|
              *inclusiveAncestor,
 | 
						|
              BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
 | 
						|
          (givenElementIsEditable &&
 | 
						|
           !HTMLEditUtils::IsSimplyEditableNode(*inclusiveAncestor))) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      if (inclusiveAncestor->IsAnyOfHTMLElements(
 | 
						|
              nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::s,
 | 
						|
              nsGkAtoms::strike, nsGkAtoms::tt, nsGkAtoms::em,
 | 
						|
              nsGkAtoms::strong, nsGkAtoms::dfn, nsGkAtoms::code,
 | 
						|
              nsGkAtoms::samp, nsGkAtoms::var, nsGkAtoms::cite, nsGkAtoms::abbr,
 | 
						|
              nsGkAtoms::acronym, nsGkAtoms::sub, nsGkAtoms::sup)) {
 | 
						|
        nsStaticAtom& tagName = const_cast<nsStaticAtom&>(
 | 
						|
            *inclusiveAncestor->NodeInfo()->NameAtom()->AsStatic());
 | 
						|
        if (NeedToAppend(tagName, nullptr)) {
 | 
						|
          aPendingStyleCacheArray.AppendElement(
 | 
						|
              PendingStyleCache(tagName, nullptr, EmptyString()));
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      if (inclusiveAncestor->IsHTMLElement(nsGkAtoms::font)) {
 | 
						|
        if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::face)) {
 | 
						|
          inclusiveAncestor->GetAttr(nsGkAtoms::face, value);
 | 
						|
          if (!value.IsEmpty()) {
 | 
						|
            aPendingStyleCacheArray.AppendElement(
 | 
						|
                PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::face, value));
 | 
						|
            value.Truncate();
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::size)) {
 | 
						|
          inclusiveAncestor->GetAttr(nsGkAtoms::size, value);
 | 
						|
          if (!value.IsEmpty()) {
 | 
						|
            aPendingStyleCacheArray.AppendElement(
 | 
						|
                PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::size, value));
 | 
						|
            value.Truncate();
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::color)) {
 | 
						|
          inclusiveAncestor->GetAttr(nsGkAtoms::color, value);
 | 
						|
          if (!value.IsEmpty()) {
 | 
						|
            aPendingStyleCacheArray.AppendElement(
 | 
						|
                PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::color, value));
 | 
						|
            value.Truncate();
 | 
						|
          }
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  for (nsStaticAtom* property : {nsGkAtoms::b,
 | 
						|
                                 nsGkAtoms::i,
 | 
						|
                                 nsGkAtoms::u,
 | 
						|
                                 nsGkAtoms::s,
 | 
						|
                                 nsGkAtoms::strike,
 | 
						|
                                 nsGkAtoms::face,
 | 
						|
                                 nsGkAtoms::size,
 | 
						|
                                 nsGkAtoms::color,
 | 
						|
                                 nsGkAtoms::tt,
 | 
						|
                                 nsGkAtoms::em,
 | 
						|
                                 nsGkAtoms::strong,
 | 
						|
                                 nsGkAtoms::dfn,
 | 
						|
                                 nsGkAtoms::code,
 | 
						|
                                 nsGkAtoms::samp,
 | 
						|
                                 nsGkAtoms::var,
 | 
						|
                                 nsGkAtoms::cite,
 | 
						|
                                 nsGkAtoms::abbr,
 | 
						|
                                 nsGkAtoms::acronym,
 | 
						|
                                 nsGkAtoms::backgroundColor,
 | 
						|
                                 nsGkAtoms::sub,
 | 
						|
                                 nsGkAtoms::sup}) {
 | 
						|
    const EditorInlineStyle style =
 | 
						|
        property == nsGkAtoms::face || property == nsGkAtoms::size ||
 | 
						|
                property == nsGkAtoms::color
 | 
						|
            ? EditorInlineStyle(*nsGkAtoms::font, property)
 | 
						|
            : EditorInlineStyle(*property);
 | 
						|
    // If type-in state is set, don't intervene
 | 
						|
    const PendingStyleState styleState =
 | 
						|
        mPendingStylesToApplyToNewContent->GetStyleState(*style.mHTMLProperty,
 | 
						|
                                                         style.mAttribute);
 | 
						|
    if (styleState != PendingStyleState::NotUpdated) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    bool isSet = false;
 | 
						|
    nsString value;  // Don't use nsAutoString here because it requires memcpy
 | 
						|
                     // at creating new PendingStyleCache instance.
 | 
						|
    // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
 | 
						|
    if (property == nsGkAtoms::size) {
 | 
						|
      isSet = HTMLEditUtils::IsInlineStyleSetByElement(aElement, style, nullptr,
 | 
						|
                                                       &value);
 | 
						|
    } else if (style.IsCSSSettable(aElement)) {
 | 
						|
      Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
 | 
						|
          CSSEditUtils::IsComputedCSSEquivalentTo(*this, aElement, style,
 | 
						|
                                                  value);
 | 
						|
      if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
 | 
						|
        NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
 | 
						|
        return isComputedCSSEquivalentToStyleOrError.unwrapErr();
 | 
						|
      }
 | 
						|
      isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
 | 
						|
    }
 | 
						|
    if (isSet) {
 | 
						|
      aPendingStyleCacheArray.AppendElement(
 | 
						|
          style.ToPendingStyleCache(std::move(value)));
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::ReapplyCachedStyles() {
 | 
						|
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
 | 
						|
 | 
						|
  // The idea here is to examine our cached list of styles and see if any have
 | 
						|
  // been removed.  If so, add typeinstate for them, so that they will be
 | 
						|
  // reinserted when new content is added.
 | 
						|
 | 
						|
  if (TopLevelEditSubActionDataRef().mCachedPendingStyles->IsEmpty() ||
 | 
						|
      !SelectionRef().RangeCount()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // remember if we are in css mode
 | 
						|
  const bool useCSS = IsCSSEnabled();
 | 
						|
 | 
						|
  const RangeBoundary& atStartOfSelection =
 | 
						|
      SelectionRef().GetRangeAt(0)->StartRef();
 | 
						|
  const RefPtr<Element> startContainerElement =
 | 
						|
      atStartOfSelection.Container() &&
 | 
						|
              atStartOfSelection.Container()->IsContent()
 | 
						|
          ? atStartOfSelection.Container()->GetAsElementOrParentElement()
 | 
						|
          : nullptr;
 | 
						|
  if (NS_WARN_IF(!startContainerElement)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  AutoPendingStyleCacheArray styleCacheArrayAtInsertionPoint;
 | 
						|
  nsresult rv =
 | 
						|
      GetInlineStyles(*startContainerElement, styleCacheArrayAtInsertionPoint);
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
  }
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("HTMLEditor::GetInlineStyles() failed, but ignored");
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  for (PendingStyleCache& styleCacheBeforeEdit :
 | 
						|
       Reversed(*TopLevelEditSubActionDataRef().mCachedPendingStyles)) {
 | 
						|
    bool isFirst = false, isAny = false, isAll = false;
 | 
						|
    nsAutoString currentValue;
 | 
						|
    const EditorInlineStyle inlineStyle = styleCacheBeforeEdit.ToInlineStyle();
 | 
						|
    if (useCSS && inlineStyle.IsCSSSettable(*startContainerElement)) {
 | 
						|
      // check computed style first in css case
 | 
						|
      // MOZ_KnownLive(styleCacheBeforeEdit.*) because they are nsStaticAtom
 | 
						|
      // and its instances are alive until shutting down.
 | 
						|
      Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
 | 
						|
          CSSEditUtils::IsComputedCSSEquivalentTo(*this, *startContainerElement,
 | 
						|
                                                  inlineStyle, currentValue);
 | 
						|
      if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
 | 
						|
        NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
 | 
						|
        return isComputedCSSEquivalentToStyleOrError.unwrapErr();
 | 
						|
      }
 | 
						|
      isAny = isComputedCSSEquivalentToStyleOrError.unwrap();
 | 
						|
    }
 | 
						|
    if (!isAny) {
 | 
						|
      // then check typeinstate and html style
 | 
						|
      nsresult rv = GetInlinePropertyBase(
 | 
						|
          inlineStyle, &styleCacheBeforeEdit.AttributeValueOrCSSValueRef(),
 | 
						|
          &isFirst, &isAny, &isAll, ¤tValue);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    // This style has disappeared through deletion.  Let's add the styles to
 | 
						|
    // mPendingStylesToApplyToNewContent when same style isn't applied to the
 | 
						|
    // node already.
 | 
						|
    if (isAny &&
 | 
						|
        !IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    AutoPendingStyleCacheArray::index_type index =
 | 
						|
        styleCacheArrayAtInsertionPoint.IndexOf(
 | 
						|
            styleCacheBeforeEdit.TagRef(), styleCacheBeforeEdit.GetAttribute());
 | 
						|
    if (index == AutoPendingStyleCacheArray::NoIndex ||
 | 
						|
        styleCacheBeforeEdit.AttributeValueOrCSSValueRef() !=
 | 
						|
            styleCacheArrayAtInsertionPoint.ElementAt(index)
 | 
						|
                .AttributeValueOrCSSValueRef()) {
 | 
						|
      mPendingStylesToApplyToNewContent->PreserveStyle(styleCacheBeforeEdit);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
 | 
						|
    const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  AutoTArray<OwningNonNull<Element>, 64> arrayOfEmptyElements;
 | 
						|
  DOMIterator iter;
 | 
						|
  if (NS_FAILED(iter.Init(aStartRef, aEndRef))) {
 | 
						|
    NS_WARNING("DOMIterator::Init() failed");
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  iter.AppendNodesToArray(
 | 
						|
      +[](nsINode& aNode, void* aSelf) {
 | 
						|
        MOZ_ASSERT(Element::FromNode(&aNode));
 | 
						|
        MOZ_ASSERT(aSelf);
 | 
						|
        Element* element = aNode.AsElement();
 | 
						|
        if (!EditorUtils::IsEditableContent(*element, EditorType::HTML) ||
 | 
						|
            (!HTMLEditUtils::IsListItem(element) &&
 | 
						|
             !HTMLEditUtils::IsTableCellOrCaption(*element))) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
        return HTMLEditUtils::IsEmptyNode(
 | 
						|
            *element, {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
                       EmptyCheckOption::TreatNonEditableContentAsInvisible});
 | 
						|
      },
 | 
						|
      arrayOfEmptyElements, this);
 | 
						|
 | 
						|
  // Put padding <br> elements for empty <li> and <td>.
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  for (auto& emptyElement : arrayOfEmptyElements) {
 | 
						|
    // Need to put br at END of node.  It may have empty containers in it and
 | 
						|
    // still pass the "IsEmptyNode" test, and we want the br's to be after
 | 
						|
    // them.  Also, we want the br to be after the selection if the selection
 | 
						|
    // is in this node.
 | 
						|
    EditorDOMPoint endOfNode(EditorDOMPoint::AtEndOf(emptyElement));
 | 
						|
    Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
        InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode);
 | 
						|
    if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
 | 
						|
          "failed");
 | 
						|
      return insertPaddingBRElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedInsertPaddingBRElementResult =
 | 
						|
        insertPaddingBRElementResult.unwrap();
 | 
						|
    unwrappedInsertPaddingBRElementResult.MoveCaretPointTo(
 | 
						|
        pointToPutCaret, *this,
 | 
						|
        {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
  }
 | 
						|
  if (pointToPutCaret.IsSet()) {
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::CollapseSelectionTo() caused destroying the editor");
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "EditorBase::CollapseSelectionTo() failed, but ignored");
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void HTMLEditor::SetSelectionInterlinePosition() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(SelectionRef().IsCollapsed());
 | 
						|
 | 
						|
  // Get the (collapsed) selection location
 | 
						|
  const nsRange* firstRange = SelectionRef().GetRangeAt(0);
 | 
						|
  if (NS_WARN_IF(!firstRange)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint atCaret(firstRange->StartRef());
 | 
						|
  if (NS_WARN_IF(!atCaret.IsSet())) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(atCaret.IsSetAndValid());
 | 
						|
 | 
						|
  // First, let's check to see if we are after a `<br>`.  We take care of this
 | 
						|
  // special-case first so that we don't accidentally fall through into one of
 | 
						|
  // the other conditionals.
 | 
						|
  // XXX Although I don't understand "interline position", if caret is
 | 
						|
  //     immediately after non-editable contents, but previous editable
 | 
						|
  //     content is `<br>`, does this do right thing?
 | 
						|
  if (Element* editingHost = ComputeEditingHost()) {
 | 
						|
    if (nsIContent* previousEditableContentInBlock =
 | 
						|
            HTMLEditUtils::GetPreviousContent(
 | 
						|
                atCaret,
 | 
						|
                {WalkTreeOption::IgnoreNonEditableNode,
 | 
						|
                 WalkTreeOption::StopAtBlockBoundary},
 | 
						|
                BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
 | 
						|
      if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
        DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
 | 
						|
            InterlinePosition::StartOfNextLine);
 | 
						|
        NS_WARNING_ASSERTION(
 | 
						|
            NS_SUCCEEDED(rvIgnored),
 | 
						|
            "Selection::SetInterlinePosition(InterlinePosition::"
 | 
						|
            "StartOfNextLine) failed, but ignored");
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!atCaret.GetChild()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // If caret is immediately after a block, set interline position to "right".
 | 
						|
  // XXX Although I don't understand "interline position", if caret is
 | 
						|
  //     immediately after non-editable contents, but previous editable
 | 
						|
  //     content is a block, does this do right thing?
 | 
						|
  if (nsIContent* previousEditableContentInBlockAtCaret =
 | 
						|
          HTMLEditUtils::GetPreviousSibling(
 | 
						|
              *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
 | 
						|
    if (HTMLEditUtils::IsBlockElement(
 | 
						|
            *previousEditableContentInBlockAtCaret,
 | 
						|
            BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
      DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
 | 
						|
          InterlinePosition::StartOfNextLine);
 | 
						|
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | 
						|
                           "Selection::SetInterlinePosition(InterlinePosition::"
 | 
						|
                           "StartOfNextLine) failed, but ignored");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If caret is immediately before a block, set interline position to "left".
 | 
						|
  // XXX Although I don't understand "interline position", if caret is
 | 
						|
  //     immediately before non-editable contents, but next editable
 | 
						|
  //     content is a block, does this do right thing?
 | 
						|
  if (nsIContent* nextEditableContentInBlockAtCaret =
 | 
						|
          HTMLEditUtils::GetNextSibling(
 | 
						|
              *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
 | 
						|
    if (HTMLEditUtils::IsBlockElement(
 | 
						|
            *nextEditableContentInBlockAtCaret,
 | 
						|
            BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
      DebugOnly<nsresult> rvIgnored =
 | 
						|
          SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
 | 
						|
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | 
						|
                           "Selection::SetInterlinePosition(InterlinePosition::"
 | 
						|
                           "EndOfLine) failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
 | 
						|
    nsIEditor::EDirection aDirectionAndAmount) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(SelectionRef().IsCollapsed());
 | 
						|
 | 
						|
  auto point = GetFirstSelectionStartPoint<EditorDOMPoint>();
 | 
						|
  if (NS_WARN_IF(!point.IsInContentNode())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // If selection start is not editable, climb up the tree until editable one.
 | 
						|
  while (!EditorUtils::IsEditableContent(*point.ContainerAs<nsIContent>(),
 | 
						|
                                         EditorType::HTML)) {
 | 
						|
    point.Set(point.GetContainer());
 | 
						|
    if (NS_WARN_IF(!point.IsInContentNode())) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If caret is in empty block element, we need to insert a `<br>` element
 | 
						|
  // because the block should have one-line height.
 | 
						|
  // XXX Even if only a part of the block is editable, shouldn't we put
 | 
						|
  //     caret if the block element is now empty?
 | 
						|
  if (Element* const editableBlockElement =
 | 
						|
          HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
              *point.ContainerAs<nsIContent>(),
 | 
						|
              HTMLEditUtils::ClosestEditableBlockElement,
 | 
						|
              BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
    if (editableBlockElement &&
 | 
						|
        HTMLEditUtils::IsEmptyNode(
 | 
						|
            *editableBlockElement,
 | 
						|
            {EmptyCheckOption::TreatSingleBRElementAsVisible}) &&
 | 
						|
        HTMLEditUtils::CanNodeContain(*point.GetContainer(), *nsGkAtoms::br)) {
 | 
						|
      Element* bodyOrDocumentElement = GetRoot();
 | 
						|
      if (NS_WARN_IF(!bodyOrDocumentElement)) {
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
      }
 | 
						|
      if (point.GetContainer() == bodyOrDocumentElement) {
 | 
						|
        // Our root node is completely empty. Don't add a <br> here.
 | 
						|
        // AfterEditInner() will add one for us when it calls
 | 
						|
        // EditorBase::MaybeCreatePaddingBRElementForEmptyEditor().
 | 
						|
        // XXX This kind of dependency between methods makes us spaghetti.
 | 
						|
        //     Let's handle it here later.
 | 
						|
        // XXX This looks odd check.  If active editing host is not a
 | 
						|
        //     `<body>`, what are we doing?
 | 
						|
        return NS_OK;
 | 
						|
      }
 | 
						|
      Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
          InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
 | 
						|
      if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
 | 
						|
            ") failed");
 | 
						|
        return insertPaddingBRElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                  SuggestCaret::AndIgnoreTrivialError});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
          "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX Perhaps, we should do something if we're in a data node but not
 | 
						|
  //     a text node.
 | 
						|
  if (point.IsInTextNode()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  // Do we need to insert a padding <br> element for empty last line?  We do
 | 
						|
  // if we are:
 | 
						|
  // 1) prior node is in same block where selection is AND
 | 
						|
  // 2) prior node is a br AND
 | 
						|
  // 3) that br is not visible
 | 
						|
  RefPtr<Element> editingHost = ComputeEditingHost();
 | 
						|
  if (!editingHost) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  if (nsCOMPtr<nsIContent> previousEditableContent =
 | 
						|
          HTMLEditUtils::GetPreviousContent(
 | 
						|
              point, {WalkTreeOption::IgnoreNonEditableNode},
 | 
						|
              BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
 | 
						|
    // If caret and previous editable content are in same block element
 | 
						|
    // (even if it's a non-editable element), we should put a padding <br>
 | 
						|
    // element at end of the block.
 | 
						|
    const Element* const blockElementContainingCaret =
 | 
						|
        HTMLEditUtils::GetInclusiveAncestorElement(
 | 
						|
            *point.ContainerAs<nsIContent>(),
 | 
						|
            HTMLEditUtils::ClosestBlockElement,
 | 
						|
            BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
    const Element* const blockElementContainingPreviousEditableContent =
 | 
						|
        HTMLEditUtils::GetAncestorElement(
 | 
						|
            *previousEditableContent, HTMLEditUtils::ClosestBlockElement,
 | 
						|
            BlockInlineCheck::UseComputedDisplayStyle);
 | 
						|
    // If previous editable content of caret is in same block and a `<br>`
 | 
						|
    // element, we need to adjust interline position.
 | 
						|
    if (blockElementContainingCaret &&
 | 
						|
        blockElementContainingCaret ==
 | 
						|
            blockElementContainingPreviousEditableContent &&
 | 
						|
        point.ContainerAs<nsIContent>()->GetEditingHost() ==
 | 
						|
            previousEditableContent->GetEditingHost() &&
 | 
						|
        previousEditableContent &&
 | 
						|
        previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
      // If it's an invisible `<br>` element, we need to insert a padding
 | 
						|
      // `<br>` element for making empty line have one-line height.
 | 
						|
      if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent) &&
 | 
						|
          !EditorUtils::IsPaddingBRElementForEmptyLastLine(
 | 
						|
              *previousEditableContent)) {
 | 
						|
        AutoEditorDOMPointChildInvalidator lockOffset(point);
 | 
						|
        Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
            InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
 | 
						|
        if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::"
 | 
						|
              "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
 | 
						|
          return insertPaddingBRElementResult.unwrapErr();
 | 
						|
        }
 | 
						|
        insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(
 | 
						|
            insertPaddingBRElementResult.inspect().GetNewNode(),
 | 
						|
            InterlinePosition::StartOfNextLine));
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
          return rv;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      // If it's a visible `<br>` element and next editable content is a
 | 
						|
      // padding `<br>` element, we need to set interline position.
 | 
						|
      else if (nsIContent* nextEditableContentInBlock =
 | 
						|
                   HTMLEditUtils::GetNextContent(
 | 
						|
                       *previousEditableContent,
 | 
						|
                       {WalkTreeOption::IgnoreNonEditableNode,
 | 
						|
                        WalkTreeOption::StopAtBlockBoundary},
 | 
						|
                       BlockInlineCheck::UseComputedDisplayStyle,
 | 
						|
                       editingHost)) {
 | 
						|
        if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
 | 
						|
                *nextEditableContentInBlock)) {
 | 
						|
          // Make it stick to the padding `<br>` element so that it will be
 | 
						|
          // on blank line.
 | 
						|
          DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
 | 
						|
              InterlinePosition::StartOfNextLine);
 | 
						|
          NS_WARNING_ASSERTION(
 | 
						|
              NS_SUCCEEDED(rvIgnored),
 | 
						|
              "Selection::SetInterlinePosition(InterlinePosition::"
 | 
						|
              "StartOfNextLine) failed, but ignored");
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If previous editable content in same block is `<br>`, text node, `<img>`
 | 
						|
  //  or `<hr>`, current caret position is fine.
 | 
						|
  if (nsIContent* previousEditableContentInBlock =
 | 
						|
          HTMLEditUtils::GetPreviousContent(
 | 
						|
              point,
 | 
						|
              {WalkTreeOption::IgnoreNonEditableNode,
 | 
						|
               WalkTreeOption::StopAtBlockBoundary},
 | 
						|
              BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
 | 
						|
    if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
 | 
						|
        previousEditableContentInBlock->IsText() ||
 | 
						|
        HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
 | 
						|
        previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If next editable content in same block is `<br>`, text node, `<img>` or
 | 
						|
  // `<hr>`, current caret position is fine.
 | 
						|
  if (nsIContent* nextEditableContentInBlock = HTMLEditUtils::GetNextContent(
 | 
						|
          point,
 | 
						|
          {WalkTreeOption::IgnoreNonEditableNode,
 | 
						|
           WalkTreeOption::StopAtBlockBoundary},
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
 | 
						|
    if (nextEditableContentInBlock->IsText() ||
 | 
						|
        nextEditableContentInBlock->IsAnyOfHTMLElements(
 | 
						|
            nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Otherwise, look for a near editable content towards edit action direction.
 | 
						|
 | 
						|
  // If there is no editable content, keep current caret position.
 | 
						|
  // XXX Why do we treat `nsIEditor::ePreviousWord` etc as forward direction?
 | 
						|
  nsIContent* nearEditableContent = HTMLEditUtils::GetAdjacentContentToPutCaret(
 | 
						|
      point,
 | 
						|
      aDirectionAndAmount == nsIEditor::ePrevious ? WalkTreeDirection::Backward
 | 
						|
                                                  : WalkTreeDirection::Forward,
 | 
						|
      *editingHost);
 | 
						|
  if (!nearEditableContent) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  EditorRawDOMPoint pointToPutCaret =
 | 
						|
      HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>(
 | 
						|
          *nearEditableContent, aDirectionAndAmount);
 | 
						|
  if (!pointToPutCaret.IsSet()) {
 | 
						|
    NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::CollapseSelectionTo() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::RemoveEmptyNodesIn(const EditorDOMRange& aRange) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aRange.IsPositioned());
 | 
						|
 | 
						|
  // Some general notes on the algorithm used here: the goal is to examine all
 | 
						|
  // the nodes in aRange, and remove the empty ones.  We do this by
 | 
						|
  // using a content iterator to traverse all the nodes in the range, and
 | 
						|
  // placing the empty nodes into an array.  After finishing the iteration,
 | 
						|
  // we delete the empty nodes in the array.  (They cannot be deleted as we
 | 
						|
  // find them because that would invalidate the iterator.)
 | 
						|
  //
 | 
						|
  // Since checking to see if a node is empty can be costly for nodes with
 | 
						|
  // many descendants, there are some optimizations made.  I rely on the fact
 | 
						|
  // that the iterator is post-order: it will visit children of a node before
 | 
						|
  // visiting the parent node.  So if I find that a child node is not empty, I
 | 
						|
  // know that its parent is not empty without even checking.  So I put the
 | 
						|
  // parent on a "skipList" which is just a voidArray of nodes I can skip the
 | 
						|
  // empty check on.  If I encounter a node on the skiplist, i skip the
 | 
						|
  // processing for that node and replace its slot in the skiplist with that
 | 
						|
  // node's parent.
 | 
						|
  //
 | 
						|
  // An interesting idea is to go ahead and regard parent nodes that are NOT
 | 
						|
  // on the skiplist as being empty (without even doing the IsEmptyNode check)
 | 
						|
  // on the theory that if they weren't empty, we would have encountered a
 | 
						|
  // non-empty child earlier and thus put this parent node on the skiplist.
 | 
						|
  //
 | 
						|
  // Unfortunately I can't use that strategy here, because the range may
 | 
						|
  // include some children of a node while excluding others.  Thus I could
 | 
						|
  // find all the _examined_ children empty, but still not have an empty
 | 
						|
  // parent.
 | 
						|
 | 
						|
  const RawRangeBoundary endOfRange = [&]() {
 | 
						|
    // If the range is not collapsed and end of the range is start of a
 | 
						|
    // container, it means that the inclusive ancestor empty element may be
 | 
						|
    // created by splitting the left nodes.
 | 
						|
    if (aRange.Collapsed() || !aRange.IsInContentNodes() ||
 | 
						|
        !aRange.EndRef().IsStartOfContainer()) {
 | 
						|
      return aRange.EndRef().ToRawRangeBoundary();
 | 
						|
    }
 | 
						|
    nsINode* const commonAncestor =
 | 
						|
        nsContentUtils::GetClosestCommonInclusiveAncestor(
 | 
						|
            aRange.StartRef().ContainerAs<nsIContent>(),
 | 
						|
            aRange.EndRef().ContainerAs<nsIContent>());
 | 
						|
    if (!commonAncestor) {
 | 
						|
      return aRange.EndRef().ToRawRangeBoundary();
 | 
						|
    }
 | 
						|
    nsIContent* maybeRightContent = nullptr;
 | 
						|
    for (nsIContent* content : aRange.EndRef()
 | 
						|
                                   .ContainerAs<nsIContent>()
 | 
						|
                                   ->InclusiveAncestorsOfType<nsIContent>()) {
 | 
						|
      if (!HTMLEditUtils::IsSimplyEditableNode(*content) ||
 | 
						|
          content == commonAncestor) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      if (aRange.StartRef().ContainerAs<nsIContent>() == content) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      EmptyCheckOptions options = {
 | 
						|
          EmptyCheckOption::TreatListItemAsVisible,
 | 
						|
          EmptyCheckOption::TreatTableCellAsVisible,
 | 
						|
          EmptyCheckOption::TreatNonEditableContentAsInvisible};
 | 
						|
      if (!HTMLEditUtils::IsBlockElement(
 | 
						|
              *content, BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
        options += EmptyCheckOption::TreatSingleBRElementAsVisible;
 | 
						|
      }
 | 
						|
      if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      maybeRightContent = content;
 | 
						|
    }
 | 
						|
    if (!maybeRightContent) {
 | 
						|
      return aRange.EndRef().ToRawRangeBoundary();
 | 
						|
    }
 | 
						|
    return EditorRawDOMPoint::After(*maybeRightContent).ToRawRangeBoundary();
 | 
						|
  }();
 | 
						|
 | 
						|
  PostContentIterator postOrderIter;
 | 
						|
  nsresult rv =
 | 
						|
      postOrderIter.Init(aRange.StartRef().ToRawRangeBoundary(), endOfRange);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("PostContentIterator::Init() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfEmptyContents,
 | 
						|
      arrayOfEmptyCites;
 | 
						|
 | 
						|
  // Collect empty nodes first.
 | 
						|
  {
 | 
						|
    const bool isMailEditor = IsMailEditor();
 | 
						|
    AutoTArray<OwningNonNull<nsIContent>, 64> knownNonEmptyContents;
 | 
						|
    Maybe<AutoRangeArray> maybeSelectionRanges;
 | 
						|
    for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
 | 
						|
      MOZ_ASSERT(postOrderIter.GetCurrentNode()->IsContent());
 | 
						|
 | 
						|
      nsIContent* content = postOrderIter.GetCurrentNode()->AsContent();
 | 
						|
      nsIContent* parentContent = content->GetParent();
 | 
						|
 | 
						|
      size_t idx = knownNonEmptyContents.IndexOf(content);
 | 
						|
      if (idx != decltype(knownNonEmptyContents)::NoIndex) {
 | 
						|
        // This node is on our skip list.  Skip processing for this node, and
 | 
						|
        // replace its value in the skip list with the value of its parent
 | 
						|
        if (parentContent) {
 | 
						|
          knownNonEmptyContents[idx] = parentContent;
 | 
						|
        }
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      const bool isEmptyNode = [&]() {
 | 
						|
        if (!content->IsElement()) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
        const bool isMailCite =
 | 
						|
            isMailEditor && HTMLEditUtils::IsMailCite(*content->AsElement());
 | 
						|
        const bool isCandidate = [&]() {
 | 
						|
          if (content->IsHTMLElement(nsGkAtoms::body)) {
 | 
						|
            // Don't delete the body
 | 
						|
            return false;
 | 
						|
          }
 | 
						|
          if (isMailCite || content->IsHTMLElement(nsGkAtoms::a) ||
 | 
						|
              HTMLEditUtils::IsInlineStyle(content) ||
 | 
						|
              HTMLEditUtils::IsAnyListElement(content) ||
 | 
						|
              content->IsHTMLElement(nsGkAtoms::div)) {
 | 
						|
            // Only consider certain nodes to be empty for purposes of removal
 | 
						|
            return true;
 | 
						|
          }
 | 
						|
          if (HTMLEditUtils::IsFormatElementForFormatBlockCommand(*content) ||
 | 
						|
              HTMLEditUtils::IsListItem(content) ||
 | 
						|
              content->IsHTMLElement(nsGkAtoms::blockquote)) {
 | 
						|
            // These node types are candidates if selection is not in them.  If
 | 
						|
            // it is one of these, don't delete if selection inside.  This is so
 | 
						|
            // we can create empty headings, etc., for the user to type into.
 | 
						|
            if (maybeSelectionRanges.isNothing()) {
 | 
						|
              maybeSelectionRanges.emplace(SelectionRef());
 | 
						|
            }
 | 
						|
            return !maybeSelectionRanges
 | 
						|
                        ->IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
 | 
						|
                            *content);
 | 
						|
          }
 | 
						|
          return false;
 | 
						|
        }();
 | 
						|
 | 
						|
        if (!isCandidate) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // We delete mailcites even if they have a solo br in them.  Other
 | 
						|
        // nodes we require to be empty.
 | 
						|
        HTMLEditUtils::EmptyCheckOptions options{
 | 
						|
            EmptyCheckOption::TreatListItemAsVisible,
 | 
						|
            EmptyCheckOption::TreatTableCellAsVisible};
 | 
						|
        if (!isMailCite) {
 | 
						|
          options += EmptyCheckOption::TreatSingleBRElementAsVisible;
 | 
						|
        } else {
 | 
						|
          // XXX Maybe unnecessary to specify this.
 | 
						|
          options += EmptyCheckOption::TreatNonEditableContentAsInvisible;
 | 
						|
        }
 | 
						|
        if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
 | 
						|
        if (isMailCite) {
 | 
						|
          // mailcites go on a separate list from other empty nodes
 | 
						|
          arrayOfEmptyCites.AppendElement(*content);
 | 
						|
        }
 | 
						|
        // Don't delete non-editable nodes in this method because this is a
 | 
						|
        // clean up method to remove unnecessary nodes of the result of
 | 
						|
        // editing.  So, we shouldn't delete non-editable nodes which were
 | 
						|
        // there before editing.  Additionally, if the element is some special
 | 
						|
        // elements such as <body>, we shouldn't delete it.
 | 
						|
        else if (HTMLEditUtils::IsSimplyEditableNode(*content) &&
 | 
						|
                 HTMLEditUtils::IsRemovableNode(*content)) {
 | 
						|
          arrayOfEmptyContents.AppendElement(*content);
 | 
						|
        }
 | 
						|
        return true;
 | 
						|
      }();
 | 
						|
      if (!isEmptyNode && parentContent) {
 | 
						|
        knownNonEmptyContents.AppendElement(*parentContent);
 | 
						|
      }
 | 
						|
    }  // end of the for-loop iterating with postOrderIter
 | 
						|
  }
 | 
						|
 | 
						|
  // now delete the empty nodes
 | 
						|
  for (OwningNonNull<nsIContent>& emptyContent : arrayOfEmptyContents) {
 | 
						|
    // MOZ_KnownLive due to bug 1622253
 | 
						|
    nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Now delete the empty mailcites.  This is a separate step because we want
 | 
						|
  // to pull out any br's and preserve them.
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  for (OwningNonNull<nsIContent>& emptyCite : arrayOfEmptyCites) {
 | 
						|
    if (!HTMLEditUtils::IsEmptyNode(
 | 
						|
            emptyCite,
 | 
						|
            {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
             EmptyCheckOption::TreatListItemAsVisible,
 | 
						|
             EmptyCheckOption::TreatTableCellAsVisible,
 | 
						|
             EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
 | 
						|
      // We are deleting a cite that has just a `<br>`.  We want to delete cite,
 | 
						|
      // but preserve `<br>`.
 | 
						|
      Result<CreateElementResult, nsresult> insertBRElementResult =
 | 
						|
          InsertBRElement(WithTransaction::Yes, EditorDOMPoint(emptyCite));
 | 
						|
      if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
        return insertBRElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      CreateElementResult unwrappedInsertBRElementResult =
 | 
						|
          insertBRElementResult.unwrap();
 | 
						|
      // XXX Is this intentional selection change?
 | 
						|
      unwrappedInsertBRElementResult.MoveCaretPointTo(
 | 
						|
          pointToPutCaret, *this,
 | 
						|
          {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
           SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
      MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
 | 
						|
    }
 | 
						|
    // MOZ_KnownLive because 'arrayOfEmptyCites' is guaranteed to keep it alive.
 | 
						|
    nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyCite));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  // XXX Is this intentional selection change?
 | 
						|
  if (pointToPutCaret.IsSet()) {
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::CollapseSelectionTo() caused destroying the editor");
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "EditorBase::CollapseSelectionTo() failed, but ignored");
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::LiftUpListItemElement(
 | 
						|
    Element& aListItemElement,
 | 
						|
    LiftUpFromAllParentListElements aLiftUpFromAllParentListElements) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  if (!HTMLEditUtils::IsListItem(&aListItemElement)) {
 | 
						|
    return NS_ERROR_INVALID_ARG;
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_WARN_IF(!aListItemElement.GetParentElement()) ||
 | 
						|
      NS_WARN_IF(!aListItemElement.GetParentElement()->GetParentNode())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // if it's first or last list item, don't need to split the list
 | 
						|
  // otherwise we do.
 | 
						|
  const bool isFirstListItem = HTMLEditUtils::IsFirstChild(
 | 
						|
      aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  const bool isLastListItem = HTMLEditUtils::IsLastChild(
 | 
						|
      aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
 | 
						|
  Element* leftListElement = aListItemElement.GetParentElement();
 | 
						|
  if (NS_WARN_IF(!leftListElement)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // If it's at middle of parent list element, split the parent list element.
 | 
						|
  // Then, aListItem becomes the first list item of the right list element.
 | 
						|
  if (!isFirstListItem && !isLastListItem) {
 | 
						|
    EditorDOMPoint atListItemElement(&aListItemElement);
 | 
						|
    if (NS_WARN_IF(!atListItemElement.IsSet())) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    MOZ_ASSERT(atListItemElement.IsSetAndValid());
 | 
						|
    Result<SplitNodeResult, nsresult> splitListItemParentResult =
 | 
						|
        SplitNodeWithTransaction(atListItemElement);
 | 
						|
    if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
 | 
						|
      return splitListItemParentResult.unwrapErr();
 | 
						|
    }
 | 
						|
    nsresult rv = splitListItemParentResult.inspect().SuggestCaretPointTo(
 | 
						|
        *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    leftListElement =
 | 
						|
        splitListItemParentResult.inspect().GetPreviousContentAs<Element>();
 | 
						|
    if (MOZ_UNLIKELY(!leftListElement)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::SplitNodeWithTransaction() didn't return left list "
 | 
						|
          "element");
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // In most cases, insert the list item into the new left list node..
 | 
						|
  EditorDOMPoint pointToInsertListItem(leftListElement);
 | 
						|
  if (NS_WARN_IF(!pointToInsertListItem.IsSet())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // But when the list item was the first child of the right list, it should
 | 
						|
  // be inserted between the both list elements.  This allows user to hit
 | 
						|
  // Enter twice at a list item breaks the parent list node.
 | 
						|
  if (!isFirstListItem) {
 | 
						|
    DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset();
 | 
						|
    NS_WARNING_ASSERTION(advanced,
 | 
						|
                         "Failed to advance offset to right list node");
 | 
						|
  }
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  {
 | 
						|
    Result<MoveNodeResult, nsresult> moveListItemElementResult =
 | 
						|
        MoveNodeWithTransaction(aListItemElement, pointToInsertListItem);
 | 
						|
    if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
 | 
						|
      return moveListItemElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    MoveNodeResult unwrappedMoveListItemElementResult =
 | 
						|
        moveListItemElementResult.unwrap();
 | 
						|
    unwrappedMoveListItemElementResult.MoveCaretPointTo(
 | 
						|
        pointToPutCaret, *this,
 | 
						|
        {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
  }
 | 
						|
 | 
						|
  // Unwrap list item contents if they are no longer in a list
 | 
						|
  // XXX If the parent list element is a child of another list element
 | 
						|
  //     (although invalid tree), the list item element won't be unwrapped.
 | 
						|
  //     That makes the parent ancestor element tree valid, but might be
 | 
						|
  //     unexpected result.
 | 
						|
  // XXX If aListItemElement is <dl> or <dd> and current parent is <ul> or <ol>,
 | 
						|
  //     the list items won't be unwrapped.  If aListItemElement is <li> and its
 | 
						|
  //     current parent is <dl>, there is same issue.
 | 
						|
  if (!HTMLEditUtils::IsAnyListElement(pointToInsertListItem.GetContainer()) &&
 | 
						|
      HTMLEditUtils::IsListItem(&aListItemElement)) {
 | 
						|
    Result<EditorDOMPoint, nsresult> unwrapOrphanListItemElementResult =
 | 
						|
        RemoveBlockContainerWithTransaction(aListItemElement);
 | 
						|
    if (MOZ_UNLIKELY(unwrapOrphanListItemElementResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
      return unwrapOrphanListItemElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    if (AllowsTransactionsToChangeSelection() &&
 | 
						|
        unwrapOrphanListItemElementResult.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = unwrapOrphanListItemElementResult.unwrap();
 | 
						|
    }
 | 
						|
    if (!pointToPutCaret.IsSet()) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "EditorBase::CollapseSelectionTo() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  if (pointToPutCaret.IsSet()) {
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "EditorBase::CollapseSelectionTo() failed, but ignored");
 | 
						|
  }
 | 
						|
 | 
						|
  if (aLiftUpFromAllParentListElements == LiftUpFromAllParentListElements::No) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  // XXX If aListItemElement is moved to unexpected element by mutation event
 | 
						|
  //     listener, shouldn't we stop calling this?
 | 
						|
  nsresult rv = LiftUpListItemElement(aListItemElement,
 | 
						|
                                      LiftUpFromAllParentListElements::Yes);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "HTMLEditor::LiftUpListItemElement("
 | 
						|
                       "LiftUpFromAllParentListElements::Yes) failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::DestroyListStructureRecursively(Element& aListElement) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement));
 | 
						|
 | 
						|
  // XXX If mutation event listener inserts new child into `aListElement`,
 | 
						|
  //     this becomes infinite loop so that we should set limit of the
 | 
						|
  //     loop count from original child count.
 | 
						|
  while (aListElement.GetFirstChild()) {
 | 
						|
    OwningNonNull<nsIContent> child = *aListElement.GetFirstChild();
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsListItem(child)) {
 | 
						|
      // XXX Using LiftUpListItemElement() is too expensive for this purpose.
 | 
						|
      //     Looks like the reason why this method uses it is, only this loop
 | 
						|
      //     wants to work with first child of aListElement.  However, what it
 | 
						|
      //     actually does is removing <li> as container.  Perhaps, we should
 | 
						|
      //     decide destination first, and then, move contents in `child`.
 | 
						|
      // XXX If aListElement is is a child of another list element (although
 | 
						|
      //     it's invalid tree), this moves the list item to outside of
 | 
						|
      //     aListElement's parent.  Is that really intentional behavior?
 | 
						|
      nsresult rv = LiftUpListItemElement(
 | 
						|
          MOZ_KnownLive(*child->AsElement()),
 | 
						|
          HTMLEditor::LiftUpFromAllParentListElements::Yes);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
 | 
						|
            ":Yes) failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(child)) {
 | 
						|
      nsresult rv =
 | 
						|
          DestroyListStructureRecursively(MOZ_KnownLive(*child->AsElement()));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Delete any non-list items for now
 | 
						|
    // XXX This is not HTML5 aware.  HTML5 allows all list elements to have
 | 
						|
    //     <script> and <template> and <dl> element to have <div> to group
 | 
						|
    //     some <dt> and <dd> elements.  So, this may break valid children.
 | 
						|
    nsresult rv = DeleteNodeWithTransaction(*child);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Delete the now-empty list
 | 
						|
  const Result<EditorDOMPoint, nsresult> unwrapListElementResult =
 | 
						|
      RemoveBlockContainerWithTransaction(aListElement);
 | 
						|
  if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
 | 
						|
    NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
 | 
						|
    return unwrapListElementResult.inspectErr();
 | 
						|
  }
 | 
						|
  const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
 | 
						|
  if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::CollapseSelectionTo() failed");
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::EnsureSelectionInBodyOrDocumentElement() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  RefPtr<Element> bodyOrDocumentElement = GetRoot();
 | 
						|
  if (NS_WARN_IF(!bodyOrDocumentElement)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  const auto atCaret = GetFirstSelectionStartPoint<EditorRawDOMPoint>();
 | 
						|
  if (NS_WARN_IF(!atCaret.IsSet())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX This does wrong things.  Web apps can put any elements as sibling
 | 
						|
  //     of `<body>` element.  Therefore, this collapses `Selection` into
 | 
						|
  //     the `<body>` element which `HTMLDocument.body` is set to.  So,
 | 
						|
  //     this makes users impossible to modify content outside of the
 | 
						|
  //     `<body>` element even if caret is in an editing host.
 | 
						|
 | 
						|
  // Check that selection start container is inside the <body> element.
 | 
						|
  // XXXsmaug this code is insane.
 | 
						|
  nsINode* temp = atCaret.GetContainer();
 | 
						|
  while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
 | 
						|
    temp = temp->GetParentOrShadowHostNode();
 | 
						|
  }
 | 
						|
 | 
						|
  // If we aren't in the <body> element, force the issue.
 | 
						|
  if (!temp) {
 | 
						|
    nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
 | 
						|
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::CollapseSelectionToStartOf() caused destroying the "
 | 
						|
          "editor");
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  const auto selectionEndPoint = GetFirstSelectionEndPoint<EditorRawDOMPoint>();
 | 
						|
  if (NS_WARN_IF(!selectionEndPoint.IsSet())) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // check that selNode is inside body
 | 
						|
  // XXXsmaug this code is insane.
 | 
						|
  temp = selectionEndPoint.GetContainer();
 | 
						|
  while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
 | 
						|
    temp = temp->GetParentOrShadowHostNode();
 | 
						|
  }
 | 
						|
 | 
						|
  // If we aren't in the <body> element, force the issue.
 | 
						|
  if (!temp) {
 | 
						|
    nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
 | 
						|
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "EditorBase::CollapseSelectionToStartOf() caused destroying the "
 | 
						|
          "editor");
 | 
						|
      return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded(
 | 
						|
    Element& aElement) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  if (!HTMLEditUtils::IsBlockElement(
 | 
						|
          aElement, BlockInlineCheck::UseComputedDisplayStyle)) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!HTMLEditUtils::IsEmptyNode(
 | 
						|
          aElement, {EmptyCheckOption::TreatSingleBRElementAsVisible,
 | 
						|
                     EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
 | 
						|
      InsertPaddingBRElementForEmptyLastLineWithTransaction(
 | 
						|
          EditorDOMPoint(&aElement, 0u));
 | 
						|
  if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
 | 
						|
        "failed");
 | 
						|
    return insertPaddingBRElementResult.unwrapErr();
 | 
						|
  }
 | 
						|
  nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo(
 | 
						|
      *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
              SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
              SuggestCaret::AndIgnoreTrivialError});
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
      "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveAlignFromDescendants(
 | 
						|
    Element& aElement, const nsAString& aAlignType, EditTarget aEditTarget) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(!aElement.IsHTMLElement(nsGkAtoms::table));
 | 
						|
 | 
						|
  const bool useCSS = IsCSSEnabled();
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
 | 
						|
  // Let's remove all alignment hints in the children of aNode; it can
 | 
						|
  // be an ALIGN attribute (in case we just remove it) or a CENTER
 | 
						|
  // element (here we have to remove the container and keep its
 | 
						|
  // children). We break on tables and don't look at their children.
 | 
						|
  nsCOMPtr<nsIContent> nextSibling;
 | 
						|
  for (nsIContent* content =
 | 
						|
           aEditTarget == EditTarget::NodeAndDescendantsExceptTable
 | 
						|
               ? &aElement
 | 
						|
               : aElement.GetFirstChild();
 | 
						|
       content; content = nextSibling) {
 | 
						|
    // Get the next sibling before removing content from the DOM tree.
 | 
						|
    // XXX If next sibling is removed from the parent and/or inserted to
 | 
						|
    //     different parent, we will behave unexpectedly.  I think that
 | 
						|
    //     we should create child list and handle it with checking whether
 | 
						|
    //     it's still a child of expected parent.
 | 
						|
    nextSibling = aEditTarget == EditTarget::NodeAndDescendantsExceptTable
 | 
						|
                      ? nullptr
 | 
						|
                      : content->GetNextSibling();
 | 
						|
 | 
						|
    if (content->IsHTMLElement(nsGkAtoms::center)) {
 | 
						|
      OwningNonNull<Element> centerElement = *content->AsElement();
 | 
						|
      {
 | 
						|
        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
            RemoveAlignFromDescendants(centerElement, aAlignType,
 | 
						|
                                       EditTarget::OnlyDescendantsExceptTable);
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
 | 
						|
              "OnlyDescendantsExceptTable) failed");
 | 
						|
          return pointToPutCaretOrError;
 | 
						|
        }
 | 
						|
        if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
          pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // We may have to insert a `<br>` element before first child of the
 | 
						|
      // `<center>` element because it should be first element of a hard line
 | 
						|
      // even after removing the `<center>` element.
 | 
						|
      {
 | 
						|
        Result<CreateElementResult, nsresult>
 | 
						|
            maybeInsertBRElementBeforeFirstChildResult =
 | 
						|
                EnsureHardLineBeginsWithFirstChildOf(centerElement);
 | 
						|
        if (MOZ_UNLIKELY(maybeInsertBRElementBeforeFirstChildResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed");
 | 
						|
          return maybeInsertBRElementBeforeFirstChildResult.propagateErr();
 | 
						|
        }
 | 
						|
        CreateElementResult unwrappedResult =
 | 
						|
            maybeInsertBRElementBeforeFirstChildResult.unwrap();
 | 
						|
        if (unwrappedResult.HasCaretPointSuggestion()) {
 | 
						|
          pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // We may have to insert a `<br>` element after last child of the
 | 
						|
      // `<center>` element because it should be last element of a hard line
 | 
						|
      // even after removing the `<center>` element.
 | 
						|
      {
 | 
						|
        Result<CreateElementResult, nsresult>
 | 
						|
            maybeInsertBRElementAfterLastChildResult =
 | 
						|
                EnsureHardLineEndsWithLastChildOf(centerElement);
 | 
						|
        if (MOZ_UNLIKELY(maybeInsertBRElementAfterLastChildResult.isErr())) {
 | 
						|
          NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed");
 | 
						|
          return maybeInsertBRElementAfterLastChildResult.propagateErr();
 | 
						|
        }
 | 
						|
        CreateElementResult unwrappedResult =
 | 
						|
            maybeInsertBRElementAfterLastChildResult.unwrap();
 | 
						|
        if (unwrappedResult.HasCaretPointSuggestion()) {
 | 
						|
          pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      {
 | 
						|
        Result<EditorDOMPoint, nsresult> unwrapCenterElementResult =
 | 
						|
            RemoveContainerWithTransaction(centerElement);
 | 
						|
        if (MOZ_UNLIKELY(unwrapCenterElementResult.isErr())) {
 | 
						|
          NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
 | 
						|
          return unwrapCenterElementResult;
 | 
						|
        }
 | 
						|
        if (unwrapCenterElementResult.inspect().IsSet()) {
 | 
						|
          pointToPutCaret = unwrapCenterElementResult.unwrap();
 | 
						|
        }
 | 
						|
      }
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!HTMLEditUtils::IsBlockElement(*content,
 | 
						|
                                       BlockInlineCheck::UseHTMLDefaultStyle) &&
 | 
						|
        !content->IsHTMLElement(nsGkAtoms::hr)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    const OwningNonNull<Element> blockOrHRElement = *content->AsElement();
 | 
						|
    if (HTMLEditUtils::SupportsAlignAttr(blockOrHRElement)) {
 | 
						|
      nsresult rv =
 | 
						|
          RemoveAttributeWithTransaction(blockOrHRElement, *nsGkAtoms::align);
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING(
 | 
						|
            "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) "
 | 
						|
            "failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (useCSS) {
 | 
						|
      if (blockOrHRElement->IsAnyOfHTMLElements(nsGkAtoms::table,
 | 
						|
                                                nsGkAtoms::hr)) {
 | 
						|
        nsresult rv = SetAttributeOrEquivalent(
 | 
						|
            blockOrHRElement, nsGkAtoms::align, aAlignType, false);
 | 
						|
        if (NS_WARN_IF(Destroyed())) {
 | 
						|
          return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
        }
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING(
 | 
						|
              "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        nsStyledElement* styledBlockOrHRElement =
 | 
						|
            nsStyledElement::FromNode(blockOrHRElement);
 | 
						|
        if (NS_WARN_IF(!styledBlockOrHRElement)) {
 | 
						|
          return Err(NS_ERROR_FAILURE);
 | 
						|
        }
 | 
						|
        // MOZ_KnownLive(*styledBlockOrHRElement): It's `blockOrHRElement
 | 
						|
        // which is OwningNonNull.
 | 
						|
        nsAutoString dummyCssValue;
 | 
						|
        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
            CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
 | 
						|
                *this, MOZ_KnownLive(*styledBlockOrHRElement),
 | 
						|
                nsGkAtoms::textAlign, dummyCssValue);
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::"
 | 
						|
              "textAlign) failed");
 | 
						|
          return pointToPutCaretOrError;
 | 
						|
        }
 | 
						|
        if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
          pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (!blockOrHRElement->IsHTMLElement(nsGkAtoms::table)) {
 | 
						|
      // unless this is a table, look at children
 | 
						|
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
          RemoveAlignFromDescendants(blockOrHRElement, aAlignType,
 | 
						|
                                     EditTarget::OnlyDescendantsExceptTable);
 | 
						|
      if (pointToPutCaretOrError.isErr()) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
 | 
						|
            "OnlyDescendantsExceptTable) failed");
 | 
						|
        return pointToPutCaretOrError;
 | 
						|
      }
 | 
						|
      if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
        pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult>
 | 
						|
HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
 | 
						|
    Element& aRemovingContainerElement) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
 | 
						|
      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (!firstEditableChild) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsBlockElement(
 | 
						|
          *firstEditableChild, BlockInlineCheck::UseComputedDisplayStyle) ||
 | 
						|
      firstEditableChild->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling(
 | 
						|
      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (!previousEditableContent) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsBlockElement(
 | 
						|
          *previousEditableContent,
 | 
						|
          BlockInlineCheck::UseComputedDisplayStyle) ||
 | 
						|
      previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
 | 
						|
      WithTransaction::Yes, EditorDOMPoint(&aRemovingContainerElement, 0u));
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      insertBRElementResult.isOk(),
 | 
						|
      "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
  return insertBRElementResult;
 | 
						|
}
 | 
						|
 | 
						|
Result<CreateElementResult, nsresult>
 | 
						|
HTMLEditor::EnsureHardLineEndsWithLastChildOf(
 | 
						|
    Element& aRemovingContainerElement) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild(
 | 
						|
      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (!firstEditableContent) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsBlockElement(
 | 
						|
          *firstEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
 | 
						|
      firstEditableContent->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling(
 | 
						|
      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
 | 
						|
  if (!nextEditableContent) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  if (HTMLEditUtils::IsBlockElement(
 | 
						|
          *nextEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
 | 
						|
      nextEditableContent->IsHTMLElement(nsGkAtoms::br)) {
 | 
						|
    return CreateElementResult::NotHandled();
 | 
						|
  }
 | 
						|
 | 
						|
  Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
 | 
						|
      WithTransaction::Yes, EditorDOMPoint::AtEndOf(aRemovingContainerElement));
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      insertBRElementResult.isOk(),
 | 
						|
      "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
 | 
						|
  return insertBRElementResult;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::SetBlockElementAlign(
 | 
						|
    Element& aBlockOrHRElement, const nsAString& aAlignType,
 | 
						|
    EditTarget aEditTarget) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
 | 
						|
                 aBlockOrHRElement, BlockInlineCheck::UseHTMLDefaultStyle) ||
 | 
						|
             aBlockOrHRElement.IsHTMLElement(nsGkAtoms::hr));
 | 
						|
  MOZ_ASSERT(IsCSSEnabled() ||
 | 
						|
             HTMLEditUtils::SupportsAlignAttr(aBlockOrHRElement));
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
  if (!aBlockOrHRElement.IsHTMLElement(nsGkAtoms::table)) {
 | 
						|
    Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
        RemoveAlignFromDescendants(aBlockOrHRElement, aAlignType, aEditTarget);
 | 
						|
    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed");
 | 
						|
      return pointToPutCaretOrError;
 | 
						|
    }
 | 
						|
    if (pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = pointToPutCaretOrError.unwrap();
 | 
						|
    }
 | 
						|
  }
 | 
						|
  nsresult rv = SetAttributeOrEquivalent(&aBlockOrHRElement, nsGkAtoms::align,
 | 
						|
                                         aAlignType, false);
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return pointToPutCaret;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditorDOMPoint, nsresult> HTMLEditor::ChangeMarginStart(
 | 
						|
    Element& aElement, ChangeMargin aChangeMargin,
 | 
						|
    const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  nsStaticAtom& marginProperty = MarginPropertyAtomForIndent(aElement);
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  nsAutoString value;
 | 
						|
  DebugOnly<nsresult> rvIgnored =
 | 
						|
      CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value);
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      NS_SUCCEEDED(rvIgnored),
 | 
						|
      "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
 | 
						|
  float f;
 | 
						|
  RefPtr<nsAtom> unit;
 | 
						|
  CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
 | 
						|
  if (!f) {
 | 
						|
    unit = nsGkAtoms::px;
 | 
						|
  }
 | 
						|
  int8_t multiplier = aChangeMargin == ChangeMargin::Increase ? 1 : -1;
 | 
						|
  if (nsGkAtoms::in == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier;
 | 
						|
  } else if (nsGkAtoms::cm == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier;
 | 
						|
  } else if (nsGkAtoms::mm == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier;
 | 
						|
  } else if (nsGkAtoms::pt == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier;
 | 
						|
  } else if (nsGkAtoms::pc == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier;
 | 
						|
  } else if (nsGkAtoms::em == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier;
 | 
						|
  } else if (nsGkAtoms::ex == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier;
 | 
						|
  } else if (nsGkAtoms::px == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier;
 | 
						|
  } else if (nsGkAtoms::percentage == unit) {
 | 
						|
    f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier;
 | 
						|
  }
 | 
						|
 | 
						|
  if (0 < f) {
 | 
						|
    if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
 | 
						|
      nsAutoString newValue;
 | 
						|
      newValue.AppendFloat(f);
 | 
						|
      newValue.Append(nsDependentAtomString(unit));
 | 
						|
      // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
 | 
						|
      // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
 | 
						|
      // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
 | 
						|
      nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction(
 | 
						|
          *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
 | 
						|
          newValue);
 | 
						|
      if (rv == NS_ERROR_EDITOR_DESTROYED) {
 | 
						|
        NS_WARNING(
 | 
						|
            "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the "
 | 
						|
            "editor");
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored");
 | 
						|
    }
 | 
						|
    return EditorDOMPoint();
 | 
						|
  }
 | 
						|
 | 
						|
  if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
 | 
						|
    // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
 | 
						|
    // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
 | 
						|
    // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
 | 
						|
    nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction(
 | 
						|
        *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
 | 
						|
        value);
 | 
						|
    if (rv == NS_ERROR_EDITOR_DESTROYED) {
 | 
						|
      NS_WARNING(
 | 
						|
          "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the "
 | 
						|
          "editor");
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        NS_SUCCEEDED(rv),
 | 
						|
        "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored");
 | 
						|
  }
 | 
						|
 | 
						|
  // Remove unnecessary divs
 | 
						|
  if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
 | 
						|
      HTMLEditUtils::ElementHasAttribute(aElement)) {
 | 
						|
    return EditorDOMPoint();
 | 
						|
  }
 | 
						|
  // Don't touch editing host nor node which is outside of it.
 | 
						|
  if (&aElement == &aEditingHost ||
 | 
						|
      !aElement.IsInclusiveDescendantOf(&aEditingHost)) {
 | 
						|
    return EditorDOMPoint();
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
 | 
						|
      RemoveContainerWithTransaction(aElement);
 | 
						|
  NS_WARNING_ASSERTION(unwrapDivElementResult.isOk(),
 | 
						|
                       "HTMLEditor::RemoveContainerWithTransaction() failed");
 | 
						|
  return unwrapDivElementResult;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult>
 | 
						|
HTMLEditor::SetSelectionToAbsoluteAsSubAction(const Element& aEditingHost) {
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(
 | 
						|
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eSetPositionToAbsolute, nsIEditor::eNext,
 | 
						|
      ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(ignoredError.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  auto EnsureCaretInElementIfCollapsedOutside =
 | 
						|
      [&](Element& aElement) MOZ_CAN_RUN_SCRIPT {
 | 
						|
        if (!SelectionRef().IsCollapsed() || !SelectionRef().RangeCount()) {
 | 
						|
          return NS_OK;
 | 
						|
        }
 | 
						|
        const auto firstRangeStartPoint =
 | 
						|
            GetFirstSelectionStartPoint<EditorRawDOMPoint>();
 | 
						|
        if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
 | 
						|
          return NS_OK;
 | 
						|
        }
 | 
						|
        const Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
 | 
						|
            HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
 | 
						|
                EditorRawDOMPoint>(aElement, firstRangeStartPoint);
 | 
						|
        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
 | 
						|
              "failed, but ignored");
 | 
						|
          return NS_OK;
 | 
						|
        }
 | 
						|
        if (!pointToPutCaretOrError.inspect().IsSet()) {
 | 
						|
          return NS_OK;
 | 
						|
        }
 | 
						|
        nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
 | 
						|
        if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
          return NS_ERROR_EDITOR_DESTROYED;
 | 
						|
        }
 | 
						|
        NS_WARNING_ASSERTION(
 | 
						|
            NS_SUCCEEDED(rv),
 | 
						|
            "EditorBase::CollapseSelectionTo() failed, but ignored");
 | 
						|
        return NS_OK;
 | 
						|
      };
 | 
						|
 | 
						|
  RefPtr<Element> focusElement = GetSelectionContainerElement();
 | 
						|
  if (focusElement && HTMLEditUtils::IsImage(focusElement)) {
 | 
						|
    nsresult rv = EnsureCaretInElementIfCollapsedOutside(*focusElement);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  // XXX Why do we do this only when there is only one selection range?
 | 
						|
  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
 | 
						|
    Result<EditorRawDOMRange, nsresult> extendedRange =
 | 
						|
        GetRangeExtendedToHardLineEdgesForBlockEditAction(
 | 
						|
            SelectionRef().GetRangeAt(0u), aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(extendedRange.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
 | 
						|
          "failed");
 | 
						|
      return extendedRange.propagateErr();
 | 
						|
    }
 | 
						|
    // Note that end point may be prior to start point.  So, we
 | 
						|
    // cannot use Selection::SetStartAndEndInLimit() here.
 | 
						|
    IgnoredErrorResult error;
 | 
						|
    SelectionRef().SetBaseAndExtentInLimiter(
 | 
						|
        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
 | 
						|
        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    if (MOZ_UNLIKELY(error.Failed())) {
 | 
						|
      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
 | 
						|
      return Err(error.StealNSResult());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Element> divElement;
 | 
						|
  rv = MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
 | 
						|
      address_of(divElement), aEditingHost);
 | 
						|
  // MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore
 | 
						|
  // selection with AutoSelectionRestorer.  Therefore, the editor might have
 | 
						|
  // already been destroyed now.
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()"
 | 
						|
        " failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsSelectionRangeContainerNotContent()) {
 | 
						|
    NS_WARNING("Mutation event listener might have changed the selection");
 | 
						|
    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
 | 
						|
  }
 | 
						|
 | 
						|
  rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
 | 
						|
        "failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  if (!divElement) {
 | 
						|
    return EditActionResult::HandledResult();
 | 
						|
  }
 | 
						|
 | 
						|
  rv = SetPositionToAbsoluteOrStatic(*divElement, true);
 | 
						|
  if (NS_WARN_IF(Destroyed())) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
 | 
						|
  rv = EnsureCaretInElementIfCollapsedOutside(*divElement);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
 | 
						|
    return Err(rv);
 | 
						|
  }
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
 | 
						|
    RefPtr<Element>* aTargetElement, const Element& aEditingHost) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
  MOZ_ASSERT(aTargetElement);
 | 
						|
 | 
						|
  AutoSelectionRestorer restoreSelectionLater(*this);
 | 
						|
 | 
						|
  EditorDOMPoint pointToPutCaret;
 | 
						|
 | 
						|
  // Use these ranges to construct a list of nodes to act on.
 | 
						|
  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
 | 
						|
  {
 | 
						|
    AutoRangeArray extendedSelectionRanges(SelectionRef());
 | 
						|
    extendedSelectionRanges.ExtendRangesToWrapLines(
 | 
						|
        EditSubAction::eSetPositionToAbsolute,
 | 
						|
        BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
    Result<EditorDOMPoint, nsresult> splitResult =
 | 
						|
        extendedSelectionRanges
 | 
						|
            .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
 | 
						|
                *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(splitResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoRangeArray::"
 | 
						|
          "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
 | 
						|
          "failed");
 | 
						|
      return splitResult.unwrapErr();
 | 
						|
    }
 | 
						|
    if (splitResult.inspect().IsSet()) {
 | 
						|
      pointToPutCaret = splitResult.unwrap();
 | 
						|
    }
 | 
						|
    nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
 | 
						|
        *this, arrayOfContents, EditSubAction::eSetPositionToAbsolute,
 | 
						|
        AutoRangeArray::CollectNonEditableNodes::Yes);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING(
 | 
						|
          "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
 | 
						|
          "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
 | 
						|
      MaybeSplitElementsAtEveryBRElement(arrayOfContents,
 | 
						|
                                         EditSubAction::eSetPositionToAbsolute);
 | 
						|
  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
 | 
						|
        "eSetPositionToAbsolute) failed");
 | 
						|
    return splitAtBRElementsResult.inspectErr();
 | 
						|
  }
 | 
						|
  if (splitAtBRElementsResult.inspect().IsSet()) {
 | 
						|
    pointToPutCaret = splitAtBRElementsResult.unwrap();
 | 
						|
  }
 | 
						|
 | 
						|
  if (AllowsTransactionsToChangeSelection() &&
 | 
						|
      pointToPutCaret.IsSetAndValid()) {
 | 
						|
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If there is no visible and editable nodes in the edit targets, make an
 | 
						|
  // empty block.
 | 
						|
  // XXX Isn't this odd if there are only non-editable visible nodes?
 | 
						|
  if (HTMLEditUtils::IsEmptyOneHardLine(
 | 
						|
          arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
 | 
						|
    const auto atCaret =
 | 
						|
        EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
 | 
						|
    if (NS_WARN_IF(!atCaret.IsSet())) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
 | 
						|
    // Make sure we can put a block here.
 | 
						|
    Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
        InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
            *nsGkAtoms::div, atCaret, BRElementNextToSplitPoint::Keep,
 | 
						|
            aEditingHost);
 | 
						|
    if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
      NS_WARNING(
 | 
						|
          "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
          "nsGkAtoms::div) failed");
 | 
						|
      return createNewDivElementResult.unwrapErr();
 | 
						|
    }
 | 
						|
    CreateElementResult unwrappedCreateNewDivElementResult =
 | 
						|
        createNewDivElementResult.unwrap();
 | 
						|
    // We'll update selection after deleting the content nodes and nobody
 | 
						|
    // refers selection until then.  Therefore, we don't need to update
 | 
						|
    // selection here.
 | 
						|
    unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
 | 
						|
    RefPtr<Element> newDivElement =
 | 
						|
        unwrappedCreateNewDivElementResult.UnwrapNewNode();
 | 
						|
    MOZ_ASSERT(newDivElement);
 | 
						|
    // Delete anything that was in the list of nodes
 | 
						|
    // XXX We don't need to remove items from the array.
 | 
						|
    for (OwningNonNull<nsIContent>& curNode : arrayOfContents) {
 | 
						|
      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
 | 
						|
      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*curNode));
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    // Don't restore the selection
 | 
						|
    restoreSelectionLater.Abort();
 | 
						|
    nsresult rv = CollapseSelectionToStartOf(*newDivElement);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "EditorBase::CollapseSelectionToStartOf() failed");
 | 
						|
    *aTargetElement = std::move(newDivElement);
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  // `<div>` element to be positioned absolutely.  This may have already
 | 
						|
  // existed or newly created by this method.
 | 
						|
  RefPtr<Element> targetDivElement;
 | 
						|
  // Newly created list element for moving selected list item elements into
 | 
						|
  // targetDivElement.  I.e., this is created in the `<div>` element.
 | 
						|
  RefPtr<Element> createdListElement;
 | 
						|
  // If we handle a parent list item element, this is set to it.  In such case,
 | 
						|
  // we should handle its children again.
 | 
						|
  RefPtr<Element> handledListItemElement;
 | 
						|
  for (OwningNonNull<nsIContent>& content : arrayOfContents) {
 | 
						|
    // Here's where we actually figure out what to do.
 | 
						|
    EditorDOMPoint atContent(content);
 | 
						|
    if (NS_WARN_IF(!atContent.IsSet())) {
 | 
						|
      return NS_ERROR_FAILURE;  // XXX not continue??
 | 
						|
    }
 | 
						|
 | 
						|
    // Ignore all non-editable nodes.  Leave them be.
 | 
						|
    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If current node is a child of a list element, we need another list
 | 
						|
    // element in absolute-positioned `<div>` element to avoid non-selected
 | 
						|
    // list items are moved into the `<div>` element.
 | 
						|
    if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
 | 
						|
      // If we cannot move current node to created list element, we need a
 | 
						|
      // list element in the target `<div>` element for the destination.
 | 
						|
      // Therefore, duplicate same list element into the target `<div>`
 | 
						|
      // element.
 | 
						|
      nsIContent* previousEditableContent =
 | 
						|
          createdListElement
 | 
						|
              ? HTMLEditUtils::GetPreviousSibling(
 | 
						|
                    content, {WalkTreeOption::IgnoreNonEditableNode})
 | 
						|
              : nullptr;
 | 
						|
      if (!createdListElement ||
 | 
						|
          (previousEditableContent &&
 | 
						|
           previousEditableContent != createdListElement)) {
 | 
						|
        nsAtom* ULOrOLOrDLTagName =
 | 
						|
            atContent.GetContainer()->NodeInfo()->NameAtom();
 | 
						|
        if (targetDivElement) {
 | 
						|
          // XXX Do we need to split the container? Since we'll append new
 | 
						|
          //     element at end of the <div> element.
 | 
						|
          Result<SplitNodeResult, nsresult> splitNodeResult =
 | 
						|
              MaybeSplitAncestorsForInsertWithTransaction(
 | 
						|
                  MOZ_KnownLive(*ULOrOLOrDLTagName), atContent, aEditingHost);
 | 
						|
          if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
 | 
						|
            NS_WARNING(
 | 
						|
                "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
 | 
						|
                "failed");
 | 
						|
            return splitNodeResult.unwrapErr();
 | 
						|
          }
 | 
						|
          // We'll update selection after creating a list element below.
 | 
						|
          // Therefore, we don't need to touch selection here.
 | 
						|
          splitNodeResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        } else {
 | 
						|
          // If we've not had a target <div> element yet, let's insert a <div>
 | 
						|
          // element with splitting the ancestors.
 | 
						|
          Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
              InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
                  *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
                  aEditingHost);
 | 
						|
          if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
            NS_WARNING(
 | 
						|
                "HTMLEditor::"
 | 
						|
                "InsertElementWithSplittingAncestorsWithTransaction(nsGkAtoms::"
 | 
						|
                "div) failed");
 | 
						|
            return createNewDivElementResult.unwrapErr();
 | 
						|
          }
 | 
						|
          // We'll update selection after creating a list element below.
 | 
						|
          // Therefor, we don't need to touch selection here.
 | 
						|
          createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
          MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
 | 
						|
          targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
 | 
						|
        }
 | 
						|
        Result<CreateElementResult, nsresult> createNewListElementResult =
 | 
						|
            CreateAndInsertElement(WithTransaction::Yes,
 | 
						|
                                   MOZ_KnownLive(*ULOrOLOrDLTagName),
 | 
						|
                                   EditorDOMPoint::AtEndOf(targetDivElement));
 | 
						|
        if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
 | 
						|
              "failed");
 | 
						|
          return createNewListElementResult.unwrapErr();
 | 
						|
        }
 | 
						|
        nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
 | 
						|
            *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                    SuggestCaret::AndIgnoreTrivialError});
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
        NS_WARNING_ASSERTION(
 | 
						|
            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
            "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
        createdListElement =
 | 
						|
            createNewListElementResult.unwrap().UnwrapNewNode();
 | 
						|
        MOZ_ASSERT(createdListElement);
 | 
						|
      }
 | 
						|
      // Move current node (maybe, assumed as a list item element) into the
 | 
						|
      // new list element in the target `<div>` element to be positioned
 | 
						|
      // absolutely.
 | 
						|
      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
 | 
						|
      Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
          MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
 | 
						|
                                       *createdListElement);
 | 
						|
      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
        return moveNodeResult.propagateErr();
 | 
						|
      }
 | 
						|
      nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                  SuggestCaret::AndIgnoreTrivialError});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
          "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // If contents in a list item element is selected, we should move current
 | 
						|
    // node into the target `<div>` element with the list item element itself
 | 
						|
    // because we want to keep indent level of the contents.
 | 
						|
    if (RefPtr<Element> listItemElement =
 | 
						|
            HTMLEditUtils::GetClosestAncestorListItemElement(content,
 | 
						|
                                                             &aEditingHost)) {
 | 
						|
      if (handledListItemElement == listItemElement) {
 | 
						|
        // Current node has already been moved into the `<div>` element.
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      // If we cannot move the list item element into created list element,
 | 
						|
      // we need another list element in the target `<div>` element.
 | 
						|
      nsIContent* previousEditableContent =
 | 
						|
          createdListElement
 | 
						|
              ? HTMLEditUtils::GetPreviousSibling(
 | 
						|
                    *listItemElement, {WalkTreeOption::IgnoreNonEditableNode})
 | 
						|
              : nullptr;
 | 
						|
      if (!createdListElement ||
 | 
						|
          (previousEditableContent &&
 | 
						|
           previousEditableContent != createdListElement)) {
 | 
						|
        EditorDOMPoint atListItem(listItemElement);
 | 
						|
        if (NS_WARN_IF(!atListItem.IsSet())) {
 | 
						|
          return NS_ERROR_FAILURE;
 | 
						|
        }
 | 
						|
        // XXX If content is the listItemElement and not in a list element,
 | 
						|
        //     we duplicate wrong element into the target `<div>` element.
 | 
						|
        nsAtom* containerName =
 | 
						|
            atListItem.GetContainer()->NodeInfo()->NameAtom();
 | 
						|
        if (targetDivElement) {
 | 
						|
          // XXX Do we need to split the container? Since we'll append new
 | 
						|
          //     element at end of the <div> element.
 | 
						|
          Result<SplitNodeResult, nsresult> splitNodeResult =
 | 
						|
              MaybeSplitAncestorsForInsertWithTransaction(
 | 
						|
                  MOZ_KnownLive(*containerName), atListItem, aEditingHost);
 | 
						|
          if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
 | 
						|
            NS_WARNING(
 | 
						|
                "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
 | 
						|
                "failed");
 | 
						|
            return splitNodeResult.unwrapErr();
 | 
						|
          }
 | 
						|
          // We'll update selection after creating a list element below.
 | 
						|
          // Therefore, we don't need to touch selection here.
 | 
						|
          splitNodeResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
        } else {
 | 
						|
          // If we've not had a target <div> element yet, let's insert a <div>
 | 
						|
          // element with splitting the ancestors.
 | 
						|
          Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
              InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
                  *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
                  aEditingHost);
 | 
						|
          if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
            NS_WARNING(
 | 
						|
                "HTMLEditor::"
 | 
						|
                "InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
                "nsGkAtoms::div) failed");
 | 
						|
            return createNewDivElementResult.unwrapErr();
 | 
						|
          }
 | 
						|
          // We'll update selection after creating a list element below.
 | 
						|
          // Therefore, we don't need to touch selection here.
 | 
						|
          createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
 | 
						|
          MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
 | 
						|
          targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
 | 
						|
        }
 | 
						|
        // XXX So, createdListElement may be set to a non-list element.
 | 
						|
        Result<CreateElementResult, nsresult> createNewListElementResult =
 | 
						|
            CreateAndInsertElement(WithTransaction::Yes,
 | 
						|
                                   MOZ_KnownLive(*containerName),
 | 
						|
                                   EditorDOMPoint::AtEndOf(targetDivElement));
 | 
						|
        if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
 | 
						|
          NS_WARNING(
 | 
						|
              "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
 | 
						|
              "failed");
 | 
						|
          return createNewListElementResult.unwrapErr();
 | 
						|
        }
 | 
						|
        nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
 | 
						|
            *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                    SuggestCaret::AndIgnoreTrivialError});
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
          return Err(rv);
 | 
						|
        }
 | 
						|
        NS_WARNING_ASSERTION(
 | 
						|
            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
            "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
        createdListElement =
 | 
						|
            createNewListElementResult.unwrap().UnwrapNewNode();
 | 
						|
        MOZ_ASSERT(createdListElement);
 | 
						|
      }
 | 
						|
      // Move current list item element into the createdListElement (could be
 | 
						|
      // non-list element due to the above bug) in a candidate `<div>` element
 | 
						|
      // to be positioned absolutely.
 | 
						|
      Result<MoveNodeResult, nsresult> moveListItemElementResult =
 | 
						|
          MoveNodeToEndWithTransaction(*listItemElement, *createdListElement);
 | 
						|
      if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
 | 
						|
        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
        return moveListItemElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      nsresult rv = moveListItemElementResult.inspect().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                  SuggestCaret::AndIgnoreTrivialError});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
 | 
						|
        return Err(rv);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
          "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
      handledListItemElement = std::move(listItemElement);
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!targetDivElement) {
 | 
						|
      // If we meet a `<div>` element, use it as the absolute-position
 | 
						|
      // container.
 | 
						|
      // XXX This looks odd.  If there are 2 or more `<div>` elements are
 | 
						|
      //     selected, first found `<div>` element will have all other
 | 
						|
      //     selected nodes.
 | 
						|
      if (content->IsHTMLElement(nsGkAtoms::div)) {
 | 
						|
        targetDivElement = content->AsElement();
 | 
						|
        MOZ_ASSERT(!createdListElement);
 | 
						|
        MOZ_ASSERT(!handledListItemElement);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      // Otherwise, create new `<div>` element to be positioned absolutely
 | 
						|
      // and to contain all selected nodes.
 | 
						|
      Result<CreateElementResult, nsresult> createNewDivElementResult =
 | 
						|
          InsertElementWithSplittingAncestorsWithTransaction(
 | 
						|
              *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
 | 
						|
              aEditingHost);
 | 
						|
      if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
 | 
						|
        NS_WARNING(
 | 
						|
            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
 | 
						|
            "nsGkAtoms::div) failed");
 | 
						|
        return createNewDivElementResult.unwrapErr();
 | 
						|
      }
 | 
						|
      nsresult rv = createNewDivElementResult.inspect().SuggestCaretPointTo(
 | 
						|
          *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
 | 
						|
      if (NS_FAILED(rv)) {
 | 
						|
        NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
      MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
 | 
						|
      targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
 | 
						|
    }
 | 
						|
 | 
						|
    // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
 | 
						|
    Result<MoveNodeResult, nsresult> moveNodeResult =
 | 
						|
        MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *targetDivElement);
 | 
						|
    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
 | 
						|
      return moveNodeResult.unwrapErr();
 | 
						|
    }
 | 
						|
    nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
 | 
						|
        *this, {SuggestCaret::OnlyIfHasSuggestion,
 | 
						|
                SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
 | 
						|
                SuggestCaret::AndIgnoreTrivialError});
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
 | 
						|
        "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
 | 
						|
    // Forget createdListElement, if any
 | 
						|
    createdListElement = nullptr;
 | 
						|
  }
 | 
						|
  *aTargetElement = std::move(targetDivElement);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult>
 | 
						|
HTMLEditor::SetSelectionToStaticAsSubAction() {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(
 | 
						|
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this, EditSubAction::eSetPositionToStatic, nsIEditor::eNext,
 | 
						|
      ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(ignoredError.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Element> element = GetAbsolutelyPositionedSelectionContainer();
 | 
						|
  if (!element) {
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
 | 
						|
        "nullptr");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    AutoSelectionRestorer restoreSelectionLater(*this);
 | 
						|
 | 
						|
    nsresult rv = SetPositionToAbsoluteOrStatic(*element, false);
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
 | 
						|
      return Err(rv);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Restoring Selection might cause destroying the HTML editor.
 | 
						|
  if (MOZ_UNLIKELY(Destroyed())) {
 | 
						|
    NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
Result<EditActionResult, nsresult> HTMLEditor::AddZIndexAsSubAction(
 | 
						|
    int32_t aChange) {
 | 
						|
  MOZ_ASSERT(IsEditActionDataAvailable());
 | 
						|
 | 
						|
  AutoPlaceholderBatch treatAsOneTransaction(
 | 
						|
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
 | 
						|
  IgnoredErrorResult ignoredError;
 | 
						|
  AutoEditSubActionNotifier startToHandleEditSubAction(
 | 
						|
      *this,
 | 
						|
      aChange < 0 ? EditSubAction::eDecreaseZIndex
 | 
						|
                  : EditSubAction::eIncreaseZIndex,
 | 
						|
      nsIEditor::eNext, ignoredError);
 | 
						|
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
 | 
						|
    return Err(ignoredError.StealNSResult());
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(
 | 
						|
      !ignoredError.Failed(),
 | 
						|
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 | 
						|
 | 
						|
  {
 | 
						|
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
    if (result.inspect().Canceled()) {
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
 | 
						|
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
 | 
						|
                       "failed, but ignored");
 | 
						|
 | 
						|
  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
 | 
						|
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
 | 
						|
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
 | 
						|
                         "failed, but ignored");
 | 
						|
    if (NS_SUCCEEDED(rv)) {
 | 
						|
      nsresult rv = PrepareInlineStylesForCaret();
 | 
						|
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
 | 
						|
        return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
      }
 | 
						|
      NS_WARNING_ASSERTION(
 | 
						|
          NS_SUCCEEDED(rv),
 | 
						|
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Element> absolutelyPositionedElement =
 | 
						|
      GetAbsolutelyPositionedSelectionContainer();
 | 
						|
  if (!absolutelyPositionedElement) {
 | 
						|
    if (NS_WARN_IF(Destroyed())) {
 | 
						|
      return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
    }
 | 
						|
    NS_WARNING(
 | 
						|
        "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
 | 
						|
        "nullptr");
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  nsStyledElement* absolutelyPositionedStyledElement =
 | 
						|
      nsStyledElement::FromNode(absolutelyPositionedElement);
 | 
						|
  if (NS_WARN_IF(!absolutelyPositionedStyledElement)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    AutoSelectionRestorer restoreSelectionLater(*this);
 | 
						|
 | 
						|
    // MOZ_KnownLive(*absolutelyPositionedStyledElement): It's
 | 
						|
    // absolutelyPositionedElement whose type is RefPtr.
 | 
						|
    Result<int32_t, nsresult> result = AddZIndexWithTransaction(
 | 
						|
        MOZ_KnownLive(*absolutelyPositionedStyledElement), aChange);
 | 
						|
    if (MOZ_UNLIKELY(result.isErr())) {
 | 
						|
      NS_WARNING("HTMLEditor::AddZIndexWithTransaction() failed");
 | 
						|
      return result.propagateErr();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Restoring Selection might cause destroying the HTML editor.
 | 
						|
  if (MOZ_UNLIKELY(Destroyed())) {
 | 
						|
    NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
 | 
						|
    return Err(NS_ERROR_EDITOR_DESTROYED);
 | 
						|
  }
 | 
						|
 | 
						|
  return EditActionResult::HandledResult();
 | 
						|
}
 | 
						|
 | 
						|
nsresult HTMLEditor::OnDocumentModified() {
 | 
						|
  if (mPendingDocumentModifiedRunner) {
 | 
						|
    return NS_OK;  // We've already posted same runnable into the queue.
 | 
						|
  }
 | 
						|
  mPendingDocumentModifiedRunner = NewRunnableMethod(
 | 
						|
      "HTMLEditor::OnModifyDocument", this, &HTMLEditor::OnModifyDocument);
 | 
						|
  nsContentUtils::AddScriptRunner(do_AddRef(mPendingDocumentModifiedRunner));
 | 
						|
  // Be aware, if OnModifyDocument() may be called synchronously, the
 | 
						|
  // editor might have been destroyed here.
 | 
						|
  return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla
 |