tor-browser

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

HTMLTableEditor.cpp (179884B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include <stdio.h>
      7 
      8 #include "HTMLEditor.h"
      9 #include "HTMLEditorInlines.h"
     10 
     11 #include "AutoSelectionRestorer.h"
     12 #include "EditAction.h"
     13 #include "EditorDOMPoint.h"
     14 #include "EditorUtils.h"
     15 #include "HTMLEditUtils.h"
     16 
     17 #include "mozilla/Assertions.h"
     18 #include "mozilla/FlushType.h"
     19 #include "mozilla/IntegerRange.h"
     20 #include "mozilla/PresShell.h"
     21 #include "mozilla/dom/Selection.h"
     22 #include "mozilla/dom/Element.h"
     23 #include "mozilla/dom/ElementInlines.h"
     24 #include "nsAString.h"
     25 #include "nsCOMPtr.h"
     26 #include "nsDebug.h"
     27 #include "nsError.h"
     28 #include "nsFrameSelection.h"
     29 #include "nsGkAtoms.h"
     30 #include "nsAtom.h"
     31 #include "nsIContent.h"
     32 #include "nsIFrame.h"
     33 #include "nsINode.h"
     34 #include "nsISupportsUtils.h"
     35 #include "nsITableCellLayout.h"  // For efficient access to table cell
     36 #include "nsLiteralString.h"
     37 #include "nsQueryFrame.h"
     38 #include "nsRange.h"
     39 #include "nsString.h"
     40 #include "nsTArray.h"
     41 #include "nsTableCellFrame.h"
     42 #include "nsTableWrapperFrame.h"
     43 #include "nscore.h"
     44 #include <algorithm>
     45 
     46 namespace mozilla {
     47 
     48 using namespace dom;
     49 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
     50 
     51 /**
     52 * Stack based helper class for restoring selection after table edit.
     53 */
     54 class MOZ_STACK_CLASS AutoSelectionSetterAfterTableEdit final {
     55 private:
     56  const RefPtr<HTMLEditor> mHTMLEditor;
     57  const RefPtr<Element> mTable;
     58  int32_t mCol, mRow, mDirection, mSelected;
     59 
     60 public:
     61  AutoSelectionSetterAfterTableEdit(HTMLEditor& aHTMLEditor, Element* aTable,
     62                                    int32_t aRow, int32_t aCol,
     63                                    int32_t aDirection, bool aSelected)
     64      : mHTMLEditor(&aHTMLEditor),
     65        mTable(aTable),
     66        mCol(aCol),
     67        mRow(aRow),
     68        mDirection(aDirection),
     69        mSelected(aSelected) {}
     70 
     71  MOZ_CAN_RUN_SCRIPT ~AutoSelectionSetterAfterTableEdit() {
     72    if (mHTMLEditor) {
     73      mHTMLEditor->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection,
     74                                              mSelected);
     75    }
     76  }
     77 };
     78 
     79 /******************************************************************************
     80 * HTMLEditor::CellIndexes
     81 ******************************************************************************/
     82 
     83 void HTMLEditor::CellIndexes::Update(HTMLEditor& aHTMLEditor,
     84                                     Selection& aSelection) {
     85  // Guarantee the life time of the cell element since Init() will access
     86  // layout methods.
     87  RefPtr<Element> cellElement =
     88      aHTMLEditor.GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
     89  if (!cellElement) {
     90    NS_WARNING(
     91        "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
     92        "failed");
     93    return;
     94  }
     95 
     96  RefPtr<PresShell> presShell{aHTMLEditor.GetPresShell()};
     97  Update(*cellElement, presShell);
     98 }
     99 
    100 void HTMLEditor::CellIndexes::Update(Element& aCellElement,
    101                                     PresShell* aPresShell) {
    102  // If the table cell is created immediately before this call, e.g., using
    103  // innerHTML, frames have not been created yet. Hence, flush layout to create
    104  // them.
    105  if (NS_WARN_IF(!aPresShell)) {
    106    return;
    107  }
    108 
    109  aPresShell->FlushPendingNotifications(FlushType::Frames);
    110 
    111  nsIFrame* frameOfCell = aCellElement.GetPrimaryFrame();
    112  if (!frameOfCell) {
    113    NS_WARNING("There was no layout information of aCellElement");
    114    return;
    115  }
    116 
    117  nsITableCellLayout* tableCellLayout = do_QueryFrame(frameOfCell);
    118  if (!tableCellLayout) {
    119    NS_WARNING("aCellElement was not a table cell");
    120    return;
    121  }
    122 
    123  if (NS_FAILED(tableCellLayout->GetCellIndexes(mRow, mColumn))) {
    124    NS_WARNING("nsITableCellLayout::GetCellIndexes() failed");
    125    mRow = mColumn = -1;
    126    return;
    127  }
    128 
    129  MOZ_ASSERT(!isErr());
    130 }
    131 
    132 /******************************************************************************
    133 * HTMLEditor::CellData
    134 ******************************************************************************/
    135 
    136 // static
    137 HTMLEditor::CellData HTMLEditor::CellData::AtIndexInTableElement(
    138    const HTMLEditor& aHTMLEditor, const Element& aTableElement,
    139    int32_t aRowIndex, int32_t aColumnIndex) {
    140  nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(&aTableElement);
    141  if (!tableFrame) {
    142    NS_WARNING("There was no layout information of the table");
    143    return CellData::Error(aRowIndex, aColumnIndex);
    144  }
    145 
    146  // If there is no cell at the indexes.  Don't set the error state to the new
    147  // instance.
    148  nsTableCellFrame* cellFrame =
    149      tableFrame->GetCellFrameAt(aRowIndex, aColumnIndex);
    150  if (!cellFrame) {
    151    return CellData::NotFound(aRowIndex, aColumnIndex);
    152  }
    153 
    154  Element* cellElement = Element::FromNodeOrNull(cellFrame->GetContent());
    155  if (!cellElement) {
    156    return CellData::Error(aRowIndex, aColumnIndex);
    157  }
    158  return CellData(*cellElement, aRowIndex, aColumnIndex, *cellFrame,
    159                  *tableFrame);
    160 }
    161 
    162 HTMLEditor::CellData::CellData(Element& aElement, int32_t aRowIndex,
    163                               int32_t aColumnIndex,
    164                               nsTableCellFrame& aTableCellFrame,
    165                               nsTableWrapperFrame& aTableWrapperFrame)
    166    : mElement(&aElement),
    167      mCurrent(aRowIndex, aColumnIndex),
    168      mFirst(aTableCellFrame.RowIndex(), aTableCellFrame.ColIndex()),
    169      mRowSpan(aTableCellFrame.GetRowSpan()),
    170      mColSpan(aTableCellFrame.GetColSpan()),
    171      mEffectiveRowSpan(
    172          aTableWrapperFrame.GetEffectiveRowSpanAt(aRowIndex, aColumnIndex)),
    173      mEffectiveColSpan(
    174          aTableWrapperFrame.GetEffectiveColSpanAt(aRowIndex, aColumnIndex)),
    175      mIsSelected(aTableCellFrame.IsSelected()) {
    176  MOZ_ASSERT(!mCurrent.isErr());
    177 }
    178 
    179 /******************************************************************************
    180 * HTMLEditor::TableSize
    181 ******************************************************************************/
    182 
    183 // static
    184 Result<HTMLEditor::TableSize, nsresult> HTMLEditor::TableSize::Create(
    185    HTMLEditor& aHTMLEditor, Element& aTableOrElementInTable) {
    186  // Currently, nsTableWrapperFrame::GetRowCount() and
    187  // nsTableWrapperFrame::GetColCount() are safe to use without grabbing
    188  // <table> element.  However, editor developers may not watch layout API
    189  // changes.  So, for keeping us safer, we should use RefPtr here.
    190  RefPtr<Element> tableElement =
    191      aHTMLEditor.GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table,
    192                                                        aTableOrElementInTable);
    193  if (!tableElement) {
    194    NS_WARNING(
    195        "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
    196        "failed");
    197    return Err(NS_ERROR_FAILURE);
    198  }
    199  nsTableWrapperFrame* tableFrame =
    200      do_QueryFrame(tableElement->GetPrimaryFrame());
    201  if (!tableFrame) {
    202    NS_WARNING("There was no layout information of the <table> element");
    203    return Err(NS_ERROR_FAILURE);
    204  }
    205  const int32_t rowCount = tableFrame->GetRowCount();
    206  const int32_t columnCount = tableFrame->GetColCount();
    207  if (NS_WARN_IF(rowCount < 0) || NS_WARN_IF(columnCount < 0)) {
    208    return Err(NS_ERROR_FAILURE);
    209  }
    210  return TableSize(rowCount, columnCount);
    211 }
    212 
    213 /******************************************************************************
    214 * HTMLEditor
    215 ******************************************************************************/
    216 
    217 nsresult HTMLEditor::InsertCell(Element* aCell, int32_t aRowSpan,
    218                                int32_t aColSpan, bool aAfter, bool aIsHeader,
    219                                Element** aNewCell) {
    220  if (aNewCell) {
    221    *aNewCell = nullptr;
    222  }
    223 
    224  if (NS_WARN_IF(!aCell)) {
    225    return NS_ERROR_INVALID_ARG;
    226  }
    227 
    228  // And the parent and offsets needed to do an insert
    229  EditorDOMPoint pointToInsert(aCell);
    230  if (NS_WARN_IF(!pointToInsert.IsSet())) {
    231    return NS_ERROR_INVALID_ARG;
    232  }
    233 
    234  RefPtr<Element> newCell =
    235      CreateElementWithDefaults(aIsHeader ? *nsGkAtoms::th : *nsGkAtoms::td);
    236  if (!newCell) {
    237    NS_WARNING(
    238        "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::th or td) failed");
    239    return NS_ERROR_FAILURE;
    240  }
    241 
    242  // Optional: return new cell created
    243  if (aNewCell) {
    244    *aNewCell = do_AddRef(newCell).take();
    245  }
    246 
    247  if (aRowSpan > 1) {
    248    // Note: Do NOT use editor transaction for this
    249    nsAutoString newRowSpan;
    250    newRowSpan.AppendInt(aRowSpan, 10);
    251    DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
    252        kNameSpaceID_None, nsGkAtoms::rowspan, newRowSpan, true);
    253    NS_WARNING_ASSERTION(
    254        NS_SUCCEEDED(rvIgnored),
    255        "Element::SetAttr(nsGkAtoms::rawspan) failed, but ignored");
    256  }
    257  if (aColSpan > 1) {
    258    // Note: Do NOT use editor transaction for this
    259    nsAutoString newColSpan;
    260    newColSpan.AppendInt(aColSpan, 10);
    261    DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
    262        kNameSpaceID_None, nsGkAtoms::colspan, newColSpan, true);
    263    NS_WARNING_ASSERTION(
    264        NS_SUCCEEDED(rvIgnored),
    265        "Element::SetAttr(nsGkAtoms::colspan) failed, but ignored");
    266  }
    267  if (aAfter) {
    268    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
    269    NS_WARNING_ASSERTION(advanced,
    270                         "Failed to advance offset to after the old cell");
    271  }
    272 
    273  // TODO: Remove AutoTransactionsConserveSelection here.  It's not necessary
    274  //       in normal cases.  However, it may be required for nested edit
    275  //       actions which may be caused by legacy mutation event listeners or
    276  //       chrome script.
    277  AutoTransactionsConserveSelection dontChangeSelection(*this);
    278  Result<CreateElementResult, nsresult> insertNewCellResult =
    279      InsertNodeWithTransaction<Element>(*newCell, pointToInsert);
    280  if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
    281    NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
    282    return insertNewCellResult.unwrapErr();
    283  }
    284  // Because of dontChangeSelection, we've never allowed to transactions to
    285  // update selection here.
    286  insertNewCellResult.inspect().IgnoreCaretPointSuggestion();
    287  return NS_OK;
    288 }
    289 
    290 nsresult HTMLEditor::SetColSpan(Element* aCell, int32_t aColSpan) {
    291  if (NS_WARN_IF(!aCell)) {
    292    return NS_ERROR_INVALID_ARG;
    293  }
    294  nsAutoString newSpan;
    295  newSpan.AppendInt(aColSpan, 10);
    296  nsresult rv =
    297      SetAttributeWithTransaction(*aCell, *nsGkAtoms::colspan, newSpan);
    298  NS_WARNING_ASSERTION(
    299      NS_SUCCEEDED(rv),
    300      "EditorBase::SetAttributeWithTransaction(nsGkAtoms::colspan) failed");
    301  return rv;
    302 }
    303 
    304 nsresult HTMLEditor::SetRowSpan(Element* aCell, int32_t aRowSpan) {
    305  if (NS_WARN_IF(!aCell)) {
    306    return NS_ERROR_INVALID_ARG;
    307  }
    308  nsAutoString newSpan;
    309  newSpan.AppendInt(aRowSpan, 10);
    310  nsresult rv =
    311      SetAttributeWithTransaction(*aCell, *nsGkAtoms::rowspan, newSpan);
    312  NS_WARNING_ASSERTION(
    313      NS_SUCCEEDED(rv),
    314      "EditorBase::SetAttributeWithTransaction(nsGkAtoms::rowspan) failed");
    315  return rv;
    316 }
    317 
    318 NS_IMETHODIMP HTMLEditor::InsertTableCell(int32_t aNumberOfCellsToInsert,
    319                                          bool aInsertAfterSelectedCell) {
    320  if (aNumberOfCellsToInsert <= 0) {
    321    return NS_OK;  // Just do nothing.
    322  }
    323 
    324  AutoEditActionDataSetter editActionData(*this,
    325                                          EditAction::eInsertTableCellElement);
    326  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
    327  if (NS_FAILED(rv)) {
    328    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    329    return EditorBase::ToGenericNSResult(rv);
    330  }
    331  const RefPtr<Element> editingHost =
    332      ComputeEditingHost(LimitInBodyElement::No);
    333  if (NS_WARN_IF(editingHost &&
    334                 editingHost->IsContentEditablePlainTextOnly())) {
    335    return NS_ERROR_NOT_AVAILABLE;
    336  }
    337  rv = editActionData.MaybeDispatchBeforeInputEvent();
    338  if (NS_FAILED(rv)) {
    339    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
    340                         "MaybeDispatchBeforeInputEvent(), failed");
    341    return EditorBase::ToGenericNSResult(rv);
    342  }
    343 
    344  Result<RefPtr<Element>, nsresult> cellElementOrError =
    345      GetFirstSelectedCellElementInTable();
    346  if (cellElementOrError.isErr()) {
    347    NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
    348    return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
    349  }
    350 
    351  if (!cellElementOrError.inspect()) {
    352    return NS_OK;
    353  }
    354 
    355  EditorDOMPoint pointToInsert(cellElementOrError.inspect());
    356  if (!pointToInsert.IsSet()) {
    357    NS_WARNING("Found an orphan cell element");
    358    return NS_ERROR_FAILURE;
    359  }
    360  if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
    361    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
    362    NS_WARNING_ASSERTION(
    363        advanced,
    364        "Failed to set insertion point after current cell, but ignored");
    365  }
    366  Result<CreateElementResult, nsresult> insertCellElementResult =
    367      InsertTableCellsWithTransaction(pointToInsert, aNumberOfCellsToInsert);
    368  if (MOZ_UNLIKELY(insertCellElementResult.isErr())) {
    369    NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
    370    return EditorBase::ToGenericNSResult(insertCellElementResult.unwrapErr());
    371  }
    372  // We don't need to modify selection here.
    373  insertCellElementResult.inspect().IgnoreCaretPointSuggestion();
    374  return NS_OK;
    375 }
    376 
    377 Result<CreateElementResult, nsresult>
    378 HTMLEditor::InsertTableCellsWithTransaction(
    379    const EditorDOMPoint& aPointToInsert, int32_t aNumberOfCellsToInsert) {
    380  MOZ_ASSERT(IsEditActionDataAvailable());
    381  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
    382  MOZ_ASSERT(aNumberOfCellsToInsert > 0);
    383 
    384  if (!HTMLEditUtils::IsTableRowElement(
    385          aPointToInsert.GetContainerAs<nsIContent>())) {
    386    NS_WARNING("Tried to insert cell elements to non-<tr> element");
    387    return Err(NS_ERROR_FAILURE);
    388  }
    389 
    390  AutoPlaceholderBatch treateAsOneTransaction(
    391      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    392  // Prevent auto insertion of BR in new cell until we're done
    393  // XXX Why? I think that we should insert <br> element for every cell
    394  //     **before** inserting new cell into the <tr> element.
    395  IgnoredErrorResult error;
    396  AutoEditSubActionNotifier startToHandleEditSubAction(
    397      *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
    398  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    399    return Err(error.StealNSResult());
    400  }
    401  NS_WARNING_ASSERTION(
    402      !error.Failed(),
    403      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    404  error.SuppressException();
    405 
    406  // Put caret into the cell before the first inserting cell, or the first
    407  // table cell in the row.
    408  RefPtr<Element> cellToPutCaret =
    409      aPointToInsert.IsEndOfContainer()
    410          ? nullptr
    411          : HTMLEditUtils::GetPreviousTableCellElementSibling(
    412                *aPointToInsert.GetChild());
    413 
    414  RefPtr<Element> firstCellElement, lastCellElement;
    415  nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT {
    416    // TODO: Remove AutoTransactionsConserveSelection here.  It's not necessary
    417    //       in normal cases.  However, it may be required for nested edit
    418    //       actions which may be caused by legacy mutation event listeners or
    419    //       chrome script.
    420    AutoTransactionsConserveSelection dontChangeSelection(*this);
    421 
    422    // Block legacy mutation events for making this job simpler.
    423    nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
    424 
    425    // If there is a child to put a cell, we need to put all cell elements
    426    // before it.  Therefore, creating `EditorDOMPoint` with the child element
    427    // is safe.  Otherwise, we need to try to append cell elements in the row.
    428    // Therefore, using `EditorDOMPoint::AtEndOf()` is safe.  Note that it's
    429    // not safe to creat it once because the offset and child relation in the
    430    // point becomes invalid after inserting a cell element.
    431    nsIContent* referenceContent = aPointToInsert.GetChild();
    432    for ([[maybe_unused]] const auto i :
    433         IntegerRange<uint32_t>(aNumberOfCellsToInsert)) {
    434      RefPtr<Element> newCell = CreateElementWithDefaults(*nsGkAtoms::td);
    435      if (!newCell) {
    436        NS_WARNING(
    437            "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
    438        return NS_ERROR_FAILURE;
    439      }
    440      Result<CreateElementResult, nsresult> insertNewCellResult =
    441          InsertNodeWithTransaction(
    442              *newCell, referenceContent
    443                            ? EditorDOMPoint(referenceContent)
    444                            : EditorDOMPoint::AtEndOf(
    445                                  *aPointToInsert.ContainerAs<Element>()));
    446      if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
    447        NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
    448        return insertNewCellResult.unwrapErr();
    449      }
    450      CreateElementResult unwrappedInsertNewCellResult =
    451          insertNewCellResult.unwrap();
    452      lastCellElement = unwrappedInsertNewCellResult.UnwrapNewNode();
    453      if (!firstCellElement) {
    454        firstCellElement = lastCellElement;
    455      }
    456      // Because of dontChangeSelection, we've never allowed to transactions
    457      // to update selection here.
    458      unwrappedInsertNewCellResult.IgnoreCaretPointSuggestion();
    459      if (!cellToPutCaret) {
    460        cellToPutCaret = std::move(newCell);  // This is first cell in the row.
    461      }
    462    }
    463 
    464    // TODO: Stop touching selection here.
    465    MOZ_ASSERT(cellToPutCaret);
    466    MOZ_ASSERT(cellToPutCaret->GetParent());
    467    CollapseSelectionToDeepestNonTableFirstChild(cellToPutCaret);
    468    return NS_OK;
    469  }();
    470  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED ||
    471                   NS_WARN_IF(Destroyed()))) {
    472    return Err(NS_ERROR_EDITOR_DESTROYED);
    473  }
    474  if (NS_FAILED(rv)) {
    475    return Err(rv);
    476  }
    477  MOZ_ASSERT(firstCellElement);
    478  MOZ_ASSERT(lastCellElement);
    479  return CreateElementResult(std::move(firstCellElement),
    480                             EditorDOMPoint(lastCellElement, 0u));
    481 }
    482 
    483 NS_IMETHODIMP HTMLEditor::GetFirstRow(Element* aTableOrElementInTable,
    484                                      Element** aFirstRowElement) {
    485  if (NS_WARN_IF(!aTableOrElementInTable) || NS_WARN_IF(!aFirstRowElement)) {
    486    return NS_ERROR_INVALID_ARG;
    487  }
    488 
    489  AutoEditActionDataSetter editActionData(*this, EditAction::eGetFirstRow);
    490  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
    491  if (NS_FAILED(rv)) {
    492    NS_WARNING("HTMLEditor::GetFirstRow() couldn't handle the job");
    493    return EditorBase::ToGenericNSResult(rv);
    494  }
    495 
    496  Result<RefPtr<Element>, nsresult> firstRowElementOrError =
    497      GetFirstTableRowElement(*aTableOrElementInTable);
    498  NS_WARNING_ASSERTION(!firstRowElementOrError.isErr(),
    499                       "HTMLEditor::GetFirstTableRowElement() failed");
    500  if (firstRowElementOrError.isErr()) {
    501    NS_WARNING("HTMLEditor::GetFirstTableRowElement() failed");
    502    return EditorBase::ToGenericNSResult(firstRowElementOrError.unwrapErr());
    503  }
    504  firstRowElementOrError.unwrap().forget(aFirstRowElement);
    505  return NS_OK;
    506 }
    507 
    508 Result<RefPtr<Element>, nsresult> HTMLEditor::GetFirstTableRowElement(
    509    const Element& aTableOrElementInTable) const {
    510  MOZ_ASSERT(IsEditActionDataAvailable());
    511 
    512  Element* tableElement = GetInclusiveAncestorByTagNameInternal(
    513      *nsGkAtoms::table, aTableOrElementInTable);
    514  // If the element is not in <table>, return error.
    515  if (!tableElement) {
    516    NS_WARNING(
    517        "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
    518        "failed");
    519    return Err(NS_ERROR_FAILURE);
    520  }
    521 
    522  for (nsIContent* tableChild = tableElement->GetFirstChild(); tableChild;
    523       tableChild = tableChild->GetNextSibling()) {
    524    if (tableChild->IsHTMLElement(nsGkAtoms::tr)) {
    525      // Found a row directly under <table>
    526      return RefPtr<Element>(tableChild->AsElement());
    527    }
    528    // <table> can have table section elements like <tbody>.  <tr> elements
    529    // may be children of them.
    530    if (tableChild->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::thead,
    531                                        nsGkAtoms::tfoot)) {
    532      for (nsIContent* tableSectionChild = tableChild->GetFirstChild();
    533           tableSectionChild;
    534           tableSectionChild = tableSectionChild->GetNextSibling()) {
    535        if (tableSectionChild->IsHTMLElement(nsGkAtoms::tr)) {
    536          return RefPtr<Element>(tableSectionChild->AsElement());
    537        }
    538      }
    539    }
    540  }
    541  // Don't return error when there is no <tr> element in the <table>.
    542  return RefPtr<Element>();
    543 }
    544 
    545 Result<RefPtr<Element>, nsresult> HTMLEditor::GetNextTableRowElement(
    546    const Element& aTableRowElement) const {
    547  if (NS_WARN_IF(!aTableRowElement.IsHTMLElement(nsGkAtoms::tr))) {
    548    return Err(NS_ERROR_INVALID_ARG);
    549  }
    550 
    551  for (nsIContent* maybeNextRow = aTableRowElement.GetNextSibling();
    552       maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
    553    if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
    554      return RefPtr<Element>(maybeNextRow->AsElement());
    555    }
    556  }
    557 
    558  // In current table section (e.g., <tbody>), there is no <tr> element.
    559  // Then, check the following table sections.
    560  Element* parentElementOfRow = aTableRowElement.GetParentElement();
    561  if (!parentElementOfRow) {
    562    NS_WARNING("aTableRowElement was an orphan node");
    563    return Err(NS_ERROR_FAILURE);
    564  }
    565 
    566  // Basically, <tr> elements should be in table section elements even if
    567  // they are not written in the source explicitly.  However, for preventing
    568  // cross table boundary, check it now.
    569  if (parentElementOfRow->IsHTMLElement(nsGkAtoms::table)) {
    570    // Don't return error since this means just not found.
    571    return RefPtr<Element>();
    572  }
    573 
    574  for (nsIContent* maybeNextTableSection = parentElementOfRow->GetNextSibling();
    575       maybeNextTableSection;
    576       maybeNextTableSection = maybeNextTableSection->GetNextSibling()) {
    577    // If the sibling of parent of given <tr> is a table section element,
    578    // check its children.
    579    if (maybeNextTableSection->IsAnyOfHTMLElements(
    580            nsGkAtoms::tbody, nsGkAtoms::thead, nsGkAtoms::tfoot)) {
    581      for (nsIContent* maybeNextRow = maybeNextTableSection->GetFirstChild();
    582           maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
    583        if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
    584          return RefPtr<Element>(maybeNextRow->AsElement());
    585        }
    586      }
    587    }
    588    // I'm not sure whether this is a possible case since table section
    589    // elements are created automatically.  However, DOM API may create
    590    // <tr> elements without table section elements.  So, let's check it.
    591    else if (maybeNextTableSection->IsHTMLElement(nsGkAtoms::tr)) {
    592      return RefPtr<Element>(maybeNextTableSection->AsElement());
    593    }
    594  }
    595  // Don't return error when the given <tr> element is the last <tr> element in
    596  // the <table>.
    597  return RefPtr<Element>();
    598 }
    599 
    600 NS_IMETHODIMP HTMLEditor::InsertTableColumn(int32_t aNumberOfColumnsToInsert,
    601                                            bool aInsertAfterSelectedCell) {
    602  if (aNumberOfColumnsToInsert <= 0) {
    603    return NS_OK;  // XXX Traditional behavior
    604  }
    605 
    606  AutoEditActionDataSetter editActionData(*this,
    607                                          EditAction::eInsertTableColumn);
    608  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
    609  if (NS_FAILED(rv)) {
    610    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    611    return EditorBase::ToGenericNSResult(rv);
    612  }
    613  const RefPtr<Element> editingHost =
    614      ComputeEditingHost(LimitInBodyElement::No);
    615  if (NS_WARN_IF(editingHost &&
    616                 editingHost->IsContentEditablePlainTextOnly())) {
    617    return NS_ERROR_NOT_AVAILABLE;
    618  }
    619  rv = editActionData.MaybeDispatchBeforeInputEvent();
    620  if (NS_FAILED(rv)) {
    621    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
    622                         "MaybeDispatchBeforeInputEvent(), failed");
    623    return EditorBase::ToGenericNSResult(rv);
    624  }
    625 
    626  Result<RefPtr<Element>, nsresult> cellElementOrError =
    627      GetFirstSelectedCellElementInTable();
    628  if (cellElementOrError.isErr()) {
    629    NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
    630    return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
    631  }
    632 
    633  if (!cellElementOrError.inspect()) {
    634    return NS_OK;
    635  }
    636 
    637  EditorDOMPoint pointToInsert(cellElementOrError.inspect());
    638  if (!pointToInsert.IsSet()) {
    639    NS_WARNING("Found an orphan cell element");
    640    return NS_ERROR_FAILURE;
    641  }
    642  if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
    643    DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
    644    NS_WARNING_ASSERTION(
    645        advanced,
    646        "Failed to set insertion point after current cell, but ignored");
    647  }
    648  rv = InsertTableColumnsWithTransaction(pointToInsert,
    649                                         aNumberOfColumnsToInsert);
    650  NS_WARNING_ASSERTION(
    651      NS_SUCCEEDED(rv),
    652      "HTMLEditor::InsertTableColumnsWithTransaction() failed");
    653  return EditorBase::ToGenericNSResult(rv);
    654 }
    655 
    656 nsresult HTMLEditor::InsertTableColumnsWithTransaction(
    657    const EditorDOMPoint& aPointToInsert, int32_t aNumberOfColumnsToInsert) {
    658  MOZ_ASSERT(IsEditActionDataAvailable());
    659  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
    660  MOZ_ASSERT(aNumberOfColumnsToInsert > 0);
    661 
    662  const RefPtr<PresShell> presShell = GetPresShell();
    663  if (NS_WARN_IF(!presShell)) {
    664    return NS_ERROR_FAILURE;
    665  }
    666 
    667  if (NS_WARN_IF(!aPointToInsert.IsInContentNode()) ||
    668      NS_WARN_IF(!HTMLEditUtils::IsTableRowElement(
    669          *aPointToInsert.ContainerAs<nsIContent>()))) {
    670    return NS_ERROR_FAILURE;
    671  }
    672 
    673  const RefPtr<Element> tableElement =
    674      HTMLEditUtils::GetClosestAncestorTableElement(
    675          *aPointToInsert.ContainerAs<Element>());
    676  if (!tableElement) {
    677    NS_WARNING("There was no ancestor <table> element");
    678    return NS_ERROR_FAILURE;
    679  }
    680 
    681  const Result<TableSize, nsresult> tableSizeOrError =
    682      TableSize::Create(*this, *tableElement);
    683  if (NS_WARN_IF(tableSizeOrError.isErr())) {
    684    return tableSizeOrError.inspectErr();
    685  }
    686  const TableSize& tableSize = tableSizeOrError.inspect();
    687  if (NS_WARN_IF(tableSize.IsEmpty())) {
    688    return NS_ERROR_FAILURE;  // We cannot handle it in an empty table
    689  }
    690 
    691  // If aPointToInsert points non-cell element or end of the row, it means that
    692  // the caller wants to insert column immediately after the last cell of
    693  // the pointing cell element or in the raw.
    694  const bool insertAfterPreviousCell = [&]() {
    695    if (HTMLEditUtils::IsTableCellElement(aPointToInsert.GetChild())) {
    696      return false;  // Insert before the cell element.
    697    }
    698    // There is a previous cell element, we should add a column after it.
    699    Element* previousCellElement =
    700        aPointToInsert.IsEndOfContainer()
    701            ? HTMLEditUtils::GetLastTableCellElementChild(
    702                  *aPointToInsert.ContainerAs<Element>())
    703            : HTMLEditUtils::GetPreviousTableCellElementSibling(
    704                  *aPointToInsert.GetChild());
    705    return previousCellElement != nullptr;
    706  }();
    707 
    708  // Consider the column index in the table from given point and direction.
    709  auto referenceColumnIndexOrError =
    710      [&]() MOZ_CAN_RUN_SCRIPT -> Result<int32_t, nsresult> {
    711    if (!insertAfterPreviousCell) {
    712      if (aPointToInsert.IsEndOfContainer()) {
    713        return tableSize.mColumnCount;  // Empty row, append columns to the end
    714      }
    715      // Insert columns immediately before current column.
    716      const OwningNonNull<Element> tableCellElement =
    717          *aPointToInsert.GetChild()->AsElement();
    718      MOZ_ASSERT(HTMLEditUtils::IsTableCellElement(*tableCellElement));
    719      CellIndexes cellIndexes(*tableCellElement, presShell);
    720      if (NS_WARN_IF(cellIndexes.isErr())) {
    721        return Err(NS_ERROR_FAILURE);
    722      }
    723      return cellIndexes.mColumn;
    724    }
    725 
    726    // Otherwise, insert columns immediately after the previous column.
    727    Element* previousCellElement =
    728        aPointToInsert.IsEndOfContainer()
    729            ? HTMLEditUtils::GetLastTableCellElementChild(
    730                  *aPointToInsert.ContainerAs<Element>())
    731            : HTMLEditUtils::GetPreviousTableCellElementSibling(
    732                  *aPointToInsert.GetChild());
    733    MOZ_ASSERT(previousCellElement);
    734    CellIndexes cellIndexes(*previousCellElement, presShell);
    735    if (NS_WARN_IF(cellIndexes.isErr())) {
    736      return Err(NS_ERROR_FAILURE);
    737    }
    738    return cellIndexes.mColumn;
    739  }();
    740  if (MOZ_UNLIKELY(referenceColumnIndexOrError.isErr())) {
    741    return referenceColumnIndexOrError.unwrapErr();
    742  }
    743 
    744  AutoPlaceholderBatch treateAsOneTransaction(
    745      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    746  // Prevent auto insertion of <br> element in new cell until we're done.
    747  // XXX Why? We should put <br> element to every cell element before inserting
    748  //     the cells into the tree.
    749  IgnoredErrorResult error;
    750  AutoEditSubActionNotifier startToHandleEditSubAction(
    751      *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
    752  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    753    return error.StealNSResult();
    754  }
    755  NS_WARNING_ASSERTION(
    756      !error.Failed(),
    757      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    758  error.SuppressException();
    759 
    760  // Suppress Rules System selection munging.
    761  AutoTransactionsConserveSelection dontChangeSelection(*this);
    762 
    763  // If we are inserting after all existing columns, make sure table is
    764  // "well formed" before appending new column.
    765  // XXX As far as I've tested, NormalizeTableInternal() always fails to
    766  //     normalize non-rectangular table.  So, the following CellData will
    767  //     fail if the table is not rectangle.
    768  if (referenceColumnIndexOrError.inspect() >= tableSize.mColumnCount) {
    769    DebugOnly<nsresult> rv = NormalizeTableInternal(*tableElement);
    770    if (MOZ_UNLIKELY(Destroyed())) {
    771      NS_WARNING(
    772          "HTMLEditor::NormalizeTableInternal() caused destroying the editor");
    773      return NS_ERROR_EDITOR_DESTROYED;
    774    }
    775    NS_WARNING_ASSERTION(
    776        NS_SUCCEEDED(rv),
    777        "HTMLEditor::NormalizeTableInternal() failed, but ignored");
    778  }
    779 
    780  // First, we should collect all reference nodes to insert new table cells.
    781  AutoTArray<CellData, 32> arrayOfCellData;
    782  {
    783    arrayOfCellData.SetCapacity(tableSize.mRowCount);
    784    for (const int32_t rowIndex : IntegerRange(tableSize.mRowCount)) {
    785      const auto cellData = CellData::AtIndexInTableElement(
    786          *this, *tableElement, rowIndex,
    787          referenceColumnIndexOrError.inspect());
    788      if (NS_WARN_IF(cellData.FailedOrNotFound())) {
    789        return NS_ERROR_FAILURE;
    790      }
    791      arrayOfCellData.AppendElement(cellData);
    792    }
    793  }
    794 
    795  // Note that checking whether the editor destroyed or not should be done
    796  // after inserting all cell elements.  Otherwise, the table is left as
    797  // not a rectangle.
    798  auto cellElementToPutCaretOrError =
    799      [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
    800    // Block legacy mutation events for making this job simpler.
    801    nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
    802    RefPtr<Element> cellElementToPutCaret;
    803    for (const CellData& cellData : arrayOfCellData) {
    804      // Don't fail entire process if we fail to find a cell (may fail just in
    805      // particular rows with < adequate cells per row).
    806      // XXX So, here wants to know whether the CellData actually failed
    807      //     above.  Fix this later.
    808      if (!cellData.mElement) {
    809        continue;
    810      }
    811 
    812      if ((!insertAfterPreviousCell && cellData.IsSpannedFromOtherColumn()) ||
    813          (insertAfterPreviousCell &&
    814           cellData.IsNextColumnSpannedFromOtherColumn())) {
    815        // If we have a cell spanning this location, simply increase its
    816        // colspan to keep table rectangular.
    817        if (cellData.mColSpan > 0) {
    818          DebugOnly<nsresult> rvIgnored = SetColSpan(
    819              cellData.mElement, cellData.mColSpan + aNumberOfColumnsToInsert);
    820          NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    821                               "HTMLEditor::SetColSpan() failed, but ignored");
    822        }
    823        continue;
    824      }
    825 
    826      EditorDOMPoint pointToInsert = [&]() {
    827        if (!insertAfterPreviousCell) {
    828          // Insert before the reference cell.
    829          return EditorDOMPoint(cellData.mElement);
    830        }
    831        if (!cellData.mElement->GetNextSibling()) {
    832          // Insert after the reference cell, but nothing follows it, append
    833          // to the end of the row.
    834          return EditorDOMPoint::AtEndOf(*cellData.mElement->GetParentNode());
    835        }
    836        // Otherwise, returns immediately before the next sibling.  Note that
    837        // the next sibling may not be a table cell element.  E.g., it may be
    838        // a text node containing only white-spaces in most cases.
    839        return EditorDOMPoint(cellData.mElement->GetNextSibling());
    840      }();
    841      if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
    842        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    843      }
    844      Result<CreateElementResult, nsresult> insertCellElementsResult =
    845          InsertTableCellsWithTransaction(pointToInsert,
    846                                          aNumberOfColumnsToInsert);
    847      if (MOZ_UNLIKELY(insertCellElementsResult.isErr())) {
    848        NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
    849        return insertCellElementsResult.propagateErr();
    850      }
    851      CreateElementResult unwrappedInsertCellElementsResult =
    852          insertCellElementsResult.unwrap();
    853      // We'll update selection later into the first inserted cell element in
    854      // the current row.
    855      unwrappedInsertCellElementsResult.IgnoreCaretPointSuggestion();
    856      if (pointToInsert.ContainerAs<Element>() ==
    857          aPointToInsert.ContainerAs<Element>()) {
    858        cellElementToPutCaret =
    859            unwrappedInsertCellElementsResult.UnwrapNewNode();
    860        MOZ_ASSERT(cellElementToPutCaret);
    861        MOZ_ASSERT(HTMLEditUtils::IsTableCellElement(*cellElementToPutCaret));
    862      }
    863    }
    864    return cellElementToPutCaret;
    865  }();
    866  if (MOZ_UNLIKELY(cellElementToPutCaretOrError.isErr())) {
    867    return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
    868                                   : cellElementToPutCaretOrError.unwrapErr();
    869  }
    870  const RefPtr<Element> cellElementToPutCaret =
    871      cellElementToPutCaretOrError.unwrap();
    872  NS_WARNING_ASSERTION(
    873      cellElementToPutCaret,
    874      "Didn't find the first inserted cell element in the specified row");
    875  if (MOZ_LIKELY(cellElementToPutCaret)) {
    876    CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
    877  }
    878  return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
    879 }
    880 
    881 NS_IMETHODIMP HTMLEditor::InsertTableRow(int32_t aNumberOfRowsToInsert,
    882                                         bool aInsertAfterSelectedCell) {
    883  if (aNumberOfRowsToInsert <= 0) {
    884    return NS_OK;
    885  }
    886 
    887  AutoEditActionDataSetter editActionData(*this,
    888                                          EditAction::eInsertTableRowElement);
    889  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
    890  if (NS_FAILED(rv)) {
    891    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
    892    return EditorBase::ToGenericNSResult(rv);
    893  }
    894  const RefPtr<Element> editingHost =
    895      ComputeEditingHost(LimitInBodyElement::No);
    896  if (NS_WARN_IF(editingHost &&
    897                 editingHost->IsContentEditablePlainTextOnly())) {
    898    return NS_ERROR_NOT_AVAILABLE;
    899  }
    900  rv = editActionData.MaybeDispatchBeforeInputEvent();
    901  if (NS_FAILED(rv)) {
    902    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
    903                         "MaybeDispatchBeforeInputEvent(), failed");
    904    return EditorBase::ToGenericNSResult(rv);
    905  }
    906 
    907  Result<RefPtr<Element>, nsresult> cellElementOrError =
    908      GetFirstSelectedCellElementInTable();
    909  if (cellElementOrError.isErr()) {
    910    NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
    911    return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
    912  }
    913 
    914  if (!cellElementOrError.inspect()) {
    915    return NS_OK;
    916  }
    917 
    918  rv = InsertTableRowsWithTransaction(
    919      MOZ_KnownLive(*cellElementOrError.inspect()), aNumberOfRowsToInsert,
    920      aInsertAfterSelectedCell ? InsertPosition::eAfterSelectedCell
    921                               : InsertPosition::eBeforeSelectedCell);
    922  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    923                       "HTMLEditor::InsertTableRowsWithTransaction() failed");
    924  return EditorBase::ToGenericNSResult(rv);
    925 }
    926 
    927 nsresult HTMLEditor::InsertTableRowsWithTransaction(
    928    Element& aCellElement, int32_t aNumberOfRowsToInsert,
    929    InsertPosition aInsertPosition) {
    930  MOZ_ASSERT(IsEditActionDataAvailable());
    931  MOZ_ASSERT(HTMLEditUtils::IsTableCellElement(aCellElement));
    932 
    933  const RefPtr<PresShell> presShell = GetPresShell();
    934  if (MOZ_UNLIKELY(NS_WARN_IF(!presShell))) {
    935    return NS_ERROR_FAILURE;
    936  }
    937 
    938  if (MOZ_UNLIKELY(
    939          !HTMLEditUtils::IsTableRowElement(aCellElement.GetParentElement()))) {
    940    NS_WARNING("Tried to insert columns to non-<tr> element");
    941    return NS_ERROR_FAILURE;
    942  }
    943 
    944  const RefPtr<Element> tableElement =
    945      HTMLEditUtils::GetClosestAncestorTableElement(aCellElement);
    946  if (MOZ_UNLIKELY(!tableElement)) {
    947    return NS_OK;
    948  }
    949 
    950  const Result<TableSize, nsresult> tableSizeOrError =
    951      TableSize::Create(*this, *tableElement);
    952  if (NS_WARN_IF(tableSizeOrError.isErr())) {
    953    return tableSizeOrError.inspectErr();
    954  }
    955  const TableSize& tableSize = tableSizeOrError.inspect();
    956  // Should not be empty since we've already found a cell.
    957  MOZ_ASSERT(!tableSize.IsEmpty());
    958 
    959  const CellIndexes cellIndexes(aCellElement, presShell);
    960  if (NS_WARN_IF(cellIndexes.isErr())) {
    961    return NS_ERROR_FAILURE;
    962  }
    963 
    964  // Get more data for current cell in row we are inserting at because we need
    965  // rowspan.
    966  const auto cellData =
    967      CellData::AtIndexInTableElement(*this, *tableElement, cellIndexes);
    968  if (NS_WARN_IF(cellData.FailedOrNotFound())) {
    969    return NS_ERROR_FAILURE;
    970  }
    971  MOZ_ASSERT(&aCellElement == cellData.mElement);
    972 
    973  AutoPlaceholderBatch treateAsOneTransaction(
    974      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    975  // Prevent auto insertion of BR in new cell until we're done
    976  IgnoredErrorResult error;
    977  AutoEditSubActionNotifier startToHandleEditSubAction(
    978      *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
    979  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    980    return error.StealNSResult();
    981  }
    982  NS_WARNING_ASSERTION(
    983      !error.Failed(),
    984      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    985 
    986  struct ElementWithNewRowSpan final {
    987    const OwningNonNull<Element> mCellElement;
    988    const int32_t mNewRowSpan;
    989 
    990    ElementWithNewRowSpan(Element& aCellElement, int32_t aNewRowSpan)
    991        : mCellElement(aCellElement), mNewRowSpan(aNewRowSpan) {}
    992  };
    993  AutoTArray<ElementWithNewRowSpan, 16> cellElementsToModifyRowSpan;
    994  if (aInsertPosition == InsertPosition::eAfterSelectedCell &&
    995      !cellData.mRowSpan) {
    996    // Detect when user is adding after a rowspan=0 case.
    997    // Assume they want to stop the "0" behavior and really add a new row.
    998    // Thus we set the rowspan to its true value.
    999    cellElementsToModifyRowSpan.AppendElement(
   1000        ElementWithNewRowSpan(aCellElement, cellData.mEffectiveRowSpan));
   1001  }
   1002 
   1003  struct MOZ_STACK_CLASS TableRowData {
   1004    RefPtr<Element> mElement;
   1005    int32_t mNumberOfCellsInStartRow;
   1006    int32_t mOffsetInTRElementToPutCaret;
   1007  };
   1008  const auto referenceRowDataOrError = [&]() -> Result<TableRowData, nsresult> {
   1009    const int32_t startRowIndex =
   1010        aInsertPosition == InsertPosition::eBeforeSelectedCell
   1011            ? cellData.mCurrent.mRow
   1012            : cellData.mCurrent.mRow + cellData.mEffectiveRowSpan;
   1013    if (startRowIndex < tableSize.mRowCount) {
   1014      // We are inserting above an existing row.  Get each cell in the insert
   1015      // row to adjust for rowspan effects while we count how many cells are
   1016      // needed.
   1017      RefPtr<Element> referenceRowElement;
   1018      int32_t numberOfCellsInStartRow = 0;
   1019      int32_t offsetInTRElementToPutCaret = 0;
   1020      for (int32_t colIndex = 0;;) {
   1021        const auto cellDataInStartRow = CellData::AtIndexInTableElement(
   1022            *this, *tableElement, startRowIndex, colIndex);
   1023        if (cellDataInStartRow.FailedOrNotFound()) {
   1024          break;  // Perhaps, we reach end of the row.
   1025        }
   1026 
   1027        // XXX So, this is impossible case. Will be removed.
   1028        if (!cellDataInStartRow.mElement) {
   1029          NS_WARNING("CellData::Update() succeeded, but didn't set mElement");
   1030          break;
   1031        }
   1032 
   1033        if (cellDataInStartRow.IsSpannedFromOtherRow()) {
   1034          // We have a cell spanning this location.  Increase its rowspan.
   1035          // Note that if rowspan is 0, we do nothing since that cell should
   1036          // automatically extend into the new row.
   1037          if (cellDataInStartRow.mRowSpan > 0) {
   1038            cellElementsToModifyRowSpan.AppendElement(ElementWithNewRowSpan(
   1039                *cellDataInStartRow.mElement,
   1040                cellDataInStartRow.mRowSpan + aNumberOfRowsToInsert));
   1041          }
   1042          colIndex = cellDataInStartRow.NextColumnIndex();
   1043          continue;
   1044        }
   1045 
   1046        if (colIndex < cellDataInStartRow.mCurrent.mColumn) {
   1047          offsetInTRElementToPutCaret++;
   1048        }
   1049 
   1050        numberOfCellsInStartRow += cellDataInStartRow.mEffectiveColSpan;
   1051        if (!referenceRowElement) {
   1052          if (Element* maybeTableRowElement =
   1053                  cellDataInStartRow.mElement->GetParentElement()) {
   1054            if (HTMLEditUtils::IsTableRowElement(*maybeTableRowElement)) {
   1055              referenceRowElement = maybeTableRowElement;
   1056            }
   1057          }
   1058        }
   1059        MOZ_ASSERT(colIndex < cellDataInStartRow.NextColumnIndex());
   1060        colIndex = cellDataInStartRow.NextColumnIndex();
   1061      }
   1062      if (MOZ_UNLIKELY(!referenceRowElement)) {
   1063        NS_WARNING(
   1064            "Reference row element to insert new row elements was not found");
   1065        return Err(NS_ERROR_FAILURE);
   1066      }
   1067      return TableRowData{std::move(referenceRowElement),
   1068                          numberOfCellsInStartRow, offsetInTRElementToPutCaret};
   1069    }
   1070 
   1071    // We are adding a new row after all others.  If it weren't for colspan=0
   1072    // effect,  we could simply use tableSize.mColumnCount for number of new
   1073    // cells...
   1074    // XXX colspan=0 support has now been removed in table layout so maybe this
   1075    //     can be cleaned up now? (bug 1243183)
   1076    int32_t numberOfCellsInStartRow = tableSize.mColumnCount;
   1077    int32_t offsetInTRElementToPutCaret = 0;
   1078 
   1079    // but we must compensate for all cells with rowspan = 0 in the last row.
   1080    const int32_t lastRowIndex = tableSize.mRowCount - 1;
   1081    for (int32_t colIndex = 0;;) {
   1082      const auto cellDataInLastRow = CellData::AtIndexInTableElement(
   1083          *this, *tableElement, lastRowIndex, colIndex);
   1084      if (cellDataInLastRow.FailedOrNotFound()) {
   1085        break;  // Perhaps, we reach end of the row.
   1086      }
   1087 
   1088      if (!cellDataInLastRow.mRowSpan) {
   1089        MOZ_ASSERT(numberOfCellsInStartRow >=
   1090                   cellDataInLastRow.mEffectiveColSpan);
   1091        numberOfCellsInStartRow -= cellDataInLastRow.mEffectiveColSpan;
   1092      } else if (colIndex < cellDataInLastRow.mCurrent.mColumn) {
   1093        offsetInTRElementToPutCaret++;
   1094      }
   1095      MOZ_ASSERT(colIndex < cellDataInLastRow.NextColumnIndex());
   1096      colIndex = cellDataInLastRow.NextColumnIndex();
   1097    }
   1098    return TableRowData{nullptr, numberOfCellsInStartRow,
   1099                        offsetInTRElementToPutCaret};
   1100  }();
   1101  if (MOZ_UNLIKELY(referenceRowDataOrError.isErr())) {
   1102    return referenceRowDataOrError.inspectErr();
   1103  }
   1104 
   1105  const TableRowData& referenceRowData = referenceRowDataOrError.inspect();
   1106  if (MOZ_UNLIKELY(!referenceRowData.mNumberOfCellsInStartRow)) {
   1107    NS_WARNING("There was no cell element in the row");
   1108    return NS_OK;
   1109  }
   1110 
   1111  MOZ_ASSERT_IF(referenceRowData.mElement,
   1112                HTMLEditUtils::IsTableRowElement(*referenceRowData.mElement));
   1113  if (NS_WARN_IF(
   1114          !HTMLEditUtils::IsTableRowElement(aCellElement.GetParentElement()))) {
   1115    return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   1116  }
   1117 
   1118  // The row parent and offset where we will insert new row.
   1119  EditorDOMPoint pointToInsert = [&]() {
   1120    if (aInsertPosition == InsertPosition::eBeforeSelectedCell) {
   1121      MOZ_ASSERT(referenceRowData.mElement);
   1122      return EditorDOMPoint(referenceRowData.mElement);
   1123    }
   1124    // Look for the last row element in the same table section or immediately
   1125    // before the reference row element.  Then, we can insert new rows
   1126    // immediately after the given row element.
   1127    Element* lastRowElement = nullptr;
   1128    for (Element* rowElement = aCellElement.GetParentElement();
   1129         rowElement && rowElement != referenceRowData.mElement;) {
   1130      lastRowElement = rowElement;
   1131      const Result<RefPtr<Element>, nsresult> nextRowElementOrError =
   1132          GetNextTableRowElement(*rowElement);
   1133      if (MOZ_UNLIKELY(nextRowElementOrError.isErr())) {
   1134        NS_WARNING("HTMLEditor::GetNextTableRowElement() failed");
   1135        return EditorDOMPoint();
   1136      }
   1137      rowElement = nextRowElementOrError.inspect();
   1138    }
   1139    MOZ_ASSERT(lastRowElement);
   1140    return EditorDOMPoint::After(*lastRowElement);
   1141  }();
   1142  if (NS_WARN_IF(!pointToInsert.IsSet())) {
   1143    return NS_ERROR_FAILURE;
   1144  }
   1145  // Note that checking whether the editor destroyed or not should be done
   1146  // after inserting all cell elements.  Otherwise, the table is left as
   1147  // not a rectangle.
   1148  auto firstInsertedTRElementOrError =
   1149      [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
   1150    // Block legacy mutation events for making this job simpler.
   1151    nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
   1152 
   1153    // Suppress Rules System selection munging.
   1154    AutoTransactionsConserveSelection dontChangeSelection(*this);
   1155 
   1156    for (const ElementWithNewRowSpan& cellElementAndNewRowSpan :
   1157         cellElementsToModifyRowSpan) {
   1158      DebugOnly<nsresult> rvIgnored =
   1159          SetRowSpan(MOZ_KnownLive(cellElementAndNewRowSpan.mCellElement),
   1160                     cellElementAndNewRowSpan.mNewRowSpan);
   1161      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1162                           "HTMLEditor::SetRowSpan() failed, but ignored");
   1163    }
   1164 
   1165    RefPtr<Element> firstInsertedTRElement;
   1166    IgnoredErrorResult error;
   1167    for ([[maybe_unused]] const int32_t rowIndex :
   1168         Reversed(IntegerRange(aNumberOfRowsToInsert))) {
   1169      // Create a new row
   1170      RefPtr<Element> newRowElement = CreateElementWithDefaults(*nsGkAtoms::tr);
   1171      if (!newRowElement) {
   1172        NS_WARNING(
   1173            "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::tr) failed");
   1174        return Err(NS_ERROR_FAILURE);
   1175      }
   1176 
   1177      for ([[maybe_unused]] const int32_t i :
   1178           IntegerRange(referenceRowData.mNumberOfCellsInStartRow)) {
   1179        const RefPtr<Element> newCellElement =
   1180            CreateElementWithDefaults(*nsGkAtoms::td);
   1181        if (!newCellElement) {
   1182          NS_WARNING(
   1183              "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
   1184          return Err(NS_ERROR_FAILURE);
   1185        }
   1186        newRowElement->AppendChild(*newCellElement, error);
   1187        if (error.Failed()) {
   1188          NS_WARNING("nsINode::AppendChild() failed");
   1189          return Err(error.StealNSResult());
   1190        }
   1191      }
   1192 
   1193      AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
   1194      Result<CreateElementResult, nsresult> insertNewRowResult =
   1195          InsertNodeWithTransaction<Element>(*newRowElement, pointToInsert);
   1196      if (MOZ_UNLIKELY(insertNewRowResult.isErr())) {
   1197        if (insertNewRowResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
   1198          NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
   1199          return insertNewRowResult.propagateErr();
   1200        }
   1201        NS_WARNING(
   1202            "EditorBase::InsertNodeWithTransaction() failed, but ignored");
   1203      }
   1204      firstInsertedTRElement = std::move(newRowElement);
   1205      // We'll update selection later.
   1206      insertNewRowResult.inspect().IgnoreCaretPointSuggestion();
   1207    }
   1208    return firstInsertedTRElement;
   1209  }();
   1210  if (NS_WARN_IF(Destroyed())) {
   1211    return NS_ERROR_EDITOR_DESTROYED;
   1212  }
   1213  if (MOZ_UNLIKELY(firstInsertedTRElementOrError.isErr())) {
   1214    return firstInsertedTRElementOrError.unwrapErr();
   1215  }
   1216 
   1217  const OwningNonNull<Element> cellElementToPutCaret = [&]() {
   1218    if (MOZ_LIKELY(firstInsertedTRElementOrError.inspect())) {
   1219      EditorRawDOMPoint point(firstInsertedTRElementOrError.inspect(),
   1220                              referenceRowData.mOffsetInTRElementToPutCaret);
   1221      if (MOZ_LIKELY(point.IsSetAndValid()) &&
   1222          MOZ_LIKELY(HTMLEditUtils::IsTableCellElement(point.GetChild()))) {
   1223        return OwningNonNull<Element>(*point.GetChild()->AsElement());
   1224      }
   1225    }
   1226    return OwningNonNull<Element>(aCellElement);
   1227  }();
   1228  CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
   1229  return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
   1230 }
   1231 
   1232 nsresult HTMLEditor::DeleteTableElementAndChildrenWithTransaction(
   1233    Element& aTableElement) {
   1234  MOZ_ASSERT(IsEditActionDataAvailable());
   1235 
   1236  // Block selectionchange event.  It's enough to dispatch selectionchange
   1237  // event immediately after removing the table element.
   1238  {
   1239    AutoHideSelectionChanges hideSelection(SelectionRef());
   1240 
   1241    // Select the <table> element after clear current selection.
   1242    if (SelectionRef().RangeCount()) {
   1243      ErrorResult error;
   1244      SelectionRef().RemoveAllRanges(error);
   1245      if (error.Failed()) {
   1246        NS_WARNING("Selection::RemoveAllRanges() failed");
   1247        return error.StealNSResult();
   1248      }
   1249    }
   1250 
   1251    RefPtr<nsRange> range = nsRange::Create(&aTableElement);
   1252    ErrorResult error;
   1253    range->SelectNode(aTableElement, error);
   1254    if (error.Failed()) {
   1255      NS_WARNING("nsRange::SelectNode() failed");
   1256      return error.StealNSResult();
   1257    }
   1258    SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*range, error);
   1259    if (error.Failed()) {
   1260      NS_WARNING(
   1261          "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
   1262      return error.StealNSResult();
   1263    }
   1264 
   1265 #ifdef DEBUG
   1266    range = SelectionRef().GetRangeAt(0);
   1267    MOZ_ASSERT(range);
   1268    MOZ_ASSERT(range->GetStartContainer() == aTableElement.GetParent());
   1269    MOZ_ASSERT(range->GetEndContainer() == aTableElement.GetParent());
   1270    MOZ_ASSERT(range->GetChildAtStartOffset() == &aTableElement);
   1271    MOZ_ASSERT(range->GetChildAtEndOffset() == aTableElement.GetNextSibling());
   1272 #endif  // #ifdef DEBUG
   1273  }
   1274 
   1275  nsresult rv = DeleteSelectionAsSubAction(eNext, eStrip);
   1276  NS_WARNING_ASSERTION(
   1277      NS_SUCCEEDED(rv),
   1278      "EditorBase::DeleteSelectionAsSubAction(eNext, eStrip) failed");
   1279  return rv;
   1280 }
   1281 
   1282 NS_IMETHODIMP HTMLEditor::DeleteTable() {
   1283  AutoEditActionDataSetter editActionData(*this,
   1284                                          EditAction::eRemoveTableElement);
   1285  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   1286  if (NS_FAILED(rv)) {
   1287    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   1288    return EditorBase::ToGenericNSResult(rv);
   1289  }
   1290  const RefPtr<Element> editingHost =
   1291      ComputeEditingHost(LimitInBodyElement::No);
   1292  if (NS_WARN_IF(editingHost &&
   1293                 editingHost->IsContentEditablePlainTextOnly())) {
   1294    return NS_ERROR_NOT_AVAILABLE;
   1295  }
   1296  rv = editActionData.MaybeDispatchBeforeInputEvent();
   1297  if (NS_FAILED(rv)) {
   1298    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   1299                         "MaybeDispatchBeforeInputEvent(), failed");
   1300    return EditorBase::ToGenericNSResult(rv);
   1301  }
   1302 
   1303  RefPtr<Element> table;
   1304  rv = GetCellContext(getter_AddRefs(table), nullptr, nullptr, nullptr, nullptr,
   1305                      nullptr);
   1306  if (NS_FAILED(rv)) {
   1307    NS_WARNING("HTMLEditor::GetCellContext() failed");
   1308    return EditorBase::ToGenericNSResult(rv);
   1309  }
   1310  if (!table) {
   1311    NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
   1312    return NS_ERROR_FAILURE;
   1313  }
   1314 
   1315  AutoPlaceholderBatch treateAsOneTransaction(
   1316      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   1317  rv = DeleteTableElementAndChildrenWithTransaction(*table);
   1318  NS_WARNING_ASSERTION(
   1319      NS_SUCCEEDED(rv),
   1320      "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
   1321  return EditorBase::ToGenericNSResult(rv);
   1322 }
   1323 
   1324 NS_IMETHODIMP HTMLEditor::DeleteTableCell(int32_t aNumberOfCellsToDelete) {
   1325  AutoEditActionDataSetter editActionData(*this,
   1326                                          EditAction::eRemoveTableCellElement);
   1327  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   1328  if (NS_FAILED(rv)) {
   1329    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   1330    return EditorBase::ToGenericNSResult(rv);
   1331  }
   1332  const RefPtr<Element> editingHost =
   1333      ComputeEditingHost(LimitInBodyElement::No);
   1334  if (NS_WARN_IF(editingHost &&
   1335                 editingHost->IsContentEditablePlainTextOnly())) {
   1336    return NS_ERROR_NOT_AVAILABLE;
   1337  }
   1338  rv = editActionData.MaybeDispatchBeforeInputEvent();
   1339  if (NS_FAILED(rv)) {
   1340    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   1341                         "MaybeDispatchBeforeInputEvent(), failed");
   1342    return EditorBase::ToGenericNSResult(rv);
   1343  }
   1344 
   1345  rv = DeleteTableCellWithTransaction(aNumberOfCellsToDelete);
   1346  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1347                       "HTMLEditor::DeleteTableCellWithTransaction() failed");
   1348  return EditorBase::ToGenericNSResult(rv);
   1349 }
   1350 
   1351 nsresult HTMLEditor::DeleteTableCellWithTransaction(
   1352    int32_t aNumberOfCellsToDelete) {
   1353  MOZ_ASSERT(IsEditActionDataAvailable());
   1354 
   1355  RefPtr<Element> table;
   1356  RefPtr<Element> cell;
   1357  int32_t startRowIndex, startColIndex;
   1358 
   1359  nsresult rv =
   1360      GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   1361                     nullptr, &startRowIndex, &startColIndex);
   1362  if (NS_FAILED(rv)) {
   1363    NS_WARNING("HTMLEditor::GetCellContext() failed");
   1364    return rv;
   1365  }
   1366  if (!table || !cell) {
   1367    NS_WARNING(
   1368        "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
   1369    // Don't fail if we didn't find a table or cell.
   1370    return NS_OK;
   1371  }
   1372 
   1373  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   1374    return NS_ERROR_FAILURE;  // XXX Should we just return NS_OK?
   1375  }
   1376 
   1377  AutoPlaceholderBatch treateAsOneTransaction(
   1378      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   1379  // Prevent rules testing until we're done
   1380  IgnoredErrorResult ignoredError;
   1381  AutoEditSubActionNotifier startToHandleEditSubAction(
   1382      *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
   1383  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   1384    return ignoredError.StealNSResult();
   1385  }
   1386  NS_WARNING_ASSERTION(
   1387      !ignoredError.Failed(),
   1388      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   1389 
   1390  MOZ_ASSERT(SelectionRef().RangeCount());
   1391 
   1392  SelectedTableCellScanner scanner(SelectionRef());
   1393 
   1394  Result<TableSize, nsresult> tableSizeOrError =
   1395      TableSize::Create(*this, *table);
   1396  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   1397    return tableSizeOrError.unwrapErr();
   1398  }
   1399  // FYI: Cannot be a const reference because the row count will be updated
   1400  TableSize tableSize = tableSizeOrError.unwrap();
   1401  MOZ_ASSERT(!tableSize.IsEmpty());
   1402 
   1403  // If only one cell is selected or no cell is selected, remove cells
   1404  // starting from the first selected cell or a cell containing first
   1405  // selection range.
   1406  if (!scanner.IsInTableCellSelectionMode() ||
   1407      SelectionRef().RangeCount() == 1) {
   1408    for (int32_t i = 0; i < aNumberOfCellsToDelete; i++) {
   1409      nsresult rv =
   1410          GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   1411                         nullptr, &startRowIndex, &startColIndex);
   1412      if (NS_FAILED(rv)) {
   1413        NS_WARNING("HTMLEditor::GetCellContext() failed");
   1414        return rv;
   1415      }
   1416      if (!table || !cell) {
   1417        NS_WARNING(
   1418            "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
   1419        // Don't fail if no cell found
   1420        return NS_OK;
   1421      }
   1422 
   1423      int32_t numberOfCellsInRow = GetNumberOfCellsInRow(*table, startRowIndex);
   1424      NS_WARNING_ASSERTION(
   1425          numberOfCellsInRow >= 0,
   1426          "HTMLEditor::GetNumberOfCellsInRow() failed, but ignored");
   1427 
   1428      if (numberOfCellsInRow == 1) {
   1429        // Remove <tr> or <table> if we're removing all cells in the row or
   1430        // the table.
   1431        if (tableSize.mRowCount == 1) {
   1432          nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
   1433          NS_WARNING_ASSERTION(
   1434              NS_SUCCEEDED(rv),
   1435              "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
   1436              "failed");
   1437          return rv;
   1438        }
   1439 
   1440        // We need to call DeleteSelectedTableRowsWithTransaction() to handle
   1441        // cells with rowspan attribute.
   1442        rv = DeleteSelectedTableRowsWithTransaction(1);
   1443        if (NS_FAILED(rv)) {
   1444          NS_WARNING(
   1445              "HTMLEditor::DeleteSelectedTableRowsWithTransaction(1) failed");
   1446          return rv;
   1447        }
   1448 
   1449        // Adjust table rows simply.  In strictly speaking, we should
   1450        // recompute table size with the latest layout information since
   1451        // mutation event listener may have changed the DOM tree. However,
   1452        // this is not in usual path of Firefox.  So, we can assume that
   1453        // there are no mutation event listeners.
   1454        MOZ_ASSERT(tableSize.mRowCount);
   1455        tableSize.mRowCount--;
   1456        continue;
   1457      }
   1458 
   1459      // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
   1460      // destructor
   1461      AutoSelectionSetterAfterTableEdit setCaret(
   1462          *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
   1463      AutoTransactionsConserveSelection dontChangeSelection(*this);
   1464 
   1465      // XXX Removing cell element causes not adjusting colspan.
   1466      rv = DeleteNodeWithTransaction(*cell);
   1467      // If we fail, don't try to delete any more cells???
   1468      if (NS_FAILED(rv)) {
   1469        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1470        return rv;
   1471      }
   1472      // Note that we don't refer column number in this loop.  So, it must
   1473      // be safe not to recompute table size since number of row is synced
   1474      // above.
   1475    }
   1476    return NS_OK;
   1477  }
   1478 
   1479  // When 2 or more cells are selected, ignore aNumberOfCellsToRemove and
   1480  // remove all selected cells.
   1481  const RefPtr<PresShell> presShell{GetPresShell()};
   1482  // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because scanner grabs
   1483  // it until it's destroyed later.
   1484  const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
   1485                                     presShell);
   1486  if (NS_WARN_IF(firstCellIndexes.isErr())) {
   1487    return NS_ERROR_FAILURE;
   1488  }
   1489  startRowIndex = firstCellIndexes.mRow;
   1490  startColIndex = firstCellIndexes.mColumn;
   1491 
   1492  // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
   1493  // destructor
   1494  AutoSelectionSetterAfterTableEdit setCaret(
   1495      *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
   1496  AutoTransactionsConserveSelection dontChangeSelection(*this);
   1497 
   1498  bool checkToDeleteRow = true;
   1499  bool checkToDeleteColumn = true;
   1500  for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
   1501       selectedCellElement;) {
   1502    if (checkToDeleteRow) {
   1503      // Optimize to delete an entire row
   1504      // Clear so we don't repeat AllCellsInRowSelected within the same row
   1505      checkToDeleteRow = false;
   1506      if (AllCellsInRowSelected(table, startRowIndex, tableSize.mColumnCount)) {
   1507        // First, find the next cell in a different row to continue after we
   1508        // delete this row.
   1509        int32_t nextRow = startRowIndex;
   1510        while (nextRow == startRowIndex) {
   1511          selectedCellElement = scanner.GetNextElement();
   1512          if (!selectedCellElement) {
   1513            break;
   1514          }
   1515          const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
   1516                                                    presShell);
   1517          if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
   1518            return NS_ERROR_FAILURE;
   1519          }
   1520          nextRow = nextSelectedCellIndexes.mRow;
   1521          startColIndex = nextSelectedCellIndexes.mColumn;
   1522        }
   1523        if (tableSize.mRowCount == 1) {
   1524          nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
   1525          NS_WARNING_ASSERTION(
   1526              NS_SUCCEEDED(rv),
   1527              "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
   1528              "failed");
   1529          return rv;
   1530        }
   1531        nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
   1532        if (NS_FAILED(rv)) {
   1533          NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
   1534          return rv;
   1535        }
   1536        // Adjust table rows simply.  In strictly speaking, we should
   1537        // recompute table size with the latest layout information since
   1538        // mutation event listener may have changed the DOM tree. However,
   1539        // this is not in usual path of Firefox.  So, we can assume that
   1540        // there are no mutation event listeners.
   1541        MOZ_ASSERT(tableSize.mRowCount);
   1542        tableSize.mRowCount--;
   1543        if (!selectedCellElement) {
   1544          break;  // XXX Seems like a dead path
   1545        }
   1546        // For the next cell: Subtract 1 for row we deleted
   1547        startRowIndex = nextRow - 1;
   1548        // Set true since we know we will look at a new row next
   1549        checkToDeleteRow = true;
   1550        continue;
   1551      }
   1552    }
   1553 
   1554    if (checkToDeleteColumn) {
   1555      // Optimize to delete an entire column
   1556      // Clear this so we don't repeat AllCellsInColSelected within the same Col
   1557      checkToDeleteColumn = false;
   1558      if (AllCellsInColumnSelected(table, startColIndex,
   1559                                   tableSize.mColumnCount)) {
   1560        // First, find the next cell in a different column to continue after
   1561        // we delete this column.
   1562        int32_t nextCol = startColIndex;
   1563        while (nextCol == startColIndex) {
   1564          selectedCellElement = scanner.GetNextElement();
   1565          if (!selectedCellElement) {
   1566            break;
   1567          }
   1568          const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
   1569                                                    presShell);
   1570          if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
   1571            return NS_ERROR_FAILURE;
   1572          }
   1573          startRowIndex = nextSelectedCellIndexes.mRow;
   1574          nextCol = nextSelectedCellIndexes.mColumn;
   1575        }
   1576        // Delete all cells which belong to the column.
   1577        nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
   1578        if (NS_FAILED(rv)) {
   1579          NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
   1580          return rv;
   1581        }
   1582        // Adjust table columns simply.  In strictly speaking, we should
   1583        // recompute table size with the latest layout information since
   1584        // mutation event listener may have changed the DOM tree. However,
   1585        // this is not in usual path of Firefox.  So, we can assume that
   1586        // there are no mutation event listeners.
   1587        MOZ_ASSERT(tableSize.mColumnCount);
   1588        tableSize.mColumnCount--;
   1589        if (!selectedCellElement) {
   1590          break;
   1591        }
   1592        // For the next cell, subtract 1 for col. deleted
   1593        startColIndex = nextCol - 1;
   1594        // Set true since we know we will look at a new column next
   1595        checkToDeleteColumn = true;
   1596        continue;
   1597      }
   1598    }
   1599 
   1600    nsresult rv = DeleteNodeWithTransaction(*selectedCellElement);
   1601    if (NS_FAILED(rv)) {
   1602      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1603      return rv;
   1604    }
   1605 
   1606    selectedCellElement = scanner.GetNextElement();
   1607    if (!selectedCellElement) {
   1608      return NS_OK;
   1609    }
   1610 
   1611    const CellIndexes nextCellIndexes(*selectedCellElement, presShell);
   1612    if (NS_WARN_IF(nextCellIndexes.isErr())) {
   1613      return NS_ERROR_FAILURE;
   1614    }
   1615    startRowIndex = nextCellIndexes.mRow;
   1616    startColIndex = nextCellIndexes.mColumn;
   1617    // When table cell is removed, table size of column may be changed.
   1618    // For example, if there are 2 rows, one has 2 cells, the other has
   1619    // 3 cells, tableSize.mColumnCount is 3.  When this removes a cell
   1620    // in the latter row, mColumnCount should be come 2.  However, we
   1621    // don't use mColumnCount in this loop, so, this must be okay for now.
   1622  }
   1623  return NS_OK;
   1624 }
   1625 
   1626 NS_IMETHODIMP HTMLEditor::DeleteTableCellContents() {
   1627  AutoEditActionDataSetter editActionData(*this,
   1628                                          EditAction::eDeleteTableCellContents);
   1629  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   1630  if (NS_FAILED(rv)) {
   1631    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   1632    return EditorBase::ToGenericNSResult(rv);
   1633  }
   1634  const RefPtr<Element> editingHost =
   1635      ComputeEditingHost(LimitInBodyElement::No);
   1636  if (NS_WARN_IF(editingHost &&
   1637                 editingHost->IsContentEditablePlainTextOnly())) {
   1638    return NS_ERROR_NOT_AVAILABLE;
   1639  }
   1640  rv = editActionData.MaybeDispatchBeforeInputEvent();
   1641  if (NS_FAILED(rv)) {
   1642    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   1643                         "MaybeDispatchBeforeInputEvent(), failed");
   1644    return EditorBase::ToGenericNSResult(rv);
   1645  }
   1646 
   1647  rv = DeleteTableCellContentsWithTransaction();
   1648  NS_WARNING_ASSERTION(
   1649      NS_SUCCEEDED(rv),
   1650      "HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
   1651  return EditorBase::ToGenericNSResult(rv);
   1652 }
   1653 
   1654 nsresult HTMLEditor::DeleteTableCellContentsWithTransaction() {
   1655  MOZ_ASSERT(IsEditActionDataAvailable());
   1656 
   1657  RefPtr<Element> table;
   1658  RefPtr<Element> cell;
   1659  int32_t startRowIndex, startColIndex;
   1660  nsresult rv =
   1661      GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   1662                     nullptr, &startRowIndex, &startColIndex);
   1663  if (NS_FAILED(rv)) {
   1664    NS_WARNING("HTMLEditor::GetCellContext() failed");
   1665    return rv;
   1666  }
   1667  if (!cell) {
   1668    NS_WARNING("HTMLEditor::GetCellContext() didn't return cell element");
   1669    // Don't fail if no cell found.
   1670    return NS_OK;
   1671  }
   1672 
   1673  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   1674    return NS_ERROR_FAILURE;  // XXX Should we just return NS_OK?
   1675  }
   1676 
   1677  AutoPlaceholderBatch treateAsOneTransaction(
   1678      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   1679  // Prevent rules testing until we're done
   1680  IgnoredErrorResult ignoredError;
   1681  AutoEditSubActionNotifier startToHandleEditSubAction(
   1682      *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
   1683  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   1684    return ignoredError.StealNSResult();
   1685  }
   1686  NS_WARNING_ASSERTION(
   1687      !ignoredError.Failed(),
   1688      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   1689 
   1690  // Don't let Rules System change the selection
   1691  AutoTransactionsConserveSelection dontChangeSelection(*this);
   1692 
   1693  SelectedTableCellScanner scanner(SelectionRef());
   1694  if (scanner.IsInTableCellSelectionMode()) {
   1695    const RefPtr<PresShell> presShell{GetPresShell()};
   1696    // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because scanner
   1697    // grabs it until it's destroyed later.
   1698    const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
   1699                                       presShell);
   1700    if (NS_WARN_IF(firstCellIndexes.isErr())) {
   1701      return NS_ERROR_FAILURE;
   1702    }
   1703    cell = scanner.ElementsRef()[0];
   1704    startRowIndex = firstCellIndexes.mRow;
   1705    startColIndex = firstCellIndexes.mColumn;
   1706  }
   1707 
   1708  AutoSelectionSetterAfterTableEdit setCaret(
   1709      *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
   1710 
   1711  for (RefPtr<Element> selectedCellElement = std::move(cell);
   1712       selectedCellElement; selectedCellElement = scanner.GetNextElement()) {
   1713    DebugOnly<nsresult> rvIgnored =
   1714        DeleteAllChildrenWithTransaction(*selectedCellElement);
   1715    if (NS_WARN_IF(Destroyed())) {
   1716      return NS_ERROR_EDITOR_DESTROYED;
   1717    }
   1718    NS_WARNING_ASSERTION(
   1719        NS_SUCCEEDED(rv),
   1720        "HTMLEditor::DeleteAllChildrenWithTransaction() failed, but ignored");
   1721    if (!scanner.IsInTableCellSelectionMode()) {
   1722      break;
   1723    }
   1724  }
   1725  return NS_OK;
   1726 }
   1727 
   1728 NS_IMETHODIMP HTMLEditor::DeleteTableColumn(int32_t aNumberOfColumnsToDelete) {
   1729  AutoEditActionDataSetter editActionData(*this,
   1730                                          EditAction::eRemoveTableColumn);
   1731  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   1732  if (NS_FAILED(rv)) {
   1733    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   1734    return EditorBase::ToGenericNSResult(rv);
   1735  }
   1736  const RefPtr<Element> editingHost =
   1737      ComputeEditingHost(LimitInBodyElement::No);
   1738  if (NS_WARN_IF(editingHost &&
   1739                 editingHost->IsContentEditablePlainTextOnly())) {
   1740    return NS_ERROR_NOT_AVAILABLE;
   1741  }
   1742  rv = editActionData.MaybeDispatchBeforeInputEvent();
   1743  if (NS_FAILED(rv)) {
   1744    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   1745                         "MaybeDispatchBeforeInputEvent(), failed");
   1746    return EditorBase::ToGenericNSResult(rv);
   1747  }
   1748 
   1749  rv = DeleteSelectedTableColumnsWithTransaction(aNumberOfColumnsToDelete);
   1750  NS_WARNING_ASSERTION(
   1751      NS_SUCCEEDED(rv),
   1752      "HTMLEditor::DeleteSelectedTableColumnsWithTransaction() failed");
   1753  return EditorBase::ToGenericNSResult(rv);
   1754 }
   1755 
   1756 nsresult HTMLEditor::DeleteSelectedTableColumnsWithTransaction(
   1757    int32_t aNumberOfColumnsToDelete) {
   1758  MOZ_ASSERT(IsEditActionDataAvailable());
   1759 
   1760  RefPtr<Element> table;
   1761  RefPtr<Element> cell;
   1762  int32_t startRowIndex, startColIndex;
   1763  nsresult rv =
   1764      GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   1765                     nullptr, &startRowIndex, &startColIndex);
   1766  if (NS_FAILED(rv)) {
   1767    NS_WARNING("HTMLEditor::GetCellContext() failed");
   1768    return rv;
   1769  }
   1770  if (!table || !cell) {
   1771    NS_WARNING(
   1772        "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
   1773    // Don't fail if no cell found.
   1774    return NS_OK;
   1775  }
   1776 
   1777  const Result<TableSize, nsresult> tableSizeOrError =
   1778      TableSize::Create(*this, *table);
   1779  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   1780    return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
   1781  }
   1782  const TableSize& tableSize = tableSizeOrError.inspect();
   1783 
   1784  AutoPlaceholderBatch treateAsOneTransaction(
   1785      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   1786 
   1787  // Prevent rules testing until we're done
   1788  IgnoredErrorResult error;
   1789  AutoEditSubActionNotifier startToHandleEditSubAction(
   1790      *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
   1791  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   1792    return error.StealNSResult();
   1793  }
   1794  NS_WARNING_ASSERTION(
   1795      !error.Failed(),
   1796      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   1797 
   1798  // Shortcut the case of deleting all columns in table
   1799  if (!startColIndex && aNumberOfColumnsToDelete >= tableSize.mColumnCount) {
   1800    nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
   1801    NS_WARNING_ASSERTION(
   1802        NS_SUCCEEDED(rv),
   1803        "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
   1804    return rv;
   1805  }
   1806 
   1807  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   1808    return NS_ERROR_FAILURE;  // XXX Should we just return NS_OK?
   1809  }
   1810 
   1811  SelectedTableCellScanner scanner(SelectionRef());
   1812  if (scanner.IsInTableCellSelectionMode() && SelectionRef().RangeCount() > 1) {
   1813    const RefPtr<PresShell> presShell{GetPresShell()};
   1814    // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
   1815    // grabs it until it's destroyed later.
   1816    const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
   1817                                       presShell);
   1818    if (NS_WARN_IF(firstCellIndexes.isErr())) {
   1819      return NS_ERROR_FAILURE;
   1820    }
   1821    startRowIndex = firstCellIndexes.mRow;
   1822    startColIndex = firstCellIndexes.mColumn;
   1823  }
   1824 
   1825  // We control selection resetting after the insert...
   1826  AutoSelectionSetterAfterTableEdit setCaret(
   1827      *this, table, startRowIndex, startColIndex, ePreviousRow, false);
   1828 
   1829  // If 2 or more cells are not selected, removing columns starting from
   1830  // a column which contains first selection range.
   1831  if (!scanner.IsInTableCellSelectionMode() ||
   1832      SelectionRef().RangeCount() == 1) {
   1833    int32_t columnCountToRemove = std::min(
   1834        aNumberOfColumnsToDelete, tableSize.mColumnCount - startColIndex);
   1835    for (int32_t i = 0; i < columnCountToRemove; i++) {
   1836      nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
   1837      if (NS_FAILED(rv)) {
   1838        NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
   1839        return rv;
   1840      }
   1841    }
   1842    return NS_OK;
   1843  }
   1844 
   1845  // If 2 or more cells are selected, remove all columns which contain selected
   1846  // cells.  I.e., we ignore aNumberOfColumnsToDelete in this case.
   1847  const RefPtr<PresShell> presShell{GetPresShell()};
   1848  for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
   1849       selectedCellElement;) {
   1850    if (selectedCellElement != scanner.ElementsRef()[0]) {
   1851      const CellIndexes cellIndexes(*selectedCellElement, presShell);
   1852      if (NS_WARN_IF(cellIndexes.isErr())) {
   1853        return NS_ERROR_FAILURE;
   1854      }
   1855      startRowIndex = cellIndexes.mRow;
   1856      startColIndex = cellIndexes.mColumn;
   1857    }
   1858    // Find the next cell in a different column
   1859    // to continue after we delete this column
   1860    int32_t nextCol = startColIndex;
   1861    while (nextCol == startColIndex) {
   1862      selectedCellElement = scanner.GetNextElement();
   1863      if (!selectedCellElement) {
   1864        break;
   1865      }
   1866      const CellIndexes cellIndexes(*selectedCellElement, presShell);
   1867      if (NS_WARN_IF(cellIndexes.isErr())) {
   1868        return NS_ERROR_FAILURE;
   1869      }
   1870      startRowIndex = cellIndexes.mRow;
   1871      nextCol = cellIndexes.mColumn;
   1872    }
   1873    nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
   1874    if (NS_FAILED(rv)) {
   1875      NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
   1876      return rv;
   1877    }
   1878  }
   1879  return NS_OK;
   1880 }
   1881 
   1882 nsresult HTMLEditor::DeleteTableColumnWithTransaction(Element& aTableElement,
   1883                                                      int32_t aColumnIndex) {
   1884  MOZ_ASSERT(IsEditActionDataAvailable());
   1885 
   1886  for (int32_t rowIndex = 0;; rowIndex++) {
   1887    const auto cellData = CellData::AtIndexInTableElement(
   1888        *this, aTableElement, rowIndex, aColumnIndex);
   1889    // Failure means that there is no more row in the table.  In this case,
   1890    // we shouldn't return error since we just reach the end of the table.
   1891    // XXX Should distinguish whether CellData returns error or just not found
   1892    //     later.
   1893    if (cellData.FailedOrNotFound()) {
   1894      return NS_OK;
   1895    }
   1896 
   1897    // Find cells that don't start in column we are deleting.
   1898    MOZ_ASSERT(cellData.mColSpan >= 0);
   1899    if (cellData.IsSpannedFromOtherColumn() || cellData.mColSpan != 1) {
   1900      // If we have a cell spanning this location, decrease its colspan to
   1901      // keep table rectangular, but if colspan is 0, it'll be adjusted
   1902      // automatically.
   1903      if (cellData.mColSpan > 0) {
   1904        NS_WARNING_ASSERTION(cellData.mColSpan > 1,
   1905                             "colspan should be 2 or larger");
   1906        DebugOnly<nsresult> rvIgnored =
   1907            SetColSpan(cellData.mElement, cellData.mColSpan - 1);
   1908        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1909                             "HTMLEditor::SetColSpan() failed, but ignored");
   1910      }
   1911      if (!cellData.IsSpannedFromOtherColumn()) {
   1912        // Cell is in column to be deleted, but must have colspan > 1,
   1913        // so delete contents of cell instead of cell itself (We must have
   1914        // reset colspan above).
   1915        DebugOnly<nsresult> rvIgnored =
   1916            DeleteAllChildrenWithTransaction(*cellData.mElement);
   1917        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1918                             "HTMLEditor::DeleteAllChildrenWithTransaction() "
   1919                             "failed, but ignored");
   1920      }
   1921      // Skip rows which the removed cell spanned.
   1922      rowIndex += cellData.NumberOfFollowingRows();
   1923      continue;
   1924    }
   1925 
   1926    // Delete the cell
   1927    int32_t numberOfCellsInRow =
   1928        GetNumberOfCellsInRow(aTableElement, cellData.mCurrent.mRow);
   1929    NS_WARNING_ASSERTION(
   1930        numberOfCellsInRow > 0,
   1931        "HTMLEditor::GetNumberOfCellsInRow() failed, but ignored");
   1932    if (numberOfCellsInRow != 1) {
   1933      // If removing cell is not the last cell of the row, we can just remove
   1934      // it.
   1935      nsresult rv = DeleteNodeWithTransaction(*cellData.mElement);
   1936      if (NS_FAILED(rv)) {
   1937        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1938        return rv;
   1939      }
   1940      // Skip rows which the removed cell spanned.
   1941      rowIndex += cellData.NumberOfFollowingRows();
   1942      continue;
   1943    }
   1944 
   1945    // When the cell is the last cell in the row, remove the row instead.
   1946    Element* parentRow = GetInclusiveAncestorByTagNameInternal(
   1947        *nsGkAtoms::tr, *cellData.mElement);
   1948    if (!parentRow) {
   1949      NS_WARNING(
   1950          "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::tr) "
   1951          "failed");
   1952      return NS_ERROR_FAILURE;
   1953    }
   1954 
   1955    // Check if its the only row left in the table.  If so, we can delete
   1956    // the table instead.
   1957    const Result<TableSize, nsresult> tableSizeOrError =
   1958        TableSize::Create(*this, aTableElement);
   1959    if (NS_WARN_IF(tableSizeOrError.isErr())) {
   1960      return tableSizeOrError.inspectErr();
   1961    }
   1962    const TableSize& tableSize = tableSizeOrError.inspect();
   1963 
   1964    if (tableSize.mRowCount == 1) {
   1965      // We're deleting the last row.  So, let's remove the <table> now.
   1966      nsresult rv = DeleteTableElementAndChildrenWithTransaction(aTableElement);
   1967      NS_WARNING_ASSERTION(
   1968          NS_SUCCEEDED(rv),
   1969          "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
   1970      return rv;
   1971    }
   1972 
   1973    // Delete the row by placing caret in cell we were to delete.  We need
   1974    // to call DeleteTableRowWithTransaction() to handle cells with rowspan.
   1975    nsresult rv =
   1976        DeleteTableRowWithTransaction(aTableElement, cellData.mFirst.mRow);
   1977    if (NS_FAILED(rv)) {
   1978      NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
   1979      return rv;
   1980    }
   1981 
   1982    // Note that we decrement rowIndex since a row was deleted.
   1983    rowIndex--;
   1984  }
   1985 
   1986  // Not reached because for (;;) loop never breaks.
   1987 }
   1988 
   1989 NS_IMETHODIMP HTMLEditor::DeleteTableRow(int32_t aNumberOfRowsToDelete) {
   1990  AutoEditActionDataSetter editActionData(*this,
   1991                                          EditAction::eRemoveTableRowElement);
   1992  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   1993  if (NS_FAILED(rv)) {
   1994    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   1995    return EditorBase::ToGenericNSResult(rv);
   1996  }
   1997  const RefPtr<Element> editingHost =
   1998      ComputeEditingHost(LimitInBodyElement::No);
   1999  if (NS_WARN_IF(editingHost &&
   2000                 editingHost->IsContentEditablePlainTextOnly())) {
   2001    return NS_ERROR_NOT_AVAILABLE;
   2002  }
   2003  rv = editActionData.MaybeDispatchBeforeInputEvent();
   2004  if (NS_FAILED(rv)) {
   2005    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   2006                         "MaybeDispatchBeforeInputEvent(), failed");
   2007    return EditorBase::ToGenericNSResult(rv);
   2008  }
   2009 
   2010  rv = DeleteSelectedTableRowsWithTransaction(aNumberOfRowsToDelete);
   2011  NS_WARNING_ASSERTION(
   2012      NS_SUCCEEDED(rv),
   2013      "HTMLEditor::DeleteSelectedTableRowsWithTransaction() failed");
   2014  return EditorBase::ToGenericNSResult(rv);
   2015 }
   2016 
   2017 nsresult HTMLEditor::DeleteSelectedTableRowsWithTransaction(
   2018    int32_t aNumberOfRowsToDelete) {
   2019  MOZ_ASSERT(IsEditActionDataAvailable());
   2020 
   2021  RefPtr<Element> table;
   2022  RefPtr<Element> cell;
   2023  int32_t startRowIndex, startColIndex;
   2024  nsresult rv =
   2025      GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   2026                     nullptr, &startRowIndex, &startColIndex);
   2027  if (NS_FAILED(rv)) {
   2028    NS_WARNING("HTMLEditor::GetCellContext() failed");
   2029    return rv;
   2030  }
   2031  if (!table || !cell) {
   2032    NS_WARNING(
   2033        "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
   2034    // Don't fail if no cell found.
   2035    return NS_OK;
   2036  }
   2037 
   2038  const Result<TableSize, nsresult> tableSizeOrError =
   2039      TableSize::Create(*this, *table);
   2040  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   2041    return tableSizeOrError.inspectErr();
   2042  }
   2043  const TableSize& tableSize = tableSizeOrError.inspect();
   2044 
   2045  AutoPlaceholderBatch treateAsOneTransaction(
   2046      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2047 
   2048  // Prevent rules testing until we're done
   2049  IgnoredErrorResult error;
   2050  AutoEditSubActionNotifier startToHandleEditSubAction(
   2051      *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
   2052  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   2053    return error.StealNSResult();
   2054  }
   2055  NS_WARNING_ASSERTION(
   2056      !error.Failed(),
   2057      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   2058 
   2059  // Shortcut the case of deleting all rows in table
   2060  if (!startRowIndex && aNumberOfRowsToDelete >= tableSize.mRowCount) {
   2061    nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
   2062    NS_WARNING_ASSERTION(
   2063        NS_SUCCEEDED(rv),
   2064        "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
   2065    return rv;
   2066  }
   2067 
   2068  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   2069    return NS_ERROR_FAILURE;  // XXX Should we just return NS_OK?
   2070  }
   2071 
   2072  SelectedTableCellScanner scanner(SelectionRef());
   2073  if (scanner.IsInTableCellSelectionMode() && SelectionRef().RangeCount() > 1) {
   2074    // Fetch indexes again - may be different for selected cells
   2075    const RefPtr<PresShell> presShell{GetPresShell()};
   2076    // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
   2077    // grabs it until it's destroyed later.
   2078    const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
   2079                                       presShell);
   2080    if (NS_WARN_IF(firstCellIndexes.isErr())) {
   2081      return NS_ERROR_FAILURE;
   2082    }
   2083    startRowIndex = firstCellIndexes.mRow;
   2084    startColIndex = firstCellIndexes.mColumn;
   2085  }
   2086 
   2087  // We control selection resetting after the insert...
   2088  AutoSelectionSetterAfterTableEdit setCaret(
   2089      *this, table, startRowIndex, startColIndex, ePreviousRow, false);
   2090  // Don't change selection during deletions
   2091  AutoTransactionsConserveSelection dontChangeSelection(*this);
   2092 
   2093  // XXX Perhaps, the following loops should collect <tr> elements to remove
   2094  //     first, then, remove them from the DOM tree since mutation event
   2095  //     listener may change the DOM tree during the loops.
   2096 
   2097  // If 2 or more cells are not selected, removing rows starting from
   2098  // a row which contains first selection range.
   2099  if (!scanner.IsInTableCellSelectionMode() ||
   2100      SelectionRef().RangeCount() == 1) {
   2101    int32_t rowCountToRemove =
   2102        std::min(aNumberOfRowsToDelete, tableSize.mRowCount - startRowIndex);
   2103    for (int32_t i = 0; i < rowCountToRemove; i++) {
   2104      nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
   2105      // If failed in current row, try the next
   2106      if (NS_FAILED(rv)) {
   2107        NS_WARNING(
   2108            "HTMLEditor::DeleteTableRowWithTransaction() failed, but trying "
   2109            "next...");
   2110        startRowIndex++;
   2111      }
   2112      // Check if there's a cell in the "next" row.
   2113      cell = GetTableCellElementAt(*table, startRowIndex, startColIndex);
   2114      if (!cell) {
   2115        return NS_OK;
   2116      }
   2117    }
   2118    return NS_OK;
   2119  }
   2120 
   2121  // If 2 or more cells are selected, remove all rows which contain selected
   2122  // cells.  I.e., we ignore aNumberOfRowsToDelete in this case.
   2123  const RefPtr<PresShell> presShell{GetPresShell()};
   2124  for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
   2125       selectedCellElement;) {
   2126    if (selectedCellElement != scanner.ElementsRef()[0]) {
   2127      const CellIndexes cellIndexes(*selectedCellElement, presShell);
   2128      if (NS_WARN_IF(cellIndexes.isErr())) {
   2129        return NS_ERROR_FAILURE;
   2130      }
   2131      startRowIndex = cellIndexes.mRow;
   2132      startColIndex = cellIndexes.mColumn;
   2133    }
   2134    // Find the next cell in a different row
   2135    // to continue after we delete this row
   2136    int32_t nextRow = startRowIndex;
   2137    while (nextRow == startRowIndex) {
   2138      selectedCellElement = scanner.GetNextElement();
   2139      if (!selectedCellElement) {
   2140        break;
   2141      }
   2142      const CellIndexes cellIndexes(*selectedCellElement, presShell);
   2143      if (NS_WARN_IF(cellIndexes.isErr())) {
   2144        return NS_ERROR_FAILURE;
   2145      }
   2146      nextRow = cellIndexes.mRow;
   2147      startColIndex = cellIndexes.mColumn;
   2148    }
   2149    // Delete the row containing selected cell(s).
   2150    nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
   2151    if (NS_FAILED(rv)) {
   2152      NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
   2153      return rv;
   2154    }
   2155  }
   2156  return NS_OK;
   2157 }
   2158 
   2159 // Helper that doesn't batch or change the selection
   2160 nsresult HTMLEditor::DeleteTableRowWithTransaction(Element& aTableElement,
   2161                                                   int32_t aRowIndex) {
   2162  MOZ_ASSERT(IsEditActionDataAvailable());
   2163 
   2164  const Result<TableSize, nsresult> tableSizeOrError =
   2165      TableSize::Create(*this, aTableElement);
   2166  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   2167    return tableSizeOrError.inspectErr();
   2168  }
   2169  const TableSize& tableSize = tableSizeOrError.inspect();
   2170 
   2171  // Prevent rules testing until we're done
   2172  IgnoredErrorResult error;
   2173  AutoEditSubActionNotifier startToHandleEditSubAction(
   2174      *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
   2175  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   2176    return error.StealNSResult();
   2177  }
   2178  NS_WARNING_ASSERTION(
   2179      !error.Failed(),
   2180      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   2181  error.SuppressException();
   2182 
   2183  // Scan through cells in row to do rowspan adjustments
   2184  // Note that after we delete row, startRowIndex will point to the cells in
   2185  // the next row to be deleted.
   2186 
   2187  // The list of cells we will change rowspan in and the new rowspan values
   2188  // for each.
   2189  struct MOZ_STACK_CLASS SpanCell final {
   2190    RefPtr<Element> mElement;
   2191    int32_t mNewRowSpanValue;
   2192 
   2193    SpanCell(Element* aSpanCellElement, int32_t aNewRowSpanValue)
   2194        : mElement(aSpanCellElement), mNewRowSpanValue(aNewRowSpanValue) {}
   2195  };
   2196  AutoTArray<SpanCell, 10> spanCellArray;
   2197  RefPtr<Element> cellInDeleteRow;
   2198  int32_t columnIndex = 0;
   2199  while (aRowIndex < tableSize.mRowCount &&
   2200         columnIndex < tableSize.mColumnCount) {
   2201    const auto cellData = CellData::AtIndexInTableElement(
   2202        *this, aTableElement, aRowIndex, columnIndex);
   2203    if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   2204      return NS_ERROR_FAILURE;
   2205    }
   2206 
   2207    // XXX So, we should distinguish if CellDate returns error or just not
   2208    //     found later.
   2209    if (!cellData.mElement) {
   2210      break;
   2211    }
   2212 
   2213    // Compensate for cells that don't start or extend below the row we are
   2214    // deleting.
   2215    if (cellData.IsSpannedFromOtherRow()) {
   2216      // If a cell starts in row above us, decrease its rowspan to keep table
   2217      // rectangular but we don't need to do this if rowspan=0, since it will
   2218      // be automatically adjusted.
   2219      if (cellData.mRowSpan > 0) {
   2220        // Build list of cells to change rowspan.  We can't do it now since
   2221        // it upsets cell map, so we will do it after deleting the row.
   2222        int32_t newRowSpanValue = std::max(cellData.NumberOfPrecedingRows(),
   2223                                           cellData.NumberOfFollowingRows());
   2224        spanCellArray.AppendElement(
   2225            SpanCell(cellData.mElement, newRowSpanValue));
   2226      }
   2227    } else {
   2228      if (cellData.mRowSpan > 1) {
   2229        // Cell spans below row to delete, so we must insert new cells to
   2230        // keep rows below.  Note that we test "rowSpan" so we don't do this
   2231        // if rowSpan = 0 (automatic readjustment).
   2232        int32_t aboveRowToInsertNewCellInto =
   2233            cellData.NumberOfPrecedingRows() + 1;
   2234        nsresult rv = SplitCellIntoRows(
   2235            &aTableElement, cellData.mFirst.mRow, cellData.mFirst.mColumn,
   2236            aboveRowToInsertNewCellInto, cellData.NumberOfFollowingRows(),
   2237            nullptr);
   2238        if (NS_FAILED(rv)) {
   2239          NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
   2240          return rv;
   2241        }
   2242      }
   2243      if (!cellInDeleteRow) {
   2244        // Reference cell to find row to delete.
   2245        cellInDeleteRow = std::move(cellData.mElement);
   2246      }
   2247    }
   2248    // Skip over other columns spanned by this cell
   2249    columnIndex += cellData.mEffectiveColSpan;
   2250  }
   2251 
   2252  // Things are messed up if we didn't find a cell in the row!
   2253  if (!cellInDeleteRow) {
   2254    NS_WARNING("There was no cell in deleting row");
   2255    return NS_ERROR_FAILURE;
   2256  }
   2257 
   2258  // Delete the entire row.
   2259  RefPtr<Element> parentRow =
   2260      GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::tr, *cellInDeleteRow);
   2261  if (parentRow) {
   2262    nsresult rv = DeleteNodeWithTransaction(*parentRow);
   2263    if (NS_FAILED(rv)) {
   2264      NS_WARNING(
   2265          "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::tr) "
   2266          "failed");
   2267      return rv;
   2268    }
   2269  }
   2270 
   2271  // Now we can set new rowspans for cells stored above.
   2272  for (SpanCell& spanCell : spanCellArray) {
   2273    if (NS_WARN_IF(!spanCell.mElement)) {
   2274      continue;
   2275    }
   2276    nsresult rv =
   2277        SetRowSpan(MOZ_KnownLive(spanCell.mElement), spanCell.mNewRowSpanValue);
   2278    if (NS_FAILED(rv)) {
   2279      NS_WARNING("HTMLEditor::SetRawSpan() failed");
   2280      return rv;
   2281    }
   2282  }
   2283  return NS_OK;
   2284 }
   2285 
   2286 NS_IMETHODIMP HTMLEditor::SelectTable() {
   2287  AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTable);
   2288  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   2289  if (NS_FAILED(rv)) {
   2290    NS_WARNING("HTMLEditor::SelectTable() couldn't handle the job");
   2291    return EditorBase::ToGenericNSResult(rv);
   2292  }
   2293 
   2294  RefPtr<Element> table =
   2295      GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
   2296  if (!table) {
   2297    NS_WARNING(
   2298        "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::table)"
   2299        " failed");
   2300    return NS_OK;  // Don't fail if we didn't find a table.
   2301  }
   2302 
   2303  rv = ClearSelection();
   2304  if (NS_FAILED(rv)) {
   2305    NS_WARNING("HTMLEditor::ClearSelection() failed");
   2306    return EditorBase::ToGenericNSResult(rv);
   2307  }
   2308  rv = AppendContentToSelectionAsRange(*table);
   2309  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2310                       "HTMLEditor::AppendContentToSelectionAsRange() failed");
   2311  return EditorBase::ToGenericNSResult(rv);
   2312 }
   2313 
   2314 NS_IMETHODIMP HTMLEditor::SelectTableCell() {
   2315  AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTableCell);
   2316  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   2317  if (NS_FAILED(rv)) {
   2318    NS_WARNING("HTMLEditor::SelectTableCell() couldn't handle the job");
   2319    return EditorBase::ToGenericNSResult(rv);
   2320  }
   2321 
   2322  RefPtr<Element> cell =
   2323      GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
   2324  if (!cell) {
   2325    NS_WARNING(
   2326        "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
   2327        "failed");
   2328    // Don't fail if we didn't find a cell.
   2329    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   2330  }
   2331 
   2332  rv = ClearSelection();
   2333  if (NS_FAILED(rv)) {
   2334    NS_WARNING("HTMLEditor::ClearSelection() failed");
   2335    return EditorBase::ToGenericNSResult(rv);
   2336  }
   2337  rv = AppendContentToSelectionAsRange(*cell);
   2338  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2339                       "HTMLEditor::AppendContentToSelectionAsRange() failed");
   2340  return EditorBase::ToGenericNSResult(rv);
   2341 }
   2342 
   2343 NS_IMETHODIMP HTMLEditor::SelectAllTableCells() {
   2344  AutoEditActionDataSetter editActionData(*this,
   2345                                          EditAction::eSelectAllTableCells);
   2346  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   2347  if (NS_FAILED(rv)) {
   2348    NS_WARNING("HTMLEditor::SelectAllTableCells() couldn't handle the job");
   2349    return EditorBase::ToGenericNSResult(rv);
   2350  }
   2351 
   2352  RefPtr<Element> cell =
   2353      GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
   2354  if (!cell) {
   2355    NS_WARNING(
   2356        "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
   2357        "failed");
   2358    // Don't fail if we didn't find a cell.
   2359    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   2360  }
   2361 
   2362  RefPtr<Element> startCell = cell;
   2363 
   2364  // Get parent table
   2365  RefPtr<Element> table =
   2366      GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *cell);
   2367  if (!table) {
   2368    NS_WARNING(
   2369        "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
   2370        "failed");
   2371    return NS_ERROR_FAILURE;
   2372  }
   2373 
   2374  const Result<TableSize, nsresult> tableSizeOrError =
   2375      TableSize::Create(*this, *table);
   2376  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   2377    return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
   2378  }
   2379  const TableSize& tableSize = tableSizeOrError.inspect();
   2380 
   2381  // Suppress nsISelectionListener notification
   2382  // until all selection changes are finished
   2383  SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
   2384 
   2385  // It is now safe to clear the selection
   2386  // BE SURE TO RESET IT BEFORE LEAVING!
   2387  rv = ClearSelection();
   2388  if (rv == NS_ERROR_EDITOR_DESTROYED) {
   2389    NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
   2390    return EditorBase::ToGenericNSResult(rv);
   2391  }
   2392  NS_WARNING_ASSERTION(
   2393      NS_SUCCEEDED(rv),
   2394      "HTMLEditor::ClearSelection() failed, but might be ignored");
   2395 
   2396  // Select all cells in the same column as current cell
   2397  bool cellSelected = false;
   2398  // Safety code to select starting cell if nothing else was selected
   2399  auto AppendContentToStartCell = [&]() MOZ_CAN_RUN_SCRIPT {
   2400    MOZ_ASSERT(!cellSelected);
   2401    // XXX In this case, we ignore `NS_ERROR_FAILURE` set by above inner
   2402    //     `for` loop.
   2403    nsresult rv = AppendContentToSelectionAsRange(*startCell);
   2404    NS_WARNING_ASSERTION(
   2405        NS_SUCCEEDED(rv),
   2406        "HTMLEditor::AppendContentToSelectionAsRange() failed");
   2407    return EditorBase::ToGenericNSResult(rv);
   2408  };
   2409  for (int32_t row = 0; row < tableSize.mRowCount; row++) {
   2410    for (int32_t col = 0; col < tableSize.mColumnCount;) {
   2411      const auto cellData =
   2412          CellData::AtIndexInTableElement(*this, *table, row, col);
   2413      if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   2414        return !cellSelected ? AppendContentToStartCell() : NS_ERROR_FAILURE;
   2415      }
   2416 
   2417      // Skip cells that are spanned from previous rows or columns
   2418      // XXX So, we should distinguish whether CellData returns error or just
   2419      //     not found later.
   2420      if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
   2421        nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
   2422        if (rv == NS_ERROR_EDITOR_DESTROYED) {
   2423          NS_WARNING(
   2424              "HTMLEditor::AppendContentToSelectionAsRange() caused "
   2425              "destroying the editor");
   2426          return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
   2427        }
   2428        if (NS_FAILED(rv)) {
   2429          NS_WARNING(
   2430              "HTMLEditor::AppendContentToSelectionAsRange() failed, but "
   2431              "might be ignored");
   2432          return !cellSelected ? AppendContentToStartCell()
   2433                               : EditorBase::ToGenericNSResult(rv);
   2434        }
   2435        cellSelected = true;
   2436      }
   2437      MOZ_ASSERT(col < cellData.NextColumnIndex());
   2438      col = cellData.NextColumnIndex();
   2439    }
   2440  }
   2441  return EditorBase::ToGenericNSResult(rv);
   2442 }
   2443 
   2444 NS_IMETHODIMP HTMLEditor::SelectTableRow() {
   2445  AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTableRow);
   2446  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   2447  if (NS_FAILED(rv)) {
   2448    NS_WARNING("HTMLEditor::SelectTableRow() couldn't handle the job");
   2449    return EditorBase::ToGenericNSResult(rv);
   2450  }
   2451 
   2452  RefPtr<Element> cell =
   2453      GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
   2454  if (!cell) {
   2455    NS_WARNING(
   2456        "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
   2457        "failed");
   2458    // Don't fail if we didn't find a cell.
   2459    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   2460  }
   2461 
   2462  RefPtr<Element> startCell = cell;
   2463 
   2464  // Get table and location of cell:
   2465  RefPtr<Element> table;
   2466  int32_t startRowIndex, startColIndex;
   2467 
   2468  rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   2469                      nullptr, &startRowIndex, &startColIndex);
   2470  if (NS_FAILED(rv)) {
   2471    NS_WARNING("HTMLEditor::GetCellContext() failed");
   2472    return EditorBase::ToGenericNSResult(rv);
   2473  }
   2474  if (!table) {
   2475    NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
   2476    return NS_ERROR_FAILURE;
   2477  }
   2478 
   2479  const Result<TableSize, nsresult> tableSizeOrError =
   2480      TableSize::Create(*this, *table);
   2481  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   2482    return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
   2483  }
   2484  const TableSize& tableSize = tableSizeOrError.inspect();
   2485 
   2486  // Note: At this point, we could get first and last cells in row,
   2487  // then call SelectBlockOfCells, but that would take just
   2488  // a little less code, so the following is more efficient
   2489 
   2490  // Suppress nsISelectionListener notification
   2491  // until all selection changes are finished
   2492  SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
   2493 
   2494  // It is now safe to clear the selection
   2495  // BE SURE TO RESET IT BEFORE LEAVING!
   2496  rv = ClearSelection();
   2497  if (rv == NS_ERROR_EDITOR_DESTROYED) {
   2498    NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
   2499    return EditorBase::ToGenericNSResult(rv);
   2500  }
   2501  NS_WARNING_ASSERTION(
   2502      NS_SUCCEEDED(rv),
   2503      "HTMLEditor::ClearSelection() failed, but might be ignored");
   2504 
   2505  // Select all cells in the same row as current cell
   2506  bool cellSelected = false;
   2507  for (int32_t col = 0; col < tableSize.mColumnCount;) {
   2508    const auto cellData =
   2509        CellData::AtIndexInTableElement(*this, *table, startRowIndex, col);
   2510    if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   2511      if (cellSelected) {
   2512        return NS_ERROR_FAILURE;
   2513      }
   2514      // Safety code to select starting cell if nothing else was selected
   2515      nsresult rv = AppendContentToSelectionAsRange(*startCell);
   2516      NS_WARNING_ASSERTION(
   2517          NS_SUCCEEDED(rv),
   2518          "HTMLEditor::AppendContentToSelectionAsRange() failed");
   2519      NS_WARNING_ASSERTION(
   2520          cellData.isOk() || NS_SUCCEEDED(rv) ||
   2521              NS_FAILED(EditorBase::ToGenericNSResult(rv)),
   2522          "CellData::AtIndexInTableElement() failed, but ignored");
   2523      return EditorBase::ToGenericNSResult(rv);
   2524    }
   2525 
   2526    // Skip cells that are spanned from previous rows or columns
   2527    // XXX So, we should distinguish whether CellData returns error or just
   2528    //     not found later.
   2529    if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
   2530      nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
   2531      if (rv == NS_ERROR_EDITOR_DESTROYED) {
   2532        NS_WARNING(
   2533            "HTMLEditor::AppendContentToSelectionAsRange() caused destroying "
   2534            "the editor");
   2535        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
   2536      }
   2537      if (NS_FAILED(rv)) {
   2538        if (cellSelected) {
   2539          NS_WARNING("HTMLEditor::AppendContentToSelectionAsRange() failed");
   2540          return EditorBase::ToGenericNSResult(rv);
   2541        }
   2542        // Safety code to select starting cell if nothing else was selected
   2543        nsresult rvTryAgain = AppendContentToSelectionAsRange(*startCell);
   2544        NS_WARNING_ASSERTION(
   2545            NS_SUCCEEDED(rv),
   2546            "HTMLEditor::AppendContentToSelectionAsRange() failed");
   2547        NS_WARNING_ASSERTION(
   2548            NS_SUCCEEDED(EditorBase::ToGenericNSResult(rv)) ||
   2549                NS_SUCCEEDED(rvTryAgain) ||
   2550                NS_FAILED(EditorBase::ToGenericNSResult(rvTryAgain)),
   2551            "HTMLEditor::AppendContentToSelectionAsRange(*cellData.mElement) "
   2552            "failed, but ignored");
   2553        return EditorBase::ToGenericNSResult(rvTryAgain);
   2554      }
   2555      cellSelected = true;
   2556    }
   2557    MOZ_ASSERT(col < cellData.NextColumnIndex());
   2558    col = cellData.NextColumnIndex();
   2559  }
   2560  return EditorBase::ToGenericNSResult(rv);
   2561 }
   2562 
   2563 NS_IMETHODIMP HTMLEditor::SelectTableColumn() {
   2564  AutoEditActionDataSetter editActionData(*this,
   2565                                          EditAction::eSelectTableColumn);
   2566  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   2567  if (NS_FAILED(rv)) {
   2568    NS_WARNING("HTMLEditor::SelectTableColumn() couldn't handle the job");
   2569    return EditorBase::ToGenericNSResult(rv);
   2570  }
   2571 
   2572  RefPtr<Element> cell =
   2573      GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
   2574  if (!cell) {
   2575    NS_WARNING(
   2576        "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
   2577        "failed");
   2578    // Don't fail if we didn't find a cell.
   2579    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   2580  }
   2581 
   2582  RefPtr<Element> startCell = cell;
   2583 
   2584  // Get location of cell:
   2585  RefPtr<Element> table;
   2586  int32_t startRowIndex, startColIndex;
   2587 
   2588  rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   2589                      nullptr, &startRowIndex, &startColIndex);
   2590  if (NS_FAILED(rv)) {
   2591    NS_WARNING("HTMLEditor::GetCellContext() failed");
   2592    return EditorBase::ToGenericNSResult(rv);
   2593  }
   2594  if (!table) {
   2595    NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
   2596    return NS_ERROR_FAILURE;
   2597  }
   2598 
   2599  const Result<TableSize, nsresult> tableSizeOrError =
   2600      TableSize::Create(*this, *table);
   2601  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   2602    return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
   2603  }
   2604  const TableSize& tableSize = tableSizeOrError.inspect();
   2605 
   2606  // Suppress nsISelectionListener notification
   2607  // until all selection changes are finished
   2608  SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
   2609 
   2610  // It is now safe to clear the selection
   2611  // BE SURE TO RESET IT BEFORE LEAVING!
   2612  rv = ClearSelection();
   2613  if (rv == NS_ERROR_EDITOR_DESTROYED) {
   2614    NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
   2615    return EditorBase::ToGenericNSResult(rv);
   2616  }
   2617  NS_WARNING_ASSERTION(
   2618      NS_SUCCEEDED(rv),
   2619      "HTMLEditor::ClearSelection() failed, but might be ignored");
   2620 
   2621  // Select all cells in the same column as current cell
   2622  bool cellSelected = false;
   2623  for (int32_t row = 0; row < tableSize.mRowCount;) {
   2624    const auto cellData =
   2625        CellData::AtIndexInTableElement(*this, *table, row, startColIndex);
   2626    if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   2627      if (cellSelected) {
   2628        return NS_ERROR_FAILURE;
   2629      }
   2630      // Safety code to select starting cell if nothing else was selected
   2631      nsresult rv = AppendContentToSelectionAsRange(*startCell);
   2632      NS_WARNING_ASSERTION(
   2633          NS_SUCCEEDED(rv),
   2634          "HTMLEditor::AppendContentToSelectionAsRange() failed");
   2635      NS_WARNING_ASSERTION(
   2636          cellData.isOk() || NS_SUCCEEDED(rv) ||
   2637              NS_FAILED(EditorBase::ToGenericNSResult(rv)),
   2638          "CellData::AtIndexInTableElement() failed, but ignored");
   2639      return EditorBase::ToGenericNSResult(rv);
   2640    }
   2641 
   2642    // Skip cells that are spanned from previous rows or columns
   2643    // XXX So, we should distinguish whether CellData returns error or just
   2644    //     not found later.
   2645    if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
   2646      nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
   2647      if (rv == NS_ERROR_EDITOR_DESTROYED) {
   2648        NS_WARNING(
   2649            "HTMLEditor::AppendContentToSelectionAsRange() caused destroying "
   2650            "the editor");
   2651        return EditorBase::ToGenericNSResult(rv);
   2652      }
   2653      if (NS_FAILED(rv)) {
   2654        if (cellSelected) {
   2655          NS_WARNING("HTMLEditor::AppendContentToSelectionAsRange() failed");
   2656          return EditorBase::ToGenericNSResult(rv);
   2657        }
   2658        // Safety code to select starting cell if nothing else was selected
   2659        nsresult rvTryAgain = AppendContentToSelectionAsRange(*startCell);
   2660        NS_WARNING_ASSERTION(
   2661            NS_SUCCEEDED(rv),
   2662            "HTMLEditor::AppendContentToSelectionAsRange() failed");
   2663        NS_WARNING_ASSERTION(
   2664            NS_SUCCEEDED(EditorBase::ToGenericNSResult(rv)) ||
   2665                NS_SUCCEEDED(rvTryAgain) ||
   2666                NS_FAILED(EditorBase::ToGenericNSResult(rvTryAgain)),
   2667            "HTMLEditor::AppendContentToSelectionAsRange(*cellData.mElement) "
   2668            "failed, but ignored");
   2669        return EditorBase::ToGenericNSResult(rvTryAgain);
   2670      }
   2671      cellSelected = true;
   2672    }
   2673    MOZ_ASSERT(row < cellData.NextRowIndex());
   2674    row = cellData.NextRowIndex();
   2675  }
   2676  return EditorBase::ToGenericNSResult(rv);
   2677 }
   2678 
   2679 NS_IMETHODIMP HTMLEditor::SplitTableCell() {
   2680  AutoEditActionDataSetter editActionData(*this,
   2681                                          EditAction::eSplitTableCellElement);
   2682  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   2683  if (NS_FAILED(rv)) {
   2684    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   2685    return EditorBase::ToGenericNSResult(rv);
   2686  }
   2687  const RefPtr<Element> editingHost =
   2688      ComputeEditingHost(LimitInBodyElement::No);
   2689  if (NS_WARN_IF(editingHost &&
   2690                 editingHost->IsContentEditablePlainTextOnly())) {
   2691    return NS_ERROR_NOT_AVAILABLE;
   2692  }
   2693  rv = editActionData.MaybeDispatchBeforeInputEvent();
   2694  if (NS_FAILED(rv)) {
   2695    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   2696                         "MaybeDispatchBeforeInputEvent(), failed");
   2697    return EditorBase::ToGenericNSResult(rv);
   2698  }
   2699 
   2700  RefPtr<Element> table;
   2701  RefPtr<Element> cell;
   2702  int32_t startRowIndex, startColIndex, actualRowSpan, actualColSpan;
   2703  // Get cell, table, etc. at selection anchor node
   2704  rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
   2705                      nullptr, &startRowIndex, &startColIndex);
   2706  if (NS_FAILED(rv)) {
   2707    NS_WARNING("HTMLEditor::GetCellContext() failed");
   2708    return EditorBase::ToGenericNSResult(rv);
   2709  }
   2710  if (!table || !cell) {
   2711    NS_WARNING(
   2712        "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
   2713    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   2714  }
   2715 
   2716  // We need rowspan and colspan data
   2717  rv = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan,
   2718                      actualColSpan);
   2719  if (NS_FAILED(rv)) {
   2720    NS_WARNING("HTMLEditor::GetCellSpansAt() failed");
   2721    return EditorBase::ToGenericNSResult(rv);
   2722  }
   2723 
   2724  // Must have some span to split
   2725  if (actualRowSpan <= 1 && actualColSpan <= 1) {
   2726    return NS_OK;
   2727  }
   2728 
   2729  AutoPlaceholderBatch treateAsOneTransaction(
   2730      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2731  // Prevent auto insertion of BR in new cell until we're done
   2732  IgnoredErrorResult ignoredError;
   2733  AutoEditSubActionNotifier startToHandleEditSubAction(
   2734      *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
   2735  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   2736    return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
   2737  }
   2738  NS_WARNING_ASSERTION(
   2739      !ignoredError.Failed(),
   2740      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   2741 
   2742  // We reset selection
   2743  AutoSelectionSetterAfterTableEdit setCaret(
   2744      *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
   2745  //...so suppress Rules System selection munging
   2746  AutoTransactionsConserveSelection dontChangeSelection(*this);
   2747 
   2748  RefPtr<Element> newCell;
   2749  int32_t rowIndex = startRowIndex;
   2750  int32_t rowSpanBelow, colSpanAfter;
   2751 
   2752  // Split up cell row-wise first into rowspan=1 above, and the rest below,
   2753  // whittling away at the cell below until no more extra span
   2754  for (rowSpanBelow = actualRowSpan - 1; rowSpanBelow >= 0; rowSpanBelow--) {
   2755    // We really split row-wise only if we had rowspan > 1
   2756    if (rowSpanBelow > 0) {
   2757      nsresult rv = SplitCellIntoRows(table, rowIndex, startColIndex, 1,
   2758                                      rowSpanBelow, getter_AddRefs(newCell));
   2759      if (NS_FAILED(rv)) {
   2760        NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
   2761        return EditorBase::ToGenericNSResult(rv);
   2762      }
   2763      DebugOnly<nsresult> rvIgnored = CopyCellBackgroundColor(newCell, cell);
   2764      NS_WARNING_ASSERTION(
   2765          NS_SUCCEEDED(rvIgnored),
   2766          "HTMLEditor::CopyCellBackgroundColor() failed, but ignored");
   2767    }
   2768    int32_t colIndex = startColIndex;
   2769    // Now split the cell with rowspan = 1 into cells if it has colSpan > 1
   2770    for (colSpanAfter = actualColSpan - 1; colSpanAfter > 0; colSpanAfter--) {
   2771      nsresult rv = SplitCellIntoColumns(table, rowIndex, colIndex, 1,
   2772                                         colSpanAfter, getter_AddRefs(newCell));
   2773      if (NS_FAILED(rv)) {
   2774        NS_WARNING("HTMLEditor::SplitCellIntoColumns() failed");
   2775        return EditorBase::ToGenericNSResult(rv);
   2776      }
   2777      DebugOnly<nsresult> rvIgnored = CopyCellBackgroundColor(newCell, cell);
   2778      NS_WARNING_ASSERTION(
   2779          NS_SUCCEEDED(rv),
   2780          "HTMLEditor::CopyCellBackgroundColor() failed, but ignored");
   2781      colIndex++;
   2782    }
   2783    // Point to the new cell and repeat
   2784    rowIndex++;
   2785  }
   2786  return NS_OK;
   2787 }
   2788 
   2789 nsresult HTMLEditor::CopyCellBackgroundColor(Element* aDestCell,
   2790                                             Element* aSourceCell) {
   2791  if (NS_WARN_IF(!aDestCell) || NS_WARN_IF(!aSourceCell)) {
   2792    return NS_ERROR_INVALID_ARG;
   2793  }
   2794 
   2795  if (!aSourceCell->HasAttr(nsGkAtoms::bgcolor)) {
   2796    return NS_OK;
   2797  }
   2798 
   2799  // Copy backgournd color to new cell.
   2800  nsString backgroundColor;
   2801  aSourceCell->GetAttr(nsGkAtoms::bgcolor, backgroundColor);
   2802  nsresult rv = SetAttributeWithTransaction(*aDestCell, *nsGkAtoms::bgcolor,
   2803                                            backgroundColor);
   2804  NS_WARNING_ASSERTION(
   2805      NS_SUCCEEDED(rv),
   2806      "EditorBase::SetAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
   2807  return rv;
   2808 }
   2809 
   2810 nsresult HTMLEditor::SplitCellIntoColumns(Element* aTable, int32_t aRowIndex,
   2811                                          int32_t aColIndex,
   2812                                          int32_t aColSpanLeft,
   2813                                          int32_t aColSpanRight,
   2814                                          Element** aNewCell) {
   2815  if (NS_WARN_IF(!aTable)) {
   2816    return NS_ERROR_INVALID_ARG;
   2817  }
   2818  if (aNewCell) {
   2819    *aNewCell = nullptr;
   2820  }
   2821 
   2822  const auto cellData =
   2823      CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, aColIndex);
   2824  if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   2825    return NS_ERROR_FAILURE;
   2826  }
   2827 
   2828  // We can't split!
   2829  if (cellData.mEffectiveColSpan <= 1 ||
   2830      aColSpanLeft + aColSpanRight > cellData.mEffectiveColSpan) {
   2831    return NS_OK;
   2832  }
   2833 
   2834  // Reduce colspan of cell to split
   2835  nsresult rv = SetColSpan(cellData.mElement, aColSpanLeft);
   2836  if (NS_FAILED(rv)) {
   2837    NS_WARNING("HTMLEditor::SetColSpan() failed");
   2838    return rv;
   2839  }
   2840 
   2841  // Insert new cell after using the remaining span
   2842  // and always get the new cell so we can copy the background color;
   2843  RefPtr<Element> newCellElement;
   2844  rv = InsertCell(cellData.mElement, cellData.mEffectiveRowSpan, aColSpanRight,
   2845                  true, false, getter_AddRefs(newCellElement));
   2846  if (NS_FAILED(rv)) {
   2847    NS_WARNING("HTMLEditor::InsertCell() failed");
   2848    return rv;
   2849  }
   2850  if (!newCellElement) {
   2851    return NS_OK;
   2852  }
   2853  if (aNewCell) {
   2854    *aNewCell = do_AddRef(newCellElement).take();
   2855  }
   2856  rv = CopyCellBackgroundColor(newCellElement, cellData.mElement);
   2857  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2858                       "HTMLEditor::CopyCellBackgroundColor() failed");
   2859  return rv;
   2860 }
   2861 
   2862 nsresult HTMLEditor::SplitCellIntoRows(Element* aTable, int32_t aRowIndex,
   2863                                       int32_t aColIndex, int32_t aRowSpanAbove,
   2864                                       int32_t aRowSpanBelow,
   2865                                       Element** aNewCell) {
   2866  if (NS_WARN_IF(!aTable)) {
   2867    return NS_ERROR_INVALID_ARG;
   2868  }
   2869 
   2870  if (aNewCell) {
   2871    *aNewCell = nullptr;
   2872  }
   2873 
   2874  const auto cellData =
   2875      CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, aColIndex);
   2876  if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   2877    return NS_ERROR_FAILURE;
   2878  }
   2879 
   2880  // We can't split!
   2881  if (cellData.mEffectiveRowSpan <= 1 ||
   2882      aRowSpanAbove + aRowSpanBelow > cellData.mEffectiveRowSpan) {
   2883    return NS_OK;
   2884  }
   2885 
   2886  const Result<TableSize, nsresult> tableSizeOrError =
   2887      TableSize::Create(*this, *aTable);
   2888  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   2889    return tableSizeOrError.inspectErr();
   2890  }
   2891  const TableSize& tableSize = tableSizeOrError.inspect();
   2892 
   2893  // Find a cell to insert before or after
   2894  RefPtr<Element> cellElementAtInsertionPoint;
   2895  RefPtr<Element> lastCellFound;
   2896  bool insertAfter = (cellData.mFirst.mColumn > 0);
   2897  for (int32_t colIndex = 0,
   2898               rowBelowIndex = cellData.mFirst.mRow + aRowSpanAbove;
   2899       colIndex <= tableSize.mColumnCount;) {
   2900    const auto cellDataAtInsertionPoint = CellData::AtIndexInTableElement(
   2901        *this, *aTable, rowBelowIndex, colIndex);
   2902    // If we fail here, it could be because row has bad rowspan values,
   2903    // such as all cells having rowspan > 1 (Call FixRowSpan first!).
   2904    // XXX According to the comment, this does not assume that
   2905    //     FixRowSpan() doesn't work well and user can create non-rectangular
   2906    //     table.  So, we should not return error when CellData cannot find
   2907    //     a cell.
   2908    if (NS_WARN_IF(cellDataAtInsertionPoint.FailedOrNotFound())) {
   2909      return NS_ERROR_FAILURE;
   2910    }
   2911 
   2912    // FYI: Don't use std::move() here since the following checks will use
   2913    //      utility methods of cellDataAtInsertionPoint, but some of them
   2914    //      check whether its mElement is not nullptr.
   2915    cellElementAtInsertionPoint = cellDataAtInsertionPoint.mElement;
   2916 
   2917    // Skip over cells spanned from above (like the one we are splitting!)
   2918    if (cellDataAtInsertionPoint.mElement &&
   2919        !cellDataAtInsertionPoint.IsSpannedFromOtherRow()) {
   2920      if (!insertAfter) {
   2921        // Inserting before, so stop at first cell in row we want to insert
   2922        // into.
   2923        break;
   2924      }
   2925      // New cell isn't first in row,
   2926      // so stop after we find the cell just before new cell's column
   2927      if (cellDataAtInsertionPoint.NextColumnIndex() ==
   2928          cellData.mFirst.mColumn) {
   2929        break;
   2930      }
   2931      // If cell found is AFTER desired new cell colum,
   2932      // we have multiple cells with rowspan > 1 that
   2933      // prevented us from finding a cell to insert after...
   2934      if (cellDataAtInsertionPoint.mFirst.mColumn > cellData.mFirst.mColumn) {
   2935        // ... so instead insert before the cell we found
   2936        insertAfter = false;
   2937        break;
   2938      }
   2939      // FYI: Don't use std::move() here since
   2940      //      cellDataAtInsertionPoint.NextColumnIndex() needs it.
   2941      lastCellFound = cellDataAtInsertionPoint.mElement;
   2942    }
   2943    MOZ_ASSERT(colIndex < cellDataAtInsertionPoint.NextColumnIndex());
   2944    colIndex = cellDataAtInsertionPoint.NextColumnIndex();
   2945  }
   2946 
   2947  if (!cellElementAtInsertionPoint && lastCellFound) {
   2948    // Edge case where we didn't find a cell to insert after
   2949    // or before because column(s) before desired column
   2950    // and all columns after it are spanned from above.
   2951    // We can insert after the last cell we found
   2952    cellElementAtInsertionPoint = std::move(lastCellFound);
   2953    insertAfter = true;  // Should always be true, but let's be sure
   2954  }
   2955 
   2956  // Reduce rowspan of cell to split
   2957  nsresult rv = SetRowSpan(cellData.mElement, aRowSpanAbove);
   2958  if (NS_FAILED(rv)) {
   2959    NS_WARNING("HTMLEditor::SetRowSpan() failed");
   2960    return rv;
   2961  }
   2962 
   2963  // Insert new cell after using the remaining span
   2964  // and always get the new cell so we can copy the background color;
   2965  RefPtr<Element> newCell;
   2966  rv = InsertCell(cellElementAtInsertionPoint, aRowSpanBelow,
   2967                  cellData.mEffectiveColSpan, insertAfter, false,
   2968                  getter_AddRefs(newCell));
   2969  if (NS_FAILED(rv)) {
   2970    NS_WARNING("HTMLEditor::InsertCell() failed");
   2971    return rv;
   2972  }
   2973  if (!newCell) {
   2974    return NS_OK;
   2975  }
   2976  if (aNewCell) {
   2977    *aNewCell = do_AddRef(newCell).take();
   2978  }
   2979  rv = CopyCellBackgroundColor(newCell, cellElementAtInsertionPoint);
   2980  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2981                       "HTMLEditor::CopyCellBackgroundColor() failed");
   2982  return rv;
   2983 }
   2984 
   2985 NS_IMETHODIMP HTMLEditor::SwitchTableCellHeaderType(Element* aSourceCell,
   2986                                                    Element** aNewCell) {
   2987  if (NS_WARN_IF(!aSourceCell)) {
   2988    return NS_ERROR_INVALID_ARG;
   2989  }
   2990 
   2991  AutoEditActionDataSetter editActionData(*this,
   2992                                          EditAction::eSetTableCellElementType);
   2993  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   2994  if (NS_FAILED(rv)) {
   2995    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   2996    return EditorBase::ToGenericNSResult(rv);
   2997  }
   2998  const RefPtr<Element> editingHost =
   2999      ComputeEditingHost(LimitInBodyElement::No);
   3000  if (NS_WARN_IF(editingHost &&
   3001                 editingHost->IsContentEditablePlainTextOnly())) {
   3002    return NS_ERROR_NOT_AVAILABLE;
   3003  }
   3004  rv = editActionData.MaybeDispatchBeforeInputEvent();
   3005  if (NS_FAILED(rv)) {
   3006    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3007                         "MaybeDispatchBeforeInputEvent(), failed");
   3008    return EditorBase::ToGenericNSResult(rv);
   3009  }
   3010 
   3011  AutoPlaceholderBatch treatAsOneTransaction(
   3012      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3013  // Prevent auto insertion of BR in new cell created by
   3014  // ReplaceContainerAndCloneAttributesWithTransaction().
   3015  IgnoredErrorResult ignoredError;
   3016  AutoEditSubActionNotifier startToHandleEditSubAction(
   3017      *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
   3018  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3019    return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
   3020  }
   3021  NS_WARNING_ASSERTION(
   3022      !ignoredError.Failed(),
   3023      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3024 
   3025  // Save current selection to restore when done.
   3026  // This is needed so ReplaceContainerAndCloneAttributesWithTransaction()
   3027  // can monitor selection when replacing nodes.
   3028  AutoSelectionRestorer restoreSelectionLater(this);
   3029 
   3030  // Set to the opposite of current type
   3031  nsAtom* newCellName =
   3032      aSourceCell->IsHTMLElement(nsGkAtoms::td) ? nsGkAtoms::th : nsGkAtoms::td;
   3033 
   3034  // This creates new node, moves children, copies attributes (true)
   3035  //   and manages the selection!
   3036  Result<CreateElementResult, nsresult> newCellElementOrError =
   3037      ReplaceContainerAndCloneAttributesWithTransaction(
   3038          *aSourceCell, MOZ_KnownLive(*newCellName));
   3039  if (MOZ_UNLIKELY(newCellElementOrError.isErr())) {
   3040    NS_WARNING(
   3041        "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
   3042        "failed");
   3043    return newCellElementOrError.unwrapErr();
   3044  }
   3045  // restoreSelectionLater will change selection
   3046  newCellElementOrError.inspect().IgnoreCaretPointSuggestion();
   3047 
   3048  // Return the new cell
   3049  if (aNewCell) {
   3050    newCellElementOrError.unwrap().UnwrapNewNode().forget(aNewCell);
   3051  }
   3052 
   3053  return NS_OK;
   3054 }
   3055 
   3056 NS_IMETHODIMP HTMLEditor::JoinTableCells(bool aMergeNonContiguousContents) {
   3057  AutoEditActionDataSetter editActionData(*this,
   3058                                          EditAction::eJoinTableCellElements);
   3059  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   3060  if (NS_FAILED(rv)) {
   3061    NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
   3062    return EditorBase::ToGenericNSResult(rv);
   3063  }
   3064  const RefPtr<Element> editingHost =
   3065      ComputeEditingHost(LimitInBodyElement::No);
   3066  if (NS_WARN_IF(editingHost &&
   3067                 editingHost->IsContentEditablePlainTextOnly())) {
   3068    return NS_ERROR_NOT_AVAILABLE;
   3069  }
   3070  rv = editActionData.MaybeDispatchBeforeInputEvent();
   3071  if (NS_FAILED(rv)) {
   3072    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3073                         "MaybeDispatchBeforeInputEvent(), failed");
   3074    return EditorBase::ToGenericNSResult(rv);
   3075  }
   3076 
   3077  RefPtr<Element> table;
   3078  RefPtr<Element> targetCell;
   3079  int32_t startRowIndex, startColIndex;
   3080 
   3081  // Get cell, table, etc. at selection anchor node
   3082  rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(targetCell),
   3083                      nullptr, nullptr, &startRowIndex, &startColIndex);
   3084  if (NS_FAILED(rv)) {
   3085    NS_WARNING("HTMLEditor::GetCellContext() failed");
   3086    return EditorBase::ToGenericNSResult(rv);
   3087  }
   3088  if (!table || !targetCell) {
   3089    NS_WARNING(
   3090        "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
   3091    return NS_OK;
   3092  }
   3093 
   3094  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   3095    return NS_ERROR_FAILURE;  // XXX Should we just return NS_OK?
   3096  }
   3097 
   3098  AutoPlaceholderBatch treateAsOneTransaction(
   3099      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3100  // Don't let Rules System change the selection
   3101  AutoTransactionsConserveSelection dontChangeSelection(*this);
   3102 
   3103  // Note: We dont' use AutoSelectionSetterAfterTableEdit here so the selection
   3104  // is retained after joining. This leaves the target cell selected
   3105  // as well as the "non-contiguous" cells, so user can see what happened.
   3106 
   3107  SelectedTableCellScanner scanner(SelectionRef());
   3108 
   3109  // If only one cell is selected, join with cell to the right
   3110  if (scanner.ElementsRef().Length() > 1) {
   3111    // We have selected cells: Join just contiguous cells
   3112    // and just merge contents if not contiguous
   3113    Result<TableSize, nsresult> tableSizeOrError =
   3114        TableSize::Create(*this, *table);
   3115    if (NS_WARN_IF(tableSizeOrError.isErr())) {
   3116      return EditorBase::ToGenericNSResult(tableSizeOrError.unwrapErr());
   3117    }
   3118    // FYI: Cannot be const because the row count will be updated
   3119    TableSize tableSize = tableSizeOrError.unwrap();
   3120 
   3121    RefPtr<PresShell> presShell = GetPresShell();
   3122    // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
   3123    // grabs it until it's destroyed later.
   3124    const CellIndexes firstSelectedCellIndexes(
   3125        MOZ_KnownLive(scanner.ElementsRef()[0]), presShell);
   3126    if (NS_WARN_IF(firstSelectedCellIndexes.isErr())) {
   3127      return NS_ERROR_FAILURE;
   3128    }
   3129 
   3130    // Get spans for cell we will merge into
   3131    int32_t firstRowSpan, firstColSpan;
   3132    nsresult rv = GetCellSpansAt(table, firstSelectedCellIndexes.mRow,
   3133                                 firstSelectedCellIndexes.mColumn, firstRowSpan,
   3134                                 firstColSpan);
   3135    if (NS_FAILED(rv)) {
   3136      NS_WARNING("HTMLEditor::GetCellSpansAt() failed");
   3137      return EditorBase::ToGenericNSResult(rv);
   3138    }
   3139 
   3140    // This defines the last indexes along the "edges"
   3141    // of the contiguous block of cells, telling us
   3142    // that we can join adjacent cells to the block
   3143    // Start with same as the first values,
   3144    // then expand as we find adjacent selected cells
   3145    int32_t lastRowIndex = firstSelectedCellIndexes.mRow;
   3146    int32_t lastColIndex = firstSelectedCellIndexes.mColumn;
   3147 
   3148    // First pass: Determine boundaries of contiguous rectangular block that
   3149    // we will join into one cell, favoring adjacent cells in the same row.
   3150    for (int32_t rowIndex = firstSelectedCellIndexes.mRow;
   3151         rowIndex <= lastRowIndex; rowIndex++) {
   3152      int32_t currentRowCount = tableSize.mRowCount;
   3153      // Be sure each row doesn't have rowspan errors
   3154      rv = FixBadRowSpan(table, rowIndex, tableSize.mRowCount);
   3155      if (NS_FAILED(rv)) {
   3156        NS_WARNING("HTMLEditor::FixBadRowSpan() failed");
   3157        return EditorBase::ToGenericNSResult(rv);
   3158      }
   3159      // Adjust rowcount by number of rows we removed
   3160      lastRowIndex -= currentRowCount - tableSize.mRowCount;
   3161 
   3162      bool cellFoundInRow = false;
   3163      bool lastRowIsSet = false;
   3164      int32_t lastColInRow = 0;
   3165      int32_t firstColInRow = firstSelectedCellIndexes.mColumn;
   3166      int32_t colIndex = firstSelectedCellIndexes.mColumn;
   3167      for (; colIndex < tableSize.mColumnCount;) {
   3168        const auto cellData =
   3169            CellData::AtIndexInTableElement(*this, *table, rowIndex, colIndex);
   3170        if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   3171          return NS_ERROR_FAILURE;
   3172        }
   3173 
   3174        if (cellData.mIsSelected) {
   3175          if (!cellFoundInRow) {
   3176            // We've just found the first selected cell in this row
   3177            firstColInRow = cellData.mCurrent.mColumn;
   3178          }
   3179          if (cellData.mCurrent.mRow > firstSelectedCellIndexes.mRow &&
   3180              firstColInRow != firstSelectedCellIndexes.mColumn) {
   3181            // We're in at least the second row,
   3182            // but left boundary is "ragged" (not the same as 1st row's start)
   3183            // Let's just end block on previous row
   3184            // and keep previous lastColIndex
   3185            // TODO: We could try to find the Maximum firstColInRow
   3186            //      so our block can still extend down more rows?
   3187            lastRowIndex = std::max(0, cellData.mCurrent.mRow - 1);
   3188            lastRowIsSet = true;
   3189            break;
   3190          }
   3191          // Save max selected column in this row, including extra colspan
   3192          lastColInRow = cellData.LastColumnIndex();
   3193          cellFoundInRow = true;
   3194        } else if (cellFoundInRow) {
   3195          // No cell or not selected, but at least one cell in row was found
   3196          if (cellData.mCurrent.mRow > firstSelectedCellIndexes.mRow + 1 &&
   3197              cellData.mCurrent.mColumn <= lastColIndex) {
   3198            // Cell is in a column less than current right border in
   3199            // the third or higher selected row, so stop block at the previous
   3200            // row
   3201            lastRowIndex = std::max(0, cellData.mCurrent.mRow - 1);
   3202            lastRowIsSet = true;
   3203          }
   3204          // We're done with this row
   3205          break;
   3206        }
   3207        MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
   3208        colIndex = cellData.NextColumnIndex();
   3209      }  // End of column loop
   3210 
   3211      // Done with this row
   3212      if (cellFoundInRow) {
   3213        if (rowIndex == firstSelectedCellIndexes.mRow) {
   3214          // First row always initializes the right boundary
   3215          lastColIndex = lastColInRow;
   3216        }
   3217 
   3218        // If we didn't determine last row above...
   3219        if (!lastRowIsSet) {
   3220          if (colIndex < lastColIndex) {
   3221            // (don't think we ever get here?)
   3222            // Cell is in a column less than current right boundary,
   3223            // so stop block at the previous row
   3224            lastRowIndex = std::max(0, rowIndex - 1);
   3225          } else {
   3226            // Go on to examine next row
   3227            lastRowIndex = rowIndex + 1;
   3228          }
   3229        }
   3230        // Use the minimum col we found so far for right boundary
   3231        lastColIndex = std::min(lastColIndex, lastColInRow);
   3232      } else {
   3233        // No selected cells in this row -- stop at row above
   3234        // and leave last column at its previous value
   3235        lastRowIndex = std::max(0, rowIndex - 1);
   3236      }
   3237    }
   3238 
   3239    // The list of cells we will delete after joining
   3240    nsTArray<RefPtr<Element>> deleteList;
   3241 
   3242    // 2nd pass: Do the joining and merging
   3243    for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
   3244      for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
   3245        const auto cellData =
   3246            CellData::AtIndexInTableElement(*this, *table, rowIndex, colIndex);
   3247        if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   3248          return NS_ERROR_FAILURE;
   3249        }
   3250 
   3251        // If this is 0, we are past last cell in row, so exit the loop
   3252        if (!cellData.mEffectiveColSpan) {
   3253          break;
   3254        }
   3255 
   3256        // Merge only selected cells (skip cell we're merging into, of course)
   3257        if (cellData.mIsSelected &&
   3258            cellData.mElement != scanner.ElementsRef()[0]) {
   3259          if (cellData.mCurrent.mRow >= firstSelectedCellIndexes.mRow &&
   3260              cellData.mCurrent.mRow <= lastRowIndex &&
   3261              cellData.mCurrent.mColumn >= firstSelectedCellIndexes.mColumn &&
   3262              cellData.mCurrent.mColumn <= lastColIndex) {
   3263            // We are within the join region
   3264            // Problem: It is very tricky to delete cells as we merge,
   3265            // since that will upset the cellmap
   3266            // Instead, build a list of cells to delete and do it later
   3267            NS_ASSERTION(!cellData.IsSpannedFromOtherRow(),
   3268                         "JoinTableCells: StartRowIndex is in row above");
   3269 
   3270            if (cellData.mEffectiveColSpan > 1) {
   3271              // Check if cell "hangs" off the boundary because of colspan > 1
   3272              // Use split methods to chop off excess
   3273              int32_t extraColSpan = cellData.mFirst.mColumn +
   3274                                     cellData.mEffectiveColSpan -
   3275                                     (lastColIndex + 1);
   3276              if (extraColSpan > 0) {
   3277                nsresult rv = SplitCellIntoColumns(
   3278                    table, cellData.mFirst.mRow, cellData.mFirst.mColumn,
   3279                    cellData.mEffectiveColSpan - extraColSpan, extraColSpan,
   3280                    nullptr);
   3281                if (NS_FAILED(rv)) {
   3282                  NS_WARNING("HTMLEditor::SplitCellIntoColumns() failed");
   3283                  return EditorBase::ToGenericNSResult(rv);
   3284                }
   3285              }
   3286            }
   3287 
   3288            nsresult rv =
   3289                MergeCells(scanner.ElementsRef()[0], cellData.mElement, false);
   3290            if (NS_FAILED(rv)) {
   3291              NS_WARNING("HTMLEditor::MergeCells() failed");
   3292              return EditorBase::ToGenericNSResult(rv);
   3293            }
   3294 
   3295            // Add cell to list to delete
   3296            deleteList.AppendElement(cellData.mElement.get());
   3297          } else if (aMergeNonContiguousContents) {
   3298            // Cell is outside join region -- just merge the contents
   3299            nsresult rv =
   3300                MergeCells(scanner.ElementsRef()[0], cellData.mElement, false);
   3301            if (NS_FAILED(rv)) {
   3302              NS_WARNING("HTMLEditor::MergeCells() failed");
   3303              return rv;
   3304            }
   3305          }
   3306        }
   3307        MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
   3308        colIndex = cellData.NextColumnIndex();
   3309      }
   3310    }
   3311 
   3312    // All cell contents are merged. Delete the empty cells we accumulated
   3313    // Prevent rules testing until we're done
   3314    IgnoredErrorResult error;
   3315    AutoEditSubActionNotifier startToHandleEditSubAction(
   3316        *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
   3317    if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3318      return EditorBase::ToGenericNSResult(error.StealNSResult());
   3319    }
   3320    NS_WARNING_ASSERTION(!error.Failed(),
   3321                         "HTMLEditor::OnStartToHandleTopLevelEditSubAction() "
   3322                         "failed, but ignored");
   3323 
   3324    for (uint32_t i = 0, n = deleteList.Length(); i < n; i++) {
   3325      RefPtr<Element> nodeToBeRemoved = deleteList[i];
   3326      if (nodeToBeRemoved) {
   3327        nsresult rv = DeleteNodeWithTransaction(*nodeToBeRemoved);
   3328        if (NS_FAILED(rv)) {
   3329          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   3330          return EditorBase::ToGenericNSResult(rv);
   3331        }
   3332      }
   3333    }
   3334    // Cleanup selection: remove ranges where cells were deleted
   3335    uint32_t rangeCount = SelectionRef().RangeCount();
   3336 
   3337    // TODO: Rewriting this with reversed ranged-loop may make it simpler.
   3338    RefPtr<nsRange> range;
   3339    for (uint32_t i = 0; i < rangeCount; i++) {
   3340      range = SelectionRef().GetRangeAt(i);
   3341      if (NS_WARN_IF(!range)) {
   3342        return NS_ERROR_FAILURE;
   3343      }
   3344 
   3345      Element* deletedCell =
   3346          HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(*range);
   3347      if (!deletedCell) {
   3348        SelectionRef().RemoveRangeAndUnselectFramesAndNotifyListeners(*range,
   3349                                                                      error);
   3350        NS_WARNING_ASSERTION(
   3351            !error.Failed(),
   3352            "Selection::RemoveRangeAndUnselectFramesAndNotifyListeners() "
   3353            "failed, but ignored");
   3354        rangeCount--;
   3355        i--;
   3356      }
   3357    }
   3358 
   3359    // Set spans for the cell everything merged into
   3360    rv = SetRowSpan(MOZ_KnownLive(scanner.ElementsRef()[0]),
   3361                    lastRowIndex - firstSelectedCellIndexes.mRow + 1);
   3362    if (NS_FAILED(rv)) {
   3363      NS_WARNING("HTMLEditor::SetRowSpan() failed");
   3364      return EditorBase::ToGenericNSResult(rv);
   3365    }
   3366    rv = SetColSpan(MOZ_KnownLive(scanner.ElementsRef()[0]),
   3367                    lastColIndex - firstSelectedCellIndexes.mColumn + 1);
   3368    if (NS_FAILED(rv)) {
   3369      NS_WARNING("HTMLEditor::SetColSpan() failed");
   3370      return EditorBase::ToGenericNSResult(rv);
   3371    }
   3372 
   3373    // Fixup disturbances in table layout
   3374    DebugOnly<nsresult> rvIgnored = NormalizeTableInternal(*table);
   3375    NS_WARNING_ASSERTION(
   3376        NS_SUCCEEDED(rvIgnored),
   3377        "HTMLEditor::NormalizeTableInternal() failed, but ignored");
   3378  } else {
   3379    // Joining with cell to the right -- get rowspan and colspan data of target
   3380    // cell.
   3381    const auto leftCellData = CellData::AtIndexInTableElement(
   3382        *this, *table, startRowIndex, startColIndex);
   3383    if (NS_WARN_IF(leftCellData.FailedOrNotFound())) {
   3384      return NS_ERROR_FAILURE;
   3385    }
   3386 
   3387    // Get data for cell to the right.
   3388    const auto rightCellData = CellData::AtIndexInTableElement(
   3389        *this, *table, leftCellData.mFirst.mRow,
   3390        leftCellData.mFirst.mColumn + leftCellData.mEffectiveColSpan);
   3391    if (NS_WARN_IF(rightCellData.FailedOrNotFound())) {
   3392      return NS_ERROR_FAILURE;
   3393    }
   3394 
   3395    // XXX So, this does not assume that CellData returns error when just not
   3396    //     found.  We need to fix this later.
   3397    if (!rightCellData.mElement) {
   3398      return NS_OK;  // Don't fail if there's no cell
   3399    }
   3400 
   3401    // sanity check
   3402    NS_ASSERTION(
   3403        rightCellData.mCurrent.mRow >= rightCellData.mFirst.mRow,
   3404        "JoinCells: rightCellData.mCurrent.mRow < rightCellData.mFirst.mRow");
   3405 
   3406    // Figure out span of merged cell starting from target's starting row
   3407    // to handle case of merged cell starting in a row above
   3408    int32_t spanAboveMergedCell = rightCellData.NumberOfPrecedingRows();
   3409    int32_t effectiveRowSpan2 =
   3410        rightCellData.mEffectiveRowSpan - spanAboveMergedCell;
   3411    if (effectiveRowSpan2 > leftCellData.mEffectiveRowSpan) {
   3412      // Cell to the right spans into row below target
   3413      // Split off portion below target cell's bottom-most row
   3414      nsresult rv = SplitCellIntoRows(
   3415          table, rightCellData.mFirst.mRow, rightCellData.mFirst.mColumn,
   3416          spanAboveMergedCell + leftCellData.mEffectiveRowSpan,
   3417          effectiveRowSpan2 - leftCellData.mEffectiveRowSpan, nullptr);
   3418      if (NS_FAILED(rv)) {
   3419        NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
   3420        return EditorBase::ToGenericNSResult(rv);
   3421      }
   3422    }
   3423 
   3424    // Move contents from cell to the right
   3425    // Delete the cell now only if it starts in the same row
   3426    //   and has enough row "height"
   3427    nsresult rv =
   3428        MergeCells(leftCellData.mElement, rightCellData.mElement,
   3429                   !rightCellData.IsSpannedFromOtherRow() &&
   3430                       effectiveRowSpan2 >= leftCellData.mEffectiveRowSpan);
   3431    if (NS_FAILED(rv)) {
   3432      NS_WARNING("HTMLEditor::MergeCells() failed");
   3433      return EditorBase::ToGenericNSResult(rv);
   3434    }
   3435 
   3436    if (effectiveRowSpan2 < leftCellData.mEffectiveRowSpan) {
   3437      // Merged cell is "shorter"
   3438      // (there are cells(s) below it that are row-spanned by target cell)
   3439      // We could try splitting those cells, but that's REAL messy,
   3440      // so the safest thing to do is NOT really join the cells
   3441      return NS_OK;
   3442    }
   3443 
   3444    if (spanAboveMergedCell > 0) {
   3445      // Cell we merged started in a row above the target cell
   3446      // Reduce rowspan to give room where target cell will extend its colspan
   3447      nsresult rv = SetRowSpan(rightCellData.mElement, spanAboveMergedCell);
   3448      if (NS_FAILED(rv)) {
   3449        NS_WARNING("HTMLEditor::SetRowSpan() failed");
   3450        return EditorBase::ToGenericNSResult(rv);
   3451      }
   3452    }
   3453 
   3454    // Reset target cell's colspan to encompass cell to the right
   3455    rv = SetColSpan(leftCellData.mElement, leftCellData.mEffectiveColSpan +
   3456                                               rightCellData.mEffectiveColSpan);
   3457    if (NS_FAILED(rv)) {
   3458      NS_WARNING("HTMLEditor::SetColSpan() failed");
   3459      return EditorBase::ToGenericNSResult(rv);
   3460    }
   3461  }
   3462  return NS_OK;
   3463 }
   3464 
   3465 nsresult HTMLEditor::MergeCells(RefPtr<Element> aTargetCell,
   3466                                RefPtr<Element> aCellToMerge,
   3467                                bool aDeleteCellToMerge) {
   3468  MOZ_ASSERT(IsEditActionDataAvailable());
   3469 
   3470  if (NS_WARN_IF(!aTargetCell) || NS_WARN_IF(!aCellToMerge)) {
   3471    return NS_ERROR_INVALID_ARG;
   3472  }
   3473 
   3474  // Prevent rules testing until we're done
   3475  IgnoredErrorResult ignoredError;
   3476  AutoEditSubActionNotifier startToHandleEditSubAction(
   3477      *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
   3478  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3479    return ignoredError.StealNSResult();
   3480  }
   3481  NS_WARNING_ASSERTION(
   3482      !ignoredError.Failed(),
   3483      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3484 
   3485  // Don't need to merge if cell is empty
   3486  if (!IsEmptyCell(aCellToMerge)) {
   3487    // Get index of last child in target cell
   3488    // If we fail or don't have children,
   3489    // we insert at index 0
   3490    int32_t insertIndex = 0;
   3491 
   3492    // Start inserting just after last child
   3493    uint32_t len = aTargetCell->GetChildCount();
   3494    if (len == 1 && IsEmptyCell(aTargetCell)) {
   3495      // Delete the empty node
   3496      nsCOMPtr<nsIContent> cellChild = aTargetCell->GetFirstChild();
   3497      if (NS_WARN_IF(!cellChild)) {
   3498        return NS_ERROR_FAILURE;
   3499      }
   3500      nsresult rv = DeleteNodeWithTransaction(*cellChild);
   3501      if (NS_FAILED(rv)) {
   3502        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   3503        return rv;
   3504      }
   3505      insertIndex = 0;
   3506    } else {
   3507      insertIndex = (int32_t)len;
   3508    }
   3509 
   3510    // Move the contents
   3511    EditorDOMPoint pointToPutCaret;
   3512    while (aCellToMerge->HasChildren()) {
   3513      nsCOMPtr<nsIContent> cellChild = aCellToMerge->GetLastChild();
   3514      if (NS_WARN_IF(!cellChild)) {
   3515        return NS_ERROR_FAILURE;
   3516      }
   3517      nsresult rv = DeleteNodeWithTransaction(*cellChild);
   3518      if (NS_FAILED(rv)) {
   3519        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   3520        return rv;
   3521      }
   3522      Result<CreateContentResult, nsresult> insertChildContentResult =
   3523          InsertNodeWithTransaction(*cellChild,
   3524                                    EditorDOMPoint(aTargetCell, insertIndex));
   3525      if (MOZ_UNLIKELY(insertChildContentResult.isErr())) {
   3526        NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
   3527        return insertChildContentResult.unwrapErr();
   3528      }
   3529      CreateContentResult unwrappedInsertChildContentResult =
   3530          insertChildContentResult.unwrap();
   3531      unwrappedInsertChildContentResult.MoveCaretPointTo(
   3532          pointToPutCaret, *this,
   3533          {SuggestCaret::OnlyIfHasSuggestion,
   3534           SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   3535    }
   3536    if (pointToPutCaret.IsSet()) {
   3537      nsresult rv = CollapseSelectionTo(pointToPutCaret);
   3538      if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3539        NS_WARNING(
   3540            "EditorBase::CollapseSelectionTo() caused destroying the editor");
   3541        return NS_ERROR_EDITOR_DESTROYED;
   3542      }
   3543      NS_WARNING_ASSERTION(
   3544          NS_SUCCEEDED(rv),
   3545          "EditorBase::CollapseSelectionTo() failed, but ignored");
   3546    }
   3547  }
   3548 
   3549  if (!aDeleteCellToMerge) {
   3550    return NS_OK;
   3551  }
   3552 
   3553  // Delete cells whose contents were moved.
   3554  nsresult rv = DeleteNodeWithTransaction(*aCellToMerge);
   3555  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3556                       "EditorBase::DeleteNodeWithTransaction() failed");
   3557  return rv;
   3558 }
   3559 
   3560 nsresult HTMLEditor::FixBadRowSpan(Element* aTable, int32_t aRowIndex,
   3561                                   int32_t& aNewRowCount) {
   3562  if (NS_WARN_IF(!aTable)) {
   3563    return NS_ERROR_INVALID_ARG;
   3564  }
   3565 
   3566  const Result<TableSize, nsresult> tableSizeOrError =
   3567      TableSize::Create(*this, *aTable);
   3568  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   3569    return tableSizeOrError.inspectErr();
   3570  }
   3571  const TableSize& tableSize = tableSizeOrError.inspect();
   3572 
   3573  int32_t minRowSpan = -1;
   3574  for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
   3575    const auto cellData =
   3576        CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, colIndex);
   3577    // NOTE: This is a *real* failure.
   3578    // CellData passes if cell is missing from cellmap
   3579    // XXX If <table> has large rowspan value or colspan value than actual
   3580    //     cells, we may hit error.  So, this method is always failed to
   3581    //     "fix" the rowspan...
   3582    if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   3583      return NS_ERROR_FAILURE;
   3584    }
   3585 
   3586    // XXX So, this does not assume that CellData returns error when just not
   3587    //     found.  We need to fix this later.
   3588    if (!cellData.mElement) {
   3589      break;
   3590    }
   3591 
   3592    if (cellData.mRowSpan > 0 && !cellData.IsSpannedFromOtherRow() &&
   3593        (cellData.mRowSpan < minRowSpan || minRowSpan == -1)) {
   3594      minRowSpan = cellData.mRowSpan;
   3595    }
   3596    MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
   3597    colIndex = cellData.NextColumnIndex();
   3598  }
   3599 
   3600  if (minRowSpan > 1) {
   3601    // The amount to reduce everyone's rowspan
   3602    // so at least one cell has rowspan = 1
   3603    int32_t rowsReduced = minRowSpan - 1;
   3604    for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
   3605      const auto cellData =
   3606          CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, colIndex);
   3607      if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   3608        return NS_ERROR_FAILURE;
   3609      }
   3610 
   3611      // Fixup rowspans only for cells starting in current row
   3612      // XXX So, this does not assume that CellData returns error when just
   3613      //     not found a cell.  Fix this later.
   3614      if (cellData.mElement && cellData.mRowSpan > 0 &&
   3615          !cellData.IsSpannedFromOtherRowOrColumn()) {
   3616        nsresult rv =
   3617            SetRowSpan(cellData.mElement, cellData.mRowSpan - rowsReduced);
   3618        if (NS_FAILED(rv)) {
   3619          NS_WARNING("HTMLEditor::SetRawSpan() failed");
   3620          return rv;
   3621        }
   3622      }
   3623      MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
   3624      colIndex = cellData.NextColumnIndex();
   3625    }
   3626  }
   3627  const Result<TableSize, nsresult> newTableSizeOrError =
   3628      TableSize::Create(*this, *aTable);
   3629  if (NS_WARN_IF(newTableSizeOrError.isErr())) {
   3630    return newTableSizeOrError.inspectErr();
   3631  }
   3632  aNewRowCount = newTableSizeOrError.inspect().mRowCount;
   3633  return NS_OK;
   3634 }
   3635 
   3636 nsresult HTMLEditor::FixBadColSpan(Element* aTable, int32_t aColIndex,
   3637                                   int32_t& aNewColCount) {
   3638  if (NS_WARN_IF(!aTable)) {
   3639    return NS_ERROR_INVALID_ARG;
   3640  }
   3641 
   3642  const Result<TableSize, nsresult> tableSizeOrError =
   3643      TableSize::Create(*this, *aTable);
   3644  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   3645    return tableSizeOrError.inspectErr();
   3646  }
   3647  const TableSize& tableSize = tableSizeOrError.inspect();
   3648 
   3649  int32_t minColSpan = -1;
   3650  for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount;) {
   3651    const auto cellData =
   3652        CellData::AtIndexInTableElement(*this, *aTable, rowIndex, aColIndex);
   3653    // NOTE: This is a *real* failure.
   3654    // CellData passes if cell is missing from cellmap
   3655    // XXX If <table> has large rowspan value or colspan value than actual
   3656    //     cells, we may hit error.  So, this method is always failed to
   3657    //     "fix" the colspan...
   3658    if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   3659      return NS_ERROR_FAILURE;
   3660    }
   3661 
   3662    // XXX So, this does not assume that CellData returns error when just
   3663    //     not found a cell.  Fix this later.
   3664    if (!cellData.mElement) {
   3665      break;
   3666    }
   3667    if (cellData.mColSpan > 0 && !cellData.IsSpannedFromOtherColumn() &&
   3668        (cellData.mColSpan < minColSpan || minColSpan == -1)) {
   3669      minColSpan = cellData.mColSpan;
   3670    }
   3671    MOZ_ASSERT(rowIndex < cellData.NextRowIndex());
   3672    rowIndex = cellData.NextRowIndex();
   3673  }
   3674 
   3675  if (minColSpan > 1) {
   3676    // The amount to reduce everyone's colspan
   3677    // so at least one cell has colspan = 1
   3678    int32_t colsReduced = minColSpan - 1;
   3679    for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount;) {
   3680      const auto cellData =
   3681          CellData::AtIndexInTableElement(*this, *aTable, rowIndex, aColIndex);
   3682      if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   3683        return NS_ERROR_FAILURE;
   3684      }
   3685 
   3686      // Fixup colspans only for cells starting in current column
   3687      // XXX So, this does not assume that CellData returns error when just
   3688      //     not found a cell.  Fix this later.
   3689      if (cellData.mElement && cellData.mColSpan > 0 &&
   3690          !cellData.IsSpannedFromOtherRowOrColumn()) {
   3691        nsresult rv =
   3692            SetColSpan(cellData.mElement, cellData.mColSpan - colsReduced);
   3693        if (NS_FAILED(rv)) {
   3694          NS_WARNING("HTMLEditor::SetColSpan() failed");
   3695          return rv;
   3696        }
   3697      }
   3698      MOZ_ASSERT(rowIndex < cellData.NextRowIndex());
   3699      rowIndex = cellData.NextRowIndex();
   3700    }
   3701  }
   3702  const Result<TableSize, nsresult> newTableSizeOrError =
   3703      TableSize::Create(*this, *aTable);
   3704  if (NS_WARN_IF(newTableSizeOrError.isErr())) {
   3705    return newTableSizeOrError.inspectErr();
   3706  }
   3707  aNewColCount = newTableSizeOrError.inspect().mColumnCount;
   3708  return NS_OK;
   3709 }
   3710 
   3711 NS_IMETHODIMP HTMLEditor::NormalizeTable(Element* aTableOrElementInTable) {
   3712  AutoEditActionDataSetter editActionData(*this, EditAction::eNormalizeTable);
   3713  if (NS_WARN_IF(!editActionData.CanHandle())) {
   3714    return NS_ERROR_NOT_INITIALIZED;
   3715  }
   3716  const RefPtr<Element> editingHost =
   3717      ComputeEditingHost(LimitInBodyElement::No);
   3718  if (NS_WARN_IF(editingHost &&
   3719                 editingHost->IsContentEditablePlainTextOnly())) {
   3720    return NS_ERROR_NOT_AVAILABLE;
   3721  }
   3722  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
   3723  if (NS_FAILED(rv)) {
   3724    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3725                         "MaybeDispatchBeforeInputEvent(), failed");
   3726    return EditorBase::ToGenericNSResult(rv);
   3727  }
   3728 
   3729  if (!aTableOrElementInTable) {
   3730    aTableOrElementInTable =
   3731        GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
   3732    if (!aTableOrElementInTable) {
   3733      NS_WARNING(
   3734          "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
   3735          "table) failed");
   3736      return NS_OK;  // Don't throw error even if the element is not in <table>.
   3737    }
   3738  }
   3739  rv = NormalizeTableInternal(*aTableOrElementInTable);
   3740  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3741                       "HTMLEditor::NormalizeTableInternal() failed");
   3742  return EditorBase::ToGenericNSResult(rv);
   3743 }
   3744 
   3745 nsresult HTMLEditor::NormalizeTableInternal(Element& aTableOrElementInTable) {
   3746  MOZ_ASSERT(IsEditActionDataAvailable());
   3747 
   3748  RefPtr<Element> tableElement;
   3749  if (aTableOrElementInTable.NodeInfo()->NameAtom() == nsGkAtoms::table) {
   3750    tableElement = &aTableOrElementInTable;
   3751  } else {
   3752    tableElement = GetInclusiveAncestorByTagNameInternal(
   3753        *nsGkAtoms::table, aTableOrElementInTable);
   3754    if (!tableElement) {
   3755      NS_WARNING(
   3756          "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
   3757          "failed");
   3758      return NS_OK;  // Don't throw error even if the element is not in <table>.
   3759    }
   3760  }
   3761 
   3762  Result<TableSize, nsresult> tableSizeOrError =
   3763      TableSize::Create(*this, *tableElement);
   3764  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   3765    return tableSizeOrError.unwrapErr();
   3766  }
   3767  // FYI: Cannot be const because the row/column count will be updated
   3768  TableSize tableSize = tableSizeOrError.unwrap();
   3769 
   3770  // Save current selection
   3771  AutoSelectionRestorer restoreSelectionLater(this);
   3772 
   3773  AutoPlaceholderBatch treateAsOneTransaction(
   3774      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3775  // Prevent auto insertion of BR in new cell until we're done
   3776  IgnoredErrorResult error;
   3777  AutoEditSubActionNotifier startToHandleEditSubAction(
   3778      *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
   3779  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3780    return error.StealNSResult();
   3781  }
   3782  NS_WARNING_ASSERTION(
   3783      !error.Failed(),
   3784      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3785 
   3786  // XXX If there is a cell which has bigger or smaller "rowspan" or "colspan"
   3787  //     values, FixBadRowSpan() will return error.  So, we can do nothing
   3788  //     if the table needs normalization...
   3789  // Scan all cells in each row to detect bad rowspan values
   3790  for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
   3791    nsresult rv = FixBadRowSpan(tableElement, rowIndex, tableSize.mRowCount);
   3792    if (NS_FAILED(rv)) {
   3793      NS_WARNING("HTMLEditor::FixBadRowSpan() failed");
   3794      return rv;
   3795    }
   3796  }
   3797  // and same for colspans
   3798  for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
   3799    nsresult rv = FixBadColSpan(tableElement, colIndex, tableSize.mColumnCount);
   3800    if (NS_FAILED(rv)) {
   3801      NS_WARNING("HTMLEditor::FixBadColSpan() failed");
   3802      return rv;
   3803    }
   3804  }
   3805 
   3806  // Fill in missing cellmap locations with empty cells
   3807  for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
   3808    RefPtr<Element> previousCellElementInRow;
   3809    for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
   3810      const auto cellData = CellData::AtIndexInTableElement(
   3811          *this, *tableElement, rowIndex, colIndex);
   3812      // NOTE: This is a *real* failure.
   3813      // CellData passes if cell is missing from cellmap
   3814      // XXX So, this method assumes that CellData won't return error when
   3815      //     just not found.  Fix this later.
   3816      if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   3817        return NS_ERROR_FAILURE;
   3818      }
   3819 
   3820      if (cellData.mElement) {
   3821        // Save the last cell found in the same row we are scanning
   3822        if (!cellData.IsSpannedFromOtherRow()) {
   3823          previousCellElementInRow = std::move(cellData.mElement);
   3824        }
   3825        continue;
   3826      }
   3827 
   3828      // We are missing a cell at a cellmap location.
   3829      // Add a cell after the previous cell element in the current row.
   3830      if (NS_WARN_IF(!previousCellElementInRow)) {
   3831        // We don't have any cells in this row -- We are really messed up!
   3832        return NS_ERROR_FAILURE;
   3833      }
   3834 
   3835      // Insert a new cell after (true), and return the new cell to us
   3836      RefPtr<Element> newCellElement;
   3837      nsresult rv = InsertCell(previousCellElementInRow, 1, 1, true, false,
   3838                               getter_AddRefs(newCellElement));
   3839      if (NS_FAILED(rv)) {
   3840        NS_WARNING("HTMLEditor::InsertCell() failed");
   3841        return rv;
   3842      }
   3843 
   3844      if (newCellElement) {
   3845        previousCellElementInRow = std::move(newCellElement);
   3846      }
   3847    }
   3848  }
   3849  return NS_OK;
   3850 }
   3851 
   3852 NS_IMETHODIMP HTMLEditor::GetCellIndexes(Element* aCellElement,
   3853                                         int32_t* aRowIndex,
   3854                                         int32_t* aColumnIndex) {
   3855  if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex)) {
   3856    return NS_ERROR_INVALID_ARG;
   3857  }
   3858 
   3859  AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellIndexes);
   3860  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   3861  if (NS_FAILED(rv)) {
   3862    NS_WARNING("HTMLEditor::GetCellIndexes() couldn't handle the job");
   3863    return EditorBase::ToGenericNSResult(rv);
   3864  }
   3865 
   3866  *aRowIndex = 0;
   3867  *aColumnIndex = 0;
   3868 
   3869  if (!aCellElement) {
   3870    // Use cell element which contains anchor of Selection when aCellElement is
   3871    // nullptr.
   3872    const CellIndexes cellIndexes(*this, SelectionRef());
   3873    if (NS_WARN_IF(cellIndexes.isErr())) {
   3874      return NS_ERROR_FAILURE;
   3875    }
   3876    *aRowIndex = cellIndexes.mRow;
   3877    *aColumnIndex = cellIndexes.mColumn;
   3878    return NS_OK;
   3879  }
   3880 
   3881  const RefPtr<PresShell> presShell{GetPresShell()};
   3882  const CellIndexes cellIndexes(*aCellElement, presShell);
   3883  if (NS_WARN_IF(cellIndexes.isErr())) {
   3884    return NS_ERROR_FAILURE;
   3885  }
   3886  *aRowIndex = cellIndexes.mRow;
   3887  *aColumnIndex = cellIndexes.mColumn;
   3888  return NS_OK;
   3889 }
   3890 
   3891 // static
   3892 nsTableWrapperFrame* HTMLEditor::GetTableFrame(const Element* aTableElement) {
   3893  if (NS_WARN_IF(!aTableElement)) {
   3894    return nullptr;
   3895  }
   3896  return do_QueryFrame(aTableElement->GetPrimaryFrame());
   3897 }
   3898 
   3899 // Return actual number of cells (a cell with colspan > 1 counts as just 1)
   3900 int32_t HTMLEditor::GetNumberOfCellsInRow(Element& aTableElement,
   3901                                          int32_t aRowIndex) {
   3902  const Result<TableSize, nsresult> tableSizeOrError =
   3903      TableSize::Create(*this, aTableElement);
   3904  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   3905    return -1;
   3906  }
   3907 
   3908  int32_t numberOfCells = 0;
   3909  for (int32_t columnIndex = 0;
   3910       columnIndex < tableSizeOrError.inspect().mColumnCount;) {
   3911    const auto cellData = CellData::AtIndexInTableElement(
   3912        *this, aTableElement, aRowIndex, columnIndex);
   3913    // Failure means that there is no more cell in the row.  In this case,
   3914    // we shouldn't return error since we just reach the end of the row.
   3915    // XXX So, this method assumes that CellData won't return error when
   3916    //     just not found.  Fix this later.
   3917    if (cellData.FailedOrNotFound()) {
   3918      break;
   3919    }
   3920 
   3921    // Only count cells that start in row we are working with
   3922    if (cellData.mElement && !cellData.IsSpannedFromOtherRow()) {
   3923      numberOfCells++;
   3924    }
   3925    MOZ_ASSERT(columnIndex < cellData.NextColumnIndex());
   3926    columnIndex = cellData.NextColumnIndex();
   3927  }
   3928  return numberOfCells;
   3929 }
   3930 
   3931 NS_IMETHODIMP HTMLEditor::GetTableSize(Element* aTableOrElementInTable,
   3932                                       int32_t* aRowCount,
   3933                                       int32_t* aColumnCount) {
   3934  if (NS_WARN_IF(!aRowCount) || NS_WARN_IF(!aColumnCount)) {
   3935    return NS_ERROR_INVALID_ARG;
   3936  }
   3937 
   3938  AutoEditActionDataSetter editActionData(*this, EditAction::eGetTableSize);
   3939  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   3940  if (NS_FAILED(rv)) {
   3941    NS_WARNING("HTMLEditor::GetTableSize() couldn't handle the job");
   3942    return EditorBase::ToGenericNSResult(rv);
   3943  }
   3944 
   3945  *aRowCount = 0;
   3946  *aColumnCount = 0;
   3947 
   3948  Element* tableOrElementInTable = aTableOrElementInTable;
   3949  if (!tableOrElementInTable) {
   3950    tableOrElementInTable =
   3951        GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
   3952    if (!tableOrElementInTable) {
   3953      NS_WARNING(
   3954          "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
   3955          "table) failed");
   3956      return NS_ERROR_FAILURE;
   3957    }
   3958  }
   3959 
   3960  const Result<TableSize, nsresult> tableSizeOrError =
   3961      TableSize::Create(*this, *tableOrElementInTable);
   3962  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   3963    return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
   3964  }
   3965  *aRowCount = tableSizeOrError.inspect().mRowCount;
   3966  *aColumnCount = tableSizeOrError.inspect().mColumnCount;
   3967  return NS_OK;
   3968 }
   3969 
   3970 NS_IMETHODIMP HTMLEditor::GetCellDataAt(
   3971    Element* aTableElement, int32_t aRowIndex, int32_t aColumnIndex,
   3972    Element** aCellElement, int32_t* aStartRowIndex, int32_t* aStartColumnIndex,
   3973    int32_t* aRowSpan, int32_t* aColSpan, int32_t* aEffectiveRowSpan,
   3974    int32_t* aEffectiveColSpan, bool* aIsSelected) {
   3975  if (NS_WARN_IF(!aCellElement) || NS_WARN_IF(!aStartRowIndex) ||
   3976      NS_WARN_IF(!aStartColumnIndex) || NS_WARN_IF(!aRowSpan) ||
   3977      NS_WARN_IF(!aColSpan) || NS_WARN_IF(!aEffectiveRowSpan) ||
   3978      NS_WARN_IF(!aEffectiveColSpan) || NS_WARN_IF(!aIsSelected)) {
   3979    return NS_ERROR_INVALID_ARG;
   3980  }
   3981 
   3982  AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellDataAt);
   3983  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   3984  if (NS_FAILED(rv)) {
   3985    NS_WARNING("HTMLEditor::GetCellDataAt() couldn't handle the job");
   3986    return EditorBase::ToGenericNSResult(rv);
   3987  }
   3988 
   3989  *aStartRowIndex = 0;
   3990  *aStartColumnIndex = 0;
   3991  *aRowSpan = 0;
   3992  *aColSpan = 0;
   3993  *aEffectiveRowSpan = 0;
   3994  *aEffectiveColSpan = 0;
   3995  *aIsSelected = false;
   3996  *aCellElement = nullptr;
   3997 
   3998  // Let's keep the table element with strong pointer since editor developers
   3999  // may not handle layout code of <table>, however, this method depends on
   4000  // them.
   4001  RefPtr<Element> table = aTableElement;
   4002  if (!table) {
   4003    // Get the selected table or the table enclosing the selection anchor.
   4004    table = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
   4005    if (!table) {
   4006      NS_WARNING(
   4007          "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
   4008          "table) failed");
   4009      return NS_ERROR_FAILURE;
   4010    }
   4011  }
   4012 
   4013  const CellData cellData =
   4014      CellData::AtIndexInTableElement(*this, *table, aRowIndex, aColumnIndex);
   4015  if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   4016    return NS_ERROR_FAILURE;
   4017  }
   4018  NS_ADDREF(*aCellElement = cellData.mElement.get());
   4019  *aIsSelected = cellData.mIsSelected;
   4020  *aStartRowIndex = cellData.mFirst.mRow;
   4021  *aStartColumnIndex = cellData.mFirst.mColumn;
   4022  *aRowSpan = cellData.mRowSpan;
   4023  *aColSpan = cellData.mColSpan;
   4024  *aEffectiveRowSpan = cellData.mEffectiveRowSpan;
   4025  *aEffectiveColSpan = cellData.mEffectiveColSpan;
   4026  return NS_OK;
   4027 }
   4028 
   4029 NS_IMETHODIMP HTMLEditor::GetCellAt(Element* aTableElement, int32_t aRowIndex,
   4030                                    int32_t aColumnIndex,
   4031                                    Element** aCellElement) {
   4032  if (NS_WARN_IF(!aCellElement)) {
   4033    return NS_ERROR_INVALID_ARG;
   4034  }
   4035 
   4036  AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellAt);
   4037  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   4038  if (NS_FAILED(rv)) {
   4039    NS_WARNING("HTMLEditor::GetCellAt() couldn't handle the job");
   4040    return EditorBase::ToGenericNSResult(rv);
   4041  }
   4042 
   4043  *aCellElement = nullptr;
   4044 
   4045  Element* tableElement = aTableElement;
   4046  if (!tableElement) {
   4047    // Get the selected table or the table enclosing the selection anchor.
   4048    tableElement = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
   4049    if (!tableElement) {
   4050      NS_WARNING(
   4051          "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
   4052          "table) failed");
   4053      return NS_ERROR_FAILURE;
   4054    }
   4055  }
   4056 
   4057  RefPtr<Element> cellElement =
   4058      GetTableCellElementAt(*tableElement, aRowIndex, aColumnIndex);
   4059  cellElement.forget(aCellElement);
   4060  return NS_OK;
   4061 }
   4062 
   4063 Element* HTMLEditor::GetTableCellElementAt(Element& aTableElement,
   4064                                           int32_t aRowIndex,
   4065                                           int32_t aColumnIndex) const {
   4066  // Let's grab the <table> element while we're retrieving layout API since
   4067  // editor developers do not watch all layout API changes.  So, it may
   4068  // become unsafe.
   4069  OwningNonNull<Element> tableElement(aTableElement);
   4070  nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(tableElement);
   4071  if (!tableFrame) {
   4072    NS_WARNING("There was no table layout information");
   4073    return nullptr;
   4074  }
   4075  nsIContent* cell = tableFrame->GetCellAt(aRowIndex, aColumnIndex);
   4076  return Element::FromNodeOrNull(cell);
   4077 }
   4078 
   4079 // When all you want are the rowspan and colspan (not exposed in nsITableEditor)
   4080 nsresult HTMLEditor::GetCellSpansAt(Element* aTable, int32_t aRowIndex,
   4081                                    int32_t aColIndex, int32_t& aActualRowSpan,
   4082                                    int32_t& aActualColSpan) {
   4083  nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(aTable);
   4084  if (!tableFrame) {
   4085    NS_WARNING("There was no table layout information");
   4086    return NS_ERROR_FAILURE;
   4087  }
   4088  aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
   4089  aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex);
   4090 
   4091  return NS_OK;
   4092 }
   4093 
   4094 nsresult HTMLEditor::GetCellContext(Element** aTable, Element** aCell,
   4095                                    nsINode** aCellParent, int32_t* aCellOffset,
   4096                                    int32_t* aRowIndex, int32_t* aColumnIndex) {
   4097  MOZ_ASSERT(IsEditActionDataAvailable());
   4098 
   4099  // Initialize return pointers
   4100  if (aTable) {
   4101    *aTable = nullptr;
   4102  }
   4103  if (aCell) {
   4104    *aCell = nullptr;
   4105  }
   4106  if (aCellParent) {
   4107    *aCellParent = nullptr;
   4108  }
   4109  if (aCellOffset) {
   4110    *aCellOffset = 0;
   4111  }
   4112  if (aRowIndex) {
   4113    *aRowIndex = 0;
   4114  }
   4115  if (aColumnIndex) {
   4116    *aColumnIndex = 0;
   4117  }
   4118 
   4119  RefPtr<Element> table;
   4120  RefPtr<Element> cell;
   4121 
   4122  // Caller may supply the cell...
   4123  if (aCell && *aCell) {
   4124    cell = *aCell;
   4125  }
   4126 
   4127  // ...but if not supplied,
   4128  //    get cell if it's the child of selection anchor node,
   4129  //    or get the enclosing by a cell
   4130  if (!cell) {
   4131    // Find a selected or enclosing table element
   4132    Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
   4133        GetSelectedOrParentTableElement();
   4134    if (cellOrRowOrTableElementOrError.isErr()) {
   4135      NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
   4136      return cellOrRowOrTableElementOrError.unwrapErr();
   4137    }
   4138    if (!cellOrRowOrTableElementOrError.inspect()) {
   4139      return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4140    }
   4141    if (cellOrRowOrTableElementOrError.inspect()->IsHTMLElement(
   4142            nsGkAtoms::table)) {
   4143      // We have a selected table, not a cell
   4144      if (aTable) {
   4145        cellOrRowOrTableElementOrError.unwrap().forget(aTable);
   4146      }
   4147      return NS_OK;
   4148    }
   4149    if (!HTMLEditUtils::IsTableCellElement(
   4150            cellOrRowOrTableElementOrError.inspect())) {
   4151      return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4152    }
   4153 
   4154    // We found a cell
   4155    cell = cellOrRowOrTableElementOrError.unwrap();
   4156  }
   4157  if (aCell) {
   4158    // we don't want to cell.forget() here, because we use it below.
   4159    *aCell = do_AddRef(cell).take();
   4160  }
   4161 
   4162  // Get containing table
   4163  table = GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *cell);
   4164  if (!table) {
   4165    NS_WARNING(
   4166        "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
   4167        "failed");
   4168    // Cell must be in a table, so fail if not found
   4169    return NS_ERROR_FAILURE;
   4170  }
   4171  if (aTable) {
   4172    table.forget(aTable);
   4173  }
   4174 
   4175  // Get the rest of the related data only if requested
   4176  if (aRowIndex || aColumnIndex) {
   4177    const RefPtr<PresShell> presShell{GetPresShell()};
   4178    const CellIndexes cellIndexes(*cell, presShell);
   4179    if (NS_WARN_IF(cellIndexes.isErr())) {
   4180      return NS_ERROR_FAILURE;
   4181    }
   4182    if (aRowIndex) {
   4183      *aRowIndex = cellIndexes.mRow;
   4184    }
   4185    if (aColumnIndex) {
   4186      *aColumnIndex = cellIndexes.mColumn;
   4187    }
   4188  }
   4189  if (aCellParent) {
   4190    // Get the immediate parent of the cell
   4191    EditorRawDOMPoint atCellElement(cell);
   4192    if (NS_WARN_IF(!atCellElement.IsSet())) {
   4193      return NS_ERROR_FAILURE;
   4194    }
   4195 
   4196    if (aCellOffset) {
   4197      *aCellOffset = atCellElement.Offset();
   4198    }
   4199 
   4200    // Now it's safe to hand over the reference to cellParent, since
   4201    // we don't need it anymore.
   4202    *aCellParent = do_AddRef(atCellElement.GetContainer()).take();
   4203  }
   4204 
   4205  return NS_OK;
   4206 }
   4207 
   4208 NS_IMETHODIMP HTMLEditor::GetSelectedCells(
   4209    nsTArray<RefPtr<Element>>& aOutSelectedCellElements) {
   4210  MOZ_ASSERT(aOutSelectedCellElements.IsEmpty());
   4211 
   4212  AutoEditActionDataSetter editActionData(*this, EditAction::eGetSelectedCells);
   4213  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   4214  if (NS_FAILED(rv)) {
   4215    NS_WARNING("HTMLEditor::GetSelectedCells() couldn't handle the job");
   4216    return EditorBase::ToGenericNSResult(rv);
   4217  }
   4218 
   4219  SelectedTableCellScanner scanner(SelectionRef());
   4220  if (!scanner.IsInTableCellSelectionMode()) {
   4221    return NS_OK;
   4222  }
   4223 
   4224  aOutSelectedCellElements.SetCapacity(scanner.ElementsRef().Length());
   4225  for (const OwningNonNull<Element>& cellElement : scanner.ElementsRef()) {
   4226    aOutSelectedCellElements.AppendElement(cellElement);
   4227  }
   4228  return NS_OK;
   4229 }
   4230 
   4231 NS_IMETHODIMP HTMLEditor::GetFirstSelectedCellInTable(int32_t* aRowIndex,
   4232                                                      int32_t* aColumnIndex,
   4233                                                      Element** aCellElement) {
   4234  if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex) ||
   4235      NS_WARN_IF(!aCellElement)) {
   4236    return NS_ERROR_INVALID_ARG;
   4237  }
   4238 
   4239  AutoEditActionDataSetter editActionData(
   4240      *this, EditAction::eGetFirstSelectedCellInTable);
   4241  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   4242  if (NS_FAILED(rv)) {
   4243    NS_WARNING(
   4244        "HTMLEditor::GetFirstSelectedCellInTable() couldn't handle the job");
   4245    return EditorBase::ToGenericNSResult(rv);
   4246  }
   4247 
   4248  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   4249    return NS_ERROR_FAILURE;  // XXX Should return NS_OK?
   4250  }
   4251 
   4252  *aRowIndex = 0;
   4253  *aColumnIndex = 0;
   4254  *aCellElement = nullptr;
   4255  RefPtr<Element> firstSelectedCellElement =
   4256      HTMLEditUtils::GetFirstSelectedTableCellElement(SelectionRef());
   4257  if (!firstSelectedCellElement) {
   4258    return NS_OK;
   4259  }
   4260 
   4261  RefPtr<PresShell> presShell = GetPresShell();
   4262  const CellIndexes indexes(*firstSelectedCellElement, presShell);
   4263  if (NS_WARN_IF(indexes.isErr())) {
   4264    return NS_ERROR_FAILURE;
   4265  }
   4266 
   4267  firstSelectedCellElement.forget(aCellElement);
   4268  *aRowIndex = indexes.mRow;
   4269  *aColumnIndex = indexes.mColumn;
   4270  return NS_OK;
   4271 }
   4272 
   4273 void HTMLEditor::SetSelectionAfterTableEdit(Element* aTable, int32_t aRow,
   4274                                            int32_t aCol, int32_t aDirection,
   4275                                            bool aSelected) {
   4276  MOZ_ASSERT(IsEditActionDataAvailable());
   4277 
   4278  if (NS_WARN_IF(!aTable) || NS_WARN_IF(Destroyed())) {
   4279    return;
   4280  }
   4281 
   4282  RefPtr<Element> cell;
   4283  bool done = false;
   4284  do {
   4285    cell = GetTableCellElementAt(*aTable, aRow, aCol);
   4286    if (cell) {
   4287      if (aSelected) {
   4288        // Reselect the cell
   4289        DebugOnly<nsresult> rv = SelectContentInternal(*cell);
   4290        NS_WARNING_ASSERTION(
   4291            NS_SUCCEEDED(rv),
   4292            "HTMLEditor::SelectContentInternal() failed, but ignored");
   4293        return;
   4294      }
   4295 
   4296      // Set the caret to deepest first child
   4297      //   but don't go into nested tables
   4298      // TODO: Should we really be placing the caret at the END
   4299      // of the cell content?
   4300      CollapseSelectionToDeepestNonTableFirstChild(cell);
   4301      return;
   4302    }
   4303 
   4304    // Setup index to find another cell in the
   4305    //   direction requested, but move in other direction if already at
   4306    //   beginning of row or column
   4307    switch (aDirection) {
   4308      case ePreviousColumn:
   4309        if (!aCol) {
   4310          if (aRow > 0) {
   4311            aRow--;
   4312          } else {
   4313            done = true;
   4314          }
   4315        } else {
   4316          aCol--;
   4317        }
   4318        break;
   4319      case ePreviousRow:
   4320        if (!aRow) {
   4321          if (aCol > 0) {
   4322            aCol--;
   4323          } else {
   4324            done = true;
   4325          }
   4326        } else {
   4327          aRow--;
   4328        }
   4329        break;
   4330      default:
   4331        done = true;
   4332    }
   4333  } while (!done);
   4334 
   4335  // We didn't find a cell
   4336  // Set selection to just before the table
   4337  if (aTable->GetParentNode()) {
   4338    EditorRawDOMPoint atTable(aTable);
   4339    if (NS_WARN_IF(!atTable.IsSetAndValid())) {
   4340      return;
   4341    }
   4342    DebugOnly<nsresult> rvIgnored = CollapseSelectionTo(atTable);
   4343    NS_WARNING_ASSERTION(
   4344        NS_SUCCEEDED(rvIgnored),
   4345        "EditorBase::CollapseSelectionTo() failed, but ignored");
   4346    return;
   4347  }
   4348  // Last resort: Set selection to start of doc
   4349  // (it's very bad to not have a valid selection!)
   4350  DebugOnly<nsresult> rvIgnored = SetSelectionAtDocumentStart();
   4351  NS_WARNING_ASSERTION(
   4352      NS_SUCCEEDED(rvIgnored),
   4353      "HTMLEditor::SetSelectionAtDocumentStart() failed, but ignored");
   4354 }
   4355 
   4356 NS_IMETHODIMP HTMLEditor::GetSelectedOrParentTableElement(
   4357    nsAString& aTagName, int32_t* aSelectedCount,
   4358    Element** aCellOrRowOrTableElement) {
   4359  if (NS_WARN_IF(!aSelectedCount) || NS_WARN_IF(!aCellOrRowOrTableElement)) {
   4360    return NS_ERROR_INVALID_ARG;
   4361  }
   4362 
   4363  aTagName.Truncate();
   4364  *aCellOrRowOrTableElement = nullptr;
   4365  *aSelectedCount = 0;
   4366 
   4367  AutoEditActionDataSetter editActionData(
   4368      *this, EditAction::eGetSelectedOrParentTableElement);
   4369  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   4370  if (NS_FAILED(rv)) {
   4371    NS_WARNING(
   4372        "HTMLEditor::GetSelectedOrParentTableElement() couldn't handle the "
   4373        "job");
   4374    return EditorBase::ToGenericNSResult(rv);
   4375  }
   4376 
   4377  bool isCellSelected = false;
   4378  Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
   4379      GetSelectedOrParentTableElement(&isCellSelected);
   4380  if (cellOrRowOrTableElementOrError.isErr()) {
   4381    NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
   4382    return EditorBase::ToGenericNSResult(
   4383        cellOrRowOrTableElementOrError.unwrapErr());
   4384  }
   4385  if (!cellOrRowOrTableElementOrError.inspect()) {
   4386    return NS_OK;
   4387  }
   4388  RefPtr<Element> cellOrRowOrTableElement =
   4389      cellOrRowOrTableElementOrError.unwrap();
   4390 
   4391  if (isCellSelected) {
   4392    aTagName.AssignLiteral("td");
   4393    *aSelectedCount = SelectionRef().RangeCount();
   4394    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
   4395    return NS_OK;
   4396  }
   4397 
   4398  if (HTMLEditUtils::IsTableCellElement(*cellOrRowOrTableElement)) {
   4399    aTagName.AssignLiteral("td");
   4400    // Keep *aSelectedCount as 0.
   4401    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
   4402    return NS_OK;
   4403  }
   4404 
   4405  if (cellOrRowOrTableElement->IsHTMLElement(nsGkAtoms::table)) {
   4406    aTagName.AssignLiteral("table");
   4407    *aSelectedCount = 1;
   4408    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
   4409    return NS_OK;
   4410  }
   4411 
   4412  if (HTMLEditUtils::IsTableRowElement(*cellOrRowOrTableElement)) {
   4413    aTagName.AssignLiteral("tr");
   4414    *aSelectedCount = 1;
   4415    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
   4416    return NS_OK;
   4417  }
   4418 
   4419  MOZ_ASSERT_UNREACHABLE("Which element was returned?");
   4420  return NS_ERROR_UNEXPECTED;
   4421 }
   4422 
   4423 Result<RefPtr<Element>, nsresult> HTMLEditor::GetSelectedOrParentTableElement(
   4424    bool* aIsCellSelected /* = nullptr */) const {
   4425  MOZ_ASSERT(IsEditActionDataAvailable());
   4426 
   4427  if (aIsCellSelected) {
   4428    *aIsCellSelected = false;
   4429  }
   4430 
   4431  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   4432    return Err(NS_ERROR_FAILURE);  // XXX Shouldn't throw an exception?
   4433  }
   4434 
   4435  // Try to get the first selected cell, first.
   4436  RefPtr<Element> cellElement =
   4437      HTMLEditUtils::GetFirstSelectedTableCellElement(SelectionRef());
   4438  if (cellElement) {
   4439    if (aIsCellSelected) {
   4440      *aIsCellSelected = true;
   4441    }
   4442    return cellElement;
   4443  }
   4444 
   4445  const RangeBoundary& anchorRef = SelectionRef().AnchorRef();
   4446  if (NS_WARN_IF(!anchorRef.IsSet())) {
   4447    return Err(NS_ERROR_FAILURE);
   4448  }
   4449 
   4450  // If anchor selects a <td>, <table> or <tr>, return it.
   4451  if (anchorRef.GetContainer()->HasChildNodes()) {
   4452    nsIContent* selectedContent = anchorRef.GetChildAtOffset();
   4453    if (selectedContent) {
   4454      // XXX Why do we ignore <th> element in this case?
   4455      if (selectedContent->IsHTMLElement(nsGkAtoms::td)) {
   4456        // FYI: If first range selects a <tr> element, but the other selects
   4457        //      a <td> element, you can reach here.
   4458        // Each cell is in its own selection range in this case.
   4459        // XXX Although, other ranges may not select cells, though.
   4460        if (aIsCellSelected) {
   4461          *aIsCellSelected = true;
   4462        }
   4463        return RefPtr<Element>(selectedContent->AsElement());
   4464      }
   4465      if (selectedContent->IsAnyOfHTMLElements(nsGkAtoms::table,
   4466                                               nsGkAtoms::tr)) {
   4467        return RefPtr<Element>(selectedContent->AsElement());
   4468      }
   4469    }
   4470  }
   4471 
   4472  if (NS_WARN_IF(!anchorRef.GetContainer()->IsContent())) {
   4473    return RefPtr<Element>();
   4474  }
   4475 
   4476  // Then, look for a cell element (either <td> or <th>) which contains
   4477  // the anchor container.
   4478  cellElement = GetInclusiveAncestorByTagNameInternal(
   4479      *nsGkAtoms::td, *anchorRef.GetContainer()->AsContent());
   4480  if (!cellElement) {
   4481    return RefPtr<Element>();  // Not in table.
   4482  }
   4483  // Don't set *aIsCellSelected to true in this case because it does NOT
   4484  // select a cell, just in a cell.
   4485  return cellElement;
   4486 }
   4487 
   4488 Result<RefPtr<Element>, nsresult>
   4489 HTMLEditor::GetFirstSelectedCellElementInTable() const {
   4490  Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
   4491      GetSelectedOrParentTableElement();
   4492  if (cellOrRowOrTableElementOrError.isErr()) {
   4493    NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
   4494    return cellOrRowOrTableElementOrError;
   4495  }
   4496 
   4497  if (!cellOrRowOrTableElementOrError.inspect()) {
   4498    return cellOrRowOrTableElementOrError;
   4499  }
   4500 
   4501  const RefPtr<Element>& element = cellOrRowOrTableElementOrError.inspect();
   4502  if (!HTMLEditUtils::IsTableCellElement(*element)) {
   4503    return RefPtr<Element>();
   4504  }
   4505 
   4506  if (!HTMLEditUtils::IsTableRowElement(element->GetParent())) {
   4507    NS_WARNING("There was no parent <tr> element for the found cell");
   4508    return RefPtr<Element>();
   4509  }
   4510 
   4511  if (!HTMLEditUtils::GetClosestAncestorTableElement(*element)) {
   4512    NS_WARNING("There was no ancestor <table> element for the found cell");
   4513    return Err(NS_ERROR_FAILURE);
   4514  }
   4515  return cellOrRowOrTableElementOrError;
   4516 }
   4517 
   4518 NS_IMETHODIMP HTMLEditor::GetSelectedCellsType(Element* aElement,
   4519                                               uint32_t* aSelectionType) {
   4520  if (NS_WARN_IF(!aSelectionType)) {
   4521    return NS_ERROR_INVALID_ARG;
   4522  }
   4523  *aSelectionType = 0;
   4524 
   4525  AutoEditActionDataSetter editActionData(*this,
   4526                                          EditAction::eGetSelectedCellsType);
   4527  nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
   4528  if (NS_FAILED(rv)) {
   4529    NS_WARNING("HTMLEditor::GetSelectedCellsType() couldn't handle the job");
   4530    return EditorBase::ToGenericNSResult(rv);
   4531  }
   4532 
   4533  if (NS_WARN_IF(!SelectionRef().RangeCount())) {
   4534    return NS_ERROR_FAILURE;  // XXX Should we just return NS_OK?
   4535  }
   4536 
   4537  // Be sure we have a table element
   4538  //  (if aElement is null, this uses selection's anchor node)
   4539  RefPtr<Element> table;
   4540  if (aElement) {
   4541    table = GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *aElement);
   4542    if (!table) {
   4543      NS_WARNING(
   4544          "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
   4545          "failed");
   4546      return NS_ERROR_FAILURE;
   4547    }
   4548  } else {
   4549    table = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
   4550    if (!table) {
   4551      NS_WARNING(
   4552          "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
   4553          "table) failed");
   4554      return NS_ERROR_FAILURE;
   4555    }
   4556  }
   4557 
   4558  const Result<TableSize, nsresult> tableSizeOrError =
   4559      TableSize::Create(*this, *table);
   4560  if (NS_WARN_IF(tableSizeOrError.isErr())) {
   4561    return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
   4562  }
   4563  const TableSize& tableSize = tableSizeOrError.inspect();
   4564 
   4565  // Traverse all selected cells
   4566  SelectedTableCellScanner scanner(SelectionRef());
   4567  if (!scanner.IsInTableCellSelectionMode()) {
   4568    return NS_OK;
   4569  }
   4570 
   4571  // We have at least one selected cell, so set return value
   4572  *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Cell);
   4573 
   4574  // Store indexes of each row/col to avoid duplication of searches
   4575  nsTArray<int32_t> indexArray;
   4576 
   4577  const RefPtr<PresShell> presShell{GetPresShell()};
   4578  bool allCellsInRowAreSelected = false;
   4579  for (const OwningNonNull<Element>& selectedCellElement :
   4580       scanner.ElementsRef()) {
   4581    // `MOZ_KnownLive(selectedCellElement)` is safe because `scanner` grabs
   4582    // it until it's destroyed later.
   4583    const CellIndexes selectedCellIndexes(MOZ_KnownLive(selectedCellElement),
   4584                                          presShell);
   4585    if (NS_WARN_IF(selectedCellIndexes.isErr())) {
   4586      return NS_ERROR_FAILURE;
   4587    }
   4588    if (!indexArray.Contains(selectedCellIndexes.mColumn)) {
   4589      indexArray.AppendElement(selectedCellIndexes.mColumn);
   4590      allCellsInRowAreSelected = AllCellsInRowSelected(
   4591          table, selectedCellIndexes.mRow, tableSize.mColumnCount);
   4592      // We're done as soon as we fail for any row
   4593      if (!allCellsInRowAreSelected) {
   4594        break;
   4595      }
   4596    }
   4597  }
   4598 
   4599  if (allCellsInRowAreSelected) {
   4600    *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Row);
   4601    return NS_OK;
   4602  }
   4603  // Test for columns
   4604 
   4605  // Empty the indexArray
   4606  indexArray.Clear();
   4607 
   4608  // Start at first cell again
   4609  bool allCellsInColAreSelected = false;
   4610  for (const OwningNonNull<Element>& selectedCellElement :
   4611       scanner.ElementsRef()) {
   4612    // `MOZ_KnownLive(selectedCellElement)` is safe because `scanner` grabs
   4613    // it until it's destroyed later.
   4614    const CellIndexes selectedCellIndexes(MOZ_KnownLive(selectedCellElement),
   4615                                          presShell);
   4616    if (NS_WARN_IF(selectedCellIndexes.isErr())) {
   4617      return NS_ERROR_FAILURE;
   4618    }
   4619 
   4620    if (!indexArray.Contains(selectedCellIndexes.mRow)) {
   4621      indexArray.AppendElement(selectedCellIndexes.mColumn);
   4622      allCellsInColAreSelected = AllCellsInColumnSelected(
   4623          table, selectedCellIndexes.mColumn, tableSize.mRowCount);
   4624      // We're done as soon as we fail for any column
   4625      if (!allCellsInRowAreSelected) {
   4626        break;
   4627      }
   4628    }
   4629  }
   4630  if (allCellsInColAreSelected) {
   4631    *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Column);
   4632  }
   4633 
   4634  return NS_OK;
   4635 }
   4636 
   4637 bool HTMLEditor::AllCellsInRowSelected(Element* aTable, int32_t aRowIndex,
   4638                                       int32_t aNumberOfColumns) {
   4639  if (NS_WARN_IF(!aTable)) {
   4640    return false;
   4641  }
   4642 
   4643  for (int32_t col = 0; col < aNumberOfColumns;) {
   4644    const auto cellData =
   4645        CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, col);
   4646    if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   4647      return false;
   4648    }
   4649 
   4650    // If no cell, we may have a "ragged" right edge, so return TRUE only if
   4651    // we already found a cell in the row.
   4652    // XXX So, this does not assume that CellData returns error when just
   4653    //     not found a cell.  Fix this later.
   4654    if (!cellData.mElement) {
   4655      NS_WARNING("CellData didn't set mElement");
   4656      return cellData.mCurrent.mColumn > 0;
   4657    }
   4658 
   4659    // Return as soon as a non-selected cell is found.
   4660    // XXX Odd, this is testing if each cell element is selected.  Why do
   4661    //     we need to warn if it's false??
   4662    if (!cellData.mIsSelected) {
   4663      NS_WARNING("CellData didn't set mIsSelected");
   4664      return false;
   4665    }
   4666 
   4667    MOZ_ASSERT(col < cellData.NextColumnIndex());
   4668    col = cellData.NextColumnIndex();
   4669  }
   4670  return true;
   4671 }
   4672 
   4673 bool HTMLEditor::AllCellsInColumnSelected(Element* aTable, int32_t aColIndex,
   4674                                          int32_t aNumberOfRows) {
   4675  if (NS_WARN_IF(!aTable)) {
   4676    return false;
   4677  }
   4678 
   4679  for (int32_t row = 0; row < aNumberOfRows;) {
   4680    const auto cellData =
   4681        CellData::AtIndexInTableElement(*this, *aTable, row, aColIndex);
   4682    if (NS_WARN_IF(cellData.FailedOrNotFound())) {
   4683      return false;
   4684    }
   4685 
   4686    // If no cell, we must have a "ragged" right edge on the last column so
   4687    // return TRUE only if we already found a cell in the row.
   4688    // XXX So, this does not assume that CellData returns error when just
   4689    //     not found a cell.  Fix this later.
   4690    if (!cellData.mElement) {
   4691      NS_WARNING("CellData didn't set mElement");
   4692      return cellData.mCurrent.mRow > 0;
   4693    }
   4694 
   4695    // Return as soon as a non-selected cell is found.
   4696    // XXX Odd, this is testing if each cell element is selected.  Why do
   4697    //     we need to warn if it's false??
   4698    if (!cellData.mIsSelected) {
   4699      NS_WARNING("CellData didn't set mIsSelected");
   4700      return false;
   4701    }
   4702 
   4703    MOZ_ASSERT(row < cellData.NextRowIndex());
   4704    row = cellData.NextRowIndex();
   4705  }
   4706  return true;
   4707 }
   4708 
   4709 bool HTMLEditor::IsEmptyCell(dom::Element* aCell) {
   4710  MOZ_ASSERT(aCell);
   4711 
   4712  // Check if target only contains empty text node or <br>
   4713  nsCOMPtr<nsINode> cellChild = aCell->GetFirstChild();
   4714  if (!cellChild) {
   4715    return false;
   4716  }
   4717 
   4718  nsCOMPtr<nsINode> nextChild = cellChild->GetNextSibling();
   4719  if (nextChild) {
   4720    return false;
   4721  }
   4722 
   4723  // We insert a single break into a cell by default
   4724  //   to have some place to locate a cursor -- it is dispensable
   4725  if (cellChild->IsHTMLElement(nsGkAtoms::br)) {
   4726    return true;
   4727  }
   4728 
   4729  // Or check if no real content
   4730  return HTMLEditUtils::IsEmptyNode(
   4731      *cellChild, {EmptyCheckOption::TreatSingleBRElementAsVisible,
   4732                   EmptyCheckOption::TreatNonEditableContentAsInvisible});
   4733 }
   4734 
   4735 }  // namespace mozilla