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