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