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, ¤tRowIndex, ¤tColIndex);
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 ¤tRowIndex, ¤tColIndex, &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 ¤tRowIndex, ¤tColIndex, &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 ¤tRowIndex, ¤tColIndex, &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 ¤tRowIndex, ¤tColIndex, &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