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/dom/Selection.h"
17 #include "mozilla/dom/Element.h"
18 #include "nsAString.h"
19 #include "nsAlgorithm.h"
20 #include "nsCOMPtr.h"
21 #include "nsDebug.h"
22 #include "nsError.h"
23 #include "nsGkAtoms.h"
24 #include "nsAtom.h"
25 #include "nsIContent.h"
26 #include "nsIDOMElement.h"
27 #include "nsIDOMNode.h"
28 #include "nsIFrame.h"
29 #include "nsINode.h"
30 #include "nsIPresShell.h"
31 #include "nsISupportsUtils.h"
32 #include "nsITableCellLayout.h"  // For efficient access to table cell
33 #include "nsLiteralString.h"
34 #include "nsQueryFrame.h"
35 #include "nsRange.h"
36 #include "nsString.h"
37 #include "nsTArray.h"
38 #include "nsTableCellFrame.h"
39 #include "nsTableWrapperFrame.h"
40 #include "nscore.h"
41 #include <algorithm>
42 
43 namespace mozilla {
44 
45 using namespace dom;
46 
47 /**
48  * Stack based helper class for restoring selection after table edit.
49  */
50 class MOZ_STACK_CLASS AutoSelectionSetterAfterTableEdit final {
51  private:
52   RefPtr<HTMLEditor> mHTMLEditor;
53   nsCOMPtr<nsIDOMElement> mTable;
54   int32_t mCol, mRow, mDirection, mSelected;
55 
56  public:
AutoSelectionSetterAfterTableEdit(HTMLEditor & aHTMLEditor,nsIDOMElement * aTable,int32_t aRow,int32_t aCol,int32_t aDirection,bool aSelected)57   AutoSelectionSetterAfterTableEdit(HTMLEditor& aHTMLEditor,
58                                     nsIDOMElement* aTable, int32_t aRow,
59                                     int32_t aCol, int32_t aDirection,
60                                     bool aSelected)
61       : mHTMLEditor(&aHTMLEditor),
62         mTable(aTable),
63         mCol(aCol),
64         mRow(aRow),
65         mDirection(aDirection),
66         mSelected(aSelected) {}
67 
~AutoSelectionSetterAfterTableEdit()68   ~AutoSelectionSetterAfterTableEdit() {
69     if (mHTMLEditor) {
70       mHTMLEditor->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection,
71                                               mSelected);
72     }
73   }
74 
75   // This is needed to abort the caret reset in the destructor
76   // when one method yields control to another
CancelSetCaret()77   void CancelSetCaret() {
78     mHTMLEditor = nullptr;
79     mTable = nullptr;
80   }
81 };
82 
InsertCell(nsIDOMElement * aDOMCell,int32_t aRowSpan,int32_t aColSpan,bool aAfter,bool aIsHeader,nsIDOMElement ** aNewDOMCell)83 nsresult HTMLEditor::InsertCell(nsIDOMElement* aDOMCell, int32_t aRowSpan,
84                                 int32_t aColSpan, bool aAfter, bool aIsHeader,
85                                 nsIDOMElement** aNewDOMCell) {
86   if (aNewDOMCell) {
87     *aNewDOMCell = nullptr;
88   }
89 
90   if (NS_WARN_IF(!aDOMCell)) {
91     return NS_ERROR_NULL_POINTER;
92   }
93 
94   // And the parent and offsets needed to do an insert
95   nsCOMPtr<nsIContent> cell = do_QueryInterface(aDOMCell);
96   if (NS_WARN_IF(!cell)) {
97     return NS_ERROR_INVALID_ARG;
98   }
99   EditorDOMPoint pointToInsert(cell);
100   if (NS_WARN_IF(!pointToInsert.IsSet())) {
101     return NS_ERROR_INVALID_ARG;
102   }
103 
104   nsCOMPtr<nsIDOMElement> newDOMCell;
105   nsresult rv = CreateElementWithDefaults(
106       aIsHeader ? NS_LITERAL_STRING("th") : NS_LITERAL_STRING("tb"),
107       getter_AddRefs(newDOMCell));
108   if (NS_WARN_IF(NS_FAILED(rv))) {
109     return rv;
110   }
111   nsCOMPtr<Element> newCell = do_QueryInterface(newDOMCell);
112   if (NS_WARN_IF(!newCell)) {
113     return NS_ERROR_FAILURE;
114   }
115 
116   // Optional: return new cell created
117   if (aNewDOMCell) {
118     newDOMCell.forget(aNewDOMCell);
119   }
120 
121   if (aRowSpan > 1) {
122     // Note: Do NOT use editor transaction for this
123     nsAutoString newRowSpan;
124     newRowSpan.AppendInt(aRowSpan, 10);
125     newCell->SetAttr(kNameSpaceID_None, nsGkAtoms::rowspan, newRowSpan, true);
126   }
127   if (aColSpan > 1) {
128     // Note: Do NOT use editor transaction for this
129     nsAutoString newColSpan;
130     newColSpan.AppendInt(aColSpan, 10);
131     newCell->SetAttr(kNameSpaceID_None, nsGkAtoms::colspan, newColSpan, true);
132   }
133   if (aAfter) {
134     DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
135     NS_WARNING_ASSERTION(advanced,
136                          "Failed to advance offset to after the old cell");
137   }
138 
139   // Don't let Rules System change the selection.
140   AutoTransactionsConserveSelection dontChangeSelection(this);
141   return InsertNode(*newCell, pointToInsert.AsRaw());
142 }
143 
SetColSpan(nsIDOMElement * aCell,int32_t aColSpan)144 nsresult HTMLEditor::SetColSpan(nsIDOMElement* aCell, int32_t aColSpan) {
145   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
146   nsAutoString newSpan;
147   newSpan.AppendInt(aColSpan, 10);
148   return SetAttribute(aCell, NS_LITERAL_STRING("colspan"), newSpan);
149 }
150 
SetRowSpan(nsIDOMElement * aCell,int32_t aRowSpan)151 nsresult HTMLEditor::SetRowSpan(nsIDOMElement* aCell, int32_t aRowSpan) {
152   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
153   nsAutoString newSpan;
154   newSpan.AppendInt(aRowSpan, 10);
155   return SetAttribute(aCell, NS_LITERAL_STRING("rowspan"), newSpan);
156 }
157 
158 NS_IMETHODIMP
InsertTableCell(int32_t aNumber,bool aAfter)159 HTMLEditor::InsertTableCell(int32_t aNumber, bool aAfter) {
160   nsCOMPtr<nsIDOMElement> table;
161   nsCOMPtr<nsIDOMElement> curCell;
162   nsCOMPtr<nsIDOMNode> cellParent;
163   int32_t cellOffset, startRowIndex, startColIndex;
164   nsresult rv = GetCellContext(
165       nullptr, getter_AddRefs(table), getter_AddRefs(curCell),
166       getter_AddRefs(cellParent), &cellOffset, &startRowIndex, &startColIndex);
167   NS_ENSURE_SUCCESS(rv, rv);
168   // Don't fail if no cell found
169   NS_ENSURE_TRUE(curCell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
170 
171   // Get more data for current cell in row we are inserting at (we need COLSPAN)
172   int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan,
173       actualColSpan;
174   bool isSelected;
175   rv = GetCellDataAt(table, startRowIndex, startColIndex,
176                      getter_AddRefs(curCell), &curStartRowIndex,
177                      &curStartColIndex, &rowSpan, &colSpan, &actualRowSpan,
178                      &actualColSpan, &isSelected);
179   NS_ENSURE_SUCCESS(rv, rv);
180   NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE);
181   int32_t newCellIndex = aAfter ? (startColIndex + colSpan) : startColIndex;
182   // We control selection resetting after the insert...
183   AutoSelectionSetterAfterTableEdit setCaret(
184       *this, table, startRowIndex, newCellIndex, ePreviousColumn, false);
185   //...so suppress Rules System selection munging
186   AutoTransactionsConserveSelection dontChangeSelection(this);
187 
188   for (int32_t i = 0; i < aNumber; i++) {
189     nsCOMPtr<nsIDOMElement> newCell;
190     rv = CreateElementWithDefaults(NS_LITERAL_STRING("td"),
191                                    getter_AddRefs(newCell));
192     if (NS_SUCCEEDED(rv) && newCell) {
193       if (aAfter) {
194         cellOffset++;
195       }
196       nsCOMPtr<nsIContent> cell = do_QueryInterface(newCell);
197       if (NS_WARN_IF(!cell)) {
198         return NS_ERROR_FAILURE;
199       }
200       rv = InsertNode(*cell, EditorRawDOMPoint(cellParent, cellOffset));
201       if (NS_FAILED(rv)) {
202         break;
203       }
204     }
205   }
206   // XXX This is perhaps the result of the last call of InsertNode() or
207   //     CreateElementWithDefaults().
208   return rv;
209 }
210 
211 NS_IMETHODIMP
GetFirstRow(nsIDOMElement * aTableElement,nsIDOMNode ** aRowNode)212 HTMLEditor::GetFirstRow(nsIDOMElement* aTableElement, nsIDOMNode** aRowNode) {
213   NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER);
214 
215   *aRowNode = nullptr;
216 
217   nsCOMPtr<nsINode> tableElement = do_QueryInterface(aTableElement);
218   NS_ENSURE_TRUE(tableElement, NS_ERROR_NULL_POINTER);
219 
220   tableElement =
221       GetElementOrParentByTagName(NS_LITERAL_STRING("table"), tableElement);
222   NS_ENSURE_TRUE(tableElement, NS_ERROR_NULL_POINTER);
223 
224   nsCOMPtr<nsIContent> tableChild = tableElement->GetFirstChild();
225   while (tableChild) {
226     if (tableChild->IsHTMLElement(nsGkAtoms::tr)) {
227       // Found a row directly under <table>
228       *aRowNode = tableChild->AsDOMNode();
229       NS_ADDREF(*aRowNode);
230       return NS_OK;
231     }
232     // Look for row in one of the row container elements
233     if (tableChild->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::thead,
234                                         nsGkAtoms::tfoot)) {
235       nsCOMPtr<nsIContent> rowNode = tableChild->GetFirstChild();
236 
237       // We can encounter textnodes here -- must find a row
238       while (rowNode && !HTMLEditUtils::IsTableRow(rowNode)) {
239         rowNode = rowNode->GetNextSibling();
240       }
241 
242       if (rowNode) {
243         *aRowNode = rowNode->AsDOMNode();
244         NS_ADDREF(*aRowNode);
245         return NS_OK;
246       }
247     }
248     // Here if table child was a CAPTION or COLGROUP
249     //  or child of a row parent wasn't a row (bad HTML?),
250     //  or first child was a textnode
251     // Look in next table child
252     tableChild = tableChild->GetNextSibling();
253   }
254   // If here, row was not found
255   return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
256 }
257 
258 NS_IMETHODIMP
GetNextRow(nsIDOMNode * aCurrentRowNode,nsIDOMNode ** aRowNode)259 HTMLEditor::GetNextRow(nsIDOMNode* aCurrentRowNode, nsIDOMNode** aRowNode) {
260   NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER);
261 
262   *aRowNode = nullptr;
263 
264   nsCOMPtr<nsINode> currentRowNode = do_QueryInterface(aCurrentRowNode);
265   NS_ENSURE_TRUE(currentRowNode, NS_ERROR_NULL_POINTER);
266 
267   if (!HTMLEditUtils::IsTableRow(currentRowNode)) {
268     return NS_ERROR_FAILURE;
269   }
270 
271   nsIContent* nextRow = currentRowNode->GetNextSibling();
272 
273   // Skip over any textnodes here
274   while (nextRow && !HTMLEditUtils::IsTableRow(nextRow)) {
275     nextRow = nextRow->GetNextSibling();
276   }
277   if (nextRow) {
278     *aRowNode = nextRow->AsDOMNode();
279     NS_ADDREF(*aRowNode);
280     return NS_OK;
281   }
282 
283   // No row found, search for rows in other table sections
284   nsINode* rowParent = currentRowNode->GetParentNode();
285   NS_ENSURE_TRUE(rowParent, NS_ERROR_NULL_POINTER);
286 
287   nsIContent* parentSibling = rowParent->GetNextSibling();
288 
289   while (parentSibling) {
290     nextRow = parentSibling->GetFirstChild();
291 
292     // We can encounter textnodes here -- must find a row
293     while (nextRow && !HTMLEditUtils::IsTableRow(nextRow)) {
294       nextRow = nextRow->GetNextSibling();
295     }
296     if (nextRow) {
297       *aRowNode = nextRow->AsDOMNode();
298       NS_ADDREF(*aRowNode);
299       return NS_OK;
300     }
301 
302     // We arrive here only if a table section has no children
303     //  or first child of section is not a row (bad HTML or more "_moz_text"
304     //  nodes!)
305     // So look for another section sibling
306     parentSibling = parentSibling->GetNextSibling();
307   }
308   // If here, row was not found
309   return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
310 }
311 
GetLastCellInRow(nsIDOMNode * aRowNode,nsIDOMNode ** aCellNode)312 nsresult HTMLEditor::GetLastCellInRow(nsIDOMNode* aRowNode,
313                                       nsIDOMNode** aCellNode) {
314   NS_ENSURE_TRUE(aCellNode, NS_ERROR_NULL_POINTER);
315 
316   *aCellNode = nullptr;
317 
318   nsCOMPtr<nsINode> rowNode = do_QueryInterface(aRowNode);
319   NS_ENSURE_TRUE(rowNode, NS_ERROR_NULL_POINTER);
320 
321   nsCOMPtr<nsINode> rowChild = rowNode->GetLastChild();
322 
323   while (rowChild && !HTMLEditUtils::IsTableCell(rowChild)) {
324     // Skip over textnodes
325     rowChild = rowChild->GetPreviousSibling();
326   }
327   if (rowChild) {
328     *aCellNode = rowChild->AsDOMNode();
329     NS_ADDREF(*aCellNode);
330     return NS_OK;
331   }
332   // If here, cell was not found
333   return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
334 }
335 
336 NS_IMETHODIMP
InsertTableColumn(int32_t aNumber,bool aAfter)337 HTMLEditor::InsertTableColumn(int32_t aNumber, bool aAfter) {
338   RefPtr<Selection> selection;
339   nsCOMPtr<nsIDOMElement> table;
340   nsCOMPtr<nsIDOMElement> curCell;
341   int32_t startRowIndex, startColIndex;
342   nsresult rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
343                                getter_AddRefs(curCell), nullptr, nullptr,
344                                &startRowIndex, &startColIndex);
345   NS_ENSURE_SUCCESS(rv, rv);
346   // Don't fail if no cell found
347   NS_ENSURE_TRUE(curCell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
348 
349   // Get more data for current cell (we need ROWSPAN)
350   int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan,
351       actualColSpan;
352   bool isSelected;
353   rv = GetCellDataAt(table, startRowIndex, startColIndex,
354                      getter_AddRefs(curCell), &curStartRowIndex,
355                      &curStartColIndex, &rowSpan, &colSpan, &actualRowSpan,
356                      &actualColSpan, &isSelected);
357   NS_ENSURE_SUCCESS(rv, rv);
358   NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE);
359 
360   AutoPlaceholderBatch beginBatching(this);
361   // Prevent auto insertion of BR in new cell until we're done
362   AutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
363 
364   // Use column after current cell if requested
365   if (aAfter) {
366     startColIndex += actualColSpan;
367     // Detect when user is adding after a COLSPAN=0 case
368     // Assume they want to stop the "0" behavior and
369     // really add a new column. Thus we set the
370     // colspan to its true value
371     if (!colSpan) {
372       SetColSpan(curCell, actualColSpan);
373     }
374   }
375 
376   int32_t rowCount, colCount, rowIndex;
377   rv = GetTableSize(table, &rowCount, &colCount);
378   NS_ENSURE_SUCCESS(rv, rv);
379 
380   // We reset caret in destructor...
381   AutoSelectionSetterAfterTableEdit setCaret(
382       *this, table, startRowIndex, startColIndex, ePreviousRow, false);
383   //.. so suppress Rules System selection munging
384   AutoTransactionsConserveSelection dontChangeSelection(this);
385 
386   // If we are inserting after all existing columns
387   // Make sure table is "well formed"
388   //  before appending new column
389   if (startColIndex >= colCount) {
390     NormalizeTable(table);
391   }
392 
393   nsCOMPtr<nsIDOMNode> rowNode;
394   for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
395     if (startColIndex < colCount) {
396       // We are inserting before an existing column
397       rv =
398           GetCellDataAt(table, rowIndex, startColIndex, getter_AddRefs(curCell),
399                         &curStartRowIndex, &curStartColIndex, &rowSpan,
400                         &colSpan, &actualRowSpan, &actualColSpan, &isSelected);
401       NS_ENSURE_SUCCESS(rv, rv);
402 
403       // Don't fail entire process if we fail to find a cell
404       //  (may fail just in particular rows with < adequate cells per row)
405       if (curCell) {
406         if (curStartColIndex < startColIndex) {
407           // We have a cell spanning this location
408           // Simply increase its colspan to keep table rectangular
409           // Note: we do nothing if colsSpan=0,
410           //  since it should automatically span the new column
411           if (colSpan > 0) {
412             SetColSpan(curCell, colSpan + aNumber);
413           }
414         } else {
415           // Simply set selection to the current cell
416           //  so we can let InsertTableCell() do the work
417           // Insert a new cell before current one
418           selection->Collapse(curCell, 0);
419           rv = InsertTableCell(aNumber, false);
420         }
421       }
422     } else {
423       // Get current row and append new cells after last cell in row
424       if (!rowIndex) {
425         rv = GetFirstRow(table.get(), getter_AddRefs(rowNode));
426         if (NS_WARN_IF(NS_FAILED(rv))) {
427           return rv;
428         }
429       } else {
430         nsCOMPtr<nsIDOMNode> nextRow;
431         rv = GetNextRow(rowNode.get(), getter_AddRefs(nextRow));
432         if (NS_WARN_IF(NS_FAILED(rv))) {
433           return rv;
434         }
435         rowNode = nextRow;
436       }
437 
438       if (rowNode) {
439         nsCOMPtr<nsIDOMNode> lastCell;
440         rv = GetLastCellInRow(rowNode, getter_AddRefs(lastCell));
441         NS_ENSURE_SUCCESS(rv, rv);
442         NS_ENSURE_TRUE(lastCell, NS_ERROR_FAILURE);
443 
444         curCell = do_QueryInterface(lastCell);
445         if (curCell) {
446           // Simply add same number of cells to each row
447           // Although tempted to check cell indexes for curCell,
448           //  the effects of COLSPAN>1 in some cells makes this futile!
449           // We must use NormalizeTable first to assure
450           //  that there are cells in each cellmap location
451           selection->Collapse(curCell, 0);
452           rv = InsertTableCell(aNumber, true);
453         }
454       }
455     }
456   }
457   // XXX This is perhaps the result of the last call of InsertTableCell().
458   return rv;
459 }
460 
461 NS_IMETHODIMP
InsertTableRow(int32_t aNumber,bool aAfter)462 HTMLEditor::InsertTableRow(int32_t aNumber, bool aAfter) {
463   nsCOMPtr<nsIDOMElement> table;
464   nsCOMPtr<nsIDOMElement> curCell;
465 
466   int32_t startRowIndex, startColIndex;
467   nsresult rv =
468       GetCellContext(nullptr, getter_AddRefs(table), getter_AddRefs(curCell),
469                      nullptr, nullptr, &startRowIndex, &startColIndex);
470   NS_ENSURE_SUCCESS(rv, rv);
471   // Don't fail if no cell found
472   NS_ENSURE_TRUE(curCell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
473 
474   // Get more data for current cell in row we are inserting at (we need COLSPAN)
475   int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan,
476       actualColSpan;
477   bool isSelected;
478   rv = GetCellDataAt(table, startRowIndex, startColIndex,
479                      getter_AddRefs(curCell), &curStartRowIndex,
480                      &curStartColIndex, &rowSpan, &colSpan, &actualRowSpan,
481                      &actualColSpan, &isSelected);
482   NS_ENSURE_SUCCESS(rv, rv);
483   NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE);
484 
485   int32_t rowCount, colCount;
486   rv = GetTableSize(table, &rowCount, &colCount);
487   NS_ENSURE_SUCCESS(rv, rv);
488 
489   AutoPlaceholderBatch beginBatching(this);
490   // Prevent auto insertion of BR in new cell until we're done
491   AutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
492 
493   if (aAfter) {
494     // Use row after current cell
495     startRowIndex += actualRowSpan;
496 
497     // Detect when user is adding after a ROWSPAN=0 case
498     // Assume they want to stop the "0" behavior and
499     // really add a new row. Thus we set the
500     // rowspan to its true value
501     if (!rowSpan) {
502       SetRowSpan(curCell, actualRowSpan);
503     }
504   }
505 
506   // We control selection resetting after the insert...
507   AutoSelectionSetterAfterTableEdit setCaret(
508       *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
509   //...so suppress Rules System selection munging
510   AutoTransactionsConserveSelection dontChangeSelection(this);
511 
512   nsCOMPtr<nsIDOMElement> cellForRowParent;
513   int32_t cellsInRow = 0;
514   if (startRowIndex < rowCount) {
515     // We are inserting above an existing row
516     // Get each cell in the insert row to adjust for COLSPAN effects while we
517     //   count how many cells are needed
518     int32_t colIndex = 0;
519     while (NS_SUCCEEDED(
520         GetCellDataAt(table, startRowIndex, colIndex, getter_AddRefs(curCell),
521                       &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan,
522                       &actualRowSpan, &actualColSpan, &isSelected))) {
523       if (curCell) {
524         if (curStartRowIndex < startRowIndex) {
525           // We have a cell spanning this location
526           // Simply increase its rowspan
527           // Note that if rowSpan == 0, we do nothing,
528           //  since that cell should automatically extend into the new row
529           if (rowSpan > 0) {
530             SetRowSpan(curCell, rowSpan + aNumber);
531           }
532         } else {
533           // We have a cell in the insert row
534 
535           // Count the number of cells we need to add to the new row
536           cellsInRow += actualColSpan;
537 
538           // Save cell we will use below
539           if (!cellForRowParent) {
540             cellForRowParent = curCell;
541           }
542         }
543         // Next cell in row
544         colIndex += actualColSpan;
545       } else {
546         colIndex++;
547       }
548     }
549   } else {
550     // We are adding a new row after all others
551     // If it weren't for colspan=0 effect,
552     // we could simply use colCount for number of new cells...
553     // XXX colspan=0 support has now been removed in table layout so maybe this
554     // can be cleaned up now? (bug 1243183)
555     cellsInRow = colCount;
556 
557     // ...but we must compensate for all cells with rowSpan = 0 in the last row
558     int32_t lastRow = rowCount - 1;
559     int32_t tempColIndex = 0;
560     while (NS_SUCCEEDED(
561         GetCellDataAt(table, lastRow, tempColIndex, getter_AddRefs(curCell),
562                       &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan,
563                       &actualRowSpan, &actualColSpan, &isSelected))) {
564       if (!rowSpan) {
565         cellsInRow -= actualColSpan;
566       }
567 
568       tempColIndex += actualColSpan;
569 
570       // Save cell from the last row that we will use below
571       if (!cellForRowParent && curStartRowIndex == lastRow) {
572         cellForRowParent = curCell;
573       }
574     }
575   }
576 
577   nsCOMPtr<nsINode> cellNodeForRowParent = do_QueryInterface(cellForRowParent);
578 
579   if (cellsInRow > 0) {
580     NS_NAMED_LITERAL_STRING(trStr, "tr");
581     if (!cellNodeForRowParent) {
582       return NS_ERROR_FAILURE;
583     }
584 
585     nsCOMPtr<Element> parentRow =
586         GetElementOrParentByTagName(trStr, cellNodeForRowParent);
587     NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER);
588 
589     // The row parent and offset where we will insert new row
590     nsCOMPtr<nsINode> parentOfRow = parentRow->GetParentNode();
591     NS_ENSURE_TRUE(parentOfRow, NS_ERROR_NULL_POINTER);
592     int32_t newRowOffset = parentOfRow->ComputeIndexOf(parentRow);
593 
594     // Adjust for when adding past the end
595     if (aAfter && startRowIndex >= rowCount) {
596       newRowOffset++;
597     }
598 
599     for (int32_t row = 0; row < aNumber; row++) {
600       // Create a new row
601       nsCOMPtr<Element> newRow = CreateElementWithDefaults(trStr);
602       NS_ENSURE_TRUE(newRow, NS_ERROR_FAILURE);
603 
604       for (int32_t i = 0; i < cellsInRow; i++) {
605         nsCOMPtr<Element> newCell =
606             CreateElementWithDefaults(NS_LITERAL_STRING("td"));
607         NS_ENSURE_TRUE(newCell, NS_ERROR_FAILURE);
608 
609         // Don't use transaction system yet! (not until entire row is
610         // inserted)
611         ErrorResult result;
612         newRow->AppendChild(*newCell, result);
613         if (NS_WARN_IF(result.Failed())) {
614           return result.StealNSResult();
615         }
616       }
617 
618       // Use transaction system to insert the entire row+cells
619       // (Note that rows are inserted at same childoffset each time)
620       rv = InsertNode(*newRow, EditorRawDOMPoint(parentOfRow, newRowOffset));
621       NS_ENSURE_SUCCESS(rv, rv);
622     }
623   }
624 
625   // SetSelectionAfterTableEdit from AutoSelectionSetterAfterTableEdit will
626   // access frame selection, so we need reframe.
627   // Because GetCellAt depends on frame.
628   nsCOMPtr<nsIPresShell> ps = GetPresShell();
629   if (ps) {
630     ps->FlushPendingNotifications(FlushType::Frames);
631   }
632 
633   return NS_OK;
634 }
635 
636 // Editor helper only
637 // XXX Code changed for bug 217717 and now we don't need aSelection param
638 //     TODO: Remove aSelection param
DeleteTable2(nsIDOMElement * aTable,Selection * aSelection)639 nsresult HTMLEditor::DeleteTable2(nsIDOMElement* aTable,
640                                   Selection* aSelection) {
641   NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
642 
643   // Select the table
644   nsresult rv = ClearSelection();
645   if (NS_WARN_IF(NS_FAILED(rv))) {
646     return rv;
647   }
648   rv = AppendNodeToSelectionAsRange(aTable);
649   NS_ENSURE_SUCCESS(rv, rv);
650 
651   return DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
652 }
653 
654 NS_IMETHODIMP
DeleteTable()655 HTMLEditor::DeleteTable() {
656   RefPtr<Selection> selection;
657   nsCOMPtr<nsIDOMElement> table;
658   nsresult rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
659                                nullptr, nullptr, nullptr, nullptr, nullptr);
660   NS_ENSURE_SUCCESS(rv, rv);
661 
662   AutoPlaceholderBatch beginBatching(this);
663   return DeleteTable2(table, selection);
664 }
665 
666 NS_IMETHODIMP
DeleteTableCell(int32_t aNumber)667 HTMLEditor::DeleteTableCell(int32_t aNumber) {
668   RefPtr<Selection> selection;
669   nsCOMPtr<nsIDOMElement> table;
670   nsCOMPtr<nsIDOMElement> cell;
671   int32_t startRowIndex, startColIndex;
672 
673   nsresult rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
674                                getter_AddRefs(cell), nullptr, nullptr,
675                                &startRowIndex, &startColIndex);
676 
677   NS_ENSURE_SUCCESS(rv, rv);
678   // Don't fail if we didn't find a table or cell
679   NS_ENSURE_TRUE(table && cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
680 
681   AutoPlaceholderBatch beginBatching(this);
682   // Prevent rules testing until we're done
683   AutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext);
684 
685   nsCOMPtr<nsIDOMElement> firstCell;
686   rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
687   NS_ENSURE_SUCCESS(rv, rv);
688 
689   if (firstCell && selection->RangeCount() > 1) {
690     // When > 1 selected cell,
691     //  ignore aNumber and use selected cells
692     cell = firstCell;
693 
694     int32_t rowCount, colCount;
695     rv = GetTableSize(table, &rowCount, &colCount);
696     NS_ENSURE_SUCCESS(rv, rv);
697 
698     // Get indexes -- may be different than original cell
699     rv = GetCellIndexes(cell, &startRowIndex, &startColIndex);
700     NS_ENSURE_SUCCESS(rv, rv);
701 
702     // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
703     // destructor
704     AutoSelectionSetterAfterTableEdit setCaret(
705         *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
706     AutoTransactionsConserveSelection dontChangeSelection(this);
707 
708     bool checkToDeleteRow = true;
709     bool checkToDeleteColumn = true;
710     while (cell) {
711       bool deleteRow = false;
712       bool deleteCol = false;
713 
714       if (checkToDeleteRow) {
715         // Optimize to delete an entire row
716         // Clear so we don't repeat AllCellsInRowSelected within the same row
717         checkToDeleteRow = false;
718 
719         deleteRow = AllCellsInRowSelected(table, startRowIndex, colCount);
720         if (deleteRow) {
721           // First, find the next cell in a different row
722           //   to continue after we delete this row
723           int32_t nextRow = startRowIndex;
724           while (nextRow == startRowIndex) {
725             rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
726             NS_ENSURE_SUCCESS(rv, rv);
727             if (!cell) {
728               break;
729             }
730             rv = GetCellIndexes(cell, &nextRow, &startColIndex);
731             NS_ENSURE_SUCCESS(rv, rv);
732           }
733           // Delete entire row
734           rv = DeleteRow(table, startRowIndex);
735           NS_ENSURE_SUCCESS(rv, rv);
736 
737           if (cell) {
738             // For the next cell: Subtract 1 for row we deleted
739             startRowIndex = nextRow - 1;
740             // Set true since we know we will look at a new row next
741             checkToDeleteRow = true;
742           }
743         }
744       }
745       if (!deleteRow) {
746         if (checkToDeleteColumn) {
747           // Optimize to delete an entire column
748           // Clear this so we don't repeat AllCellsInColSelected within the same
749           // Col
750           checkToDeleteColumn = false;
751 
752           deleteCol = AllCellsInColumnSelected(table, startColIndex, colCount);
753           if (deleteCol) {
754             // First, find the next cell in a different column
755             //   to continue after we delete this column
756             int32_t nextCol = startColIndex;
757             while (nextCol == startColIndex) {
758               rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
759               NS_ENSURE_SUCCESS(rv, rv);
760               if (!cell) {
761                 break;
762               }
763               rv = GetCellIndexes(cell, &startRowIndex, &nextCol);
764               NS_ENSURE_SUCCESS(rv, rv);
765             }
766             // Delete entire Col
767             rv = DeleteColumn(table, startColIndex);
768             NS_ENSURE_SUCCESS(rv, rv);
769             if (cell) {
770               // For the next cell, subtract 1 for col. deleted
771               startColIndex = nextCol - 1;
772               // Set true since we know we will look at a new column next
773               checkToDeleteColumn = true;
774             }
775           }
776         }
777         if (!deleteCol) {
778           // First get the next cell to delete
779           nsCOMPtr<nsIDOMElement> nextCell;
780           rv = GetNextSelectedCell(nullptr, getter_AddRefs(nextCell));
781           NS_ENSURE_SUCCESS(rv, rv);
782 
783           // Then delete the cell
784           rv = DeleteNode(cell);
785           NS_ENSURE_SUCCESS(rv, rv);
786 
787           // The next cell to delete
788           cell = nextCell;
789           if (cell) {
790             rv = GetCellIndexes(cell, &startRowIndex, &startColIndex);
791             NS_ENSURE_SUCCESS(rv, rv);
792           }
793         }
794       }
795     }
796   } else {
797     for (int32_t i = 0; i < aNumber; i++) {
798       rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
799                           getter_AddRefs(cell), nullptr, nullptr,
800                           &startRowIndex, &startColIndex);
801       NS_ENSURE_SUCCESS(rv, rv);
802       // Don't fail if no cell found
803       NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
804 
805       if (GetNumberOfCellsInRow(table, startRowIndex) == 1) {
806         nsCOMPtr<nsIDOMElement> parentRow;
807         rv = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell,
808                                          getter_AddRefs(parentRow));
809         NS_ENSURE_SUCCESS(rv, rv);
810         NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER);
811 
812         // We should delete the row instead,
813         //  but first check if its the only row left
814         //  so we can delete the entire table
815         int32_t rowCount, colCount;
816         rv = GetTableSize(table, &rowCount, &colCount);
817         NS_ENSURE_SUCCESS(rv, rv);
818 
819         if (rowCount == 1) {
820           return DeleteTable2(table, selection);
821         }
822 
823         // We need to call DeleteTableRow to handle cells with rowspan
824         rv = DeleteTableRow(1);
825         NS_ENSURE_SUCCESS(rv, rv);
826       } else {
827         // More than 1 cell in the row
828 
829         // The setCaret object will call AutoSelectionSetterAfterTableEdit in
830         // its destructor
831         AutoSelectionSetterAfterTableEdit setCaret(
832             *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
833         AutoTransactionsConserveSelection dontChangeSelection(this);
834 
835         rv = DeleteNode(cell);
836         // If we fail, don't try to delete any more cells???
837         NS_ENSURE_SUCCESS(rv, rv);
838       }
839     }
840   }
841   return NS_OK;
842 }
843 
844 NS_IMETHODIMP
DeleteTableCellContents()845 HTMLEditor::DeleteTableCellContents() {
846   RefPtr<Selection> selection;
847   nsCOMPtr<nsIDOMElement> table;
848   nsCOMPtr<nsIDOMElement> cell;
849   int32_t startRowIndex, startColIndex;
850   nsresult rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
851                                getter_AddRefs(cell), nullptr, nullptr,
852                                &startRowIndex, &startColIndex);
853   NS_ENSURE_SUCCESS(rv, rv);
854   // Don't fail if no cell found
855   NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
856 
857   AutoPlaceholderBatch beginBatching(this);
858   // Prevent rules testing until we're done
859   AutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext);
860   // Don't let Rules System change the selection
861   AutoTransactionsConserveSelection dontChangeSelection(this);
862 
863   nsCOMPtr<nsIDOMElement> firstCell;
864   rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
865   NS_ENSURE_SUCCESS(rv, rv);
866 
867   if (firstCell) {
868     cell = firstCell;
869     rv = GetCellIndexes(cell, &startRowIndex, &startColIndex);
870     NS_ENSURE_SUCCESS(rv, rv);
871   }
872 
873   AutoSelectionSetterAfterTableEdit setCaret(
874       *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
875 
876   while (cell) {
877     DeleteCellContents(cell);
878     if (firstCell) {
879       // We doing a selected cells, so do all of them
880       rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
881       NS_ENSURE_SUCCESS(rv, rv);
882     } else {
883       cell = nullptr;
884     }
885   }
886   return NS_OK;
887 }
888 
DeleteCellContents(nsIDOMElement * aCell)889 nsresult HTMLEditor::DeleteCellContents(nsIDOMElement* aCell) {
890   nsCOMPtr<Element> cell = do_QueryInterface(aCell);
891   NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER);
892 
893   // Prevent rules testing until we're done
894   AutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext);
895 
896   while (nsCOMPtr<nsINode> child = cell->GetLastChild()) {
897     nsresult rv = DeleteNode(child);
898     NS_ENSURE_SUCCESS(rv, rv);
899   }
900   return NS_OK;
901 }
902 
903 NS_IMETHODIMP
DeleteTableColumn(int32_t aNumber)904 HTMLEditor::DeleteTableColumn(int32_t aNumber) {
905   RefPtr<Selection> selection;
906   nsCOMPtr<nsIDOMElement> table;
907   nsCOMPtr<nsIDOMElement> cell;
908   int32_t startRowIndex, startColIndex, rowCount, colCount;
909   nsresult rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
910                                getter_AddRefs(cell), nullptr, nullptr,
911                                &startRowIndex, &startColIndex);
912   NS_ENSURE_SUCCESS(rv, rv);
913   // Don't fail if no cell found
914   NS_ENSURE_TRUE(table && cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
915 
916   rv = GetTableSize(table, &rowCount, &colCount);
917   NS_ENSURE_SUCCESS(rv, rv);
918 
919   // Shortcut the case of deleting all columns in table
920   if (!startColIndex && aNumber >= colCount) {
921     return DeleteTable2(table, selection);
922   }
923 
924   // Check for counts too high
925   aNumber = std::min(aNumber, (colCount - startColIndex));
926 
927   AutoPlaceholderBatch beginBatching(this);
928   // Prevent rules testing until we're done
929   AutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext);
930 
931   // Test if deletion is controlled by selected cells
932   nsCOMPtr<nsIDOMElement> firstCell;
933   rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
934   NS_ENSURE_SUCCESS(rv, rv);
935 
936   uint32_t rangeCount = selection->RangeCount();
937 
938   if (firstCell && rangeCount > 1) {
939     // Fetch indexes again - may be different for selected cells
940     rv = GetCellIndexes(firstCell, &startRowIndex, &startColIndex);
941     NS_ENSURE_SUCCESS(rv, rv);
942   }
943   // We control selection resetting after the insert...
944   AutoSelectionSetterAfterTableEdit setCaret(
945       *this, table, startRowIndex, startColIndex, ePreviousRow, false);
946 
947   if (firstCell && rangeCount > 1) {
948     // Use selected cells to determine what rows to delete
949     cell = firstCell;
950 
951     while (cell) {
952       if (cell != firstCell) {
953         rv = GetCellIndexes(cell, &startRowIndex, &startColIndex);
954         NS_ENSURE_SUCCESS(rv, rv);
955       }
956       // Find the next cell in a different column
957       // to continue after we delete this column
958       int32_t nextCol = startColIndex;
959       while (nextCol == startColIndex) {
960         rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
961         NS_ENSURE_SUCCESS(rv, rv);
962         if (!cell) {
963           break;
964         }
965         rv = GetCellIndexes(cell, &startRowIndex, &nextCol);
966         NS_ENSURE_SUCCESS(rv, rv);
967       }
968       rv = DeleteColumn(table, startColIndex);
969       NS_ENSURE_SUCCESS(rv, rv);
970     }
971   } else {
972     for (int32_t i = 0; i < aNumber; i++) {
973       rv = DeleteColumn(table, startColIndex);
974       NS_ENSURE_SUCCESS(rv, rv);
975     }
976   }
977   return NS_OK;
978 }
979 
DeleteColumn(nsIDOMElement * aTable,int32_t aColIndex)980 nsresult HTMLEditor::DeleteColumn(nsIDOMElement* aTable, int32_t aColIndex) {
981   NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
982 
983   nsCOMPtr<nsIDOMElement> cell;
984   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
985       actualColSpan;
986   bool isSelected;
987   int32_t rowIndex = 0;
988 
989   do {
990     nsresult rv =
991         GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
992                       &startRowIndex, &startColIndex, &rowSpan, &colSpan,
993                       &actualRowSpan, &actualColSpan, &isSelected);
994     NS_ENSURE_SUCCESS(rv, rv);
995 
996     if (cell) {
997       // Find cells that don't start in column we are deleting
998       if (startColIndex < aColIndex || colSpan > 1 || !colSpan) {
999         // We have a cell spanning this location
1000         // Decrease its colspan to keep table rectangular,
1001         // but if colSpan=0, it will adjust automatically
1002         if (colSpan > 0) {
1003           NS_ASSERTION((colSpan > 1), "Bad COLSPAN in DeleteTableColumn");
1004           SetColSpan(cell, colSpan - 1);
1005         }
1006         if (startColIndex == aColIndex) {
1007           // Cell is in column to be deleted, but must have colspan > 1,
1008           // so delete contents of cell instead of cell itself
1009           // (We must have reset colspan above)
1010           DeleteCellContents(cell);
1011         }
1012         // To next cell in column
1013         rowIndex += actualRowSpan;
1014       } else {
1015         // Delete the cell
1016         if (GetNumberOfCellsInRow(aTable, rowIndex) == 1) {
1017           // Only 1 cell in row - delete the row
1018           nsCOMPtr<nsIDOMElement> parentRow;
1019           rv = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell,
1020                                            getter_AddRefs(parentRow));
1021           NS_ENSURE_SUCCESS(rv, rv);
1022           if (!parentRow) {
1023             return NS_ERROR_NULL_POINTER;
1024           }
1025 
1026           //  But first check if its the only row left
1027           //  so we can delete the entire table
1028           //  (This should never happen but it's the safe thing to do)
1029           int32_t rowCount, colCount;
1030           rv = GetTableSize(aTable, &rowCount, &colCount);
1031           NS_ENSURE_SUCCESS(rv, rv);
1032 
1033           if (rowCount == 1) {
1034             RefPtr<Selection> selection = GetSelection();
1035             NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1036             return DeleteTable2(aTable, selection);
1037           }
1038 
1039           // Delete the row by placing caret in cell we were to delete
1040           // We need to call DeleteTableRow to handle cells with rowspan
1041           rv = DeleteRow(aTable, startRowIndex);
1042           NS_ENSURE_SUCCESS(rv, rv);
1043 
1044           // Note that we don't incremenet rowIndex
1045           // since a row was deleted and "next"
1046           // row now has current rowIndex
1047         } else {
1048           // A more "normal" deletion
1049           rv = DeleteNode(cell);
1050           NS_ENSURE_SUCCESS(rv, rv);
1051 
1052           // Skip over any rows spanned by this cell
1053           rowIndex += actualRowSpan;
1054         }
1055       }
1056     }
1057   } while (cell);
1058 
1059   return NS_OK;
1060 }
1061 
1062 NS_IMETHODIMP
DeleteTableRow(int32_t aNumber)1063 HTMLEditor::DeleteTableRow(int32_t aNumber) {
1064   RefPtr<Selection> selection;
1065   nsCOMPtr<nsIDOMElement> table;
1066   nsCOMPtr<nsIDOMElement> cell;
1067   int32_t startRowIndex, startColIndex;
1068   int32_t rowCount, colCount;
1069   nsresult rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
1070                                getter_AddRefs(cell), nullptr, nullptr,
1071                                &startRowIndex, &startColIndex);
1072   NS_ENSURE_SUCCESS(rv, rv);
1073   // Don't fail if no cell found
1074   NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
1075 
1076   rv = GetTableSize(table, &rowCount, &colCount);
1077   NS_ENSURE_SUCCESS(rv, rv);
1078 
1079   // Shortcut the case of deleting all rows in table
1080   if (!startRowIndex && aNumber >= rowCount) {
1081     return DeleteTable2(table, selection);
1082   }
1083 
1084   AutoPlaceholderBatch beginBatching(this);
1085   // Prevent rules testing until we're done
1086   AutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext);
1087 
1088   nsCOMPtr<nsIDOMElement> firstCell;
1089   rv = GetFirstSelectedCell(nullptr, getter_AddRefs(firstCell));
1090   NS_ENSURE_SUCCESS(rv, rv);
1091 
1092   uint32_t rangeCount = selection->RangeCount();
1093 
1094   if (firstCell && rangeCount > 1) {
1095     // Fetch indexes again - may be different for selected cells
1096     rv = GetCellIndexes(firstCell, &startRowIndex, &startColIndex);
1097     NS_ENSURE_SUCCESS(rv, rv);
1098   }
1099 
1100   // We control selection resetting after the insert...
1101   AutoSelectionSetterAfterTableEdit setCaret(
1102       *this, table, startRowIndex, startColIndex, ePreviousRow, false);
1103   // Don't change selection during deletions
1104   AutoTransactionsConserveSelection dontChangeSelection(this);
1105 
1106   if (firstCell && rangeCount > 1) {
1107     // Use selected cells to determine what rows to delete
1108     cell = firstCell;
1109 
1110     while (cell) {
1111       if (cell != firstCell) {
1112         rv = GetCellIndexes(cell, &startRowIndex, &startColIndex);
1113         NS_ENSURE_SUCCESS(rv, rv);
1114       }
1115       // Find the next cell in a different row
1116       // to continue after we delete this row
1117       int32_t nextRow = startRowIndex;
1118       while (nextRow == startRowIndex) {
1119         rv = GetNextSelectedCell(nullptr, getter_AddRefs(cell));
1120         NS_ENSURE_SUCCESS(rv, rv);
1121         if (!cell) break;
1122         rv = GetCellIndexes(cell, &nextRow, &startColIndex);
1123         NS_ENSURE_SUCCESS(rv, rv);
1124       }
1125       // Delete entire row
1126       rv = DeleteRow(table, startRowIndex);
1127       NS_ENSURE_SUCCESS(rv, rv);
1128     }
1129   } else {
1130     // Check for counts too high
1131     aNumber = std::min(aNumber, (rowCount - startRowIndex));
1132     for (int32_t i = 0; i < aNumber; i++) {
1133       rv = DeleteRow(table, startRowIndex);
1134       // If failed in current row, try the next
1135       if (NS_FAILED(rv)) {
1136         startRowIndex++;
1137       }
1138 
1139       // Check if there's a cell in the "next" row
1140       rv = GetCellAt(table, startRowIndex, startColIndex, getter_AddRefs(cell));
1141       NS_ENSURE_SUCCESS(rv, rv);
1142       if (!cell) {
1143         break;
1144       }
1145     }
1146   }
1147   return NS_OK;
1148 }
1149 
1150 // Helper that doesn't batch or change the selection
DeleteRow(nsIDOMElement * aTable,int32_t aRowIndex)1151 nsresult HTMLEditor::DeleteRow(nsIDOMElement* aTable, int32_t aRowIndex) {
1152   NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
1153 
1154   nsCOMPtr<nsIDOMElement> cell;
1155   nsCOMPtr<nsIDOMElement> cellInDeleteRow;
1156   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
1157       actualColSpan;
1158   bool isSelected;
1159   int32_t colIndex = 0;
1160 
1161   // Prevent rules testing until we're done
1162   AutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext);
1163 
1164   // The list of cells we will change rowspan in
1165   //  and the new rowspan values for each
1166   nsTArray<nsCOMPtr<nsIDOMElement> > spanCellList;
1167   nsTArray<int32_t> newSpanList;
1168 
1169   int32_t rowCount, colCount;
1170   nsresult rv = GetTableSize(aTable, &rowCount, &colCount);
1171   NS_ENSURE_SUCCESS(rv, rv);
1172 
1173   // Scan through cells in row to do rowspan adjustments
1174   // Note that after we delete row, startRowIndex will point to the
1175   //   cells in the next row to be deleted
1176   do {
1177     if (aRowIndex >= rowCount || colIndex >= colCount) {
1178       break;
1179     }
1180 
1181     rv = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
1182                        &startRowIndex, &startColIndex, &rowSpan, &colSpan,
1183                        &actualRowSpan, &actualColSpan, &isSelected);
1184     // We don't fail if we don't find a cell, so this must be real bad
1185     if (NS_FAILED(rv)) {
1186       return rv;
1187     }
1188 
1189     // Compensate for cells that don't start or extend below the row we are
1190     // deleting
1191     if (cell) {
1192       if (startRowIndex < aRowIndex) {
1193         // Cell starts in row above us
1194         // Decrease its rowspan to keep table rectangular
1195         //  but we don't need to do this if rowspan=0,
1196         //  since it will automatically adjust
1197         if (rowSpan > 0) {
1198           // Build list of cells to change rowspan
1199           // We can't do it now since it upsets cell map,
1200           //  so we will do it after deleting the row
1201           spanCellList.AppendElement(cell);
1202           newSpanList.AppendElement(
1203               std::max((aRowIndex - startRowIndex), actualRowSpan - 1));
1204         }
1205       } else {
1206         if (rowSpan > 1) {
1207           // Cell spans below row to delete, so we must insert new cells to
1208           // keep rows below.  Note that we test "rowSpan" so we don't do this
1209           // if rowSpan = 0 (automatic readjustment).
1210           int32_t aboveRowToInsertNewCellInto = aRowIndex - startRowIndex + 1;
1211           int32_t numOfRawSpanRemainingBelow = actualRowSpan - 1;
1212           rv = SplitCellIntoRows(aTable, startRowIndex, startColIndex,
1213                                  aboveRowToInsertNewCellInto,
1214                                  numOfRawSpanRemainingBelow, nullptr);
1215           NS_ENSURE_SUCCESS(rv, rv);
1216         }
1217         if (!cellInDeleteRow) {
1218           cellInDeleteRow = cell;  // Reference cell to find row to delete
1219         }
1220       }
1221       // Skip over other columns spanned by this cell
1222       colIndex += actualColSpan;
1223     }
1224   } while (cell);
1225 
1226   // Things are messed up if we didn't find a cell in the row!
1227   NS_ENSURE_TRUE(cellInDeleteRow, NS_ERROR_FAILURE);
1228 
1229   // Delete the entire row
1230   nsCOMPtr<nsIDOMElement> parentRow;
1231   rv = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cellInDeleteRow,
1232                                    getter_AddRefs(parentRow));
1233   NS_ENSURE_SUCCESS(rv, rv);
1234 
1235   if (parentRow) {
1236     rv = DeleteNode(parentRow);
1237     NS_ENSURE_SUCCESS(rv, rv);
1238   }
1239 
1240   // Now we can set new rowspans for cells stored above
1241   for (uint32_t i = 0, n = spanCellList.Length(); i < n; i++) {
1242     nsIDOMElement* cellPtr = spanCellList[i];
1243     if (cellPtr) {
1244       rv = SetRowSpan(cellPtr, newSpanList[i]);
1245       NS_ENSURE_SUCCESS(rv, rv);
1246     }
1247   }
1248   return NS_OK;
1249 }
1250 
1251 NS_IMETHODIMP
SelectTable()1252 HTMLEditor::SelectTable() {
1253   nsCOMPtr<nsIDOMElement> table;
1254   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr,
1255                                             getter_AddRefs(table));
1256   NS_ENSURE_SUCCESS(rv, rv);
1257   // Don't fail if we didn't find a table
1258   NS_ENSURE_TRUE(table, NS_OK);
1259 
1260   rv = ClearSelection();
1261   if (NS_FAILED(rv)) {
1262     return rv;
1263   }
1264   return AppendNodeToSelectionAsRange(table);
1265 }
1266 
1267 NS_IMETHODIMP
SelectTableCell()1268 HTMLEditor::SelectTableCell() {
1269   nsCOMPtr<nsIDOMElement> cell;
1270   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr,
1271                                             getter_AddRefs(cell));
1272   NS_ENSURE_SUCCESS(rv, rv);
1273   NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
1274 
1275   rv = ClearSelection();
1276   if (NS_FAILED(rv)) {
1277     return rv;
1278   }
1279   return AppendNodeToSelectionAsRange(cell);
1280 }
1281 
1282 NS_IMETHODIMP
SelectBlockOfCells(nsIDOMElement * aStartCell,nsIDOMElement * aEndCell)1283 HTMLEditor::SelectBlockOfCells(nsIDOMElement* aStartCell,
1284                                nsIDOMElement* aEndCell) {
1285   NS_ENSURE_TRUE(aStartCell && aEndCell, NS_ERROR_NULL_POINTER);
1286 
1287   RefPtr<Selection> selection = GetSelection();
1288   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1289 
1290   NS_NAMED_LITERAL_STRING(tableStr, "table");
1291   nsCOMPtr<nsIDOMElement> table;
1292   nsresult rv =
1293       GetElementOrParentByTagName(tableStr, aStartCell, getter_AddRefs(table));
1294   NS_ENSURE_SUCCESS(rv, rv);
1295   NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
1296 
1297   nsCOMPtr<nsIDOMElement> endTable;
1298   rv =
1299       GetElementOrParentByTagName(tableStr, aEndCell, getter_AddRefs(endTable));
1300   NS_ENSURE_SUCCESS(rv, rv);
1301   NS_ENSURE_TRUE(endTable, NS_ERROR_FAILURE);
1302 
1303   // We can only select a block if within the same table,
1304   // so do nothing if not within one table
1305   if (table != endTable) {
1306     return NS_OK;
1307   }
1308 
1309   int32_t startRowIndex, startColIndex, endRowIndex, endColIndex;
1310 
1311   // Get starting and ending cells' location in the cellmap
1312   rv = GetCellIndexes(aStartCell, &startRowIndex, &startColIndex);
1313   if (NS_FAILED(rv)) {
1314     return rv;
1315   }
1316 
1317   rv = GetCellIndexes(aEndCell, &endRowIndex, &endColIndex);
1318   if (NS_FAILED(rv)) {
1319     return rv;
1320   }
1321 
1322   // Suppress nsISelectionListener notification
1323   //  until all selection changes are finished
1324   SelectionBatcher selectionBatcher(selection);
1325 
1326   // Examine all cell nodes in current selection and
1327   // remove those outside the new block cell region
1328   int32_t minColumn = std::min(startColIndex, endColIndex);
1329   int32_t minRow = std::min(startRowIndex, endRowIndex);
1330   int32_t maxColumn = std::max(startColIndex, endColIndex);
1331   int32_t maxRow = std::max(startRowIndex, endRowIndex);
1332 
1333   nsCOMPtr<nsIDOMElement> cell;
1334   int32_t currentRowIndex, currentColIndex;
1335   nsCOMPtr<nsIDOMRange> range;
1336   rv = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
1337   NS_ENSURE_SUCCESS(rv, rv);
1338   if (rv == NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND) {
1339     return NS_OK;
1340   }
1341 
1342   while (cell) {
1343     rv = GetCellIndexes(cell, &currentRowIndex, &currentColIndex);
1344     NS_ENSURE_SUCCESS(rv, rv);
1345 
1346     if (currentRowIndex < maxRow || currentRowIndex > maxRow ||
1347         currentColIndex < maxColumn || currentColIndex > maxColumn) {
1348       selection->RemoveRange(range);
1349       // Since we've removed the range, decrement pointer to next range
1350       mSelectedCellIndex--;
1351     }
1352     rv = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
1353     NS_ENSURE_SUCCESS(rv, rv);
1354   }
1355 
1356   int32_t rowSpan, colSpan, actualRowSpan, actualColSpan;
1357   bool isSelected;
1358   for (int32_t row = minRow; row <= maxRow; row++) {
1359     for (int32_t col = minColumn; col <= maxColumn;
1360          col += std::max(actualColSpan, 1)) {
1361       rv = GetCellDataAt(table, row, col, getter_AddRefs(cell),
1362                          &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
1363                          &actualRowSpan, &actualColSpan, &isSelected);
1364       if (NS_FAILED(rv)) {
1365         break;
1366       }
1367       // Skip cells that already selected or are spanned from previous locations
1368       if (!isSelected && cell && row == currentRowIndex &&
1369           col == currentColIndex) {
1370         rv = AppendNodeToSelectionAsRange(cell);
1371         if (NS_FAILED(rv)) {
1372           break;
1373         }
1374       }
1375     }
1376   }
1377   // NS_OK, otherwise, the last failure of GetCellDataAt() or
1378   // AppendNodeToSelectionAsRange().
1379   return rv;
1380 }
1381 
1382 NS_IMETHODIMP
SelectAllTableCells()1383 HTMLEditor::SelectAllTableCells() {
1384   nsCOMPtr<nsIDOMElement> cell;
1385   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr,
1386                                             getter_AddRefs(cell));
1387   NS_ENSURE_SUCCESS(rv, rv);
1388 
1389   // Don't fail if we didn't find a cell
1390   NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
1391 
1392   nsCOMPtr<nsIDOMElement> startCell = cell;
1393 
1394   // Get parent table
1395   nsCOMPtr<nsIDOMElement> table;
1396   rv = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell,
1397                                    getter_AddRefs(table));
1398   NS_ENSURE_SUCCESS(rv, rv);
1399   if (!table) {
1400     return NS_ERROR_NULL_POINTER;
1401   }
1402 
1403   int32_t rowCount, colCount;
1404   rv = GetTableSize(table, &rowCount, &colCount);
1405   NS_ENSURE_SUCCESS(rv, rv);
1406 
1407   RefPtr<Selection> selection = GetSelection();
1408   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1409 
1410   // Suppress nsISelectionListener notification
1411   // until all selection changes are finished
1412   SelectionBatcher selectionBatcher(selection);
1413 
1414   // It is now safe to clear the selection
1415   // BE SURE TO RESET IT BEFORE LEAVING!
1416   rv = ClearSelection();
1417 
1418   // Select all cells in the same column as current cell
1419   bool cellSelected = false;
1420   int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex,
1421       currentColIndex;
1422   bool isSelected;
1423   for (int32_t row = 0; row < rowCount; row++) {
1424     for (int32_t col = 0; col < colCount; col += std::max(actualColSpan, 1)) {
1425       rv = GetCellDataAt(table, row, col, getter_AddRefs(cell),
1426                          &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
1427                          &actualRowSpan, &actualColSpan, &isSelected);
1428       if (NS_FAILED(rv)) {
1429         break;
1430       }
1431       // Skip cells that are spanned from previous rows or columns
1432       if (cell && row == currentRowIndex && col == currentColIndex) {
1433         rv = AppendNodeToSelectionAsRange(cell);
1434         if (NS_FAILED(rv)) {
1435           break;
1436         }
1437         cellSelected = true;
1438       }
1439     }
1440   }
1441   // Safety code to select starting cell if nothing else was selected
1442   if (!cellSelected) {
1443     return AppendNodeToSelectionAsRange(startCell);
1444   }
1445   // NS_OK, otherwise, the error of ClearSelection() when there is no column or
1446   // the last failure of GetCellDataAt() or AppendNodeToSelectionAsRange().
1447   return rv;
1448 }
1449 
1450 NS_IMETHODIMP
SelectTableRow()1451 HTMLEditor::SelectTableRow() {
1452   nsCOMPtr<nsIDOMElement> cell;
1453   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr,
1454                                             getter_AddRefs(cell));
1455   NS_ENSURE_SUCCESS(rv, rv);
1456 
1457   // Don't fail if we didn't find a cell
1458   NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
1459   nsCOMPtr<nsIDOMElement> startCell = cell;
1460 
1461   // Get table and location of cell:
1462   RefPtr<Selection> selection;
1463   nsCOMPtr<nsIDOMElement> table;
1464   int32_t startRowIndex, startColIndex;
1465 
1466   rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
1467                       getter_AddRefs(cell), nullptr, nullptr, &startRowIndex,
1468                       &startColIndex);
1469   NS_ENSURE_SUCCESS(rv, rv);
1470   NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
1471 
1472   int32_t rowCount, colCount;
1473   rv = GetTableSize(table, &rowCount, &colCount);
1474   NS_ENSURE_SUCCESS(rv, rv);
1475 
1476   // Note: At this point, we could get first and last cells in row,
1477   // then call SelectBlockOfCells, but that would take just
1478   // a little less code, so the following is more efficient
1479 
1480   // Suppress nsISelectionListener notification
1481   // until all selection changes are finished
1482   SelectionBatcher selectionBatcher(selection);
1483 
1484   // It is now safe to clear the selection
1485   // BE SURE TO RESET IT BEFORE LEAVING!
1486   rv = ClearSelection();
1487 
1488   // Select all cells in the same row as current cell
1489   bool cellSelected = false;
1490   int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex,
1491       currentColIndex;
1492   bool isSelected;
1493   for (int32_t col = 0; col < colCount; col += std::max(actualColSpan, 1)) {
1494     rv = GetCellDataAt(table, startRowIndex, col, getter_AddRefs(cell),
1495                        &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
1496                        &actualRowSpan, &actualColSpan, &isSelected);
1497     if (NS_FAILED(rv)) {
1498       break;
1499     }
1500     // Skip cells that are spanned from previous rows or columns
1501     if (cell && currentRowIndex == startRowIndex && currentColIndex == col) {
1502       rv = AppendNodeToSelectionAsRange(cell);
1503       if (NS_FAILED(rv)) {
1504         break;
1505       }
1506       cellSelected = true;
1507     }
1508   }
1509   // Safety code to select starting cell if nothing else was selected
1510   if (!cellSelected) {
1511     return AppendNodeToSelectionAsRange(startCell);
1512   }
1513   // NS_OK, otherwise, the error of ClearSelection() when there is no column or
1514   // the last failure of GetCellDataAt() or AppendNodeToSelectionAsRange().
1515   return rv;
1516 }
1517 
1518 NS_IMETHODIMP
SelectTableColumn()1519 HTMLEditor::SelectTableColumn() {
1520   nsCOMPtr<nsIDOMElement> cell;
1521   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr,
1522                                             getter_AddRefs(cell));
1523   NS_ENSURE_SUCCESS(rv, rv);
1524 
1525   // Don't fail if we didn't find a cell
1526   NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
1527 
1528   nsCOMPtr<nsIDOMElement> startCell = cell;
1529 
1530   // Get location of cell:
1531   RefPtr<Selection> selection;
1532   nsCOMPtr<nsIDOMElement> table;
1533   int32_t startRowIndex, startColIndex;
1534 
1535   rv = GetCellContext(getter_AddRefs(selection), getter_AddRefs(table),
1536                       getter_AddRefs(cell), nullptr, nullptr, &startRowIndex,
1537                       &startColIndex);
1538   NS_ENSURE_SUCCESS(rv, rv);
1539   NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
1540 
1541   int32_t rowCount, colCount;
1542   rv = GetTableSize(table, &rowCount, &colCount);
1543   NS_ENSURE_SUCCESS(rv, rv);
1544 
1545   // Suppress nsISelectionListener notification
1546   // until all selection changes are finished
1547   SelectionBatcher selectionBatcher(selection);
1548 
1549   // It is now safe to clear the selection
1550   // BE SURE TO RESET IT BEFORE LEAVING!
1551   rv = ClearSelection();
1552 
1553   // Select all cells in the same column as current cell
1554   bool cellSelected = false;
1555   int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex,
1556       currentColIndex;
1557   bool isSelected;
1558   for (int32_t row = 0; row < rowCount; row += std::max(actualRowSpan, 1)) {
1559     rv = GetCellDataAt(table, row, startColIndex, getter_AddRefs(cell),
1560                        &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
1561                        &actualRowSpan, &actualColSpan, &isSelected);
1562     if (NS_FAILED(rv)) {
1563       break;
1564     }
1565     // Skip cells that are spanned from previous rows or columns
1566     if (cell && currentRowIndex == row && currentColIndex == startColIndex) {
1567       rv = AppendNodeToSelectionAsRange(cell);
1568       if (NS_FAILED(rv)) {
1569         break;
1570       }
1571       cellSelected = true;
1572     }
1573   }
1574   // Safety code to select starting cell if nothing else was selected
1575   if (!cellSelected) {
1576     return AppendNodeToSelectionAsRange(startCell);
1577   }
1578   // NS_OK, otherwise, the error of ClearSelection() when there is no row or
1579   // the last failure of GetCellDataAt() or AppendNodeToSelectionAsRange().
1580   return rv;
1581 }
1582 
1583 NS_IMETHODIMP
SplitTableCell()1584 HTMLEditor::SplitTableCell() {
1585   nsCOMPtr<nsIDOMElement> table;
1586   nsCOMPtr<nsIDOMElement> cell;
1587   int32_t startRowIndex, startColIndex, actualRowSpan, actualColSpan;
1588   // Get cell, table, etc. at selection anchor node
1589   nsresult rv =
1590       GetCellContext(nullptr, getter_AddRefs(table), getter_AddRefs(cell),
1591                      nullptr, nullptr, &startRowIndex, &startColIndex);
1592   NS_ENSURE_SUCCESS(rv, rv);
1593   if (!table || !cell) {
1594     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
1595   }
1596 
1597   // We need rowspan and colspan data
1598   rv = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan,
1599                       actualColSpan);
1600   NS_ENSURE_SUCCESS(rv, rv);
1601 
1602   // Must have some span to split
1603   if (actualRowSpan <= 1 && actualColSpan <= 1) {
1604     return NS_OK;
1605   }
1606 
1607   AutoPlaceholderBatch beginBatching(this);
1608   // Prevent auto insertion of BR in new cell until we're done
1609   AutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
1610 
1611   // We reset selection
1612   AutoSelectionSetterAfterTableEdit setCaret(
1613       *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
1614   //...so suppress Rules System selection munging
1615   AutoTransactionsConserveSelection dontChangeSelection(this);
1616 
1617   nsCOMPtr<nsIDOMElement> newCell;
1618   int32_t rowIndex = startRowIndex;
1619   int32_t rowSpanBelow, colSpanAfter;
1620 
1621   // Split up cell row-wise first into rowspan=1 above, and the rest below,
1622   // whittling away at the cell below until no more extra span
1623   for (rowSpanBelow = actualRowSpan - 1; rowSpanBelow >= 0; rowSpanBelow--) {
1624     // We really split row-wise only if we had rowspan > 1
1625     if (rowSpanBelow > 0) {
1626       rv = SplitCellIntoRows(table, rowIndex, startColIndex, 1, rowSpanBelow,
1627                              getter_AddRefs(newCell));
1628       NS_ENSURE_SUCCESS(rv, rv);
1629       CopyCellBackgroundColor(newCell, cell);
1630     }
1631     int32_t colIndex = startColIndex;
1632     // Now split the cell with rowspan = 1 into cells if it has colSpan > 1
1633     for (colSpanAfter = actualColSpan - 1; colSpanAfter > 0; colSpanAfter--) {
1634       rv = SplitCellIntoColumns(table, rowIndex, colIndex, 1, colSpanAfter,
1635                                 getter_AddRefs(newCell));
1636       NS_ENSURE_SUCCESS(rv, rv);
1637       CopyCellBackgroundColor(newCell, cell);
1638       colIndex++;
1639     }
1640     // Point to the new cell and repeat
1641     rowIndex++;
1642   }
1643   return NS_OK;
1644 }
1645 
CopyCellBackgroundColor(nsIDOMElement * destCell,nsIDOMElement * sourceCell)1646 nsresult HTMLEditor::CopyCellBackgroundColor(nsIDOMElement* destCell,
1647                                              nsIDOMElement* sourceCell) {
1648   NS_ENSURE_TRUE(destCell && sourceCell, NS_ERROR_NULL_POINTER);
1649 
1650   // Copy backgournd color to new cell
1651   NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor");
1652   nsAutoString color;
1653   bool isSet;
1654   nsresult rv = GetAttributeValue(sourceCell, bgcolor, color, &isSet);
1655   if (NS_FAILED(rv)) {
1656     return rv;
1657   }
1658   if (!isSet) {
1659     return NS_OK;
1660   }
1661   return SetAttribute(destCell, bgcolor, color);
1662 }
1663 
SplitCellIntoColumns(nsIDOMElement * aTable,int32_t aRowIndex,int32_t aColIndex,int32_t aColSpanLeft,int32_t aColSpanRight,nsIDOMElement ** aNewCell)1664 nsresult HTMLEditor::SplitCellIntoColumns(nsIDOMElement* aTable,
1665                                           int32_t aRowIndex, int32_t aColIndex,
1666                                           int32_t aColSpanLeft,
1667                                           int32_t aColSpanRight,
1668                                           nsIDOMElement** aNewCell) {
1669   NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
1670   if (aNewCell) {
1671     *aNewCell = nullptr;
1672   }
1673 
1674   nsCOMPtr<nsIDOMElement> cell;
1675   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
1676       actualColSpan;
1677   bool isSelected;
1678   nsresult rv =
1679       GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
1680                     &startRowIndex, &startColIndex, &rowSpan, &colSpan,
1681                     &actualRowSpan, &actualColSpan, &isSelected);
1682   NS_ENSURE_SUCCESS(rv, rv);
1683   NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER);
1684 
1685   // We can't split!
1686   if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan) {
1687     return NS_OK;
1688   }
1689 
1690   // Reduce colspan of cell to split
1691   rv = SetColSpan(cell, aColSpanLeft);
1692   NS_ENSURE_SUCCESS(rv, rv);
1693 
1694   // Insert new cell after using the remaining span
1695   // and always get the new cell so we can copy the background color;
1696   nsCOMPtr<nsIDOMElement> newCell;
1697   rv = InsertCell(cell, actualRowSpan, aColSpanRight, true, false,
1698                   getter_AddRefs(newCell));
1699   NS_ENSURE_SUCCESS(rv, rv);
1700   if (!newCell) {
1701     return NS_OK;
1702   }
1703   if (aNewCell) {
1704     NS_ADDREF(*aNewCell = newCell.get());
1705   }
1706   return CopyCellBackgroundColor(newCell, cell);
1707 }
1708 
SplitCellIntoRows(nsIDOMElement * aTable,int32_t aRowIndex,int32_t aColIndex,int32_t aRowSpanAbove,int32_t aRowSpanBelow,nsIDOMElement ** aNewCell)1709 nsresult HTMLEditor::SplitCellIntoRows(nsIDOMElement* aTable, int32_t aRowIndex,
1710                                        int32_t aColIndex, int32_t aRowSpanAbove,
1711                                        int32_t aRowSpanBelow,
1712                                        nsIDOMElement** aNewCell) {
1713   NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
1714   if (aNewCell) *aNewCell = nullptr;
1715 
1716   nsCOMPtr<nsIDOMElement> cell;
1717   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
1718       actualColSpan;
1719   bool isSelected;
1720   nsresult rv =
1721       GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
1722                     &startRowIndex, &startColIndex, &rowSpan, &colSpan,
1723                     &actualRowSpan, &actualColSpan, &isSelected);
1724   NS_ENSURE_SUCCESS(rv, rv);
1725   NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER);
1726 
1727   // We can't split!
1728   if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan) {
1729     return NS_OK;
1730   }
1731 
1732   int32_t rowCount, colCount;
1733   rv = GetTableSize(aTable, &rowCount, &colCount);
1734   NS_ENSURE_SUCCESS(rv, rv);
1735 
1736   nsCOMPtr<nsIDOMElement> cell2;
1737   nsCOMPtr<nsIDOMElement> lastCellFound;
1738   int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2,
1739       actualColSpan2;
1740   bool isSelected2;
1741   int32_t colIndex = 0;
1742   bool insertAfter = (startColIndex > 0);
1743   // This is the row we will insert new cell into
1744   int32_t rowBelowIndex = startRowIndex + aRowSpanAbove;
1745 
1746   // Find a cell to insert before or after
1747   for (;;) {
1748     // Search for a cell to insert before
1749     rv = GetCellDataAt(aTable, rowBelowIndex, colIndex, getter_AddRefs(cell2),
1750                        &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2,
1751                        &actualRowSpan2, &actualColSpan2, &isSelected2);
1752     // If we fail here, it could be because row has bad rowspan values,
1753     //   such as all cells having rowspan > 1 (Call FixRowSpan first!)
1754     if (NS_FAILED(rv) || !cell) {
1755       return NS_ERROR_FAILURE;
1756     }
1757 
1758     // Skip over cells spanned from above (like the one we are splitting!)
1759     if (cell2 && startRowIndex2 == rowBelowIndex) {
1760       if (!insertAfter) {
1761         // Inserting before, so stop at first cell in row we want to insert
1762         // into.
1763         break;
1764       }
1765       // New cell isn't first in row,
1766       // so stop after we find the cell just before new cell's column
1767       if (startColIndex2 + actualColSpan2 == startColIndex) {
1768         break;
1769       }
1770       // If cell found is AFTER desired new cell colum,
1771       // we have multiple cells with rowspan > 1 that
1772       // prevented us from finding a cell to insert after...
1773       if (startColIndex2 > startColIndex) {
1774         // ... so instead insert before the cell we found
1775         insertAfter = false;
1776         break;
1777       }
1778       lastCellFound = cell2;
1779     }
1780     // Skip to next available cellmap location
1781     colIndex += std::max(actualColSpan2, 1);
1782 
1783     // Done when past end of total number of columns
1784     if (colIndex > colCount) {
1785       break;
1786     }
1787   }
1788 
1789   if (!cell2 && lastCellFound) {
1790     // Edge case where we didn't find a cell to insert after
1791     // or before because column(s) before desired column
1792     // and all columns after it are spanned from above.
1793     // We can insert after the last cell we found
1794     cell2 = lastCellFound;
1795     insertAfter = true;  // Should always be true, but let's be sure
1796   }
1797 
1798   // Reduce rowspan of cell to split
1799   rv = SetRowSpan(cell, aRowSpanAbove);
1800   NS_ENSURE_SUCCESS(rv, rv);
1801 
1802   // Insert new cell after using the remaining span
1803   // and always get the new cell so we can copy the background color;
1804   nsCOMPtr<nsIDOMElement> newCell;
1805   rv = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, false,
1806                   getter_AddRefs(newCell));
1807   NS_ENSURE_SUCCESS(rv, rv);
1808   if (!newCell) {
1809     return NS_OK;
1810   }
1811   if (aNewCell) {
1812     NS_ADDREF(*aNewCell = newCell.get());
1813   }
1814   return CopyCellBackgroundColor(newCell, cell2);
1815 }
1816 
1817 NS_IMETHODIMP
SwitchTableCellHeaderType(nsIDOMElement * aSourceCell,nsIDOMElement ** aNewCell)1818 HTMLEditor::SwitchTableCellHeaderType(nsIDOMElement* aSourceCell,
1819                                       nsIDOMElement** aNewCell) {
1820   nsCOMPtr<Element> sourceCell = do_QueryInterface(aSourceCell);
1821   NS_ENSURE_TRUE(sourceCell, NS_ERROR_NULL_POINTER);
1822 
1823   AutoPlaceholderBatch beginBatching(this);
1824   // Prevent auto insertion of BR in new cell created by ReplaceContainer
1825   AutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
1826 
1827   // Save current selection to restore when done
1828   // This is needed so ReplaceContainer can monitor selection
1829   //  when replacing nodes
1830   RefPtr<Selection> selection = GetSelection();
1831   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1832   AutoSelectionRestorer selectionRestorer(selection, this);
1833 
1834   // Set to the opposite of current type
1835   nsAtom* newCellType =
1836       sourceCell->IsHTMLElement(nsGkAtoms::td) ? nsGkAtoms::th : nsGkAtoms::td;
1837 
1838   // This creates new node, moves children, copies attributes (true)
1839   //   and manages the selection!
1840   nsCOMPtr<Element> newNode = ReplaceContainer(
1841       sourceCell, newCellType, nullptr, nullptr, EditorBase::eCloneAttributes);
1842   NS_ENSURE_TRUE(newNode, NS_ERROR_FAILURE);
1843 
1844   // Return the new cell
1845   if (aNewCell) {
1846     nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newNode);
1847     *aNewCell = newElement.get();
1848     NS_ADDREF(*aNewCell);
1849   }
1850 
1851   return NS_OK;
1852 }
1853 
1854 NS_IMETHODIMP
JoinTableCells(bool aMergeNonContiguousContents)1855 HTMLEditor::JoinTableCells(bool aMergeNonContiguousContents) {
1856   nsCOMPtr<nsIDOMElement> table;
1857   nsCOMPtr<nsIDOMElement> targetCell;
1858   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
1859       actualColSpan;
1860   bool isSelected;
1861   nsCOMPtr<nsIDOMElement> cell2;
1862   int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2,
1863       actualColSpan2;
1864   bool isSelected2;
1865 
1866   // Get cell, table, etc. at selection anchor node
1867   nsresult rv =
1868       GetCellContext(nullptr, getter_AddRefs(table), getter_AddRefs(targetCell),
1869                      nullptr, nullptr, &startRowIndex, &startColIndex);
1870   NS_ENSURE_SUCCESS(rv, rv);
1871   if (!table || !targetCell) {
1872     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
1873   }
1874 
1875   AutoPlaceholderBatch beginBatching(this);
1876   // Don't let Rules System change the selection
1877   AutoTransactionsConserveSelection dontChangeSelection(this);
1878 
1879   // Note: We dont' use AutoSelectionSetterAfterTableEdit here so the selection
1880   // is retained after joining. This leaves the target cell selected
1881   // as well as the "non-contiguous" cells, so user can see what happened.
1882 
1883   nsCOMPtr<nsIDOMElement> firstCell;
1884   int32_t firstRowIndex, firstColIndex;
1885   rv = GetFirstSelectedCellInTable(&firstRowIndex, &firstColIndex,
1886                                    getter_AddRefs(firstCell));
1887   NS_ENSURE_SUCCESS(rv, rv);
1888 
1889   bool joinSelectedCells = false;
1890   if (firstCell) {
1891     nsCOMPtr<nsIDOMElement> secondCell;
1892     rv = GetNextSelectedCell(nullptr, getter_AddRefs(secondCell));
1893     NS_ENSURE_SUCCESS(rv, rv);
1894 
1895     // If only one cell is selected, join with cell to the right
1896     joinSelectedCells = (secondCell != nullptr);
1897   }
1898 
1899   if (joinSelectedCells) {
1900     // We have selected cells: Join just contiguous cells
1901     // and just merge contents if not contiguous
1902 
1903     int32_t rowCount, colCount;
1904     rv = GetTableSize(table, &rowCount, &colCount);
1905     NS_ENSURE_SUCCESS(rv, rv);
1906 
1907     // Get spans for cell we will merge into
1908     int32_t firstRowSpan, firstColSpan;
1909     rv = GetCellSpansAt(table, firstRowIndex, firstColIndex, firstRowSpan,
1910                         firstColSpan);
1911     NS_ENSURE_SUCCESS(rv, rv);
1912 
1913     // This defines the last indexes along the "edges"
1914     // of the contiguous block of cells, telling us
1915     // that we can join adjacent cells to the block
1916     // Start with same as the first values,
1917     // then expand as we find adjacent selected cells
1918     int32_t lastRowIndex = firstRowIndex;
1919     int32_t lastColIndex = firstColIndex;
1920     int32_t rowIndex, colIndex;
1921 
1922     // First pass: Determine boundaries of contiguous rectangular block
1923     // that we will join into one cell,
1924     // favoring adjacent cells in the same row
1925     for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++) {
1926       int32_t currentRowCount = rowCount;
1927       // Be sure each row doesn't have rowspan errors
1928       rv = FixBadRowSpan(table, rowIndex, rowCount);
1929       NS_ENSURE_SUCCESS(rv, rv);
1930       // Adjust rowcount by number of rows we removed
1931       lastRowIndex -= (currentRowCount - rowCount);
1932 
1933       bool cellFoundInRow = false;
1934       bool lastRowIsSet = false;
1935       int32_t lastColInRow = 0;
1936       int32_t firstColInRow = firstColIndex;
1937       for (colIndex = firstColIndex; colIndex < colCount;
1938            colIndex += std::max(actualColSpan2, 1)) {
1939         rv = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
1940                            &startRowIndex2, &startColIndex2, &rowSpan2,
1941                            &colSpan2, &actualRowSpan2, &actualColSpan2,
1942                            &isSelected2);
1943         NS_ENSURE_SUCCESS(rv, rv);
1944 
1945         if (isSelected2) {
1946           if (!cellFoundInRow) {
1947             // We've just found the first selected cell in this row
1948             firstColInRow = colIndex;
1949           }
1950           if (rowIndex > firstRowIndex && firstColInRow != firstColIndex) {
1951             // We're in at least the second row,
1952             // but left boundary is "ragged" (not the same as 1st row's start)
1953             // Let's just end block on previous row
1954             // and keep previous lastColIndex
1955             // TODO: We could try to find the Maximum firstColInRow
1956             //      so our block can still extend down more rows?
1957             lastRowIndex = std::max(0, rowIndex - 1);
1958             lastRowIsSet = true;
1959             break;
1960           }
1961           // Save max selected column in this row, including extra colspan
1962           lastColInRow = colIndex + (actualColSpan2 - 1);
1963           cellFoundInRow = true;
1964         } else if (cellFoundInRow) {
1965           // No cell or not selected, but at least one cell in row was found
1966           if (rowIndex > (firstRowIndex + 1) && colIndex <= lastColIndex) {
1967             // Cell is in a column less than current right border in
1968             // the third or higher selected row, so stop block at the previous
1969             // row
1970             lastRowIndex = std::max(0, rowIndex - 1);
1971             lastRowIsSet = true;
1972           }
1973           // We're done with this row
1974           break;
1975         }
1976       }  // End of column loop
1977 
1978       // Done with this row
1979       if (cellFoundInRow) {
1980         if (rowIndex == firstRowIndex) {
1981           // First row always initializes the right boundary
1982           lastColIndex = lastColInRow;
1983         }
1984 
1985         // If we didn't determine last row above...
1986         if (!lastRowIsSet) {
1987           if (colIndex < lastColIndex) {
1988             // (don't think we ever get here?)
1989             // Cell is in a column less than current right boundary,
1990             // so stop block at the previous row
1991             lastRowIndex = std::max(0, rowIndex - 1);
1992           } else {
1993             // Go on to examine next row
1994             lastRowIndex = rowIndex + 1;
1995           }
1996         }
1997         // Use the minimum col we found so far for right boundary
1998         lastColIndex = std::min(lastColIndex, lastColInRow);
1999       } else {
2000         // No selected cells in this row -- stop at row above
2001         // and leave last column at its previous value
2002         lastRowIndex = std::max(0, rowIndex - 1);
2003       }
2004     }
2005 
2006     // The list of cells we will delete after joining
2007     nsTArray<nsCOMPtr<nsIDOMElement> > deleteList;
2008 
2009     // 2nd pass: Do the joining and merging
2010     for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
2011       for (colIndex = 0; colIndex < colCount;
2012            colIndex += std::max(actualColSpan2, 1)) {
2013         rv = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
2014                            &startRowIndex2, &startColIndex2, &rowSpan2,
2015                            &colSpan2, &actualRowSpan2, &actualColSpan2,
2016                            &isSelected2);
2017         NS_ENSURE_SUCCESS(rv, rv);
2018 
2019         // If this is 0, we are past last cell in row, so exit the loop
2020         if (!actualColSpan2) {
2021           break;
2022         }
2023 
2024         // Merge only selected cells (skip cell we're merging into, of course)
2025         if (isSelected2 && cell2 != firstCell) {
2026           if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex &&
2027               colIndex >= firstColIndex && colIndex <= lastColIndex) {
2028             // We are within the join region
2029             // Problem: It is very tricky to delete cells as we merge,
2030             // since that will upset the cellmap
2031             // Instead, build a list of cells to delete and do it later
2032             NS_ASSERTION(startRowIndex2 == rowIndex,
2033                          "JoinTableCells: StartRowIndex is in row above");
2034 
2035             if (actualColSpan2 > 1) {
2036               // Check if cell "hangs" off the boundary because of colspan > 1
2037               // Use split methods to chop off excess
2038               int32_t extraColSpan =
2039                   (startColIndex2 + actualColSpan2) - (lastColIndex + 1);
2040               if (extraColSpan > 0) {
2041                 rv = SplitCellIntoColumns(table, startRowIndex2, startColIndex2,
2042                                           actualColSpan2 - extraColSpan,
2043                                           extraColSpan, nullptr);
2044                 NS_ENSURE_SUCCESS(rv, rv);
2045               }
2046             }
2047 
2048             rv = MergeCells(firstCell, cell2, false);
2049             NS_ENSURE_SUCCESS(rv, rv);
2050 
2051             // Add cell to list to delete
2052             deleteList.AppendElement(cell2.get());
2053           } else if (aMergeNonContiguousContents) {
2054             // Cell is outside join region -- just merge the contents
2055             rv = MergeCells(firstCell, cell2, false);
2056             NS_ENSURE_SUCCESS(rv, rv);
2057           }
2058         }
2059       }
2060     }
2061 
2062     // All cell contents are merged. Delete the empty cells we accumulated
2063     // Prevent rules testing until we're done
2064     AutoRules beginRulesSniffing(this, EditAction::deleteNode,
2065                                  nsIEditor::eNext);
2066 
2067     for (uint32_t i = 0, n = deleteList.Length(); i < n; i++) {
2068       nsIDOMElement* elementPtr = deleteList[i];
2069       if (elementPtr) {
2070         nsCOMPtr<nsIDOMNode> node = do_QueryInterface(elementPtr);
2071         rv = DeleteNode(node);
2072         NS_ENSURE_SUCCESS(rv, rv);
2073       }
2074     }
2075     // Cleanup selection: remove ranges where cells were deleted
2076     RefPtr<Selection> selection = GetSelection();
2077     NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2078 
2079     uint32_t rangeCount = selection->RangeCount();
2080 
2081     RefPtr<nsRange> range;
2082     for (uint32_t i = 0; i < rangeCount; i++) {
2083       range = selection->GetRangeAt(i);
2084       NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
2085 
2086       nsCOMPtr<nsIDOMElement> deletedCell;
2087       GetCellFromRange(range, getter_AddRefs(deletedCell));
2088       if (!deletedCell) {
2089         selection->RemoveRange(range);
2090         rangeCount--;
2091         i--;
2092       }
2093     }
2094 
2095     // Set spans for the cell everthing merged into
2096     rv = SetRowSpan(firstCell, lastRowIndex - firstRowIndex + 1);
2097     NS_ENSURE_SUCCESS(rv, rv);
2098     rv = SetColSpan(firstCell, lastColIndex - firstColIndex + 1);
2099     NS_ENSURE_SUCCESS(rv, rv);
2100 
2101     // Fixup disturbances in table layout
2102     NormalizeTable(table);
2103   } else {
2104     // Joining with cell to the right -- get rowspan and colspan data of target
2105     // cell
2106     rv = GetCellDataAt(table, startRowIndex, startColIndex,
2107                        getter_AddRefs(targetCell), &startRowIndex,
2108                        &startColIndex, &rowSpan, &colSpan, &actualRowSpan,
2109                        &actualColSpan, &isSelected);
2110     NS_ENSURE_SUCCESS(rv, rv);
2111     NS_ENSURE_TRUE(targetCell, NS_ERROR_NULL_POINTER);
2112 
2113     // Get data for cell to the right
2114     rv = GetCellDataAt(table, startRowIndex, startColIndex + actualColSpan,
2115                        getter_AddRefs(cell2), &startRowIndex2, &startColIndex2,
2116                        &rowSpan2, &colSpan2, &actualRowSpan2, &actualColSpan2,
2117                        &isSelected2);
2118     NS_ENSURE_SUCCESS(rv, rv);
2119     if (!cell2) {
2120       return NS_OK;  // Don't fail if there's no cell
2121     }
2122 
2123     // sanity check
2124     NS_ASSERTION((startRowIndex >= startRowIndex2),
2125                  "JoinCells: startRowIndex < startRowIndex2");
2126 
2127     // Figure out span of merged cell starting from target's starting row
2128     // to handle case of merged cell starting in a row above
2129     int32_t spanAboveMergedCell = startRowIndex - startRowIndex2;
2130     int32_t effectiveRowSpan2 = actualRowSpan2 - spanAboveMergedCell;
2131 
2132     if (effectiveRowSpan2 > actualRowSpan) {
2133       // Cell to the right spans into row below target
2134       // Split off portion below target cell's bottom-most row
2135       rv = SplitCellIntoRows(table, startRowIndex2, startColIndex2,
2136                              spanAboveMergedCell + actualRowSpan,
2137                              effectiveRowSpan2 - actualRowSpan, nullptr);
2138       NS_ENSURE_SUCCESS(rv, rv);
2139     }
2140 
2141     // Move contents from cell to the right
2142     // Delete the cell now only if it starts in the same row
2143     //   and has enough row "height"
2144     rv = MergeCells(targetCell, cell2,
2145                     (startRowIndex2 == startRowIndex) &&
2146                         (effectiveRowSpan2 >= actualRowSpan));
2147     NS_ENSURE_SUCCESS(rv, rv);
2148 
2149     if (effectiveRowSpan2 < actualRowSpan) {
2150       // Merged cell is "shorter"
2151       // (there are cells(s) below it that are row-spanned by target cell)
2152       // We could try splitting those cells, but that's REAL messy,
2153       // so the safest thing to do is NOT really join the cells
2154       return NS_OK;
2155     }
2156 
2157     if (spanAboveMergedCell > 0) {
2158       // Cell we merged started in a row above the target cell
2159       // Reduce rowspan to give room where target cell will extend its colspan
2160       rv = SetRowSpan(cell2, spanAboveMergedCell);
2161       NS_ENSURE_SUCCESS(rv, rv);
2162     }
2163 
2164     // Reset target cell's colspan to encompass cell to the right
2165     rv = SetColSpan(targetCell, actualColSpan + actualColSpan2);
2166     NS_ENSURE_SUCCESS(rv, rv);
2167   }
2168   return NS_OK;
2169 }
2170 
MergeCells(nsCOMPtr<nsIDOMElement> aTargetCell,nsCOMPtr<nsIDOMElement> aCellToMerge,bool aDeleteCellToMerge)2171 nsresult HTMLEditor::MergeCells(nsCOMPtr<nsIDOMElement> aTargetCell,
2172                                 nsCOMPtr<nsIDOMElement> aCellToMerge,
2173                                 bool aDeleteCellToMerge) {
2174   nsCOMPtr<dom::Element> targetCell = do_QueryInterface(aTargetCell);
2175   nsCOMPtr<dom::Element> cellToMerge = do_QueryInterface(aCellToMerge);
2176   NS_ENSURE_TRUE(targetCell && cellToMerge, NS_ERROR_NULL_POINTER);
2177 
2178   // Prevent rules testing until we're done
2179   AutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext);
2180 
2181   // Don't need to merge if cell is empty
2182   if (!IsEmptyCell(cellToMerge)) {
2183     // Get index of last child in target cell
2184     // If we fail or don't have children,
2185     // we insert at index 0
2186     int32_t insertIndex = 0;
2187 
2188     // Start inserting just after last child
2189     uint32_t len = targetCell->GetChildCount();
2190     if (len == 1 && IsEmptyCell(targetCell)) {
2191       // Delete the empty node
2192       nsIContent* cellChild = targetCell->GetFirstChild();
2193       nsresult rv = DeleteNode(cellChild->AsDOMNode());
2194       NS_ENSURE_SUCCESS(rv, rv);
2195       insertIndex = 0;
2196     } else {
2197       insertIndex = (int32_t)len;
2198     }
2199 
2200     // Move the contents
2201     while (cellToMerge->HasChildren()) {
2202       nsCOMPtr<nsIContent> cellChild = cellToMerge->GetLastChild();
2203       // XXX We need HTMLEditor::DeleteNode(nsINode&).
2204       nsresult rv = DeleteNode(cellChild->AsDOMNode());
2205       NS_ENSURE_SUCCESS(rv, rv);
2206 
2207       rv = InsertNode(*cellChild, EditorRawDOMPoint(aTargetCell, insertIndex));
2208       NS_ENSURE_SUCCESS(rv, rv);
2209     }
2210   }
2211 
2212   // Delete cells whose contents were moved
2213   if (aDeleteCellToMerge) {
2214     return DeleteNode(aCellToMerge);
2215   }
2216 
2217   return NS_OK;
2218 }
2219 
FixBadRowSpan(nsIDOMElement * aTable,int32_t aRowIndex,int32_t & aNewRowCount)2220 nsresult HTMLEditor::FixBadRowSpan(nsIDOMElement* aTable, int32_t aRowIndex,
2221                                    int32_t& aNewRowCount) {
2222   NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
2223 
2224   int32_t rowCount, colCount;
2225   nsresult rv = GetTableSize(aTable, &rowCount, &colCount);
2226   NS_ENSURE_SUCCESS(rv, rv);
2227 
2228   nsCOMPtr<nsIDOMElement> cell;
2229   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
2230       actualColSpan;
2231   bool isSelected;
2232 
2233   int32_t minRowSpan = -1;
2234   int32_t colIndex;
2235 
2236   for (colIndex = 0; colIndex < colCount;
2237        colIndex += std::max(actualColSpan, 1)) {
2238     rv = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
2239                        &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2240                        &actualRowSpan, &actualColSpan, &isSelected);
2241     // NOTE: This is a *real* failure.
2242     // GetCellDataAt passes if cell is missing from cellmap
2243     if (NS_FAILED(rv)) {
2244       return rv;
2245     }
2246     if (!cell) {
2247       break;
2248     }
2249     if (rowSpan > 0 && startRowIndex == aRowIndex &&
2250         (rowSpan < minRowSpan || minRowSpan == -1)) {
2251       minRowSpan = rowSpan;
2252     }
2253     NS_ASSERTION((actualColSpan > 0), "ActualColSpan = 0 in FixBadRowSpan");
2254   }
2255   if (minRowSpan > 1) {
2256     // The amount to reduce everyone's rowspan
2257     // so at least one cell has rowspan = 1
2258     int32_t rowsReduced = minRowSpan - 1;
2259     for (colIndex = 0; colIndex < colCount;
2260          colIndex += std::max(actualColSpan, 1)) {
2261       rv = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
2262                          &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2263                          &actualRowSpan, &actualColSpan, &isSelected);
2264       if (NS_FAILED(rv)) {
2265         return rv;
2266       }
2267       // Fixup rowspans only for cells starting in current row
2268       if (cell && rowSpan > 0 && startRowIndex == aRowIndex &&
2269           startColIndex == colIndex) {
2270         rv = SetRowSpan(cell, rowSpan - rowsReduced);
2271         if (NS_FAILED(rv)) {
2272           return rv;
2273         }
2274       }
2275       NS_ASSERTION((actualColSpan > 0), "ActualColSpan = 0 in FixBadRowSpan");
2276     }
2277   }
2278   return GetTableSize(aTable, &aNewRowCount, &colCount);
2279 }
2280 
FixBadColSpan(nsIDOMElement * aTable,int32_t aColIndex,int32_t & aNewColCount)2281 nsresult HTMLEditor::FixBadColSpan(nsIDOMElement* aTable, int32_t aColIndex,
2282                                    int32_t& aNewColCount) {
2283   NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
2284 
2285   int32_t rowCount, colCount;
2286   nsresult rv = GetTableSize(aTable, &rowCount, &colCount);
2287   NS_ENSURE_SUCCESS(rv, rv);
2288 
2289   nsCOMPtr<nsIDOMElement> cell;
2290   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
2291       actualColSpan;
2292   bool isSelected;
2293 
2294   int32_t minColSpan = -1;
2295   int32_t rowIndex;
2296 
2297   for (rowIndex = 0; rowIndex < rowCount;
2298        rowIndex += std::max(actualRowSpan, 1)) {
2299     rv = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
2300                        &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2301                        &actualRowSpan, &actualColSpan, &isSelected);
2302     // NOTE: This is a *real* failure.
2303     // GetCellDataAt passes if cell is missing from cellmap
2304     if (NS_FAILED(rv)) {
2305       return rv;
2306     }
2307     if (!cell) {
2308       break;
2309     }
2310     if (colSpan > 0 && startColIndex == aColIndex &&
2311         (colSpan < minColSpan || minColSpan == -1)) {
2312       minColSpan = colSpan;
2313     }
2314     NS_ASSERTION((actualRowSpan > 0), "ActualRowSpan = 0 in FixBadColSpan");
2315   }
2316   if (minColSpan > 1) {
2317     // The amount to reduce everyone's colspan
2318     // so at least one cell has colspan = 1
2319     int32_t colsReduced = minColSpan - 1;
2320     for (rowIndex = 0; rowIndex < rowCount;
2321          rowIndex += std::max(actualRowSpan, 1)) {
2322       rv = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
2323                          &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2324                          &actualRowSpan, &actualColSpan, &isSelected);
2325       if (NS_FAILED(rv)) {
2326         return rv;
2327       }
2328       // Fixup colspans only for cells starting in current column
2329       if (cell && colSpan > 0 && startColIndex == aColIndex &&
2330           startRowIndex == rowIndex) {
2331         rv = SetColSpan(cell, colSpan - colsReduced);
2332         if (NS_FAILED(rv)) {
2333           return rv;
2334         }
2335       }
2336       NS_ASSERTION((actualRowSpan > 0), "ActualRowSpan = 0 in FixBadColSpan");
2337     }
2338   }
2339   return GetTableSize(aTable, &rowCount, &aNewColCount);
2340 }
2341 
2342 NS_IMETHODIMP
NormalizeTable(nsIDOMElement * aTable)2343 HTMLEditor::NormalizeTable(nsIDOMElement* aTable) {
2344   RefPtr<Selection> selection = GetSelection();
2345   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2346 
2347   nsCOMPtr<nsIDOMElement> table;
2348   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable,
2349                                             getter_AddRefs(table));
2350   NS_ENSURE_SUCCESS(rv, rv);
2351   // Don't fail if we didn't find a table
2352   NS_ENSURE_TRUE(table, NS_OK);
2353 
2354   int32_t rowCount, colCount, rowIndex, colIndex;
2355   rv = GetTableSize(table, &rowCount, &colCount);
2356   NS_ENSURE_SUCCESS(rv, rv);
2357 
2358   // Save current selection
2359   AutoSelectionRestorer selectionRestorer(selection, this);
2360 
2361   AutoPlaceholderBatch beginBatching(this);
2362   // Prevent auto insertion of BR in new cell until we're done
2363   AutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
2364 
2365   nsCOMPtr<nsIDOMElement> cell;
2366   int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
2367       actualColSpan;
2368   bool isSelected;
2369 
2370   // Scan all cells in each row to detect bad rowspan values
2371   for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
2372     rv = FixBadRowSpan(table, rowIndex, rowCount);
2373     NS_ENSURE_SUCCESS(rv, rv);
2374   }
2375   // and same for colspans
2376   for (colIndex = 0; colIndex < colCount; colIndex++) {
2377     rv = FixBadColSpan(table, colIndex, colCount);
2378     NS_ENSURE_SUCCESS(rv, rv);
2379   }
2380 
2381   // Fill in missing cellmap locations with empty cells
2382   for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
2383     nsCOMPtr<nsIDOMElement> previousCellInRow;
2384     for (colIndex = 0; colIndex < colCount; colIndex++) {
2385       rv = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell),
2386                          &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2387                          &actualRowSpan, &actualColSpan, &isSelected);
2388       // NOTE: This is a *real* failure.
2389       // GetCellDataAt passes if cell is missing from cellmap
2390       if (NS_FAILED(rv)) {
2391         return rv;
2392       }
2393       if (!cell) {
2394       // We are missing a cell at a cellmap location
2395 #ifdef DEBUG
2396         printf("NormalizeTable found missing cell at row=%d, col=%d\n",
2397                rowIndex, colIndex);
2398 #endif
2399         // Add a cell after the previous Cell in the current row
2400         if (!previousCellInRow) {
2401         // We don't have any cells in this row -- We are really messed up!
2402 #ifdef DEBUG
2403           printf("NormalizeTable found no cells in row=%d, col=%d\n", rowIndex,
2404                  colIndex);
2405 #endif
2406           return NS_ERROR_FAILURE;
2407         }
2408 
2409         // Insert a new cell after (true), and return the new cell to us
2410         rv = InsertCell(previousCellInRow, 1, 1, true, false,
2411                         getter_AddRefs(cell));
2412         NS_ENSURE_SUCCESS(rv, rv);
2413 
2414         // Set this so we use returned new "cell" to set previousCellInRow below
2415         if (cell) {
2416           startRowIndex = rowIndex;
2417         }
2418       }
2419       // Save the last cell found in the same row we are scanning
2420       if (startRowIndex == rowIndex) {
2421         previousCellInRow = cell;
2422       }
2423     }
2424   }
2425   return NS_OK;
2426 }
2427 
2428 NS_IMETHODIMP
GetCellIndexes(nsIDOMElement * aCell,int32_t * aRowIndex,int32_t * aColIndex)2429 HTMLEditor::GetCellIndexes(nsIDOMElement* aCell, int32_t* aRowIndex,
2430                            int32_t* aColIndex) {
2431   NS_ENSURE_ARG_POINTER(aRowIndex);
2432   *aColIndex = 0;  // initialize out params
2433   NS_ENSURE_ARG_POINTER(aColIndex);
2434   *aRowIndex = 0;
2435   if (!aCell) {
2436     // Get the selected cell or the cell enclosing the selection anchor
2437     nsCOMPtr<nsIDOMElement> cell;
2438     nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr,
2439                                               getter_AddRefs(cell));
2440     if (NS_FAILED(rv) || !cell) {
2441       return NS_ERROR_FAILURE;
2442     }
2443     aCell = cell;
2444   }
2445 
2446   nsCOMPtr<nsIPresShell> ps = GetPresShell();
2447   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
2448 
2449   nsCOMPtr<nsIContent> nodeAsContent(do_QueryInterface(aCell));
2450   NS_ENSURE_TRUE(nodeAsContent, NS_ERROR_FAILURE);
2451   // frames are not ref counted, so don't use an nsCOMPtr
2452   nsIFrame* layoutObject = nodeAsContent->GetPrimaryFrame();
2453   NS_ENSURE_TRUE(layoutObject, NS_ERROR_FAILURE);
2454 
2455   nsITableCellLayout* cellLayoutObject = do_QueryFrame(layoutObject);
2456   NS_ENSURE_TRUE(cellLayoutObject, NS_ERROR_FAILURE);
2457   return cellLayoutObject->GetCellIndexes(*aRowIndex, *aColIndex);
2458 }
2459 
GetTableFrame(nsIDOMElement * aTable)2460 nsTableWrapperFrame* HTMLEditor::GetTableFrame(nsIDOMElement* aTable) {
2461   NS_ENSURE_TRUE(aTable, nullptr);
2462 
2463   nsCOMPtr<nsIContent> nodeAsContent(do_QueryInterface(aTable));
2464   NS_ENSURE_TRUE(nodeAsContent, nullptr);
2465   return do_QueryFrame(nodeAsContent->GetPrimaryFrame());
2466 }
2467 
2468 // Return actual number of cells (a cell with colspan > 1 counts as just 1)
GetNumberOfCellsInRow(nsIDOMElement * aTable,int32_t rowIndex)2469 int32_t HTMLEditor::GetNumberOfCellsInRow(nsIDOMElement* aTable,
2470                                           int32_t rowIndex) {
2471   int32_t cellCount = 0;
2472   nsCOMPtr<nsIDOMElement> cell;
2473   int32_t colIndex = 0;
2474   do {
2475     int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan,
2476         actualColSpan;
2477     bool isSelected;
2478     nsresult rv =
2479         GetCellDataAt(aTable, rowIndex, colIndex, getter_AddRefs(cell),
2480                       &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2481                       &actualRowSpan, &actualColSpan, &isSelected);
2482     NS_ENSURE_SUCCESS(rv, 0);
2483     if (cell) {
2484       // Only count cells that start in row we are working with
2485       if (startRowIndex == rowIndex) {
2486         cellCount++;
2487       }
2488       // Next possible location for a cell
2489       colIndex += actualColSpan;
2490     } else {
2491       colIndex++;
2492     }
2493   } while (cell);
2494 
2495   return cellCount;
2496 }
2497 
2498 NS_IMETHODIMP
GetTableSize(nsIDOMElement * aTable,int32_t * aRowCount,int32_t * aColCount)2499 HTMLEditor::GetTableSize(nsIDOMElement* aTable, int32_t* aRowCount,
2500                          int32_t* aColCount) {
2501   nsCOMPtr<Element> table = do_QueryInterface(aTable);
2502   return GetTableSize(table, aRowCount, aColCount);
2503 }
2504 
GetTableSize(Element * aTable,int32_t * aRowCount,int32_t * aColCount)2505 nsresult HTMLEditor::GetTableSize(Element* aTable, int32_t* aRowCount,
2506                                   int32_t* aColCount) {
2507   NS_ENSURE_ARG_POINTER(aRowCount);
2508   NS_ENSURE_ARG_POINTER(aColCount);
2509   *aRowCount = 0;
2510   *aColCount = 0;
2511   // Get the selected talbe or the table enclosing the selection anchor
2512   RefPtr<Element> table =
2513       GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable);
2514   NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
2515 
2516   nsTableWrapperFrame* tableFrame = do_QueryFrame(table->GetPrimaryFrame());
2517   NS_ENSURE_TRUE(tableFrame, NS_ERROR_FAILURE);
2518 
2519   *aRowCount = tableFrame->GetRowCount();
2520   *aColCount = tableFrame->GetColCount();
2521 
2522   return NS_OK;
2523 }
2524 
2525 NS_IMETHODIMP
GetCellDataAt(nsIDOMElement * aTable,int32_t aRowIndex,int32_t aColIndex,nsIDOMElement ** aCell,int32_t * aStartRowIndex,int32_t * aStartColIndex,int32_t * aRowSpan,int32_t * aColSpan,int32_t * aActualRowSpan,int32_t * aActualColSpan,bool * aIsSelected)2526 HTMLEditor::GetCellDataAt(nsIDOMElement* aTable, int32_t aRowIndex,
2527                           int32_t aColIndex, nsIDOMElement** aCell,
2528                           int32_t* aStartRowIndex, int32_t* aStartColIndex,
2529                           int32_t* aRowSpan, int32_t* aColSpan,
2530                           int32_t* aActualRowSpan, int32_t* aActualColSpan,
2531                           bool* aIsSelected) {
2532   NS_ENSURE_ARG_POINTER(aStartRowIndex);
2533   NS_ENSURE_ARG_POINTER(aStartColIndex);
2534   NS_ENSURE_ARG_POINTER(aRowSpan);
2535   NS_ENSURE_ARG_POINTER(aColSpan);
2536   NS_ENSURE_ARG_POINTER(aActualRowSpan);
2537   NS_ENSURE_ARG_POINTER(aActualColSpan);
2538   NS_ENSURE_ARG_POINTER(aIsSelected);
2539   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
2540 
2541   *aStartRowIndex = 0;
2542   *aStartColIndex = 0;
2543   *aRowSpan = 0;
2544   *aColSpan = 0;
2545   *aActualRowSpan = 0;
2546   *aActualColSpan = 0;
2547   *aIsSelected = false;
2548 
2549   *aCell = nullptr;
2550 
2551   if (!aTable) {
2552     // Get the selected table or the table enclosing the selection anchor
2553     nsCOMPtr<nsIDOMElement> table;
2554     nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("table"),
2555                                               nullptr, getter_AddRefs(table));
2556     NS_ENSURE_SUCCESS(rv, rv);
2557     if (!table) {
2558       return NS_ERROR_FAILURE;
2559     }
2560     aTable = table;
2561   }
2562 
2563   nsTableWrapperFrame* tableFrame = GetTableFrame(aTable);
2564   NS_ENSURE_TRUE(tableFrame, NS_ERROR_FAILURE);
2565 
2566   nsTableCellFrame* cellFrame =
2567       tableFrame->GetCellFrameAt(aRowIndex, aColIndex);
2568   if (!cellFrame) {
2569     return NS_ERROR_FAILURE;
2570   }
2571 
2572   *aIsSelected = cellFrame->IsSelected();
2573   *aStartRowIndex = cellFrame->RowIndex();
2574   *aStartColIndex = cellFrame->ColIndex();
2575   *aRowSpan = cellFrame->GetRowSpan();
2576   *aColSpan = cellFrame->GetColSpan();
2577   *aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
2578   *aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex);
2579   nsCOMPtr<nsIDOMElement> domCell = do_QueryInterface(cellFrame->GetContent());
2580   domCell.forget(aCell);
2581 
2582   return NS_OK;
2583 }
2584 
2585 // When all you want is the cell
2586 NS_IMETHODIMP
GetCellAt(nsIDOMElement * aTable,int32_t aRowIndex,int32_t aColIndex,nsIDOMElement ** aCell)2587 HTMLEditor::GetCellAt(nsIDOMElement* aTable, int32_t aRowIndex,
2588                       int32_t aColIndex, nsIDOMElement** aCell) {
2589   NS_ENSURE_ARG_POINTER(aCell);
2590   *aCell = nullptr;
2591 
2592   if (!aTable) {
2593     // Get the selected table or the table enclosing the selection anchor
2594     nsCOMPtr<nsIDOMElement> table;
2595     nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("table"),
2596                                               nullptr, getter_AddRefs(table));
2597     NS_ENSURE_SUCCESS(rv, rv);
2598     NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
2599     aTable = table;
2600   }
2601 
2602   nsTableWrapperFrame* tableFrame = GetTableFrame(aTable);
2603   if (!tableFrame) {
2604     *aCell = nullptr;
2605     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2606   }
2607 
2608   nsCOMPtr<nsIDOMElement> domCell =
2609       do_QueryInterface(tableFrame->GetCellAt(aRowIndex, aColIndex));
2610   domCell.forget(aCell);
2611 
2612   return NS_OK;
2613 }
2614 
2615 // When all you want are the rowspan and colspan (not exposed in nsITableEditor)
GetCellSpansAt(nsIDOMElement * aTable,int32_t aRowIndex,int32_t aColIndex,int32_t & aActualRowSpan,int32_t & aActualColSpan)2616 nsresult HTMLEditor::GetCellSpansAt(nsIDOMElement* aTable, int32_t aRowIndex,
2617                                     int32_t aColIndex, int32_t& aActualRowSpan,
2618                                     int32_t& aActualColSpan) {
2619   nsTableWrapperFrame* tableFrame = GetTableFrame(aTable);
2620   if (!tableFrame) {
2621     return NS_ERROR_FAILURE;
2622   }
2623   aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
2624   aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex);
2625 
2626   return NS_OK;
2627 }
2628 
GetCellContext(Selection ** aSelection,nsIDOMElement ** aTable,nsIDOMElement ** aCell,nsIDOMNode ** aCellParent,int32_t * aCellOffset,int32_t * aRowIndex,int32_t * aColIndex)2629 nsresult HTMLEditor::GetCellContext(Selection** aSelection,
2630                                     nsIDOMElement** aTable,
2631                                     nsIDOMElement** aCell,
2632                                     nsIDOMNode** aCellParent,
2633                                     int32_t* aCellOffset, int32_t* aRowIndex,
2634                                     int32_t* aColIndex) {
2635   // Initialize return pointers
2636   if (aSelection) {
2637     *aSelection = nullptr;
2638   }
2639   if (aTable) {
2640     *aTable = nullptr;
2641   }
2642   if (aCell) {
2643     *aCell = nullptr;
2644   }
2645   if (aCellParent) {
2646     *aCellParent = nullptr;
2647   }
2648   if (aCellOffset) {
2649     *aCellOffset = 0;
2650   }
2651   if (aRowIndex) {
2652     *aRowIndex = 0;
2653   }
2654   if (aColIndex) {
2655     *aColIndex = 0;
2656   }
2657 
2658   RefPtr<Selection> selection = GetSelection();
2659   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2660 
2661   if (aSelection) {
2662     *aSelection = selection.get();
2663     NS_ADDREF(*aSelection);
2664   }
2665   nsCOMPtr<nsIDOMElement> table;
2666   nsCOMPtr<nsIDOMElement> cell;
2667 
2668   // Caller may supply the cell...
2669   if (aCell && *aCell) {
2670     cell = *aCell;
2671   }
2672 
2673   // ...but if not supplied,
2674   //    get cell if it's the child of selection anchor node,
2675   //    or get the enclosing by a cell
2676   if (!cell) {
2677     // Find a selected or enclosing table element
2678     nsCOMPtr<nsIDOMElement> cellOrTableElement;
2679     int32_t selectedCount;
2680     nsAutoString tagName;
2681     nsresult rv = GetSelectedOrParentTableElement(
2682         tagName, &selectedCount, getter_AddRefs(cellOrTableElement));
2683     NS_ENSURE_SUCCESS(rv, rv);
2684     if (tagName.EqualsLiteral("table")) {
2685       // We have a selected table, not a cell
2686       if (aTable) {
2687         *aTable = cellOrTableElement.get();
2688         NS_ADDREF(*aTable);
2689       }
2690       return NS_OK;
2691     }
2692     if (!tagName.EqualsLiteral("td")) {
2693       return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2694     }
2695 
2696     // We found a cell
2697     cell = cellOrTableElement;
2698   }
2699   if (aCell) {
2700     *aCell = cell.get();
2701     NS_ADDREF(*aCell);
2702   }
2703 
2704   // Get containing table
2705   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell,
2706                                             getter_AddRefs(table));
2707   NS_ENSURE_SUCCESS(rv, rv);
2708   // Cell must be in a table, so fail if not found
2709   NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
2710   if (aTable) {
2711     *aTable = table.get();
2712     NS_ADDREF(*aTable);
2713   }
2714 
2715   // Get the rest of the related data only if requested
2716   if (aRowIndex || aColIndex) {
2717     int32_t rowIndex, colIndex;
2718     // Get current cell location so we can put caret back there when done
2719     rv = GetCellIndexes(cell, &rowIndex, &colIndex);
2720     if (NS_FAILED(rv)) {
2721       return rv;
2722     }
2723     if (aRowIndex) {
2724       *aRowIndex = rowIndex;
2725     }
2726     if (aColIndex) {
2727       *aColIndex = colIndex;
2728     }
2729   }
2730   if (aCellParent) {
2731     nsCOMPtr<nsINode> cellNode = do_QueryInterface(cell);
2732     // Get the immediate parent of the cell
2733     nsINode* cellParent = cellNode->GetParentNode();
2734     // Cell has to have a parent, so fail if not found
2735     NS_ENSURE_TRUE(cellParent, NS_ERROR_FAILURE);
2736 
2737     *aCellParent = cellParent->AsDOMNode();
2738     NS_ADDREF(*aCellParent);
2739 
2740     if (aCellOffset) {
2741       *aCellOffset = GetChildOffset(cell, cellParent->AsDOMNode());
2742     }
2743   }
2744 
2745   return NS_OK;
2746 }
2747 
GetCellFromRange(nsRange * aRange,nsIDOMElement ** aCell)2748 nsresult HTMLEditor::GetCellFromRange(nsRange* aRange, nsIDOMElement** aCell) {
2749   // Note: this might return a node that is outside of the range.
2750   // Use carefully.
2751   NS_ENSURE_TRUE(aRange && aCell, NS_ERROR_NULL_POINTER);
2752 
2753   *aCell = nullptr;
2754 
2755   nsCOMPtr<nsINode> startContainer = aRange->GetStartContainer();
2756   if (NS_WARN_IF(!startContainer)) {
2757     return NS_ERROR_FAILURE;
2758   }
2759 
2760   uint32_t startOffset = aRange->StartOffset();
2761 
2762   nsCOMPtr<nsINode> childNode = aRange->GetChildAtStartOffset();
2763   // This means selection is probably at a text node (or end of doc?)
2764   if (!childNode) {
2765     return NS_ERROR_FAILURE;
2766   }
2767 
2768   nsCOMPtr<nsINode> endContainer = aRange->GetEndContainer();
2769   if (NS_WARN_IF(!endContainer)) {
2770     return NS_ERROR_FAILURE;
2771   }
2772 
2773   // If a cell is deleted, the range is collapse
2774   //   (startOffset == aRange->EndOffset())
2775   //   so tell caller the cell wasn't found
2776   if (startContainer == endContainer &&
2777       aRange->EndOffset() == startOffset + 1 &&
2778       HTMLEditUtils::IsTableCell(childNode)) {
2779     // Should we also test if frame is selected? (Use GetCellDataAt())
2780     // (Let's not for now -- more efficient)
2781     nsCOMPtr<nsIDOMElement> cellElement = do_QueryInterface(childNode);
2782     cellElement.forget(aCell);
2783     return NS_OK;
2784   }
2785   return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2786 }
2787 
2788 NS_IMETHODIMP
GetFirstSelectedCell(nsIDOMRange ** aRange,nsIDOMElement ** aCell)2789 HTMLEditor::GetFirstSelectedCell(nsIDOMRange** aRange, nsIDOMElement** aCell) {
2790   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
2791   *aCell = nullptr;
2792   if (aRange) {
2793     *aRange = nullptr;
2794   }
2795 
2796   RefPtr<Selection> selection = GetSelection();
2797   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2798 
2799   RefPtr<nsRange> range = selection->GetRangeAt(0);
2800   NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
2801 
2802   mSelectedCellIndex = 0;
2803 
2804   nsresult rv = GetCellFromRange(range, aCell);
2805   // Failure here probably means selection is in a text node,
2806   //  so there's no selected cell
2807   if (NS_FAILED(rv)) {
2808     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2809   }
2810   // No cell means range was collapsed (cell was deleted)
2811   if (!*aCell) {
2812     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2813   }
2814 
2815   if (aRange) {
2816     *aRange = range.get();
2817     NS_ADDREF(*aRange);
2818   }
2819 
2820   // Setup for next cell
2821   mSelectedCellIndex = 1;
2822 
2823   return NS_OK;
2824 }
2825 
2826 NS_IMETHODIMP
GetNextSelectedCell(nsIDOMRange ** aRange,nsIDOMElement ** aCell)2827 HTMLEditor::GetNextSelectedCell(nsIDOMRange** aRange, nsIDOMElement** aCell) {
2828   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
2829   *aCell = nullptr;
2830   if (aRange) {
2831     *aRange = nullptr;
2832   }
2833 
2834   RefPtr<Selection> selection = GetSelection();
2835   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2836 
2837   int32_t rangeCount = selection->RangeCount();
2838 
2839   // Don't even try if index exceeds range count
2840   if (mSelectedCellIndex >= rangeCount) {
2841     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2842   }
2843 
2844   // Scan through ranges to find next valid selected cell
2845   RefPtr<nsRange> range;
2846   for (; mSelectedCellIndex < rangeCount; mSelectedCellIndex++) {
2847     range = selection->GetRangeAt(mSelectedCellIndex);
2848     NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
2849 
2850     nsresult rv = GetCellFromRange(range, aCell);
2851     // Failure here means the range doesn't contain a cell
2852     NS_ENSURE_SUCCESS(rv, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
2853 
2854     // We found a selected cell
2855     if (*aCell) {
2856       break;
2857     }
2858 
2859     // If we didn't find a cell, continue to next range in selection
2860   }
2861   // No cell means all remaining ranges were collapsed (cells were deleted)
2862   NS_ENSURE_TRUE(*aCell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
2863 
2864   if (aRange) {
2865     *aRange = range.get();
2866     NS_ADDREF(*aRange);
2867   }
2868 
2869   // Setup for next cell
2870   mSelectedCellIndex++;
2871 
2872   return NS_OK;
2873 }
2874 
2875 NS_IMETHODIMP
GetFirstSelectedCellInTable(int32_t * aRowIndex,int32_t * aColIndex,nsIDOMElement ** aCell)2876 HTMLEditor::GetFirstSelectedCellInTable(int32_t* aRowIndex, int32_t* aColIndex,
2877                                         nsIDOMElement** aCell) {
2878   NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
2879   *aCell = nullptr;
2880   if (aRowIndex) {
2881     *aRowIndex = 0;
2882   }
2883   if (aColIndex) {
2884     *aColIndex = 0;
2885   }
2886 
2887   nsCOMPtr<nsIDOMElement> cell;
2888   nsresult rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
2889   NS_ENSURE_SUCCESS(rv, rv);
2890   NS_ENSURE_TRUE(cell, NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND);
2891 
2892   *aCell = cell.get();
2893   NS_ADDREF(*aCell);
2894 
2895   // Also return the row and/or column if requested
2896   if (aRowIndex || aColIndex) {
2897     int32_t startRowIndex, startColIndex;
2898     rv = GetCellIndexes(cell, &startRowIndex, &startColIndex);
2899     if (NS_FAILED(rv)) {
2900       return rv;
2901     }
2902 
2903     if (aRowIndex) {
2904       *aRowIndex = startRowIndex;
2905     }
2906     if (aColIndex) {
2907       *aColIndex = startColIndex;
2908     }
2909   }
2910 
2911   return NS_OK;
2912 }
2913 
SetSelectionAfterTableEdit(nsIDOMElement * aTable,int32_t aRow,int32_t aCol,int32_t aDirection,bool aSelected)2914 void HTMLEditor::SetSelectionAfterTableEdit(nsIDOMElement* aTable, int32_t aRow,
2915                                             int32_t aCol, int32_t aDirection,
2916                                             bool aSelected) {
2917   if (NS_WARN_IF(!aTable) || Destroyed()) {
2918     return;
2919   }
2920 
2921   RefPtr<Selection> selection = GetSelection();
2922   if (!selection) {
2923     return;
2924   }
2925 
2926   nsCOMPtr<nsIDOMElement> cell;
2927   bool done = false;
2928   do {
2929     nsresult rv = GetCellAt(aTable, aRow, aCol, getter_AddRefs(cell));
2930     if (NS_FAILED(rv)) {
2931       break;
2932     }
2933 
2934     if (cell) {
2935       if (aSelected) {
2936         // Reselect the cell
2937         SelectElement(cell);
2938         return;
2939       }
2940 
2941       // Set the caret to deepest first child
2942       // but don't go into nested tables
2943       // TODO: Should we really be placing the caret at the END
2944       // of the cell content?
2945       nsCOMPtr<nsINode> cellNode = do_QueryInterface(cell);
2946       if (cellNode) {
2947         CollapseSelectionToDeepestNonTableFirstChild(selection, cellNode);
2948       }
2949       return;
2950     }
2951 
2952     // Setup index to find another cell in the
2953     //   direction requested, but move in other direction if already at
2954     //   beginning of row or column
2955     switch (aDirection) {
2956       case ePreviousColumn:
2957         if (!aCol) {
2958           if (aRow > 0) {
2959             aRow--;
2960           } else {
2961             done = true;
2962           }
2963         } else {
2964           aCol--;
2965         }
2966         break;
2967       case ePreviousRow:
2968         if (!aRow) {
2969           if (aCol > 0) {
2970             aCol--;
2971           } else {
2972             done = true;
2973           }
2974         } else {
2975           aRow--;
2976         }
2977         break;
2978       default:
2979         done = true;
2980     }
2981   } while (!done);
2982 
2983   // We didn't find a cell
2984   // Set selection to just before the table
2985   nsCOMPtr<nsINode> table = do_QueryInterface(aTable);
2986   if (table->GetParentNode()) {
2987     nsCOMPtr<nsIContent> table = do_QueryInterface(aTable);
2988     if (NS_WARN_IF(!table)) {
2989       return;
2990     }
2991     EditorRawDOMPoint atTable(table);
2992     if (NS_WARN_IF(!atTable.IsSetAndValid())) {
2993       return;
2994     }
2995     selection->Collapse(atTable);
2996     return;
2997   }
2998   // Last resort: Set selection to start of doc
2999   // (it's very bad to not have a valid selection!)
3000   SetSelectionAtDocumentStart(selection);
3001 }
3002 
3003 NS_IMETHODIMP
GetSelectedOrParentTableElement(nsAString & aTagName,int32_t * aSelectedCount,nsIDOMElement ** aTableElement)3004 HTMLEditor::GetSelectedOrParentTableElement(nsAString& aTagName,
3005                                             int32_t* aSelectedCount,
3006                                             nsIDOMElement** aTableElement) {
3007   NS_ENSURE_ARG_POINTER(aTableElement);
3008   NS_ENSURE_ARG_POINTER(aSelectedCount);
3009   *aTableElement = nullptr;
3010   aTagName.Truncate();
3011   *aSelectedCount = 0;
3012 
3013   RefPtr<Selection> selection = GetSelection();
3014   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
3015 
3016   // Try to get the first selected cell
3017   nsCOMPtr<nsIDOMElement> tableOrCellElement;
3018   nsresult rv =
3019       GetFirstSelectedCell(nullptr, getter_AddRefs(tableOrCellElement));
3020   NS_ENSURE_SUCCESS(rv, rv);
3021 
3022   NS_NAMED_LITERAL_STRING(tdName, "td");
3023 
3024   if (tableOrCellElement) {
3025     // Each cell is in its own selection range,
3026     //  so count signals multiple-cell selection
3027     *aSelectedCount = selection->RangeCount();
3028     aTagName = tdName;
3029   } else {
3030     nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode();
3031     if (NS_WARN_IF(!anchorNode)) {
3032       return NS_ERROR_FAILURE;
3033     }
3034 
3035     // Get child of anchor node, if exists
3036     if (anchorNode->HasChildNodes()) {
3037       nsINode* selectedNode = selection->GetChildAtAnchorOffset();
3038       if (!selectedNode) {
3039         selectedNode = anchorNode;
3040         // If anchor doesn't have a child, we can't be selecting a table
3041         // element,
3042         //  so don't do the following:
3043       } else {
3044         if (selectedNode->IsHTMLElement(nsGkAtoms::td)) {
3045           tableOrCellElement = do_QueryInterface(selectedNode);
3046           aTagName = tdName;
3047           // Each cell is in its own selection range,
3048           //  so count signals multiple-cell selection
3049           *aSelectedCount = selection->RangeCount();
3050         } else if (selectedNode->IsHTMLElement(nsGkAtoms::table)) {
3051           tableOrCellElement = do_QueryInterface(selectedNode);
3052           aTagName.AssignLiteral("table");
3053           *aSelectedCount = 1;
3054         } else if (selectedNode->IsHTMLElement(nsGkAtoms::tr)) {
3055           tableOrCellElement = do_QueryInterface(selectedNode);
3056           aTagName.AssignLiteral("tr");
3057           *aSelectedCount = 1;
3058         }
3059       }
3060     }
3061     if (!tableOrCellElement) {
3062       // Didn't find a table element -- find a cell parent
3063       rv = GetElementOrParentByTagName(tdName, GetAsDOMNode(anchorNode),
3064                                        getter_AddRefs(tableOrCellElement));
3065       if (NS_FAILED(rv)) {
3066         return rv;
3067       }
3068       if (tableOrCellElement) {
3069         aTagName = tdName;
3070       }
3071     }
3072   }
3073   if (tableOrCellElement) {
3074     *aTableElement = tableOrCellElement.get();
3075     NS_ADDREF(*aTableElement);
3076   }
3077   return NS_OK;
3078 }
3079 
3080 NS_IMETHODIMP
GetSelectedCellsType(nsIDOMElement * aElement,uint32_t * aSelectionType)3081 HTMLEditor::GetSelectedCellsType(nsIDOMElement* aElement,
3082                                  uint32_t* aSelectionType) {
3083   NS_ENSURE_ARG_POINTER(aSelectionType);
3084   *aSelectionType = 0;
3085 
3086   // Be sure we have a table element
3087   //  (if aElement is null, this uses selection's anchor node)
3088   nsCOMPtr<nsIDOMElement> table;
3089 
3090   nsresult rv = GetElementOrParentByTagName(NS_LITERAL_STRING("table"),
3091                                             aElement, getter_AddRefs(table));
3092   NS_ENSURE_SUCCESS(rv, rv);
3093 
3094   int32_t rowCount, colCount;
3095   rv = GetTableSize(table, &rowCount, &colCount);
3096   NS_ENSURE_SUCCESS(rv, rv);
3097 
3098   // Traverse all selected cells
3099   nsCOMPtr<nsIDOMElement> selectedCell;
3100   rv = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell));
3101   NS_ENSURE_SUCCESS(rv, rv);
3102   if (rv == NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND) {
3103     return NS_OK;
3104   }
3105 
3106   // We have at least one selected cell, so set return value
3107   *aSelectionType = nsISelectionPrivate::TABLESELECTION_CELL;
3108 
3109   // Store indexes of each row/col to avoid duplication of searches
3110   nsTArray<int32_t> indexArray;
3111 
3112   bool allCellsInRowAreSelected = false;
3113   bool allCellsInColAreSelected = false;
3114   while (NS_SUCCEEDED(rv) && selectedCell) {
3115     // Get the cell's location in the cellmap
3116     int32_t startRowIndex, startColIndex;
3117     rv = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex);
3118     if (NS_FAILED(rv)) {
3119       return rv;
3120     }
3121 
3122     if (!indexArray.Contains(startColIndex)) {
3123       indexArray.AppendElement(startColIndex);
3124       allCellsInRowAreSelected =
3125           AllCellsInRowSelected(table, startRowIndex, colCount);
3126       // We're done as soon as we fail for any row
3127       if (!allCellsInRowAreSelected) {
3128         break;
3129       }
3130     }
3131     rv = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell));
3132   }
3133 
3134   if (allCellsInRowAreSelected) {
3135     *aSelectionType = nsISelectionPrivate::TABLESELECTION_ROW;
3136     return NS_OK;
3137   }
3138   // Test for columns
3139 
3140   // Empty the indexArray
3141   indexArray.Clear();
3142 
3143   // Start at first cell again
3144   rv = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell));
3145   while (NS_SUCCEEDED(rv) && selectedCell) {
3146     // Get the cell's location in the cellmap
3147     int32_t startRowIndex, startColIndex;
3148     rv = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex);
3149     if (NS_FAILED(rv)) {
3150       return rv;
3151     }
3152 
3153     if (!indexArray.Contains(startRowIndex)) {
3154       indexArray.AppendElement(startColIndex);
3155       allCellsInColAreSelected =
3156           AllCellsInColumnSelected(table, startColIndex, rowCount);
3157       // We're done as soon as we fail for any column
3158       if (!allCellsInRowAreSelected) {
3159         break;
3160       }
3161     }
3162     rv = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell));
3163   }
3164   if (allCellsInColAreSelected) {
3165     *aSelectionType = nsISelectionPrivate::TABLESELECTION_COLUMN;
3166   }
3167 
3168   return NS_OK;
3169 }
3170 
AllCellsInRowSelected(nsIDOMElement * aTable,int32_t aRowIndex,int32_t aNumberOfColumns)3171 bool HTMLEditor::AllCellsInRowSelected(nsIDOMElement* aTable, int32_t aRowIndex,
3172                                        int32_t aNumberOfColumns) {
3173   NS_ENSURE_TRUE(aTable, false);
3174 
3175   int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan,
3176       actualColSpan;
3177   bool isSelected;
3178 
3179   for (int32_t col = 0; col < aNumberOfColumns;
3180        col += std::max(actualColSpan, 1)) {
3181     nsCOMPtr<nsIDOMElement> cell;
3182     nsresult rv =
3183         GetCellDataAt(aTable, aRowIndex, col, getter_AddRefs(cell),
3184                       &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan,
3185                       &actualRowSpan, &actualColSpan, &isSelected);
3186 
3187     NS_ENSURE_SUCCESS(rv, false);
3188     // If no cell, we may have a "ragged" right edge,
3189     //   so return TRUE only if we already found a cell in the row
3190     NS_ENSURE_TRUE(cell, (col > 0) ? true : false);
3191 
3192     // Return as soon as a non-selected cell is found
3193     NS_ENSURE_TRUE(isSelected, false);
3194 
3195     NS_ASSERTION((actualColSpan > 0),
3196                  "ActualColSpan = 0 in AllCellsInRowSelected");
3197   }
3198   return true;
3199 }
3200 
AllCellsInColumnSelected(nsIDOMElement * aTable,int32_t aColIndex,int32_t aNumberOfRows)3201 bool HTMLEditor::AllCellsInColumnSelected(nsIDOMElement* aTable,
3202                                           int32_t aColIndex,
3203                                           int32_t aNumberOfRows) {
3204   NS_ENSURE_TRUE(aTable, false);
3205 
3206   int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan,
3207       actualColSpan;
3208   bool isSelected;
3209 
3210   for (int32_t row = 0; row < aNumberOfRows;
3211        row += std::max(actualRowSpan, 1)) {
3212     nsCOMPtr<nsIDOMElement> cell;
3213     nsresult rv =
3214         GetCellDataAt(aTable, row, aColIndex, getter_AddRefs(cell),
3215                       &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan,
3216                       &actualRowSpan, &actualColSpan, &isSelected);
3217 
3218     NS_ENSURE_SUCCESS(rv, false);
3219     // If no cell, we must have a "ragged" right edge on the last column
3220     //   so return TRUE only if we already found a cell in the row
3221     NS_ENSURE_TRUE(cell, (row > 0) ? true : false);
3222 
3223     // Return as soon as a non-selected cell is found
3224     NS_ENSURE_TRUE(isSelected, false);
3225   }
3226   return true;
3227 }
3228 
IsEmptyCell(dom::Element * aCell)3229 bool HTMLEditor::IsEmptyCell(dom::Element* aCell) {
3230   MOZ_ASSERT(aCell);
3231 
3232   // Check if target only contains empty text node or <br>
3233   nsCOMPtr<nsINode> cellChild = aCell->GetFirstChild();
3234   if (!cellChild) {
3235     return false;
3236   }
3237 
3238   nsCOMPtr<nsINode> nextChild = cellChild->GetNextSibling();
3239   if (nextChild) {
3240     return false;
3241   }
3242 
3243   // We insert a single break into a cell by default
3244   //   to have some place to locate a cursor -- it is dispensable
3245   if (cellChild->IsHTMLElement(nsGkAtoms::br)) {
3246     return true;
3247   }
3248 
3249   bool isEmpty;
3250   // Or check if no real content
3251   nsresult rv = IsEmptyNode(cellChild, &isEmpty, false, false);
3252   NS_ENSURE_SUCCESS(rv, false);
3253   return isEmpty;
3254 }
3255 
3256 }  // namespace mozilla
3257