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

Quelle  HTMLTableEditor.cpp   Sprache: C

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


#include <stdio.h>

#include "HTMLEditor.h"
#include "HTMLEditorInlines.h"

#include "AutoSelectionRestorer.h"
#include "EditAction.h"
#include "EditorDOMPoint.h"
#include "EditorUtils.h"
#include "HTMLEditUtils.h"

#include "mozilla/Assertions.h"
#include "mozilla/FlushType.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsAtom.h"
#include "nsIContent.h"
#include "nsIFrame.h"
#include "nsINode.h"
#include "nsISupportsUtils.h"
#include "nsITableCellLayout.h"  // For efficient access to table cell
#include "nsLiteralString.h"
#include "nsQueryFrame.h"
#include "nsRange.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTableCellFrame.h"
#include "nsTableWrapperFrame.h"
#include "nscore.h"
#include <algorithm>

namespace mozilla {

using namespace dom;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;

/**
 * Stack based helper class for restoring selection after table edit.
 */

class MOZ_STACK_CLASS AutoSelectionSetterAfterTableEdit final {
 private:
  const RefPtr<HTMLEditor> mHTMLEditor;
  const RefPtr<Element> mTable;
  int32_t mCol, mRow, mDirection, mSelected;

 public:
  AutoSelectionSetterAfterTableEdit(HTMLEditor& aHTMLEditor, Element* aTable,
                                    int32_t aRow, int32_t aCol,
                                    int32_t aDirection, bool aSelected)
      : mHTMLEditor(&aHTMLEditor),
        mTable(aTable),
        mCol(aCol),
        mRow(aRow),
        mDirection(aDirection),
        mSelected(aSelected) {}

  MOZ_CAN_RUN_SCRIPT ~AutoSelectionSetterAfterTableEdit() {
    if (mHTMLEditor) {
      mHTMLEditor->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection,
                                              mSelected);
    }
  }
};

/******************************************************************************
 * HTMLEditor::CellIndexes
 ******************************************************************************/


void HTMLEditor::CellIndexes::Update(HTMLEditor& aHTMLEditor,
                                     Selection& aSelection) {
  // Guarantee the life time of the cell element since Init() will access
  // layout methods.
  RefPtr<Element> cellElement =
      aHTMLEditor.GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
  if (!cellElement) {
    NS_WARNING(
        "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
        "failed");
    return;
  }

  RefPtr<PresShell> presShell{aHTMLEditor.GetPresShell()};
  Update(*cellElement, presShell);
}

void HTMLEditor::CellIndexes::Update(Element& aCellElement,
                                     PresShell* aPresShell) {
  // If the table cell is created immediately before this call, e.g., using
  // innerHTML, frames have not been created yet. Hence, flush layout to create
  // them.
  if (NS_WARN_IF(!aPresShell)) {
    return;
  }

  aPresShell->FlushPendingNotifications(FlushType::Frames);

  nsIFrame* frameOfCell = aCellElement.GetPrimaryFrame();
  if (!frameOfCell) {
    NS_WARNING("There was no layout information of aCellElement");
    return;
  }

  nsITableCellLayout* tableCellLayout = do_QueryFrame(frameOfCell);
  if (!tableCellLayout) {
    NS_WARNING("aCellElement was not a table cell");
    return;
  }

  if (NS_FAILED(tableCellLayout->GetCellIndexes(mRow, mColumn))) {
    NS_WARNING("nsITableCellLayout::GetCellIndexes() failed");
    mRow = mColumn = -1;
    return;
  }

  MOZ_ASSERT(!isErr());
}

/******************************************************************************
 * HTMLEditor::CellData
 ******************************************************************************/


// static
HTMLEditor::CellData HTMLEditor::CellData::AtIndexInTableElement(
    const HTMLEditor& aHTMLEditor, const Element& aTableElement,
    int32_t aRowIndex, int32_t aColumnIndex) {
  nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(&aTableElement);
  if (!tableFrame) {
    NS_WARNING("There was no layout information of the table");
    return CellData::Error(aRowIndex, aColumnIndex);
  }

  // If there is no cell at the indexes.  Don't set the error state to the new
  // instance.
  nsTableCellFrame* cellFrame =
      tableFrame->GetCellFrameAt(aRowIndex, aColumnIndex);
  if (!cellFrame) {
    return CellData::NotFound(aRowIndex, aColumnIndex);
  }

  Element* cellElement = Element::FromNodeOrNull(cellFrame->GetContent());
  if (!cellElement) {
    return CellData::Error(aRowIndex, aColumnIndex);
  }
  return CellData(*cellElement, aRowIndex, aColumnIndex, *cellFrame,
                  *tableFrame);
}

HTMLEditor::CellData::CellData(Element& aElement, int32_t aRowIndex,
                               int32_t aColumnIndex,
                               nsTableCellFrame& aTableCellFrame,
                               nsTableWrapperFrame& aTableWrapperFrame)
    : mElement(&aElement),
      mCurrent(aRowIndex, aColumnIndex),
      mFirst(aTableCellFrame.RowIndex(), aTableCellFrame.ColIndex()),
      mRowSpan(aTableCellFrame.GetRowSpan()),
      mColSpan(aTableCellFrame.GetColSpan()),
      mEffectiveRowSpan(
          aTableWrapperFrame.GetEffectiveRowSpanAt(aRowIndex, aColumnIndex)),
      mEffectiveColSpan(
          aTableWrapperFrame.GetEffectiveColSpanAt(aRowIndex, aColumnIndex)),
      mIsSelected(aTableCellFrame.IsSelected()) {
  MOZ_ASSERT(!mCurrent.isErr());
}

/******************************************************************************
 * HTMLEditor::TableSize
 ******************************************************************************/


// static
Result<HTMLEditor::TableSize, nsresult> HTMLEditor::TableSize::Create(
    HTMLEditor& aHTMLEditor, Element& aTableOrElementInTable) {
  // Currently, nsTableWrapperFrame::GetRowCount() and
  // nsTableWrapperFrame::GetColCount() are safe to use without grabbing
  // <table> element.  However, editor developers may not watch layout API
  // changes.  So, for keeping us safer, we should use RefPtr here.
  RefPtr<Element> tableElement =
      aHTMLEditor.GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table,
                                                        aTableOrElementInTable);
  if (!tableElement) {
    NS_WARNING(
        "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
        "failed");
    return Err(NS_ERROR_FAILURE);
  }
  nsTableWrapperFrame* tableFrame =
      do_QueryFrame(tableElement->GetPrimaryFrame());
  if (!tableFrame) {
    NS_WARNING("There was no layout information of the element");
    return Err(NS_ERROR_FAILURE);
  }
  const int32_t rowCount = tableFrame->GetRowCount();
  const int32_t columnCount = tableFrame->GetColCount();
  if (NS_WARN_IF(rowCount < 0) || NS_WARN_IF(columnCount < 0)) {
    return Err(NS_ERROR_FAILURE);
  }
  return TableSize(rowCount, columnCount);
}

/******************************************************************************
 * HTMLEditor
 ******************************************************************************/


nsresult HTMLEditor::InsertCell(Element* aCell, int32_t aRowSpan,
                                int32_t aColSpan, bool aAfter, bool aIsHeader,
                                Element** aNewCell) {
  if (aNewCell) {
    *aNewCell = nullptr;
  }

  if (NS_WARN_IF(!aCell)) {
    return NS_ERROR_INVALID_ARG;
  }

  // And the parent and offsets needed to do an insert
  EditorDOMPoint pointToInsert(aCell);
  if (NS_WARN_IF(!pointToInsert.IsSet())) {
    return NS_ERROR_INVALID_ARG;
  }

  RefPtr<Element> newCell =
      CreateElementWithDefaults(aIsHeader ? *nsGkAtoms::th : *nsGkAtoms::td);
  if (!newCell) {
    NS_WARNING(
        "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::th or td) failed");
    return NS_ERROR_FAILURE;
  }

  // Optional: return new cell created
  if (aNewCell) {
    *aNewCell = do_AddRef(newCell).take();
  }

  if (aRowSpan > 1) {
    // Note: Do NOT use editor transaction for this
    nsAutoString newRowSpan;
    newRowSpan.AppendInt(aRowSpan, 10);
    DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
        kNameSpaceID_None, nsGkAtoms::rowspan, newRowSpan, true);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "Element::SetAttr(nsGkAtoms::rawspan) failed, but ignored");
  }
  if (aColSpan > 1) {
    // Note: Do NOT use editor transaction for this
    nsAutoString newColSpan;
    newColSpan.AppendInt(aColSpan, 10);
    DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
        kNameSpaceID_None, nsGkAtoms::colspan, newColSpan, true);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "Element::SetAttr(nsGkAtoms::colspan) failed, but ignored");
  }
  if (aAfter) {
    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
    NS_WARNING_ASSERTION(advanced,
                         "Failed to advance offset to after the old cell");
  }

  // TODO: Remove AutoTransactionsConserveSelection here.  It's not necessary
  //       in normal cases.  However, it may be required for nested edit
  //       actions which may be caused by legacy mutation event listeners or
  //       chrome script.
  AutoTransactionsConserveSelection dontChangeSelection(*this);
  Result<CreateElementResult, nsresult> insertNewCellResult =
      InsertNodeWithTransaction<Element>(*newCell, pointToInsert);
  if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
    NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
    return insertNewCellResult.unwrapErr();
  }
  // Because of dontChangeSelection, we've never allowed to transactions to
  // update selection here.
  insertNewCellResult.inspect().IgnoreCaretPointSuggestion();
  return NS_OK;
}

nsresult HTMLEditor::SetColSpan(Element* aCell, int32_t aColSpan) {
  if (NS_WARN_IF(!aCell)) {
    return NS_ERROR_INVALID_ARG;
  }
  nsAutoString newSpan;
  newSpan.AppendInt(aColSpan, 10);
  nsresult rv =
      SetAttributeWithTransaction(*aCell, *nsGkAtoms::colspan, newSpan);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "EditorBase::SetAttributeWithTransaction(nsGkAtoms::colspan) failed");
  return rv;
}

nsresult HTMLEditor::SetRowSpan(Element* aCell, int32_t aRowSpan) {
  if (NS_WARN_IF(!aCell)) {
    return NS_ERROR_INVALID_ARG;
  }
  nsAutoString newSpan;
  newSpan.AppendInt(aRowSpan, 10);
  nsresult rv =
      SetAttributeWithTransaction(*aCell, *nsGkAtoms::rowspan, newSpan);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "EditorBase::SetAttributeWithTransaction(nsGkAtoms::rowspan) failed");
  return rv;
}

NS_IMETHODIMP HTMLEditor::InsertTableCell(int32_t aNumberOfCellsToInsert,
                                          bool aInsertAfterSelectedCell) {
  if (aNumberOfCellsToInsert <= 0) {
    return NS_OK;  // Just do nothing.
  }

  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eInsertTableCellElement);
  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
  if (NS_FAILED(rv)) {
    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(editingHost &&
                 editingHost->IsContentEditablePlainTextOnly())) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  Result<RefPtr<Element>, nsresult> cellElementOrError =
      GetFirstSelectedCellElementInTable();
  if (cellElementOrError.isErr()) {
    NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
    return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
  }

  if (!cellElementOrError.inspect()) {
    return NS_OK;
  }

  EditorDOMPoint pointToInsert(cellElementOrError.inspect());
  if (!pointToInsert.IsSet()) {
    NS_WARNING("Found an orphan cell element");
    return NS_ERROR_FAILURE;
  }
  if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
    NS_WARNING_ASSERTION(
        advanced,
        "Failed to set insertion point after current cell, but ignored");
  }
  Result<CreateElementResult, nsresult> insertCellElementResult =
      InsertTableCellsWithTransaction(pointToInsert, aNumberOfCellsToInsert);
  if (MOZ_UNLIKELY(insertCellElementResult.isErr())) {
    NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
    return EditorBase::ToGenericNSResult(insertCellElementResult.unwrapErr());
  }
  // We don't need to modify selection here.
  insertCellElementResult.inspect().IgnoreCaretPointSuggestion();
  return NS_OK;
}

Result<CreateElementResult, nsresult>
HTMLEditor::InsertTableCellsWithTransaction(
    const EditorDOMPoint& aPointToInsert, int32_t aNumberOfCellsToInsert) {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
  MOZ_ASSERT(aNumberOfCellsToInsert > 0);

  if (!HTMLEditUtils::IsTableRow(aPointToInsert.GetContainer())) {
    NS_WARNING("Tried to insert cell elements to non- element");
    return Err(NS_ERROR_FAILURE);
  }

  AutoPlaceholderBatch treateAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  // Prevent auto insertion of BR in new cell until we're done
  // XXX Why? I think that we should insert <br> element for every cell
  //     **before** inserting new cell into the <tr> element.
  IgnoredErrorResult error;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eInsertNode, 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");
  error.SuppressException();

  // Put caret into the cell before the first inserting cell, or the first
  // table cell in the row.
  RefPtr<Element> cellToPutCaret =
      aPointToInsert.IsEndOfContainer()
          ? nullptr
          : HTMLEditUtils::GetPreviousTableCellElementSibling(
                *aPointToInsert.GetChild());

  RefPtr<Element> firstCellElement, lastCellElement;
  nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT {
    // TODO: Remove AutoTransactionsConserveSelection here.  It's not necessary
    //       in normal cases.  However, it may be required for nested edit
    //       actions which may be caused by legacy mutation event listeners or
    //       chrome script.
    AutoTransactionsConserveSelection dontChangeSelection(*this);

    // Block legacy mutation events for making this job simpler.
    nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;

    // If there is a child to put a cell, we need to put all cell elements
    // before it.  Therefore, creating `EditorDOMPoint` with the child element
    // is safe.  Otherwise, we need to try to append cell elements in the row.
    // Therefore, using `EditorDOMPoint::AtEndOf()` is safe.  Note that it's
    // not safe to creat it once because the offset and child relation in the
    // point becomes invalid after inserting a cell element.
    nsIContent* referenceContent = aPointToInsert.GetChild();
    for ([[maybe_unused]] const auto i :
         IntegerRange<uint32_t>(aNumberOfCellsToInsert)) {
      RefPtr<Element> newCell = CreateElementWithDefaults(*nsGkAtoms::td);
      if (!newCell) {
        NS_WARNING(
            "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
        return NS_ERROR_FAILURE;
      }
      Result<CreateElementResult, nsresult> insertNewCellResult =
          InsertNodeWithTransaction(
              *newCell, referenceContent
                            ? EditorDOMPoint(referenceContent)
                            : EditorDOMPoint::AtEndOf(
                                  *aPointToInsert.ContainerAs<Element>()));
      if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
        NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
        return insertNewCellResult.unwrapErr();
      }
      CreateElementResult unwrappedInsertNewCellResult =
          insertNewCellResult.unwrap();
      lastCellElement = unwrappedInsertNewCellResult.UnwrapNewNode();
      if (!firstCellElement) {
        firstCellElement = lastCellElement;
      }
      // Because of dontChangeSelection, we've never allowed to transactions
      // to update selection here.
      unwrappedInsertNewCellResult.IgnoreCaretPointSuggestion();
      if (!cellToPutCaret) {
        cellToPutCaret = std::move(newCell);  // This is first cell in the row.
      }
    }

    // TODO: Stop touching selection here.
    MOZ_ASSERT(cellToPutCaret);
    MOZ_ASSERT(cellToPutCaret->GetParent());
    CollapseSelectionToDeepestNonTableFirstChild(cellToPutCaret);
    return NS_OK;
  }();
  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED ||
                   NS_WARN_IF(Destroyed()))) {
    return Err(NS_ERROR_EDITOR_DESTROYED);
  }
  if (NS_FAILED(rv)) {
    return Err(rv);
  }
  MOZ_ASSERT(firstCellElement);
  MOZ_ASSERT(lastCellElement);
  return CreateElementResult(std::move(firstCellElement),
                             EditorDOMPoint(lastCellElement, 0u));
}

NS_IMETHODIMP HTMLEditor::GetFirstRow(Element* aTableOrElementInTable,
                                      Element** aFirstRowElement) {
  if (NS_WARN_IF(!aTableOrElementInTable) || NS_WARN_IF(!aFirstRowElement)) {
    return NS_ERROR_INVALID_ARG;
  }

  AutoEditActionDataSetter editActionData(*this, EditAction::eGetFirstRow);
  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
  if (NS_FAILED(rv)) {
    NS_WARNING("HTMLEditor::GetFirstRow() couldn't handle the job");
    return EditorBase::ToGenericNSResult(rv);
  }

  Result<RefPtr<Element>, nsresult> firstRowElementOrError =
      GetFirstTableRowElement(*aTableOrElementInTable);
  NS_WARNING_ASSERTION(!firstRowElementOrError.isErr(),
                       "HTMLEditor::GetFirstTableRowElement() failed");
  if (firstRowElementOrError.isErr()) {
    NS_WARNING("HTMLEditor::GetFirstTableRowElement() failed");
    return EditorBase::ToGenericNSResult(firstRowElementOrError.unwrapErr());
  }
  firstRowElementOrError.unwrap().forget(aFirstRowElement);
  return NS_OK;
}

Result<RefPtr<Element>, nsresult> HTMLEditor::GetFirstTableRowElement(
    const Element& aTableOrElementInTable) const {
  MOZ_ASSERT(IsEditActionDataAvailable());

  Element* tableElement = GetInclusiveAncestorByTagNameInternal(
      *nsGkAtoms::table, aTableOrElementInTable);
  // If the element is not in <table>, return error.
  if (!tableElement) {
    NS_WARNING(
        "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
        "failed");
    return Err(NS_ERROR_FAILURE);
  }

  for (nsIContent* tableChild = tableElement->GetFirstChild(); tableChild;
       tableChild = tableChild->GetNextSibling()) {
    if (tableChild->IsHTMLElement(nsGkAtoms::tr)) {
      // Found a row directly under <table>
      return RefPtr<Element>(tableChild->AsElement());
    }
    // <table> can have table section elements like <tbody>.  <tr> elements
    // may be children of them.
    if (tableChild->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::thead,
                                        nsGkAtoms::tfoot)) {
      for (nsIContent* tableSectionChild = tableChild->GetFirstChild();
           tableSectionChild;
           tableSectionChild = tableSectionChild->GetNextSibling()) {
        if (tableSectionChild->IsHTMLElement(nsGkAtoms::tr)) {
          return RefPtr<Element>(tableSectionChild->AsElement());
        }
      }
    }
  }
  // Don't return error when there is no <tr> element in the <table>.
  return RefPtr<Element>();
}

Result<RefPtr<Element>, nsresult> HTMLEditor::GetNextTableRowElement(
    const Element& aTableRowElement) const {
  if (NS_WARN_IF(!aTableRowElement.IsHTMLElement(nsGkAtoms::tr))) {
    return Err(NS_ERROR_INVALID_ARG);
  }

  for (nsIContent* maybeNextRow = aTableRowElement.GetNextSibling();
       maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
    if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
      return RefPtr<Element>(maybeNextRow->AsElement());
    }
  }

  // In current table section (e.g., <tbody>), there is no <tr> element.
  // Then, check the following table sections.
  Element* parentElementOfRow = aTableRowElement.GetParentElement();
  if (!parentElementOfRow) {
    NS_WARNING("aTableRowElement was an orphan node");
    return Err(NS_ERROR_FAILURE);
  }

  // Basically, <tr> elements should be in table section elements even if
  // they are not written in the source explicitly.  However, for preventing
  // cross table boundary, check it now.
  if (parentElementOfRow->IsHTMLElement(nsGkAtoms::table)) {
    // Don't return error since this means just not found.
    return RefPtr<Element>();
  }

  for (nsIContent* maybeNextTableSection = parentElementOfRow->GetNextSibling();
       maybeNextTableSection;
       maybeNextTableSection = maybeNextTableSection->GetNextSibling()) {
    // If the sibling of parent of given <tr> is a table section element,
    // check its children.
    if (maybeNextTableSection->IsAnyOfHTMLElements(
            nsGkAtoms::tbody, nsGkAtoms::thead, nsGkAtoms::tfoot)) {
      for (nsIContent* maybeNextRow = maybeNextTableSection->GetFirstChild();
           maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
        if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
          return RefPtr<Element>(maybeNextRow->AsElement());
        }
      }
    }
    // I'm not sure whether this is a possible case since table section
    // elements are created automatically.  However, DOM API may create
    // <tr> elements without table section elements.  So, let's check it.
    else if (maybeNextTableSection->IsHTMLElement(nsGkAtoms::tr)) {
      return RefPtr<Element>(maybeNextTableSection->AsElement());
    }
  }
  // Don't return error when the given <tr> element is the last <tr> element in
  // the <table>.
  return RefPtr<Element>();
}

NS_IMETHODIMP HTMLEditor::InsertTableColumn(int32_t aNumberOfColumnsToInsert,
                                            bool aInsertAfterSelectedCell) {
  if (aNumberOfColumnsToInsert <= 0) {
    return NS_OK;  // XXX Traditional behavior
  }

  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eInsertTableColumn);
  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
  if (NS_FAILED(rv)) {
    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(editingHost &&
                 editingHost->IsContentEditablePlainTextOnly())) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  Result<RefPtr<Element>, nsresult> cellElementOrError =
      GetFirstSelectedCellElementInTable();
  if (cellElementOrError.isErr()) {
    NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
    return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
  }

  if (!cellElementOrError.inspect()) {
    return NS_OK;
  }

  EditorDOMPoint pointToInsert(cellElementOrError.inspect());
  if (!pointToInsert.IsSet()) {
    NS_WARNING("Found an orphan cell element");
    return NS_ERROR_FAILURE;
  }
  if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
    NS_WARNING_ASSERTION(
        advanced,
        "Failed to set insertion point after current cell, but ignored");
  }
  rv = InsertTableColumnsWithTransaction(pointToInsert,
                                         aNumberOfColumnsToInsert);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "HTMLEditor::InsertTableColumnsWithTransaction() failed");
  return EditorBase::ToGenericNSResult(rv);
}

nsresult HTMLEditor::InsertTableColumnsWithTransaction(
    const EditorDOMPoint& aPointToInsert, int32_t aNumberOfColumnsToInsert) {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
  MOZ_ASSERT(aNumberOfColumnsToInsert > 0);

  const RefPtr<PresShell> presShell = GetPresShell();
  if (NS_WARN_IF(!presShell)) {
    return NS_ERROR_FAILURE;
  }

  if (!HTMLEditUtils::IsTableRow(aPointToInsert.GetContainer())) {
    NS_WARNING("Tried to insert columns to non- element");
    return NS_ERROR_FAILURE;
  }

  const RefPtr<Element> tableElement =
      HTMLEditUtils::GetClosestAncestorTableElement(
          *aPointToInsert.ContainerAs<Element>());
  if (!tableElement) {
    NS_WARNING("There was no ancestor
element");
    return NS_ERROR_FAILURE;
  }

  const Result<TableSize, nsresult> tableSizeOrError =
      TableSize::Create(*this, *tableElement);
  if (NS_WARN_IF(tableSizeOrError.isErr())) {
    return tableSizeOrError.inspectErr();
  }
  const TableSize& tableSize = tableSizeOrError.inspect();
  if (NS_WARN_IF(tableSize.IsEmpty())) {
    return NS_ERROR_FAILURE;  // We cannot handle it in an empty table
  }

  // If aPointToInsert points non-cell element or end of the row, it means that
  // the caller wants to insert column immediately after the last cell of
  // the pointing cell element or in the raw.
  const bool insertAfterPreviousCell = [&]() {
    if (!aPointToInsert.IsEndOfContainer() &&
        HTMLEditUtils::IsTableCell(aPointToInsert.GetChild())) {
      return false;  // Insert before the cell element.
    }
    // There is a previous cell element, we should add a column after it.
    Element* previousCellElement =
        aPointToInsert.IsEndOfContainer()
            ? HTMLEditUtils::GetLastTableCellElementChild(
                  *aPointToInsert.ContainerAs<Element>())
            : HTMLEditUtils::GetPreviousTableCellElementSibling(
                  *aPointToInsert.GetChild());
    return previousCellElement != nullptr;
  }();

  // Consider the column index in the table from given point and direction.
  auto referenceColumnIndexOrError =
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<int32_t, nsresult> {
    if (!insertAfterPreviousCell) {
      if (aPointToInsert.IsEndOfContainer()) {
        return tableSize.mColumnCount;  // Empty row, append columns to the end
      }
      // Insert columns immediately before current column.
      const OwningNonNull<Element> tableCellElement =
          *aPointToInsert.GetChild()->AsElement();
      MOZ_ASSERT(HTMLEditUtils::IsTableCell(tableCellElement));
      CellIndexes cellIndexes(*tableCellElement, presShell);
      if (NS_WARN_IF(cellIndexes.isErr())) {
        return Err(NS_ERROR_FAILURE);
      }
      return cellIndexes.mColumn;
    }

    // Otherwise, insert columns immediately after the previous column.
    Element* previousCellElement =
        aPointToInsert.IsEndOfContainer()
            ? HTMLEditUtils::GetLastTableCellElementChild(
                  *aPointToInsert.ContainerAs<Element>())
            : HTMLEditUtils::GetPreviousTableCellElementSibling(
                  *aPointToInsert.GetChild());
    MOZ_ASSERT(previousCellElement);
    CellIndexes cellIndexes(*previousCellElement, presShell);
    if (NS_WARN_IF(cellIndexes.isErr())) {
      return Err(NS_ERROR_FAILURE);
    }
    return cellIndexes.mColumn;
  }();
  if (MOZ_UNLIKELY(referenceColumnIndexOrError.isErr())) {
    return referenceColumnIndexOrError.unwrapErr();
  }

  AutoPlaceholderBatch treateAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  // Prevent auto insertion of <br> element in new cell until we're done.
  // XXX Why? We should put <br> element to every cell element before inserting
  //     the cells into the tree.
  IgnoredErrorResult error;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eInsertNode, 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");
  error.SuppressException();

  // Suppress Rules System selection munging.
  AutoTransactionsConserveSelection dontChangeSelection(*this);

  // If we are inserting after all existing columns, make sure table is
  // "well formed" before appending new column.
  // XXX As far as I've tested, NormalizeTableInternal() always fails to
  //     normalize non-rectangular table.  So, the following CellData will
  //     fail if the table is not rectangle.
  if (referenceColumnIndexOrError.inspect() >= tableSize.mColumnCount) {
    DebugOnly<nsresult> rv = NormalizeTableInternal(*tableElement);
    if (MOZ_UNLIKELY(Destroyed())) {
      NS_WARNING(
          "HTMLEditor::NormalizeTableInternal() caused destroying the editor");
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "HTMLEditor::NormalizeTableInternal() failed, but ignored");
  }

  // First, we should collect all reference nodes to insert new table cells.
  AutoTArray<CellData, 32> arrayOfCellData;
  {
    arrayOfCellData.SetCapacity(tableSize.mRowCount);
    for (const int32_t rowIndex : IntegerRange(tableSize.mRowCount)) {
      const auto cellData = CellData::AtIndexInTableElement(
          *this, *tableElement, rowIndex,
          referenceColumnIndexOrError.inspect());
      if (NS_WARN_IF(cellData.FailedOrNotFound())) {
        return NS_ERROR_FAILURE;
      }
      arrayOfCellData.AppendElement(cellData);
    }
  }

  // Note that checking whether the editor destroyed or not should be done
  // after inserting all cell elements.  Otherwise, the table is left as
  // not a rectangle.
  auto cellElementToPutCaretOrError =
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
    // Block legacy mutation events for making this job simpler.
    nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
    RefPtr<Element> cellElementToPutCaret;
    for (const CellData& cellData : arrayOfCellData) {
      // Don't fail entire process if we fail to find a cell (may fail just in
      // particular rows with < adequate cells per row).
      // XXX So, here wants to know whether the CellData actually failed
      //     above.  Fix this later.
      if (!cellData.mElement) {
        continue;
      }

      if ((!insertAfterPreviousCell && cellData.IsSpannedFromOtherColumn()) ||
          (insertAfterPreviousCell &&
           cellData.IsNextColumnSpannedFromOtherColumn())) {
        // If we have a cell spanning this location, simply increase its
        // colspan to keep table rectangular.
        if (cellData.mColSpan > 0) {
          DebugOnly<nsresult> rvIgnored = SetColSpan(
              cellData.mElement, cellData.mColSpan + aNumberOfColumnsToInsert);
          NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                               "HTMLEditor::SetColSpan() failed, but ignored");
        }
        continue;
      }

      EditorDOMPoint pointToInsert = [&]() {
        if (!insertAfterPreviousCell) {
          // Insert before the reference cell.
          return EditorDOMPoint(cellData.mElement);
        }
        if (!cellData.mElement->GetNextSibling()) {
          // Insert after the reference cell, but nothing follows it, append
          // to the end of the row.
          return EditorDOMPoint::AtEndOf(*cellData.mElement->GetParentNode());
        }
        // Otherwise, returns immediately before the next sibling.  Note that
        // the next sibling may not be a table cell element.  E.g., it may be
        // a text node containing only white-spaces in most cases.
        return EditorDOMPoint(cellData.mElement->GetNextSibling());
      }();
      if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
      }
      Result<CreateElementResult, nsresult> insertCellElementsResult =
          InsertTableCellsWithTransaction(pointToInsert,
                                          aNumberOfColumnsToInsert);
      if (MOZ_UNLIKELY(insertCellElementsResult.isErr())) {
        NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
        return insertCellElementsResult.propagateErr();
      }
      CreateElementResult unwrappedInsertCellElementsResult =
          insertCellElementsResult.unwrap();
      // We'll update selection later into the first inserted cell element in
      // the current row.
      unwrappedInsertCellElementsResult.IgnoreCaretPointSuggestion();
      if (pointToInsert.ContainerAs<Element>() ==
          aPointToInsert.ContainerAs<Element>()) {
        cellElementToPutCaret =
            unwrappedInsertCellElementsResult.UnwrapNewNode();
        MOZ_ASSERT(cellElementToPutCaret);
        MOZ_ASSERT(HTMLEditUtils::IsTableCell(cellElementToPutCaret));
      }
    }
    return cellElementToPutCaret;
  }();
  if (MOZ_UNLIKELY(cellElementToPutCaretOrError.isErr())) {
    return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
                                   : cellElementToPutCaretOrError.unwrapErr();
  }
  const RefPtr<Element> cellElementToPutCaret =
      cellElementToPutCaretOrError.unwrap();
  NS_WARNING_ASSERTION(
      cellElementToPutCaret,
      "Didn't find the first inserted cell element in the specified row");
  if (MOZ_LIKELY(cellElementToPutCaret)) {
    CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
  }
  return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
}

NS_IMETHODIMP HTMLEditor::InsertTableRow(int32_t aNumberOfRowsToInsert,
                                         bool aInsertAfterSelectedCell) {
  if (aNumberOfRowsToInsert <= 0) {
    return NS_OK;
  }

  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eInsertTableRowElement);
  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
  if (NS_FAILED(rv)) {
    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(editingHost &&
                 editingHost->IsContentEditablePlainTextOnly())) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  Result<RefPtr<Element>, nsresult> cellElementOrError =
      GetFirstSelectedCellElementInTable();
  if (cellElementOrError.isErr()) {
    NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
    return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
  }

  if (!cellElementOrError.inspect()) {
    return NS_OK;
  }

  rv = InsertTableRowsWithTransaction(
      MOZ_KnownLive(*cellElementOrError.inspect()), aNumberOfRowsToInsert,
      aInsertAfterSelectedCell ? InsertPosition::eAfterSelectedCell
                               : InsertPosition::eBeforeSelectedCell);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertTableRowsWithTransaction() failed");
  return EditorBase::ToGenericNSResult(rv);
}

nsresult HTMLEditor::InsertTableRowsWithTransaction(
    Element& aCellElement, int32_t aNumberOfRowsToInsert,
    InsertPosition aInsertPosition) {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(HTMLEditUtils::IsTableCell(&aCellElement));

  const RefPtr<PresShell> presShell = GetPresShell();
  if (MOZ_UNLIKELY(NS_WARN_IF(!presShell))) {
    return NS_ERROR_FAILURE;
  }

  if (MOZ_UNLIKELY(
          !HTMLEditUtils::IsTableRow(aCellElement.GetParentElement()))) {
    NS_WARNING("Tried to insert columns to non- element");
    return NS_ERROR_FAILURE;
  }

  const RefPtr<Element> tableElement =
      HTMLEditUtils::GetClosestAncestorTableElement(aCellElement);
  if (MOZ_UNLIKELY(!tableElement)) {
    return NS_OK;
  }

  const Result<TableSize, nsresult> tableSizeOrError =
      TableSize::Create(*this, *tableElement);
  if (NS_WARN_IF(tableSizeOrError.isErr())) {
    return tableSizeOrError.inspectErr();
  }
  const TableSize& tableSize = tableSizeOrError.inspect();
  // Should not be empty since we've already found a cell.
  MOZ_ASSERT(!tableSize.IsEmpty());

  const CellIndexes cellIndexes(aCellElement, presShell);
  if (NS_WARN_IF(cellIndexes.isErr())) {
    return NS_ERROR_FAILURE;
  }

  // Get more data for current cell in row we are inserting at because we need
  // rowspan.
  const auto cellData =
      CellData::AtIndexInTableElement(*this, *tableElement, cellIndexes);
  if (NS_WARN_IF(cellData.FailedOrNotFound())) {
    return NS_ERROR_FAILURE;
  }
  MOZ_ASSERT(&aCellElement == cellData.mElement);

  AutoPlaceholderBatch treateAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  // Prevent auto insertion of BR in new cell until we're done
  IgnoredErrorResult error;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eInsertNode, 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");

  struct ElementWithNewRowSpan final {
    const OwningNonNull<Element> mCellElement;
    const int32_t mNewRowSpan;

    ElementWithNewRowSpan(Element& aCellElement, int32_t aNewRowSpan)
        : mCellElement(aCellElement), mNewRowSpan(aNewRowSpan) {}
  };
  AutoTArray<ElementWithNewRowSpan, 16> cellElementsToModifyRowSpan;
  if (aInsertPosition == InsertPosition::eAfterSelectedCell &&
      !cellData.mRowSpan) {
    // Detect when user is adding after a rowspan=0 case.
    // Assume they want to stop the "0" behavior and really add a new row.
    // Thus we set the rowspan to its true value.
    cellElementsToModifyRowSpan.AppendElement(
        ElementWithNewRowSpan(aCellElement, cellData.mEffectiveRowSpan));
  }

  struct MOZ_STACK_CLASS TableRowData {
    RefPtr<Element> mElement;
    int32_t mNumberOfCellsInStartRow;
    int32_t mOffsetInTRElementToPutCaret;
  };
  const auto referenceRowDataOrError = [&]() -> Result<TableRowData, nsresult> {
    const int32_t startRowIndex =
        aInsertPosition == InsertPosition::eBeforeSelectedCell
            ? cellData.mCurrent.mRow
            : cellData.mCurrent.mRow + cellData.mEffectiveRowSpan;
    if (startRowIndex < tableSize.mRowCount) {
      // We are inserting above an existing row.  Get each cell in the insert
      // row to adjust for rowspan effects while we count how many cells are
      // needed.
      RefPtr<Element> referenceRowElement;
      int32_t numberOfCellsInStartRow = 0;
      int32_t offsetInTRElementToPutCaret = 0;
      for (int32_t colIndex = 0;;) {
        const auto cellDataInStartRow = CellData::AtIndexInTableElement(
            *this, *tableElement, startRowIndex, colIndex);
        if (cellDataInStartRow.FailedOrNotFound()) {
          break;  // Perhaps, we reach end of the row.
        }

        // XXX So, this is impossible case. Will be removed.
        if (!cellDataInStartRow.mElement) {
          NS_WARNING("CellData::Update() succeeded, but didn't set mElement");
          break;
        }

        if (cellDataInStartRow.IsSpannedFromOtherRow()) {
          // We have a cell spanning this location.  Increase its rowspan.
          // Note that if rowspan is 0, we do nothing since that cell should
          // automatically extend into the new row.
          if (cellDataInStartRow.mRowSpan > 0) {
            cellElementsToModifyRowSpan.AppendElement(ElementWithNewRowSpan(
                *cellDataInStartRow.mElement,
                cellDataInStartRow.mRowSpan + aNumberOfRowsToInsert));
          }
          colIndex = cellDataInStartRow.NextColumnIndex();
          continue;
        }

        if (colIndex < cellDataInStartRow.mCurrent.mColumn) {
          offsetInTRElementToPutCaret++;
        }

        numberOfCellsInStartRow += cellDataInStartRow.mEffectiveColSpan;
        if (!referenceRowElement) {
          if (Element* maybeTableRowElement =
                  cellDataInStartRow.mElement->GetParentElement()) {
            if (HTMLEditUtils::IsTableRow(maybeTableRowElement)) {
              referenceRowElement = maybeTableRowElement;
            }
          }
        }
        MOZ_ASSERT(colIndex < cellDataInStartRow.NextColumnIndex());
        colIndex = cellDataInStartRow.NextColumnIndex();
      }
      if (MOZ_UNLIKELY(!referenceRowElement)) {
        NS_WARNING(
            "Reference row element to insert new row elements was not found");
        return Err(NS_ERROR_FAILURE);
      }
      return TableRowData{std::move(referenceRowElement),
                          numberOfCellsInStartRow, offsetInTRElementToPutCaret};
    }

    // We are adding a new row after all others.  If it weren't for colspan=0
    // effect,  we could simply use tableSize.mColumnCount for number of new
    // cells...
    // XXX colspan=0 support has now been removed in table layout so maybe this
    //     can be cleaned up now? (bug 1243183)
    int32_t numberOfCellsInStartRow = tableSize.mColumnCount;
    int32_t offsetInTRElementToPutCaret = 0;

    // but we must compensate for all cells with rowspan = 0 in the last row.
    const int32_t lastRowIndex = tableSize.mRowCount - 1;
    for (int32_t colIndex = 0;;) {
      const auto cellDataInLastRow = CellData::AtIndexInTableElement(
          *this, *tableElement, lastRowIndex, colIndex);
      if (cellDataInLastRow.FailedOrNotFound()) {
        break;  // Perhaps, we reach end of the row.
      }

      if (!cellDataInLastRow.mRowSpan) {
        MOZ_ASSERT(numberOfCellsInStartRow >=
                   cellDataInLastRow.mEffectiveColSpan);
        numberOfCellsInStartRow -= cellDataInLastRow.mEffectiveColSpan;
      } else if (colIndex < cellDataInLastRow.mCurrent.mColumn) {
        offsetInTRElementToPutCaret++;
      }
      MOZ_ASSERT(colIndex < cellDataInLastRow.NextColumnIndex());
      colIndex = cellDataInLastRow.NextColumnIndex();
    }
    return TableRowData{nullptr, numberOfCellsInStartRow,
                        offsetInTRElementToPutCaret};
  }();
  if (MOZ_UNLIKELY(referenceRowDataOrError.isErr())) {
    return referenceRowDataOrError.inspectErr();
  }

  const TableRowData& referenceRowData = referenceRowDataOrError.inspect();
  if (MOZ_UNLIKELY(!referenceRowData.mNumberOfCellsInStartRow)) {
    NS_WARNING("There was no cell element in the row");
    return NS_OK;
  }

  MOZ_ASSERT_IF(referenceRowData.mElement,
                HTMLEditUtils::IsTableRow(referenceRowData.mElement));
  if (NS_WARN_IF(!HTMLEditUtils::IsTableRow(aCellElement.GetParentElement()))) {
    return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
  }

  // The row parent and offset where we will insert new row.
  EditorDOMPoint pointToInsert = [&]() {
    if (aInsertPosition == InsertPosition::eBeforeSelectedCell) {
      MOZ_ASSERT(referenceRowData.mElement);
      return EditorDOMPoint(referenceRowData.mElement);
    }
    // Look for the last row element in the same table section or immediately
    // before the reference row element.  Then, we can insert new rows
    // immediately after the given row element.
    Element* lastRowElement = nullptr;
    for (Element* rowElement = aCellElement.GetParentElement();
         rowElement && rowElement != referenceRowData.mElement;) {
      lastRowElement = rowElement;
      const Result<RefPtr<Element>, nsresult> nextRowElementOrError =
          GetNextTableRowElement(*rowElement);
      if (MOZ_UNLIKELY(nextRowElementOrError.isErr())) {
        NS_WARNING("HTMLEditor::GetNextTableRowElement() failed");
        return EditorDOMPoint();
      }
      rowElement = nextRowElementOrError.inspect();
    }
    MOZ_ASSERT(lastRowElement);
    return EditorDOMPoint::After(*lastRowElement);
  }();
  if (NS_WARN_IF(!pointToInsert.IsSet())) {
    return NS_ERROR_FAILURE;
  }
  // Note that checking whether the editor destroyed or not should be done
  // after inserting all cell elements.  Otherwise, the table is left as
  // not a rectangle.
  auto firstInsertedTRElementOrError =
      [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
    // Block legacy mutation events for making this job simpler.
    nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;

    // Suppress Rules System selection munging.
    AutoTransactionsConserveSelection dontChangeSelection(*this);

    for (const ElementWithNewRowSpan& cellElementAndNewRowSpan :
         cellElementsToModifyRowSpan) {
      DebugOnly<nsresult> rvIgnored =
          SetRowSpan(MOZ_KnownLive(cellElementAndNewRowSpan.mCellElement),
                     cellElementAndNewRowSpan.mNewRowSpan);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                           "HTMLEditor::SetRowSpan() failed, but ignored");
    }

    RefPtr<Element> firstInsertedTRElement;
    IgnoredErrorResult error;
    for ([[maybe_unused]] const int32_t rowIndex :
         Reversed(IntegerRange(aNumberOfRowsToInsert))) {
      // Create a new row
      RefPtr<Element> newRowElement = CreateElementWithDefaults(*nsGkAtoms::tr);
      if (!newRowElement) {
        NS_WARNING(
            "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::tr) failed");
        return Err(NS_ERROR_FAILURE);
      }

      for ([[maybe_unused]] const int32_t i :
           IntegerRange(referenceRowData.mNumberOfCellsInStartRow)) {
        const RefPtr<Element> newCellElement =
            CreateElementWithDefaults(*nsGkAtoms::td);
        if (!newCellElement) {
          NS_WARNING(
              "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
          return Err(NS_ERROR_FAILURE);
        }
        newRowElement->AppendChild(*newCellElement, error);
        if (error.Failed()) {
          NS_WARNING("nsINode::AppendChild() failed");
          return Err(error.StealNSResult());
        }
      }

      AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
      Result<CreateElementResult, nsresult> insertNewRowResult =
          InsertNodeWithTransaction<Element>(*newRowElement, pointToInsert);
      if (MOZ_UNLIKELY(insertNewRowResult.isErr())) {
        if (insertNewRowResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
          NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
          return insertNewRowResult.propagateErr();
        }
        NS_WARNING(
            "EditorBase::InsertNodeWithTransaction() failed, but ignored");
      }
      firstInsertedTRElement = std::move(newRowElement);
      // We'll update selection later.
      insertNewRowResult.inspect().IgnoreCaretPointSuggestion();
    }
    return firstInsertedTRElement;
  }();
  if (NS_WARN_IF(Destroyed())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  if (MOZ_UNLIKELY(firstInsertedTRElementOrError.isErr())) {
    return firstInsertedTRElementOrError.unwrapErr();
  }

  const OwningNonNull<Element> cellElementToPutCaret = [&]() {
    if (MOZ_LIKELY(firstInsertedTRElementOrError.inspect())) {
      EditorRawDOMPoint point(firstInsertedTRElementOrError.inspect(),
                              referenceRowData.mOffsetInTRElementToPutCaret);
      if (MOZ_LIKELY(point.IsSetAndValid()) &&
          MOZ_LIKELY(!point.IsEndOfContainer()) &&
          MOZ_LIKELY(HTMLEditUtils::IsTableCell(point.GetChild()))) {
        return OwningNonNull<Element>(*point.GetChild()->AsElement());
      }
    }
    return OwningNonNull<Element>(aCellElement);
  }();
  CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
  return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
}

nsresult HTMLEditor::DeleteTableElementAndChildrenWithTransaction(
    Element& aTableElement) {
  MOZ_ASSERT(IsEditActionDataAvailable());

  // Block selectionchange event.  It's enough to dispatch selectionchange
  // event immediately after removing the table element.
  {
    AutoHideSelectionChanges hideSelection(SelectionRef());

    // Select the <table> element after clear current selection.
    if (SelectionRef().RangeCount()) {
      ErrorResult error;
      SelectionRef().RemoveAllRanges(error);
      if (error.Failed()) {
        NS_WARNING("Selection::RemoveAllRanges() failed");
        return error.StealNSResult();
      }
    }

    RefPtr<nsRange> range = nsRange::Create(&aTableElement);
    ErrorResult error;
    range->SelectNode(aTableElement, error);
    if (error.Failed()) {
      NS_WARNING("nsRange::SelectNode() failed");
      return error.StealNSResult();
    }
    SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*range, error);
    if (error.Failed()) {
      NS_WARNING(
          "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
      return error.StealNSResult();
    }

#ifdef DEBUG
    range = SelectionRef().GetRangeAt(0);
    MOZ_ASSERT(range);
    MOZ_ASSERT(range->GetStartContainer() == aTableElement.GetParent());
    MOZ_ASSERT(range->GetEndContainer() == aTableElement.GetParent());
    MOZ_ASSERT(range->GetChildAtStartOffset() == &aTableElement);
    MOZ_ASSERT(range->GetChildAtEndOffset() == aTableElement.GetNextSibling());
#endif  // #ifdef DEBUG
  }

  nsresult rv = DeleteSelectionAsSubAction(eNext, eStrip);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "EditorBase::DeleteSelectionAsSubAction(eNext, eStrip) failed");
  return rv;
}

NS_IMETHODIMP HTMLEditor::DeleteTable() {
  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eRemoveTableElement);
  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
  if (NS_FAILED(rv)) {
    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(editingHost &&
                 editingHost->IsContentEditablePlainTextOnly())) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  RefPtr<Element> table;
  rv = GetCellContext(getter_AddRefs(table), nullptr, nullptr, nullptr, nullptr,
                      nullptr);
  if (NS_FAILED(rv)) {
    NS_WARNING("HTMLEditor::GetCellContext() failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  if (!table) {
    NS_WARNING("HTMLEditor::GetCellContext() didn't return
element");
    return NS_ERROR_FAILURE;
  }

  AutoPlaceholderBatch treateAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  rv = DeleteTableElementAndChildrenWithTransaction(*table);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
  return EditorBase::ToGenericNSResult(rv);
}

NS_IMETHODIMP HTMLEditor::DeleteTableCell(int32_t aNumberOfCellsToDelete) {
  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eRemoveTableCellElement);
  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
  if (NS_FAILED(rv)) {
    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(editingHost &&
                 editingHost->IsContentEditablePlainTextOnly())) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  rv = DeleteTableCellWithTransaction(aNumberOfCellsToDelete);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::DeleteTableCellWithTransaction() failed");
  return EditorBase::ToGenericNSResult(rv);
}

nsresult HTMLEditor::DeleteTableCellWithTransaction(
    int32_t aNumberOfCellsToDelete) {
  MOZ_ASSERT(IsEditActionDataAvailable());

  RefPtr<Element> table;
  RefPtr<Element> cell;
  int32_t startRowIndex, startColIndex;

  nsresult rv =
      GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
                     nullptr, &startRowIndex, &startColIndex);
  if (NS_FAILED(rv)) {
    NS_WARNING("HTMLEditor::GetCellContext() failed");
    return rv;
  }
  if (!table || !cell) {
    NS_WARNING(
        "HTMLEditor::GetCellContext() didn't return
and/or cell");
    // Don't fail if we didn't find a table or cell.
    return NS_OK;
  }

  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
    return NS_ERROR_FAILURE;  // XXX Should we just return NS_OK?
  }

  AutoPlaceholderBatch treateAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  // Prevent rules testing until we're done
  IgnoredErrorResult ignoredError;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eDeleteNode, 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");

  MOZ_ASSERT(SelectionRef().RangeCount());

  SelectedTableCellScanner scanner(SelectionRef());

  Result<TableSize, nsresult> tableSizeOrError =
      TableSize::Create(*this, *table);
  if (NS_WARN_IF(tableSizeOrError.isErr())) {
    return tableSizeOrError.unwrapErr();
  }
  // FYI: Cannot be a const reference because the row count will be updated
  TableSize tableSize = tableSizeOrError.unwrap();
  MOZ_ASSERT(!tableSize.IsEmpty());

  // If only one cell is selected or no cell is selected, remove cells
  // starting from the first selected cell or a cell containing first
  // selection range.
  if (!scanner.IsInTableCellSelectionMode() ||
      SelectionRef().RangeCount() == 1) {
    for (int32_t i = 0; i < aNumberOfCellsToDelete; i++) {
      nsresult rv =
          GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
                         nullptr, &startRowIndex, &startColIndex);
      if (NS_FAILED(rv)) {
        NS_WARNING("HTMLEditor::GetCellContext() failed");
        return rv;
      }
      if (!table || !cell) {
        NS_WARNING(
            "HTMLEditor::GetCellContext() didn't return
and/or cell");
        // Don't fail if no cell found
        return NS_OK;
      }

      int32_t numberOfCellsInRow = GetNumberOfCellsInRow(*table, startRowIndex);
      NS_WARNING_ASSERTION(
          numberOfCellsInRow >= 0,
          "HTMLEditor::GetNumberOfCellsInRow() failed, but ignored");

      if (numberOfCellsInRow == 1) {
        // Remove <tr> or <table> if we're removing all cells in the row or
        // the table.
        if (tableSize.mRowCount == 1) {
          nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
          NS_WARNING_ASSERTION(
              NS_SUCCEEDED(rv),
              "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
              "failed");
          return rv;
        }

        // We need to call DeleteSelectedTableRowsWithTransaction() to handle
        // cells with rowspan attribute.
        rv = DeleteSelectedTableRowsWithTransaction(1);
        if (NS_FAILED(rv)) {
          NS_WARNING(
              "HTMLEditor::DeleteSelectedTableRowsWithTransaction(1) failed");
          return rv;
        }

        // Adjust table rows simply.  In strictly speaking, we should
        // recompute table size with the latest layout information since
        // mutation event listener may have changed the DOM tree. However,
        // this is not in usual path of Firefox.  So, we can assume that
        // there are no mutation event listeners.
        MOZ_ASSERT(tableSize.mRowCount);
        tableSize.mRowCount--;
        continue;
      }

      // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
      // destructor
      AutoSelectionSetterAfterTableEdit setCaret(
          *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
      AutoTransactionsConserveSelection dontChangeSelection(*this);

      // XXX Removing cell element causes not adjusting colspan.
      rv = DeleteNodeWithTransaction(*cell);
      // If we fail, don't try to delete any more cells???
      if (NS_FAILED(rv)) {
        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
        return rv;
      }
      // Note that we don't refer column number in this loop.  So, it must
      // be safe not to recompute table size since number of row is synced
      // above.
    }
    return NS_OK;
  }

  // When 2 or more cells are selected, ignore aNumberOfCellsToRemove and
  // remove all selected cells.
  const RefPtr<PresShell> presShell{GetPresShell()};
  // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because scanner grabs
  // it until it's destroyed later.
  const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
                                     presShell);
  if (NS_WARN_IF(firstCellIndexes.isErr())) {
    return NS_ERROR_FAILURE;
  }
  startRowIndex = firstCellIndexes.mRow;
  startColIndex = firstCellIndexes.mColumn;

  // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
  // destructor
  AutoSelectionSetterAfterTableEdit setCaret(
      *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
  AutoTransactionsConserveSelection dontChangeSelection(*this);

  bool checkToDeleteRow = true;
  bool checkToDeleteColumn = true;
  for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
       selectedCellElement;) {
    if (checkToDeleteRow) {
      // Optimize to delete an entire row
      // Clear so we don't repeat AllCellsInRowSelected within the same row
      checkToDeleteRow = false;
      if (AllCellsInRowSelected(table, startRowIndex, tableSize.mColumnCount)) {
        // First, find the next cell in a different row to continue after we
        // delete this row.
        int32_t nextRow = startRowIndex;
        while (nextRow == startRowIndex) {
          selectedCellElement = scanner.GetNextElement();
          if (!selectedCellElement) {
            break;
          }
          const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
                                                    presShell);
          if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
            return NS_ERROR_FAILURE;
          }
          nextRow = nextSelectedCellIndexes.mRow;
          startColIndex = nextSelectedCellIndexes.mColumn;
        }
        if (tableSize.mRowCount == 1) {
          nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
          NS_WARNING_ASSERTION(
              NS_SUCCEEDED(rv),
              "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
              "failed");
          return rv;
        }
        nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
        if (NS_FAILED(rv)) {
          NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
          return rv;
        }
        // Adjust table rows simply.  In strictly speaking, we should
        // recompute table size with the latest layout information since
        // mutation event listener may have changed the DOM tree. However,
        // this is not in usual path of Firefox.  So, we can assume that
        // there are no mutation event listeners.
        MOZ_ASSERT(tableSize.mRowCount);
        tableSize.mRowCount--;
        if (!selectedCellElement) {
          break;  // XXX Seems like a dead path
        }
        // For the next cell: Subtract 1 for row we deleted
        startRowIndex = nextRow - 1;
        // Set true since we know we will look at a new row next
        checkToDeleteRow = true;
        continue;
      }
    }

    if (checkToDeleteColumn) {
      // Optimize to delete an entire column
      // Clear this so we don't repeat AllCellsInColSelected within the same Col
      checkToDeleteColumn = false;
      if (AllCellsInColumnSelected(table, startColIndex,
                                   tableSize.mColumnCount)) {
        // First, find the next cell in a different column to continue after
        // we delete this column.
        int32_t nextCol = startColIndex;
        while (nextCol == startColIndex) {
          selectedCellElement = scanner.GetNextElement();
          if (!selectedCellElement) {
            break;
          }
          const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
                                                    presShell);
          if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
            return NS_ERROR_FAILURE;
          }
          startRowIndex = nextSelectedCellIndexes.mRow;
          nextCol = nextSelectedCellIndexes.mColumn;
        }
        // Delete all cells which belong to the column.
        nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
        if (NS_FAILED(rv)) {
          NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
          return rv;
        }
        // Adjust table columns simply.  In strictly speaking, we should
        // recompute table size with the latest layout information since
        // mutation event listener may have changed the DOM tree. However,
        // this is not in usual path of Firefox.  So, we can assume that
        // there are no mutation event listeners.
        MOZ_ASSERT(tableSize.mColumnCount);
        tableSize.mColumnCount--;
        if (!selectedCellElement) {
          break;
        }
        // For the next cell, subtract 1 for col. deleted
        startColIndex = nextCol - 1;
        // Set true since we know we will look at a new column next
        checkToDeleteColumn = true;
        continue;
      }
    }

    nsresult rv = DeleteNodeWithTransaction(*selectedCellElement);
    if (NS_FAILED(rv)) {
      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
      return rv;
    }

    selectedCellElement = scanner.GetNextElement();
    if (!selectedCellElement) {
      return NS_OK;
    }

    const CellIndexes nextCellIndexes(*selectedCellElement, presShell);
    if (NS_WARN_IF(nextCellIndexes.isErr())) {
      return NS_ERROR_FAILURE;
    }
    startRowIndex = nextCellIndexes.mRow;
    startColIndex = nextCellIndexes.mColumn;
    // When table cell is removed, table size of column may be changed.
    // For example, if there are 2 rows, one has 2 cells, the other has
    // 3 cells, tableSize.mColumnCount is 3.  When this removes a cell
    // in the latter row, mColumnCount should be come 2.  However, we
    // don't use mColumnCount in this loop, so, this must be okay for now.
  }
  return NS_OK;
}

NS_IMETHODIMP HTMLEditor::DeleteTableCellContents() {
  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eDeleteTableCellContents);
  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
  if (NS_FAILED(rv)) {
    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(editingHost &&
                 editingHost->IsContentEditablePlainTextOnly())) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  rv = DeleteTableCellContentsWithTransaction();
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
  return EditorBase::ToGenericNSResult(rv);
}

nsresult HTMLEditor::DeleteTableCellContentsWithTransaction() {
  MOZ_ASSERT(IsEditActionDataAvailable());

  RefPtr<Element> table;
  RefPtr<Element> cell;
--> --------------------

--> maximum size reached

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

93%


¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.