editor/libeditor/html/nsTableEditor.cpp

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

mercurial