Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / editor / libeditor / HTMLTableEditor.cpp
blobea470b220352810848576ebccacf3b74d8d4338a
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/. */
6 #include <stdio.h>
8 #include "HTMLEditor.h"
9 #include "HTMLEditorInlines.h"
11 #include "EditAction.h"
12 #include "EditorDOMPoint.h"
13 #include "EditorUtils.h"
14 #include "HTMLEditUtils.h"
16 #include "mozilla/Assertions.h"
17 #include "mozilla/FlushType.h"
18 #include "mozilla/IntegerRange.h"
19 #include "mozilla/PresShell.h"
20 #include "mozilla/dom/Selection.h"
21 #include "mozilla/dom/Element.h"
22 #include "nsAString.h"
23 #include "nsAlgorithm.h"
24 #include "nsCOMPtr.h"
25 #include "nsDebug.h"
26 #include "nsError.h"
27 #include "nsFrameSelection.h"
28 #include "nsGkAtoms.h"
29 #include "nsAtom.h"
30 #include "nsIContent.h"
31 #include "nsIFrame.h"
32 #include "nsINode.h"
33 #include "nsISupportsUtils.h"
34 #include "nsITableCellLayout.h" // For efficient access to table cell
35 #include "nsLiteralString.h"
36 #include "nsQueryFrame.h"
37 #include "nsRange.h"
38 #include "nsString.h"
39 #include "nsTArray.h"
40 #include "nsTableCellFrame.h"
41 #include "nsTableWrapperFrame.h"
42 #include "nscore.h"
43 #include <algorithm>
45 namespace mozilla {
47 using namespace dom;
48 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
50 /**
51 * Stack based helper class for restoring selection after table edit.
53 class MOZ_STACK_CLASS AutoSelectionSetterAfterTableEdit final {
54 private:
55 const RefPtr<HTMLEditor> mHTMLEditor;
56 const RefPtr<Element> mTable;
57 int32_t mCol, mRow, mDirection, mSelected;
59 public:
60 AutoSelectionSetterAfterTableEdit(HTMLEditor& aHTMLEditor, Element* aTable,
61 int32_t aRow, int32_t aCol,
62 int32_t aDirection, bool aSelected)
63 : mHTMLEditor(&aHTMLEditor),
64 mTable(aTable),
65 mCol(aCol),
66 mRow(aRow),
67 mDirection(aDirection),
68 mSelected(aSelected) {}
70 MOZ_CAN_RUN_SCRIPT ~AutoSelectionSetterAfterTableEdit() {
71 if (mHTMLEditor) {
72 mHTMLEditor->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection,
73 mSelected);
78 /******************************************************************************
79 * HTMLEditor::CellIndexes
80 ******************************************************************************/
82 void HTMLEditor::CellIndexes::Update(HTMLEditor& aHTMLEditor,
83 Selection& aSelection) {
84 // Guarantee the life time of the cell element since Init() will access
85 // layout methods.
86 RefPtr<Element> cellElement =
87 aHTMLEditor.GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
88 if (!cellElement) {
89 NS_WARNING(
90 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
91 "failed");
92 return;
95 RefPtr<PresShell> presShell{aHTMLEditor.GetPresShell()};
96 Update(*cellElement, presShell);
99 void HTMLEditor::CellIndexes::Update(Element& aCellElement,
100 PresShell* aPresShell) {
101 // If the table cell is created immediately before this call, e.g., using
102 // innerHTML, frames have not been created yet. Hence, flush layout to create
103 // them.
104 if (NS_WARN_IF(!aPresShell)) {
105 return;
108 aPresShell->FlushPendingNotifications(FlushType::Frames);
110 nsIFrame* frameOfCell = aCellElement.GetPrimaryFrame();
111 if (!frameOfCell) {
112 NS_WARNING("There was no layout information of aCellElement");
113 return;
116 nsITableCellLayout* tableCellLayout = do_QueryFrame(frameOfCell);
117 if (!tableCellLayout) {
118 NS_WARNING("aCellElement was not a table cell");
119 return;
122 if (NS_FAILED(tableCellLayout->GetCellIndexes(mRow, mColumn))) {
123 NS_WARNING("nsITableCellLayout::GetCellIndexes() failed");
124 mRow = mColumn = -1;
125 return;
128 MOZ_ASSERT(!isErr());
131 /******************************************************************************
132 * HTMLEditor::CellData
133 ******************************************************************************/
135 // static
136 HTMLEditor::CellData HTMLEditor::CellData::AtIndexInTableElement(
137 const HTMLEditor& aHTMLEditor, const Element& aTableElement,
138 int32_t aRowIndex, int32_t aColumnIndex) {
139 nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(&aTableElement);
140 if (!tableFrame) {
141 NS_WARNING("There was no layout information of the table");
142 return CellData::Error(aRowIndex, aColumnIndex);
145 // If there is no cell at the indexes. Don't set the error state to the new
146 // instance.
147 nsTableCellFrame* cellFrame =
148 tableFrame->GetCellFrameAt(aRowIndex, aColumnIndex);
149 if (!cellFrame) {
150 return CellData::NotFound(aRowIndex, aColumnIndex);
153 Element* cellElement = Element::FromNodeOrNull(cellFrame->GetContent());
154 if (!cellElement) {
155 return CellData::Error(aRowIndex, aColumnIndex);
157 return CellData(*cellElement, aRowIndex, aColumnIndex, *cellFrame,
158 *tableFrame);
161 HTMLEditor::CellData::CellData(Element& aElement, int32_t aRowIndex,
162 int32_t aColumnIndex,
163 nsTableCellFrame& aTableCellFrame,
164 nsTableWrapperFrame& aTableWrapperFrame)
165 : mElement(&aElement),
166 mCurrent(aRowIndex, aColumnIndex),
167 mFirst(aTableCellFrame.RowIndex(), aTableCellFrame.ColIndex()),
168 mRowSpan(aTableCellFrame.GetRowSpan()),
169 mColSpan(aTableCellFrame.GetColSpan()),
170 mEffectiveRowSpan(
171 aTableWrapperFrame.GetEffectiveRowSpanAt(aRowIndex, aColumnIndex)),
172 mEffectiveColSpan(
173 aTableWrapperFrame.GetEffectiveColSpanAt(aRowIndex, aColumnIndex)),
174 mIsSelected(aTableCellFrame.IsSelected()) {
175 MOZ_ASSERT(!mCurrent.isErr());
178 /******************************************************************************
179 * HTMLEditor::TableSize
180 ******************************************************************************/
182 // static
183 Result<HTMLEditor::TableSize, nsresult> HTMLEditor::TableSize::Create(
184 HTMLEditor& aHTMLEditor, Element& aTableOrElementInTable) {
185 // Currently, nsTableWrapperFrame::GetRowCount() and
186 // nsTableWrapperFrame::GetColCount() are safe to use without grabbing
187 // <table> element. However, editor developers may not watch layout API
188 // changes. So, for keeping us safer, we should use RefPtr here.
189 RefPtr<Element> tableElement =
190 aHTMLEditor.GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table,
191 aTableOrElementInTable);
192 if (!tableElement) {
193 NS_WARNING(
194 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
195 "failed");
196 return Err(NS_ERROR_FAILURE);
198 nsTableWrapperFrame* tableFrame =
199 do_QueryFrame(tableElement->GetPrimaryFrame());
200 if (!tableFrame) {
201 NS_WARNING("There was no layout information of the <table> element");
202 return Err(NS_ERROR_FAILURE);
204 const int32_t rowCount = tableFrame->GetRowCount();
205 const int32_t columnCount = tableFrame->GetColCount();
206 if (NS_WARN_IF(rowCount < 0) || NS_WARN_IF(columnCount < 0)) {
207 return Err(NS_ERROR_FAILURE);
209 return TableSize(rowCount, columnCount);
212 /******************************************************************************
213 * HTMLEditor
214 ******************************************************************************/
216 nsresult HTMLEditor::InsertCell(Element* aCell, int32_t aRowSpan,
217 int32_t aColSpan, bool aAfter, bool aIsHeader,
218 Element** aNewCell) {
219 if (aNewCell) {
220 *aNewCell = nullptr;
223 if (NS_WARN_IF(!aCell)) {
224 return NS_ERROR_INVALID_ARG;
227 // And the parent and offsets needed to do an insert
228 EditorDOMPoint pointToInsert(aCell);
229 if (NS_WARN_IF(!pointToInsert.IsSet())) {
230 return NS_ERROR_INVALID_ARG;
233 RefPtr<Element> newCell =
234 CreateElementWithDefaults(aIsHeader ? *nsGkAtoms::th : *nsGkAtoms::td);
235 if (!newCell) {
236 NS_WARNING(
237 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::th or td) failed");
238 return NS_ERROR_FAILURE;
241 // Optional: return new cell created
242 if (aNewCell) {
243 *aNewCell = do_AddRef(newCell).take();
246 if (aRowSpan > 1) {
247 // Note: Do NOT use editor transaction for this
248 nsAutoString newRowSpan;
249 newRowSpan.AppendInt(aRowSpan, 10);
250 DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
251 kNameSpaceID_None, nsGkAtoms::rowspan, newRowSpan, true);
252 NS_WARNING_ASSERTION(
253 NS_SUCCEEDED(rvIgnored),
254 "Element::SetAttr(nsGkAtoms::rawspan) failed, but ignored");
256 if (aColSpan > 1) {
257 // Note: Do NOT use editor transaction for this
258 nsAutoString newColSpan;
259 newColSpan.AppendInt(aColSpan, 10);
260 DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
261 kNameSpaceID_None, nsGkAtoms::colspan, newColSpan, true);
262 NS_WARNING_ASSERTION(
263 NS_SUCCEEDED(rvIgnored),
264 "Element::SetAttr(nsGkAtoms::colspan) failed, but ignored");
266 if (aAfter) {
267 DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
268 NS_WARNING_ASSERTION(advanced,
269 "Failed to advance offset to after the old cell");
272 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
273 // in normal cases. However, it may be required for nested edit
274 // actions which may be caused by legacy mutation event listeners or
275 // chrome script.
276 AutoTransactionsConserveSelection dontChangeSelection(*this);
277 Result<CreateElementResult, nsresult> insertNewCellResult =
278 InsertNodeWithTransaction<Element>(*newCell, pointToInsert);
279 if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
280 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
281 return insertNewCellResult.unwrapErr();
283 // Because of dontChangeSelection, we've never allowed to transactions to
284 // update selection here.
285 insertNewCellResult.inspect().IgnoreCaretPointSuggestion();
286 return NS_OK;
289 nsresult HTMLEditor::SetColSpan(Element* aCell, int32_t aColSpan) {
290 if (NS_WARN_IF(!aCell)) {
291 return NS_ERROR_INVALID_ARG;
293 nsAutoString newSpan;
294 newSpan.AppendInt(aColSpan, 10);
295 nsresult rv =
296 SetAttributeWithTransaction(*aCell, *nsGkAtoms::colspan, newSpan);
297 NS_WARNING_ASSERTION(
298 NS_SUCCEEDED(rv),
299 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::colspan) failed");
300 return rv;
303 nsresult HTMLEditor::SetRowSpan(Element* aCell, int32_t aRowSpan) {
304 if (NS_WARN_IF(!aCell)) {
305 return NS_ERROR_INVALID_ARG;
307 nsAutoString newSpan;
308 newSpan.AppendInt(aRowSpan, 10);
309 nsresult rv =
310 SetAttributeWithTransaction(*aCell, *nsGkAtoms::rowspan, newSpan);
311 NS_WARNING_ASSERTION(
312 NS_SUCCEEDED(rv),
313 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::rowspan) failed");
314 return rv;
317 NS_IMETHODIMP HTMLEditor::InsertTableCell(int32_t aNumberOfCellsToInsert,
318 bool aInsertAfterSelectedCell) {
319 if (aNumberOfCellsToInsert <= 0) {
320 return NS_OK; // Just do nothing.
323 AutoEditActionDataSetter editActionData(*this,
324 EditAction::eInsertTableCellElement);
325 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
326 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
327 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
328 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
329 return EditorBase::ToGenericNSResult(rv);
332 Result<RefPtr<Element>, nsresult> cellElementOrError =
333 GetFirstSelectedCellElementInTable();
334 if (cellElementOrError.isErr()) {
335 NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
336 return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
339 if (!cellElementOrError.inspect()) {
340 return NS_OK;
343 EditorDOMPoint pointToInsert(cellElementOrError.inspect());
344 if (!pointToInsert.IsSet()) {
345 NS_WARNING("Found an orphan cell element");
346 return NS_ERROR_FAILURE;
348 if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
349 DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
350 NS_WARNING_ASSERTION(
351 advanced,
352 "Failed to set insertion point after current cell, but ignored");
354 Result<CreateElementResult, nsresult> insertCellElementResult =
355 InsertTableCellsWithTransaction(pointToInsert, aNumberOfCellsToInsert);
356 if (MOZ_UNLIKELY(insertCellElementResult.isErr())) {
357 NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
358 return EditorBase::ToGenericNSResult(insertCellElementResult.unwrapErr());
360 // We don't need to modify selection here.
361 insertCellElementResult.inspect().IgnoreCaretPointSuggestion();
362 return NS_OK;
365 Result<CreateElementResult, nsresult>
366 HTMLEditor::InsertTableCellsWithTransaction(
367 const EditorDOMPoint& aPointToInsert, int32_t aNumberOfCellsToInsert) {
368 MOZ_ASSERT(IsEditActionDataAvailable());
369 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
370 MOZ_ASSERT(aNumberOfCellsToInsert > 0);
372 if (!HTMLEditUtils::IsTableRow(aPointToInsert.GetContainer())) {
373 NS_WARNING("Tried to insert cell elements to non-<tr> element");
374 return Err(NS_ERROR_FAILURE);
377 AutoPlaceholderBatch treateAsOneTransaction(
378 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
379 // Prevent auto insertion of BR in new cell until we're done
380 // XXX Why? I think that we should insert <br> element for every cell
381 // **before** inserting new cell into the <tr> element.
382 IgnoredErrorResult error;
383 AutoEditSubActionNotifier startToHandleEditSubAction(
384 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
385 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
386 return Err(error.StealNSResult());
388 NS_WARNING_ASSERTION(
389 !error.Failed(),
390 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
391 error.SuppressException();
393 // Put caret into the cell before the first inserting cell, or the first
394 // table cell in the row.
395 RefPtr<Element> cellToPutCaret =
396 aPointToInsert.IsEndOfContainer()
397 ? nullptr
398 : HTMLEditUtils::GetPreviousTableCellElementSibling(
399 *aPointToInsert.GetChild());
401 RefPtr<Element> firstCellElement, lastCellElement;
402 nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT {
403 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
404 // in normal cases. However, it may be required for nested edit
405 // actions which may be caused by legacy mutation event listeners or
406 // chrome script.
407 AutoTransactionsConserveSelection dontChangeSelection(*this);
409 // Block legacy mutation events for making this job simpler.
410 nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
412 // If there is a child to put a cell, we need to put all cell elements
413 // before it. Therefore, creating `EditorDOMPoint` with the child element
414 // is safe. Otherwise, we need to try to append cell elements in the row.
415 // Therefore, using `EditorDOMPoint::AtEndOf()` is safe. Note that it's
416 // not safe to creat it once because the offset and child relation in the
417 // point becomes invalid after inserting a cell element.
418 nsIContent* referenceContent = aPointToInsert.GetChild();
419 for ([[maybe_unused]] const auto i :
420 IntegerRange<uint32_t>(aNumberOfCellsToInsert)) {
421 RefPtr<Element> newCell = CreateElementWithDefaults(*nsGkAtoms::td);
422 if (!newCell) {
423 NS_WARNING(
424 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
425 return NS_ERROR_FAILURE;
427 Result<CreateElementResult, nsresult> insertNewCellResult =
428 InsertNodeWithTransaction(
429 *newCell, referenceContent
430 ? EditorDOMPoint(referenceContent)
431 : EditorDOMPoint::AtEndOf(
432 *aPointToInsert.ContainerAs<Element>()));
433 if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
434 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
435 return insertNewCellResult.unwrapErr();
437 CreateElementResult unwrappedInsertNewCellResult =
438 insertNewCellResult.unwrap();
439 lastCellElement = unwrappedInsertNewCellResult.UnwrapNewNode();
440 if (!firstCellElement) {
441 firstCellElement = lastCellElement;
443 // Because of dontChangeSelection, we've never allowed to transactions
444 // to update selection here.
445 unwrappedInsertNewCellResult.IgnoreCaretPointSuggestion();
446 if (!cellToPutCaret) {
447 cellToPutCaret = std::move(newCell); // This is first cell in the row.
451 // TODO: Stop touching selection here.
452 MOZ_ASSERT(cellToPutCaret);
453 MOZ_ASSERT(cellToPutCaret->GetParent());
454 CollapseSelectionToDeepestNonTableFirstChild(cellToPutCaret);
455 return NS_OK;
456 }();
457 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED ||
458 NS_WARN_IF(Destroyed()))) {
459 return Err(NS_ERROR_EDITOR_DESTROYED);
461 if (NS_FAILED(rv)) {
462 return Err(rv);
464 MOZ_ASSERT(firstCellElement);
465 MOZ_ASSERT(lastCellElement);
466 return CreateElementResult(std::move(firstCellElement),
467 EditorDOMPoint(lastCellElement, 0u));
470 NS_IMETHODIMP HTMLEditor::GetFirstRow(Element* aTableOrElementInTable,
471 Element** aFirstRowElement) {
472 if (NS_WARN_IF(!aTableOrElementInTable) || NS_WARN_IF(!aFirstRowElement)) {
473 return NS_ERROR_INVALID_ARG;
476 AutoEditActionDataSetter editActionData(*this, EditAction::eGetFirstRow);
477 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
478 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
479 NS_WARNING("HTMLEditor::GetFirstRow() couldn't handle the job");
480 return EditorBase::ToGenericNSResult(rv);
483 Result<RefPtr<Element>, nsresult> firstRowElementOrError =
484 GetFirstTableRowElement(*aTableOrElementInTable);
485 NS_WARNING_ASSERTION(!firstRowElementOrError.isErr(),
486 "HTMLEditor::GetFirstTableRowElement() failed");
487 if (firstRowElementOrError.isErr()) {
488 NS_WARNING("HTMLEditor::GetFirstTableRowElement() failed");
489 return EditorBase::ToGenericNSResult(firstRowElementOrError.unwrapErr());
491 firstRowElementOrError.unwrap().forget(aFirstRowElement);
492 return NS_OK;
495 Result<RefPtr<Element>, nsresult> HTMLEditor::GetFirstTableRowElement(
496 const Element& aTableOrElementInTable) const {
497 MOZ_ASSERT(IsEditActionDataAvailable());
499 Element* tableElement = GetInclusiveAncestorByTagNameInternal(
500 *nsGkAtoms::table, aTableOrElementInTable);
501 // If the element is not in <table>, return error.
502 if (!tableElement) {
503 NS_WARNING(
504 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
505 "failed");
506 return Err(NS_ERROR_FAILURE);
509 for (nsIContent* tableChild = tableElement->GetFirstChild(); tableChild;
510 tableChild = tableChild->GetNextSibling()) {
511 if (tableChild->IsHTMLElement(nsGkAtoms::tr)) {
512 // Found a row directly under <table>
513 return RefPtr<Element>(tableChild->AsElement());
515 // <table> can have table section elements like <tbody>. <tr> elements
516 // may be children of them.
517 if (tableChild->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::thead,
518 nsGkAtoms::tfoot)) {
519 for (nsIContent* tableSectionChild = tableChild->GetFirstChild();
520 tableSectionChild;
521 tableSectionChild = tableSectionChild->GetNextSibling()) {
522 if (tableSectionChild->IsHTMLElement(nsGkAtoms::tr)) {
523 return RefPtr<Element>(tableSectionChild->AsElement());
528 // Don't return error when there is no <tr> element in the <table>.
529 return RefPtr<Element>();
532 Result<RefPtr<Element>, nsresult> HTMLEditor::GetNextTableRowElement(
533 const Element& aTableRowElement) const {
534 if (NS_WARN_IF(!aTableRowElement.IsHTMLElement(nsGkAtoms::tr))) {
535 return Err(NS_ERROR_INVALID_ARG);
538 for (nsIContent* maybeNextRow = aTableRowElement.GetNextSibling();
539 maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
540 if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
541 return RefPtr<Element>(maybeNextRow->AsElement());
545 // In current table section (e.g., <tbody>), there is no <tr> element.
546 // Then, check the following table sections.
547 Element* parentElementOfRow = aTableRowElement.GetParentElement();
548 if (!parentElementOfRow) {
549 NS_WARNING("aTableRowElement was an orphan node");
550 return Err(NS_ERROR_FAILURE);
553 // Basically, <tr> elements should be in table section elements even if
554 // they are not written in the source explicitly. However, for preventing
555 // cross table boundary, check it now.
556 if (parentElementOfRow->IsHTMLElement(nsGkAtoms::table)) {
557 // Don't return error since this means just not found.
558 return RefPtr<Element>();
561 for (nsIContent* maybeNextTableSection = parentElementOfRow->GetNextSibling();
562 maybeNextTableSection;
563 maybeNextTableSection = maybeNextTableSection->GetNextSibling()) {
564 // If the sibling of parent of given <tr> is a table section element,
565 // check its children.
566 if (maybeNextTableSection->IsAnyOfHTMLElements(
567 nsGkAtoms::tbody, nsGkAtoms::thead, nsGkAtoms::tfoot)) {
568 for (nsIContent* maybeNextRow = maybeNextTableSection->GetFirstChild();
569 maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
570 if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
571 return RefPtr<Element>(maybeNextRow->AsElement());
575 // I'm not sure whether this is a possible case since table section
576 // elements are created automatically. However, DOM API may create
577 // <tr> elements without table section elements. So, let's check it.
578 else if (maybeNextTableSection->IsHTMLElement(nsGkAtoms::tr)) {
579 return RefPtr<Element>(maybeNextTableSection->AsElement());
582 // Don't return error when the given <tr> element is the last <tr> element in
583 // the <table>.
584 return RefPtr<Element>();
587 NS_IMETHODIMP HTMLEditor::InsertTableColumn(int32_t aNumberOfColumnsToInsert,
588 bool aInsertAfterSelectedCell) {
589 if (aNumberOfColumnsToInsert <= 0) {
590 return NS_OK; // XXX Traditional behavior
593 AutoEditActionDataSetter editActionData(*this,
594 EditAction::eInsertTableColumn);
595 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
596 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
597 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
598 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
599 return EditorBase::ToGenericNSResult(rv);
602 Result<RefPtr<Element>, nsresult> cellElementOrError =
603 GetFirstSelectedCellElementInTable();
604 if (cellElementOrError.isErr()) {
605 NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
606 return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
609 if (!cellElementOrError.inspect()) {
610 return NS_OK;
613 EditorDOMPoint pointToInsert(cellElementOrError.inspect());
614 if (!pointToInsert.IsSet()) {
615 NS_WARNING("Found an orphan cell element");
616 return NS_ERROR_FAILURE;
618 if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
619 DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
620 NS_WARNING_ASSERTION(
621 advanced,
622 "Failed to set insertion point after current cell, but ignored");
624 rv = InsertTableColumnsWithTransaction(pointToInsert,
625 aNumberOfColumnsToInsert);
626 NS_WARNING_ASSERTION(
627 NS_SUCCEEDED(rv),
628 "HTMLEditor::InsertTableColumnsWithTransaction() failed");
629 return EditorBase::ToGenericNSResult(rv);
632 nsresult HTMLEditor::InsertTableColumnsWithTransaction(
633 const EditorDOMPoint& aPointToInsert, int32_t aNumberOfColumnsToInsert) {
634 MOZ_ASSERT(IsEditActionDataAvailable());
635 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
636 MOZ_ASSERT(aNumberOfColumnsToInsert > 0);
638 const RefPtr<PresShell> presShell = GetPresShell();
639 if (NS_WARN_IF(!presShell)) {
640 return NS_ERROR_FAILURE;
643 if (!HTMLEditUtils::IsTableRow(aPointToInsert.GetContainer())) {
644 NS_WARNING("Tried to insert columns to non-<tr> element");
645 return NS_ERROR_FAILURE;
648 const RefPtr<Element> tableElement =
649 HTMLEditUtils::GetClosestAncestorTableElement(
650 *aPointToInsert.ContainerAs<Element>());
651 if (!tableElement) {
652 NS_WARNING("There was no ancestor <table> element");
653 return NS_ERROR_FAILURE;
656 const Result<TableSize, nsresult> tableSizeOrError =
657 TableSize::Create(*this, *tableElement);
658 if (NS_WARN_IF(tableSizeOrError.isErr())) {
659 return tableSizeOrError.inspectErr();
661 const TableSize& tableSize = tableSizeOrError.inspect();
662 if (NS_WARN_IF(tableSize.IsEmpty())) {
663 return NS_ERROR_FAILURE; // We cannot handle it in an empty table
666 // If aPointToInsert points non-cell element or end of the row, it means that
667 // the caller wants to insert column immediately after the last cell of
668 // the pointing cell element or in the raw.
669 const bool insertAfterPreviousCell = [&]() {
670 if (!aPointToInsert.IsEndOfContainer() &&
671 HTMLEditUtils::IsTableCell(aPointToInsert.GetChild())) {
672 return false; // Insert before the cell element.
674 // There is a previous cell element, we should add a column after it.
675 Element* previousCellElement =
676 aPointToInsert.IsEndOfContainer()
677 ? HTMLEditUtils::GetLastTableCellElementChild(
678 *aPointToInsert.ContainerAs<Element>())
679 : HTMLEditUtils::GetPreviousTableCellElementSibling(
680 *aPointToInsert.GetChild());
681 return previousCellElement != nullptr;
682 }();
684 // Consider the column index in the table from given point and direction.
685 auto referenceColumnIndexOrError =
686 [&]() MOZ_CAN_RUN_SCRIPT -> Result<int32_t, nsresult> {
687 if (!insertAfterPreviousCell) {
688 if (aPointToInsert.IsEndOfContainer()) {
689 return tableSize.mColumnCount; // Empty row, append columns to the end
691 // Insert columns immediately before current column.
692 const OwningNonNull<Element> tableCellElement =
693 *aPointToInsert.GetChild()->AsElement();
694 MOZ_ASSERT(HTMLEditUtils::IsTableCell(tableCellElement));
695 CellIndexes cellIndexes(*tableCellElement, presShell);
696 if (NS_WARN_IF(cellIndexes.isErr())) {
697 return Err(NS_ERROR_FAILURE);
699 return cellIndexes.mColumn;
702 // Otherwise, insert columns immediately after the previous column.
703 Element* previousCellElement =
704 aPointToInsert.IsEndOfContainer()
705 ? HTMLEditUtils::GetLastTableCellElementChild(
706 *aPointToInsert.ContainerAs<Element>())
707 : HTMLEditUtils::GetPreviousTableCellElementSibling(
708 *aPointToInsert.GetChild());
709 MOZ_ASSERT(previousCellElement);
710 CellIndexes cellIndexes(*previousCellElement, presShell);
711 if (NS_WARN_IF(cellIndexes.isErr())) {
712 return Err(NS_ERROR_FAILURE);
714 return cellIndexes.mColumn;
715 }();
716 if (MOZ_UNLIKELY(referenceColumnIndexOrError.isErr())) {
717 return referenceColumnIndexOrError.unwrapErr();
720 AutoPlaceholderBatch treateAsOneTransaction(
721 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
722 // Prevent auto insertion of <br> element in new cell until we're done.
723 // XXX Why? We should put <br> element to every cell element before inserting
724 // the cells into the tree.
725 IgnoredErrorResult error;
726 AutoEditSubActionNotifier startToHandleEditSubAction(
727 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
728 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
729 return error.StealNSResult();
731 NS_WARNING_ASSERTION(
732 !error.Failed(),
733 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
734 error.SuppressException();
736 // Suppress Rules System selection munging.
737 AutoTransactionsConserveSelection dontChangeSelection(*this);
739 // If we are inserting after all existing columns, make sure table is
740 // "well formed" before appending new column.
741 // XXX As far as I've tested, NormalizeTableInternal() always fails to
742 // normalize non-rectangular table. So, the following CellData will
743 // fail if the table is not rectangle.
744 if (referenceColumnIndexOrError.inspect() >= tableSize.mColumnCount) {
745 DebugOnly<nsresult> rv = NormalizeTableInternal(*tableElement);
746 if (MOZ_UNLIKELY(Destroyed())) {
747 NS_WARNING(
748 "HTMLEditor::NormalizeTableInternal() caused destroying the editor");
749 return NS_ERROR_EDITOR_DESTROYED;
751 NS_WARNING_ASSERTION(
752 NS_SUCCEEDED(rv),
753 "HTMLEditor::NormalizeTableInternal() failed, but ignored");
756 // First, we should collect all reference nodes to insert new table cells.
757 AutoTArray<CellData, 32> arrayOfCellData;
759 arrayOfCellData.SetCapacity(tableSize.mRowCount);
760 for (const int32_t rowIndex : IntegerRange(tableSize.mRowCount)) {
761 const auto cellData = CellData::AtIndexInTableElement(
762 *this, *tableElement, rowIndex,
763 referenceColumnIndexOrError.inspect());
764 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
765 return NS_ERROR_FAILURE;
767 arrayOfCellData.AppendElement(cellData);
771 // Note that checking whether the editor destroyed or not should be done
772 // after inserting all cell elements. Otherwise, the table is left as
773 // not a rectangle.
774 auto cellElementToPutCaretOrError =
775 [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
776 // Block legacy mutation events for making this job simpler.
777 nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
778 RefPtr<Element> cellElementToPutCaret;
779 for (const CellData& cellData : arrayOfCellData) {
780 // Don't fail entire process if we fail to find a cell (may fail just in
781 // particular rows with < adequate cells per row).
782 // XXX So, here wants to know whether the CellData actually failed
783 // above. Fix this later.
784 if (!cellData.mElement) {
785 continue;
788 if ((!insertAfterPreviousCell && cellData.IsSpannedFromOtherColumn()) ||
789 (insertAfterPreviousCell &&
790 cellData.IsNextColumnSpannedFromOtherColumn())) {
791 // If we have a cell spanning this location, simply increase its
792 // colspan to keep table rectangular.
793 if (cellData.mColSpan > 0) {
794 DebugOnly<nsresult> rvIgnored = SetColSpan(
795 cellData.mElement, cellData.mColSpan + aNumberOfColumnsToInsert);
796 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
797 "HTMLEditor::SetColSpan() failed, but ignored");
799 continue;
802 EditorDOMPoint pointToInsert = [&]() {
803 if (!insertAfterPreviousCell) {
804 // Insert before the reference cell.
805 return EditorDOMPoint(cellData.mElement);
807 if (!cellData.mElement->GetNextSibling()) {
808 // Insert after the reference cell, but nothing follows it, append
809 // to the end of the row.
810 return EditorDOMPoint::AtEndOf(*cellData.mElement->GetParentNode());
812 // Otherwise, returns immediately before the next sibling. Note that
813 // the next sibling may not be a table cell element. E.g., it may be
814 // a text node containing only white-spaces in most cases.
815 return EditorDOMPoint(cellData.mElement->GetNextSibling());
816 }();
817 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
818 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
820 Result<CreateElementResult, nsresult> insertCellElementsResult =
821 InsertTableCellsWithTransaction(pointToInsert,
822 aNumberOfColumnsToInsert);
823 if (MOZ_UNLIKELY(insertCellElementsResult.isErr())) {
824 NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
825 return insertCellElementsResult.propagateErr();
827 CreateElementResult unwrappedInsertCellElementsResult =
828 insertCellElementsResult.unwrap();
829 // We'll update selection later into the first inserted cell element in
830 // the current row.
831 unwrappedInsertCellElementsResult.IgnoreCaretPointSuggestion();
832 if (pointToInsert.ContainerAs<Element>() ==
833 aPointToInsert.ContainerAs<Element>()) {
834 cellElementToPutCaret =
835 unwrappedInsertCellElementsResult.UnwrapNewNode();
836 MOZ_ASSERT(cellElementToPutCaret);
837 MOZ_ASSERT(HTMLEditUtils::IsTableCell(cellElementToPutCaret));
840 return cellElementToPutCaret;
841 }();
842 if (MOZ_UNLIKELY(cellElementToPutCaretOrError.isErr())) {
843 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
844 : cellElementToPutCaretOrError.unwrapErr();
846 const RefPtr<Element> cellElementToPutCaret =
847 cellElementToPutCaretOrError.unwrap();
848 NS_WARNING_ASSERTION(
849 cellElementToPutCaret,
850 "Didn't find the first inserted cell element in the specified row");
851 if (MOZ_LIKELY(cellElementToPutCaret)) {
852 CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
854 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
857 NS_IMETHODIMP HTMLEditor::InsertTableRow(int32_t aNumberOfRowsToInsert,
858 bool aInsertAfterSelectedCell) {
859 if (aNumberOfRowsToInsert <= 0) {
860 return NS_OK;
863 AutoEditActionDataSetter editActionData(*this,
864 EditAction::eInsertTableRowElement);
865 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
866 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
867 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
868 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
869 return EditorBase::ToGenericNSResult(rv);
872 Result<RefPtr<Element>, nsresult> cellElementOrError =
873 GetFirstSelectedCellElementInTable();
874 if (cellElementOrError.isErr()) {
875 NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
876 return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
879 if (!cellElementOrError.inspect()) {
880 return NS_OK;
883 rv = InsertTableRowsWithTransaction(
884 MOZ_KnownLive(*cellElementOrError.inspect()), aNumberOfRowsToInsert,
885 aInsertAfterSelectedCell ? InsertPosition::eAfterSelectedCell
886 : InsertPosition::eBeforeSelectedCell);
887 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
888 "HTMLEditor::InsertTableRowsWithTransaction() failed");
889 return EditorBase::ToGenericNSResult(rv);
892 nsresult HTMLEditor::InsertTableRowsWithTransaction(
893 Element& aCellElement, int32_t aNumberOfRowsToInsert,
894 InsertPosition aInsertPosition) {
895 MOZ_ASSERT(IsEditActionDataAvailable());
896 MOZ_ASSERT(HTMLEditUtils::IsTableCell(&aCellElement));
898 const RefPtr<PresShell> presShell = GetPresShell();
899 if (MOZ_UNLIKELY(NS_WARN_IF(!presShell))) {
900 return NS_ERROR_FAILURE;
903 if (MOZ_UNLIKELY(
904 !HTMLEditUtils::IsTableRow(aCellElement.GetParentElement()))) {
905 NS_WARNING("Tried to insert columns to non-<tr> element");
906 return NS_ERROR_FAILURE;
909 const RefPtr<Element> tableElement =
910 HTMLEditUtils::GetClosestAncestorTableElement(aCellElement);
911 if (MOZ_UNLIKELY(!tableElement)) {
912 return NS_OK;
915 const Result<TableSize, nsresult> tableSizeOrError =
916 TableSize::Create(*this, *tableElement);
917 if (NS_WARN_IF(tableSizeOrError.isErr())) {
918 return tableSizeOrError.inspectErr();
920 const TableSize& tableSize = tableSizeOrError.inspect();
921 // Should not be empty since we've already found a cell.
922 MOZ_ASSERT(!tableSize.IsEmpty());
924 const CellIndexes cellIndexes(aCellElement, presShell);
925 if (NS_WARN_IF(cellIndexes.isErr())) {
926 return NS_ERROR_FAILURE;
929 // Get more data for current cell in row we are inserting at because we need
930 // rowspan.
931 const auto cellData =
932 CellData::AtIndexInTableElement(*this, *tableElement, cellIndexes);
933 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
934 return NS_ERROR_FAILURE;
936 MOZ_ASSERT(&aCellElement == cellData.mElement);
938 AutoPlaceholderBatch treateAsOneTransaction(
939 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
940 // Prevent auto insertion of BR in new cell until we're done
941 IgnoredErrorResult error;
942 AutoEditSubActionNotifier startToHandleEditSubAction(
943 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
944 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
945 return error.StealNSResult();
947 NS_WARNING_ASSERTION(
948 !error.Failed(),
949 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
951 struct ElementWithNewRowSpan final {
952 const OwningNonNull<Element> mCellElement;
953 const int32_t mNewRowSpan;
955 ElementWithNewRowSpan(Element& aCellElement, int32_t aNewRowSpan)
956 : mCellElement(aCellElement), mNewRowSpan(aNewRowSpan) {}
958 AutoTArray<ElementWithNewRowSpan, 16> cellElementsToModifyRowSpan;
959 if (aInsertPosition == InsertPosition::eAfterSelectedCell &&
960 !cellData.mRowSpan) {
961 // Detect when user is adding after a rowspan=0 case.
962 // Assume they want to stop the "0" behavior and really add a new row.
963 // Thus we set the rowspan to its true value.
964 cellElementsToModifyRowSpan.AppendElement(
965 ElementWithNewRowSpan(aCellElement, cellData.mEffectiveRowSpan));
968 struct MOZ_STACK_CLASS TableRowData {
969 RefPtr<Element> mElement;
970 int32_t mNumberOfCellsInStartRow;
971 int32_t mOffsetInTRElementToPutCaret;
973 const auto referenceRowDataOrError = [&]() -> Result<TableRowData, nsresult> {
974 const int32_t startRowIndex =
975 aInsertPosition == InsertPosition::eBeforeSelectedCell
976 ? cellData.mCurrent.mRow
977 : cellData.mCurrent.mRow + cellData.mEffectiveRowSpan;
978 if (startRowIndex < tableSize.mRowCount) {
979 // We are inserting above an existing row. Get each cell in the insert
980 // row to adjust for rowspan effects while we count how many cells are
981 // needed.
982 RefPtr<Element> referenceRowElement;
983 int32_t numberOfCellsInStartRow = 0;
984 int32_t offsetInTRElementToPutCaret = 0;
985 for (int32_t colIndex = 0;;) {
986 const auto cellDataInStartRow = CellData::AtIndexInTableElement(
987 *this, *tableElement, startRowIndex, colIndex);
988 if (cellDataInStartRow.FailedOrNotFound()) {
989 break; // Perhaps, we reach end of the row.
992 // XXX So, this is impossible case. Will be removed.
993 if (!cellDataInStartRow.mElement) {
994 NS_WARNING("CellData::Update() succeeded, but didn't set mElement");
995 break;
998 if (cellDataInStartRow.IsSpannedFromOtherRow()) {
999 // We have a cell spanning this location. Increase its rowspan.
1000 // Note that if rowspan is 0, we do nothing since that cell should
1001 // automatically extend into the new row.
1002 if (cellDataInStartRow.mRowSpan > 0) {
1003 cellElementsToModifyRowSpan.AppendElement(ElementWithNewRowSpan(
1004 *cellDataInStartRow.mElement,
1005 cellDataInStartRow.mRowSpan + aNumberOfRowsToInsert));
1007 colIndex = cellDataInStartRow.NextColumnIndex();
1008 continue;
1011 if (colIndex < cellDataInStartRow.mCurrent.mColumn) {
1012 offsetInTRElementToPutCaret++;
1015 numberOfCellsInStartRow += cellDataInStartRow.mEffectiveColSpan;
1016 if (!referenceRowElement) {
1017 if (Element* maybeTableRowElement =
1018 cellDataInStartRow.mElement->GetParentElement()) {
1019 if (HTMLEditUtils::IsTableRow(maybeTableRowElement)) {
1020 referenceRowElement = maybeTableRowElement;
1024 MOZ_ASSERT(colIndex < cellDataInStartRow.NextColumnIndex());
1025 colIndex = cellDataInStartRow.NextColumnIndex();
1027 if (MOZ_UNLIKELY(!referenceRowElement)) {
1028 NS_WARNING(
1029 "Reference row element to insert new row elements was not found");
1030 return Err(NS_ERROR_FAILURE);
1032 return TableRowData{std::move(referenceRowElement),
1033 numberOfCellsInStartRow, offsetInTRElementToPutCaret};
1036 // We are adding a new row after all others. If it weren't for colspan=0
1037 // effect, we could simply use tableSize.mColumnCount for number of new
1038 // cells...
1039 // XXX colspan=0 support has now been removed in table layout so maybe this
1040 // can be cleaned up now? (bug 1243183)
1041 int32_t numberOfCellsInStartRow = tableSize.mColumnCount;
1042 int32_t offsetInTRElementToPutCaret = 0;
1044 // but we must compensate for all cells with rowspan = 0 in the last row.
1045 const int32_t lastRowIndex = tableSize.mRowCount - 1;
1046 for (int32_t colIndex = 0;;) {
1047 const auto cellDataInLastRow = CellData::AtIndexInTableElement(
1048 *this, *tableElement, lastRowIndex, colIndex);
1049 if (cellDataInLastRow.FailedOrNotFound()) {
1050 break; // Perhaps, we reach end of the row.
1053 if (!cellDataInLastRow.mRowSpan) {
1054 MOZ_ASSERT(numberOfCellsInStartRow >=
1055 cellDataInLastRow.mEffectiveColSpan);
1056 numberOfCellsInStartRow -= cellDataInLastRow.mEffectiveColSpan;
1057 } else if (colIndex < cellDataInLastRow.mCurrent.mColumn) {
1058 offsetInTRElementToPutCaret++;
1060 MOZ_ASSERT(colIndex < cellDataInLastRow.NextColumnIndex());
1061 colIndex = cellDataInLastRow.NextColumnIndex();
1063 return TableRowData{nullptr, numberOfCellsInStartRow,
1064 offsetInTRElementToPutCaret};
1065 }();
1066 if (MOZ_UNLIKELY(referenceRowDataOrError.isErr())) {
1067 return referenceRowDataOrError.inspectErr();
1070 const TableRowData& referenceRowData = referenceRowDataOrError.inspect();
1071 if (MOZ_UNLIKELY(!referenceRowData.mNumberOfCellsInStartRow)) {
1072 NS_WARNING("There was no cell element in the row");
1073 return NS_OK;
1076 MOZ_ASSERT_IF(referenceRowData.mElement,
1077 HTMLEditUtils::IsTableRow(referenceRowData.mElement));
1078 if (NS_WARN_IF(!HTMLEditUtils::IsTableRow(aCellElement.GetParentElement()))) {
1079 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
1082 // The row parent and offset where we will insert new row.
1083 EditorDOMPoint pointToInsert = [&]() {
1084 if (aInsertPosition == InsertPosition::eBeforeSelectedCell) {
1085 MOZ_ASSERT(referenceRowData.mElement);
1086 return EditorDOMPoint(referenceRowData.mElement);
1088 // Look for the last row element in the same table section or immediately
1089 // before the reference row element. Then, we can insert new rows
1090 // immediately after the given row element.
1091 Element* lastRowElement = nullptr;
1092 for (Element* rowElement = aCellElement.GetParentElement();
1093 rowElement && rowElement != referenceRowData.mElement;) {
1094 lastRowElement = rowElement;
1095 const Result<RefPtr<Element>, nsresult> nextRowElementOrError =
1096 GetNextTableRowElement(*rowElement);
1097 if (MOZ_UNLIKELY(nextRowElementOrError.isErr())) {
1098 NS_WARNING("HTMLEditor::GetNextTableRowElement() failed");
1099 return EditorDOMPoint();
1101 rowElement = nextRowElementOrError.inspect();
1103 MOZ_ASSERT(lastRowElement);
1104 return EditorDOMPoint::After(*lastRowElement);
1105 }();
1106 if (NS_WARN_IF(!pointToInsert.IsSet())) {
1107 return NS_ERROR_FAILURE;
1109 // Note that checking whether the editor destroyed or not should be done
1110 // after inserting all cell elements. Otherwise, the table is left as
1111 // not a rectangle.
1112 auto firstInsertedTRElementOrError =
1113 [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
1114 // Block legacy mutation events for making this job simpler.
1115 nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
1117 // Suppress Rules System selection munging.
1118 AutoTransactionsConserveSelection dontChangeSelection(*this);
1120 for (const ElementWithNewRowSpan& cellElementAndNewRowSpan :
1121 cellElementsToModifyRowSpan) {
1122 DebugOnly<nsresult> rvIgnored =
1123 SetRowSpan(MOZ_KnownLive(cellElementAndNewRowSpan.mCellElement),
1124 cellElementAndNewRowSpan.mNewRowSpan);
1125 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1126 "HTMLEditor::SetRowSpan() failed, but ignored");
1129 RefPtr<Element> firstInsertedTRElement;
1130 IgnoredErrorResult error;
1131 for ([[maybe_unused]] const int32_t rowIndex :
1132 Reversed(IntegerRange(aNumberOfRowsToInsert))) {
1133 // Create a new row
1134 RefPtr<Element> newRowElement = CreateElementWithDefaults(*nsGkAtoms::tr);
1135 if (!newRowElement) {
1136 NS_WARNING(
1137 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::tr) failed");
1138 return Err(NS_ERROR_FAILURE);
1141 for ([[maybe_unused]] const int32_t i :
1142 IntegerRange(referenceRowData.mNumberOfCellsInStartRow)) {
1143 const RefPtr<Element> newCellElement =
1144 CreateElementWithDefaults(*nsGkAtoms::td);
1145 if (!newCellElement) {
1146 NS_WARNING(
1147 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
1148 return Err(NS_ERROR_FAILURE);
1150 newRowElement->AppendChild(*newCellElement, error);
1151 if (error.Failed()) {
1152 NS_WARNING("nsINode::AppendChild() failed");
1153 return Err(error.StealNSResult());
1157 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
1158 Result<CreateElementResult, nsresult> insertNewRowResult =
1159 InsertNodeWithTransaction<Element>(*newRowElement, pointToInsert);
1160 if (MOZ_UNLIKELY(insertNewRowResult.isErr())) {
1161 if (insertNewRowResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
1162 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
1163 return insertNewRowResult.propagateErr();
1165 NS_WARNING(
1166 "EditorBase::InsertNodeWithTransaction() failed, but ignored");
1168 firstInsertedTRElement = std::move(newRowElement);
1169 // We'll update selection later.
1170 insertNewRowResult.inspect().IgnoreCaretPointSuggestion();
1172 return firstInsertedTRElement;
1173 }();
1174 if (NS_WARN_IF(Destroyed())) {
1175 return NS_ERROR_EDITOR_DESTROYED;
1177 if (MOZ_UNLIKELY(firstInsertedTRElementOrError.isErr())) {
1178 return firstInsertedTRElementOrError.unwrapErr();
1181 const OwningNonNull<Element> cellElementToPutCaret = [&]() {
1182 if (MOZ_LIKELY(firstInsertedTRElementOrError.inspect())) {
1183 EditorRawDOMPoint point(firstInsertedTRElementOrError.inspect(),
1184 referenceRowData.mOffsetInTRElementToPutCaret);
1185 if (MOZ_LIKELY(point.IsSetAndValid()) &&
1186 MOZ_LIKELY(!point.IsEndOfContainer()) &&
1187 MOZ_LIKELY(HTMLEditUtils::IsTableCell(point.GetChild()))) {
1188 return OwningNonNull<Element>(*point.GetChild()->AsElement());
1191 return OwningNonNull<Element>(aCellElement);
1192 }();
1193 CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
1194 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
1197 nsresult HTMLEditor::DeleteTableElementAndChildrenWithTransaction(
1198 Element& aTableElement) {
1199 MOZ_ASSERT(IsEditActionDataAvailable());
1201 // Block selectionchange event. It's enough to dispatch selectionchange
1202 // event immediately after removing the table element.
1204 AutoHideSelectionChanges hideSelection(SelectionRef());
1206 // Select the <table> element after clear current selection.
1207 if (SelectionRef().RangeCount()) {
1208 ErrorResult error;
1209 SelectionRef().RemoveAllRanges(error);
1210 if (error.Failed()) {
1211 NS_WARNING("Selection::RemoveAllRanges() failed");
1212 return error.StealNSResult();
1216 RefPtr<nsRange> range = nsRange::Create(&aTableElement);
1217 ErrorResult error;
1218 range->SelectNode(aTableElement, error);
1219 if (error.Failed()) {
1220 NS_WARNING("nsRange::SelectNode() failed");
1221 return error.StealNSResult();
1223 SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*range, error);
1224 if (error.Failed()) {
1225 NS_WARNING(
1226 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
1227 return error.StealNSResult();
1230 #ifdef DEBUG
1231 range = SelectionRef().GetRangeAt(0);
1232 MOZ_ASSERT(range);
1233 MOZ_ASSERT(range->GetStartContainer() == aTableElement.GetParent());
1234 MOZ_ASSERT(range->GetEndContainer() == aTableElement.GetParent());
1235 MOZ_ASSERT(range->GetChildAtStartOffset() == &aTableElement);
1236 MOZ_ASSERT(range->GetChildAtEndOffset() == aTableElement.GetNextSibling());
1237 #endif // #ifdef DEBUG
1240 nsresult rv = DeleteSelectionAsSubAction(eNext, eStrip);
1241 NS_WARNING_ASSERTION(
1242 NS_SUCCEEDED(rv),
1243 "EditorBase::DeleteSelectionAsSubAction(eNext, eStrip) failed");
1244 return rv;
1247 NS_IMETHODIMP HTMLEditor::DeleteTable() {
1248 AutoEditActionDataSetter editActionData(*this,
1249 EditAction::eRemoveTableElement);
1250 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1251 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
1252 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1253 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1254 return EditorBase::ToGenericNSResult(rv);
1257 RefPtr<Element> table;
1258 rv = GetCellContext(getter_AddRefs(table), nullptr, nullptr, nullptr, nullptr,
1259 nullptr);
1260 if (NS_FAILED(rv)) {
1261 NS_WARNING("HTMLEditor::GetCellContext() failed");
1262 return EditorBase::ToGenericNSResult(rv);
1264 if (!table) {
1265 NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
1266 return NS_ERROR_FAILURE;
1269 AutoPlaceholderBatch treateAsOneTransaction(
1270 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1271 rv = DeleteTableElementAndChildrenWithTransaction(*table);
1272 NS_WARNING_ASSERTION(
1273 NS_SUCCEEDED(rv),
1274 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
1275 return EditorBase::ToGenericNSResult(rv);
1278 NS_IMETHODIMP HTMLEditor::DeleteTableCell(int32_t aNumberOfCellsToDelete) {
1279 AutoEditActionDataSetter editActionData(*this,
1280 EditAction::eRemoveTableCellElement);
1281 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1282 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
1283 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1284 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1285 return EditorBase::ToGenericNSResult(rv);
1288 rv = DeleteTableCellWithTransaction(aNumberOfCellsToDelete);
1289 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1290 "HTMLEditor::DeleteTableCellWithTransaction() failed");
1291 return EditorBase::ToGenericNSResult(rv);
1294 nsresult HTMLEditor::DeleteTableCellWithTransaction(
1295 int32_t aNumberOfCellsToDelete) {
1296 MOZ_ASSERT(IsEditActionDataAvailable());
1298 RefPtr<Element> table;
1299 RefPtr<Element> cell;
1300 int32_t startRowIndex, startColIndex;
1302 nsresult rv =
1303 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1304 nullptr, &startRowIndex, &startColIndex);
1305 if (NS_FAILED(rv)) {
1306 NS_WARNING("HTMLEditor::GetCellContext() failed");
1307 return rv;
1309 if (!table || !cell) {
1310 NS_WARNING(
1311 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
1312 // Don't fail if we didn't find a table or cell.
1313 return NS_OK;
1316 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
1317 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
1320 AutoPlaceholderBatch treateAsOneTransaction(
1321 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1322 // Prevent rules testing until we're done
1323 IgnoredErrorResult ignoredError;
1324 AutoEditSubActionNotifier startToHandleEditSubAction(
1325 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
1326 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1327 return ignoredError.StealNSResult();
1329 NS_WARNING_ASSERTION(
1330 !ignoredError.Failed(),
1331 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1333 MOZ_ASSERT(SelectionRef().RangeCount());
1335 SelectedTableCellScanner scanner(SelectionRef());
1337 Result<TableSize, nsresult> tableSizeOrError =
1338 TableSize::Create(*this, *table);
1339 if (NS_WARN_IF(tableSizeOrError.isErr())) {
1340 return tableSizeOrError.unwrapErr();
1342 // FYI: Cannot be a const reference because the row count will be updated
1343 TableSize tableSize = tableSizeOrError.unwrap();
1344 MOZ_ASSERT(!tableSize.IsEmpty());
1346 // If only one cell is selected or no cell is selected, remove cells
1347 // starting from the first selected cell or a cell containing first
1348 // selection range.
1349 if (!scanner.IsInTableCellSelectionMode() ||
1350 SelectionRef().RangeCount() == 1) {
1351 for (int32_t i = 0; i < aNumberOfCellsToDelete; i++) {
1352 nsresult rv =
1353 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1354 nullptr, &startRowIndex, &startColIndex);
1355 if (NS_FAILED(rv)) {
1356 NS_WARNING("HTMLEditor::GetCellContext() failed");
1357 return rv;
1359 if (!table || !cell) {
1360 NS_WARNING(
1361 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
1362 // Don't fail if no cell found
1363 return NS_OK;
1366 int32_t numberOfCellsInRow = GetNumberOfCellsInRow(*table, startRowIndex);
1367 NS_WARNING_ASSERTION(
1368 numberOfCellsInRow >= 0,
1369 "HTMLEditor::GetNumberOfCellsInRow() failed, but ignored");
1371 if (numberOfCellsInRow == 1) {
1372 // Remove <tr> or <table> if we're removing all cells in the row or
1373 // the table.
1374 if (tableSize.mRowCount == 1) {
1375 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
1376 NS_WARNING_ASSERTION(
1377 NS_SUCCEEDED(rv),
1378 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
1379 "failed");
1380 return rv;
1383 // We need to call DeleteSelectedTableRowsWithTransaction() to handle
1384 // cells with rowspan attribute.
1385 rv = DeleteSelectedTableRowsWithTransaction(1);
1386 if (NS_FAILED(rv)) {
1387 NS_WARNING(
1388 "HTMLEditor::DeleteSelectedTableRowsWithTransaction(1) failed");
1389 return rv;
1392 // Adjust table rows simply. In strictly speaking, we should
1393 // recompute table size with the latest layout information since
1394 // mutation event listener may have changed the DOM tree. However,
1395 // this is not in usual path of Firefox. So, we can assume that
1396 // there are no mutation event listeners.
1397 MOZ_ASSERT(tableSize.mRowCount);
1398 tableSize.mRowCount--;
1399 continue;
1402 // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
1403 // destructor
1404 AutoSelectionSetterAfterTableEdit setCaret(
1405 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
1406 AutoTransactionsConserveSelection dontChangeSelection(*this);
1408 // XXX Removing cell element causes not adjusting colspan.
1409 rv = DeleteNodeWithTransaction(*cell);
1410 // If we fail, don't try to delete any more cells???
1411 if (NS_FAILED(rv)) {
1412 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1413 return rv;
1415 // Note that we don't refer column number in this loop. So, it must
1416 // be safe not to recompute table size since number of row is synced
1417 // above.
1419 return NS_OK;
1422 // When 2 or more cells are selected, ignore aNumberOfCellsToRemove and
1423 // remove all selected cells.
1424 const RefPtr<PresShell> presShell{GetPresShell()};
1425 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because scanner grabs
1426 // it until it's destroyed later.
1427 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
1428 presShell);
1429 if (NS_WARN_IF(firstCellIndexes.isErr())) {
1430 return NS_ERROR_FAILURE;
1432 startRowIndex = firstCellIndexes.mRow;
1433 startColIndex = firstCellIndexes.mColumn;
1435 // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
1436 // destructor
1437 AutoSelectionSetterAfterTableEdit setCaret(
1438 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
1439 AutoTransactionsConserveSelection dontChangeSelection(*this);
1441 bool checkToDeleteRow = true;
1442 bool checkToDeleteColumn = true;
1443 for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
1444 selectedCellElement;) {
1445 if (checkToDeleteRow) {
1446 // Optimize to delete an entire row
1447 // Clear so we don't repeat AllCellsInRowSelected within the same row
1448 checkToDeleteRow = false;
1449 if (AllCellsInRowSelected(table, startRowIndex, tableSize.mColumnCount)) {
1450 // First, find the next cell in a different row to continue after we
1451 // delete this row.
1452 int32_t nextRow = startRowIndex;
1453 while (nextRow == startRowIndex) {
1454 selectedCellElement = scanner.GetNextElement();
1455 if (!selectedCellElement) {
1456 break;
1458 const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
1459 presShell);
1460 if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
1461 return NS_ERROR_FAILURE;
1463 nextRow = nextSelectedCellIndexes.mRow;
1464 startColIndex = nextSelectedCellIndexes.mColumn;
1466 if (tableSize.mRowCount == 1) {
1467 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
1468 NS_WARNING_ASSERTION(
1469 NS_SUCCEEDED(rv),
1470 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
1471 "failed");
1472 return rv;
1474 nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
1475 if (NS_FAILED(rv)) {
1476 NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
1477 return rv;
1479 // Adjust table rows simply. In strictly speaking, we should
1480 // recompute table size with the latest layout information since
1481 // mutation event listener may have changed the DOM tree. However,
1482 // this is not in usual path of Firefox. So, we can assume that
1483 // there are no mutation event listeners.
1484 MOZ_ASSERT(tableSize.mRowCount);
1485 tableSize.mRowCount--;
1486 if (!selectedCellElement) {
1487 break; // XXX Seems like a dead path
1489 // For the next cell: Subtract 1 for row we deleted
1490 startRowIndex = nextRow - 1;
1491 // Set true since we know we will look at a new row next
1492 checkToDeleteRow = true;
1493 continue;
1497 if (checkToDeleteColumn) {
1498 // Optimize to delete an entire column
1499 // Clear this so we don't repeat AllCellsInColSelected within the same Col
1500 checkToDeleteColumn = false;
1501 if (AllCellsInColumnSelected(table, startColIndex,
1502 tableSize.mColumnCount)) {
1503 // First, find the next cell in a different column to continue after
1504 // we delete this column.
1505 int32_t nextCol = startColIndex;
1506 while (nextCol == startColIndex) {
1507 selectedCellElement = scanner.GetNextElement();
1508 if (!selectedCellElement) {
1509 break;
1511 const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
1512 presShell);
1513 if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
1514 return NS_ERROR_FAILURE;
1516 startRowIndex = nextSelectedCellIndexes.mRow;
1517 nextCol = nextSelectedCellIndexes.mColumn;
1519 // Delete all cells which belong to the column.
1520 nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1521 if (NS_FAILED(rv)) {
1522 NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
1523 return rv;
1525 // Adjust table columns simply. In strictly speaking, we should
1526 // recompute table size with the latest layout information since
1527 // mutation event listener may have changed the DOM tree. However,
1528 // this is not in usual path of Firefox. So, we can assume that
1529 // there are no mutation event listeners.
1530 MOZ_ASSERT(tableSize.mColumnCount);
1531 tableSize.mColumnCount--;
1532 if (!selectedCellElement) {
1533 break;
1535 // For the next cell, subtract 1 for col. deleted
1536 startColIndex = nextCol - 1;
1537 // Set true since we know we will look at a new column next
1538 checkToDeleteColumn = true;
1539 continue;
1543 nsresult rv = DeleteNodeWithTransaction(*selectedCellElement);
1544 if (NS_FAILED(rv)) {
1545 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1546 return rv;
1549 selectedCellElement = scanner.GetNextElement();
1550 if (!selectedCellElement) {
1551 return NS_OK;
1554 const CellIndexes nextCellIndexes(*selectedCellElement, presShell);
1555 if (NS_WARN_IF(nextCellIndexes.isErr())) {
1556 return NS_ERROR_FAILURE;
1558 startRowIndex = nextCellIndexes.mRow;
1559 startColIndex = nextCellIndexes.mColumn;
1560 // When table cell is removed, table size of column may be changed.
1561 // For example, if there are 2 rows, one has 2 cells, the other has
1562 // 3 cells, tableSize.mColumnCount is 3. When this removes a cell
1563 // in the latter row, mColumnCount should be come 2. However, we
1564 // don't use mColumnCount in this loop, so, this must be okay for now.
1566 return NS_OK;
1569 NS_IMETHODIMP HTMLEditor::DeleteTableCellContents() {
1570 AutoEditActionDataSetter editActionData(*this,
1571 EditAction::eDeleteTableCellContents);
1572 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1573 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
1574 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1575 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1576 return EditorBase::ToGenericNSResult(rv);
1579 rv = DeleteTableCellContentsWithTransaction();
1580 NS_WARNING_ASSERTION(
1581 NS_SUCCEEDED(rv),
1582 "HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
1583 return EditorBase::ToGenericNSResult(rv);
1586 nsresult HTMLEditor::DeleteTableCellContentsWithTransaction() {
1587 MOZ_ASSERT(IsEditActionDataAvailable());
1589 RefPtr<Element> table;
1590 RefPtr<Element> cell;
1591 int32_t startRowIndex, startColIndex;
1592 nsresult rv =
1593 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1594 nullptr, &startRowIndex, &startColIndex);
1595 if (NS_FAILED(rv)) {
1596 NS_WARNING("HTMLEditor::GetCellContext() failed");
1597 return rv;
1599 if (!cell) {
1600 NS_WARNING("HTMLEditor::GetCellContext() didn't return cell element");
1601 // Don't fail if no cell found.
1602 return NS_OK;
1605 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
1606 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
1609 AutoPlaceholderBatch treateAsOneTransaction(
1610 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1611 // Prevent rules testing until we're done
1612 IgnoredErrorResult ignoredError;
1613 AutoEditSubActionNotifier startToHandleEditSubAction(
1614 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
1615 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1616 return ignoredError.StealNSResult();
1618 NS_WARNING_ASSERTION(
1619 !ignoredError.Failed(),
1620 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1622 // Don't let Rules System change the selection
1623 AutoTransactionsConserveSelection dontChangeSelection(*this);
1625 SelectedTableCellScanner scanner(SelectionRef());
1626 if (scanner.IsInTableCellSelectionMode()) {
1627 const RefPtr<PresShell> presShell{GetPresShell()};
1628 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because scanner
1629 // grabs it until it's destroyed later.
1630 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
1631 presShell);
1632 if (NS_WARN_IF(firstCellIndexes.isErr())) {
1633 return NS_ERROR_FAILURE;
1635 cell = scanner.ElementsRef()[0];
1636 startRowIndex = firstCellIndexes.mRow;
1637 startColIndex = firstCellIndexes.mColumn;
1640 AutoSelectionSetterAfterTableEdit setCaret(
1641 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
1643 for (RefPtr<Element> selectedCellElement = std::move(cell);
1644 selectedCellElement; selectedCellElement = scanner.GetNextElement()) {
1645 DebugOnly<nsresult> rvIgnored =
1646 DeleteAllChildrenWithTransaction(*selectedCellElement);
1647 if (NS_WARN_IF(Destroyed())) {
1648 return NS_ERROR_EDITOR_DESTROYED;
1650 NS_WARNING_ASSERTION(
1651 NS_SUCCEEDED(rv),
1652 "HTMLEditor::DeleteAllChildrenWithTransaction() failed, but ignored");
1653 if (!scanner.IsInTableCellSelectionMode()) {
1654 break;
1657 return NS_OK;
1660 NS_IMETHODIMP HTMLEditor::DeleteTableColumn(int32_t aNumberOfColumnsToDelete) {
1661 AutoEditActionDataSetter editActionData(*this,
1662 EditAction::eRemoveTableColumn);
1663 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1664 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
1665 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1666 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1667 return EditorBase::ToGenericNSResult(rv);
1670 rv = DeleteSelectedTableColumnsWithTransaction(aNumberOfColumnsToDelete);
1671 NS_WARNING_ASSERTION(
1672 NS_SUCCEEDED(rv),
1673 "HTMLEditor::DeleteSelectedTableColumnsWithTransaction() failed");
1674 return EditorBase::ToGenericNSResult(rv);
1677 nsresult HTMLEditor::DeleteSelectedTableColumnsWithTransaction(
1678 int32_t aNumberOfColumnsToDelete) {
1679 MOZ_ASSERT(IsEditActionDataAvailable());
1681 RefPtr<Element> table;
1682 RefPtr<Element> cell;
1683 int32_t startRowIndex, startColIndex;
1684 nsresult rv =
1685 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1686 nullptr, &startRowIndex, &startColIndex);
1687 if (NS_FAILED(rv)) {
1688 NS_WARNING("HTMLEditor::GetCellContext() failed");
1689 return rv;
1691 if (!table || !cell) {
1692 NS_WARNING(
1693 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
1694 // Don't fail if no cell found.
1695 return NS_OK;
1698 const Result<TableSize, nsresult> tableSizeOrError =
1699 TableSize::Create(*this, *table);
1700 if (NS_WARN_IF(tableSizeOrError.isErr())) {
1701 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
1703 const TableSize& tableSize = tableSizeOrError.inspect();
1705 AutoPlaceholderBatch treateAsOneTransaction(
1706 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1708 // Prevent rules testing until we're done
1709 IgnoredErrorResult error;
1710 AutoEditSubActionNotifier startToHandleEditSubAction(
1711 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
1712 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1713 return error.StealNSResult();
1715 NS_WARNING_ASSERTION(
1716 !error.Failed(),
1717 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1719 // Shortcut the case of deleting all columns in table
1720 if (!startColIndex && aNumberOfColumnsToDelete >= tableSize.mColumnCount) {
1721 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
1722 NS_WARNING_ASSERTION(
1723 NS_SUCCEEDED(rv),
1724 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
1725 return rv;
1728 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
1729 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
1732 SelectedTableCellScanner scanner(SelectionRef());
1733 if (scanner.IsInTableCellSelectionMode() && SelectionRef().RangeCount() > 1) {
1734 const RefPtr<PresShell> presShell{GetPresShell()};
1735 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
1736 // grabs it until it's destroyed later.
1737 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
1738 presShell);
1739 if (NS_WARN_IF(firstCellIndexes.isErr())) {
1740 return NS_ERROR_FAILURE;
1742 startRowIndex = firstCellIndexes.mRow;
1743 startColIndex = firstCellIndexes.mColumn;
1746 // We control selection resetting after the insert...
1747 AutoSelectionSetterAfterTableEdit setCaret(
1748 *this, table, startRowIndex, startColIndex, ePreviousRow, false);
1750 // If 2 or more cells are not selected, removing columns starting from
1751 // a column which contains first selection range.
1752 if (!scanner.IsInTableCellSelectionMode() ||
1753 SelectionRef().RangeCount() == 1) {
1754 int32_t columnCountToRemove = std::min(
1755 aNumberOfColumnsToDelete, tableSize.mColumnCount - startColIndex);
1756 for (int32_t i = 0; i < columnCountToRemove; i++) {
1757 nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1758 if (NS_FAILED(rv)) {
1759 NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
1760 return rv;
1763 return NS_OK;
1766 // If 2 or more cells are selected, remove all columns which contain selected
1767 // cells. I.e., we ignore aNumberOfColumnsToDelete in this case.
1768 const RefPtr<PresShell> presShell{GetPresShell()};
1769 for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
1770 selectedCellElement;) {
1771 if (selectedCellElement != scanner.ElementsRef()[0]) {
1772 const CellIndexes cellIndexes(*selectedCellElement, presShell);
1773 if (NS_WARN_IF(cellIndexes.isErr())) {
1774 return NS_ERROR_FAILURE;
1776 startRowIndex = cellIndexes.mRow;
1777 startColIndex = cellIndexes.mColumn;
1779 // Find the next cell in a different column
1780 // to continue after we delete this column
1781 int32_t nextCol = startColIndex;
1782 while (nextCol == startColIndex) {
1783 selectedCellElement = scanner.GetNextElement();
1784 if (!selectedCellElement) {
1785 break;
1787 const CellIndexes cellIndexes(*selectedCellElement, presShell);
1788 if (NS_WARN_IF(cellIndexes.isErr())) {
1789 return NS_ERROR_FAILURE;
1791 startRowIndex = cellIndexes.mRow;
1792 nextCol = cellIndexes.mColumn;
1794 nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1795 if (NS_FAILED(rv)) {
1796 NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
1797 return rv;
1800 return NS_OK;
1803 nsresult HTMLEditor::DeleteTableColumnWithTransaction(Element& aTableElement,
1804 int32_t aColumnIndex) {
1805 MOZ_ASSERT(IsEditActionDataAvailable());
1807 for (int32_t rowIndex = 0;; rowIndex++) {
1808 const auto cellData = CellData::AtIndexInTableElement(
1809 *this, aTableElement, rowIndex, aColumnIndex);
1810 // Failure means that there is no more row in the table. In this case,
1811 // we shouldn't return error since we just reach the end of the table.
1812 // XXX Should distinguish whether CellData returns error or just not found
1813 // later.
1814 if (cellData.FailedOrNotFound()) {
1815 return NS_OK;
1818 // Find cells that don't start in column we are deleting.
1819 MOZ_ASSERT(cellData.mColSpan >= 0);
1820 if (cellData.IsSpannedFromOtherColumn() || cellData.mColSpan != 1) {
1821 // If we have a cell spanning this location, decrease its colspan to
1822 // keep table rectangular, but if colspan is 0, it'll be adjusted
1823 // automatically.
1824 if (cellData.mColSpan > 0) {
1825 NS_WARNING_ASSERTION(cellData.mColSpan > 1,
1826 "colspan should be 2 or larger");
1827 DebugOnly<nsresult> rvIgnored =
1828 SetColSpan(cellData.mElement, cellData.mColSpan - 1);
1829 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1830 "HTMLEditor::SetColSpan() failed, but ignored");
1832 if (!cellData.IsSpannedFromOtherColumn()) {
1833 // Cell is in column to be deleted, but must have colspan > 1,
1834 // so delete contents of cell instead of cell itself (We must have
1835 // reset colspan above).
1836 DebugOnly<nsresult> rvIgnored =
1837 DeleteAllChildrenWithTransaction(*cellData.mElement);
1838 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1839 "HTMLEditor::DeleteAllChildrenWithTransaction() "
1840 "failed, but ignored");
1842 // Skip rows which the removed cell spanned.
1843 rowIndex += cellData.NumberOfFollowingRows();
1844 continue;
1847 // Delete the cell
1848 int32_t numberOfCellsInRow =
1849 GetNumberOfCellsInRow(aTableElement, cellData.mCurrent.mRow);
1850 NS_WARNING_ASSERTION(
1851 numberOfCellsInRow > 0,
1852 "HTMLEditor::GetNumberOfCellsInRow() failed, but ignored");
1853 if (numberOfCellsInRow != 1) {
1854 // If removing cell is not the last cell of the row, we can just remove
1855 // it.
1856 nsresult rv = DeleteNodeWithTransaction(*cellData.mElement);
1857 if (NS_FAILED(rv)) {
1858 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1859 return rv;
1861 // Skip rows which the removed cell spanned.
1862 rowIndex += cellData.NumberOfFollowingRows();
1863 continue;
1866 // When the cell is the last cell in the row, remove the row instead.
1867 Element* parentRow = GetInclusiveAncestorByTagNameInternal(
1868 *nsGkAtoms::tr, *cellData.mElement);
1869 if (!parentRow) {
1870 NS_WARNING(
1871 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::tr) "
1872 "failed");
1873 return NS_ERROR_FAILURE;
1876 // Check if its the only row left in the table. If so, we can delete
1877 // the table instead.
1878 const Result<TableSize, nsresult> tableSizeOrError =
1879 TableSize::Create(*this, aTableElement);
1880 if (NS_WARN_IF(tableSizeOrError.isErr())) {
1881 return tableSizeOrError.inspectErr();
1883 const TableSize& tableSize = tableSizeOrError.inspect();
1885 if (tableSize.mRowCount == 1) {
1886 // We're deleting the last row. So, let's remove the <table> now.
1887 nsresult rv = DeleteTableElementAndChildrenWithTransaction(aTableElement);
1888 NS_WARNING_ASSERTION(
1889 NS_SUCCEEDED(rv),
1890 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
1891 return rv;
1894 // Delete the row by placing caret in cell we were to delete. We need
1895 // to call DeleteTableRowWithTransaction() to handle cells with rowspan.
1896 nsresult rv =
1897 DeleteTableRowWithTransaction(aTableElement, cellData.mFirst.mRow);
1898 if (NS_FAILED(rv)) {
1899 NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
1900 return rv;
1903 // Note that we decrement rowIndex since a row was deleted.
1904 rowIndex--;
1907 // Not reached because for (;;) loop never breaks.
1910 NS_IMETHODIMP HTMLEditor::DeleteTableRow(int32_t aNumberOfRowsToDelete) {
1911 AutoEditActionDataSetter editActionData(*this,
1912 EditAction::eRemoveTableRowElement);
1913 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1914 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
1915 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1916 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1917 return EditorBase::ToGenericNSResult(rv);
1920 rv = DeleteSelectedTableRowsWithTransaction(aNumberOfRowsToDelete);
1921 NS_WARNING_ASSERTION(
1922 NS_SUCCEEDED(rv),
1923 "HTMLEditor::DeleteSelectedTableRowsWithTransaction() failed");
1924 return EditorBase::ToGenericNSResult(rv);
1927 nsresult HTMLEditor::DeleteSelectedTableRowsWithTransaction(
1928 int32_t aNumberOfRowsToDelete) {
1929 MOZ_ASSERT(IsEditActionDataAvailable());
1931 RefPtr<Element> table;
1932 RefPtr<Element> cell;
1933 int32_t startRowIndex, startColIndex;
1934 nsresult rv =
1935 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1936 nullptr, &startRowIndex, &startColIndex);
1937 if (NS_FAILED(rv)) {
1938 NS_WARNING("HTMLEditor::GetCellContext() failed");
1939 return rv;
1941 if (!table || !cell) {
1942 NS_WARNING(
1943 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
1944 // Don't fail if no cell found.
1945 return NS_OK;
1948 const Result<TableSize, nsresult> tableSizeOrError =
1949 TableSize::Create(*this, *table);
1950 if (NS_WARN_IF(tableSizeOrError.isErr())) {
1951 return tableSizeOrError.inspectErr();
1953 const TableSize& tableSize = tableSizeOrError.inspect();
1955 AutoPlaceholderBatch treateAsOneTransaction(
1956 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1958 // Prevent rules testing until we're done
1959 IgnoredErrorResult error;
1960 AutoEditSubActionNotifier startToHandleEditSubAction(
1961 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
1962 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1963 return error.StealNSResult();
1965 NS_WARNING_ASSERTION(
1966 !error.Failed(),
1967 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1969 // Shortcut the case of deleting all rows in table
1970 if (!startRowIndex && aNumberOfRowsToDelete >= tableSize.mRowCount) {
1971 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
1972 NS_WARNING_ASSERTION(
1973 NS_SUCCEEDED(rv),
1974 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
1975 return rv;
1978 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
1979 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
1982 SelectedTableCellScanner scanner(SelectionRef());
1983 if (scanner.IsInTableCellSelectionMode() && SelectionRef().RangeCount() > 1) {
1984 // Fetch indexes again - may be different for selected cells
1985 const RefPtr<PresShell> presShell{GetPresShell()};
1986 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
1987 // grabs it until it's destroyed later.
1988 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
1989 presShell);
1990 if (NS_WARN_IF(firstCellIndexes.isErr())) {
1991 return NS_ERROR_FAILURE;
1993 startRowIndex = firstCellIndexes.mRow;
1994 startColIndex = firstCellIndexes.mColumn;
1997 // We control selection resetting after the insert...
1998 AutoSelectionSetterAfterTableEdit setCaret(
1999 *this, table, startRowIndex, startColIndex, ePreviousRow, false);
2000 // Don't change selection during deletions
2001 AutoTransactionsConserveSelection dontChangeSelection(*this);
2003 // XXX Perhaps, the following loops should collect <tr> elements to remove
2004 // first, then, remove them from the DOM tree since mutation event
2005 // listener may change the DOM tree during the loops.
2007 // If 2 or more cells are not selected, removing rows starting from
2008 // a row which contains first selection range.
2009 if (!scanner.IsInTableCellSelectionMode() ||
2010 SelectionRef().RangeCount() == 1) {
2011 int32_t rowCountToRemove =
2012 std::min(aNumberOfRowsToDelete, tableSize.mRowCount - startRowIndex);
2013 for (int32_t i = 0; i < rowCountToRemove; i++) {
2014 nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
2015 // If failed in current row, try the next
2016 if (NS_FAILED(rv)) {
2017 NS_WARNING(
2018 "HTMLEditor::DeleteTableRowWithTransaction() failed, but trying "
2019 "next...");
2020 startRowIndex++;
2022 // Check if there's a cell in the "next" row.
2023 cell = GetTableCellElementAt(*table, startRowIndex, startColIndex);
2024 if (!cell) {
2025 return NS_OK;
2028 return NS_OK;
2031 // If 2 or more cells are selected, remove all rows which contain selected
2032 // cells. I.e., we ignore aNumberOfRowsToDelete in this case.
2033 const RefPtr<PresShell> presShell{GetPresShell()};
2034 for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
2035 selectedCellElement;) {
2036 if (selectedCellElement != scanner.ElementsRef()[0]) {
2037 const CellIndexes cellIndexes(*selectedCellElement, presShell);
2038 if (NS_WARN_IF(cellIndexes.isErr())) {
2039 return NS_ERROR_FAILURE;
2041 startRowIndex = cellIndexes.mRow;
2042 startColIndex = cellIndexes.mColumn;
2044 // Find the next cell in a different row
2045 // to continue after we delete this row
2046 int32_t nextRow = startRowIndex;
2047 while (nextRow == startRowIndex) {
2048 selectedCellElement = scanner.GetNextElement();
2049 if (!selectedCellElement) {
2050 break;
2052 const CellIndexes cellIndexes(*selectedCellElement, presShell);
2053 if (NS_WARN_IF(cellIndexes.isErr())) {
2054 return NS_ERROR_FAILURE;
2056 nextRow = cellIndexes.mRow;
2057 startColIndex = cellIndexes.mColumn;
2059 // Delete the row containing selected cell(s).
2060 nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
2061 if (NS_FAILED(rv)) {
2062 NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
2063 return rv;
2066 return NS_OK;
2069 // Helper that doesn't batch or change the selection
2070 nsresult HTMLEditor::DeleteTableRowWithTransaction(Element& aTableElement,
2071 int32_t aRowIndex) {
2072 MOZ_ASSERT(IsEditActionDataAvailable());
2074 const Result<TableSize, nsresult> tableSizeOrError =
2075 TableSize::Create(*this, aTableElement);
2076 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2077 return tableSizeOrError.inspectErr();
2079 const TableSize& tableSize = tableSizeOrError.inspect();
2081 // Prevent rules testing until we're done
2082 IgnoredErrorResult error;
2083 AutoEditSubActionNotifier startToHandleEditSubAction(
2084 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
2085 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2086 return error.StealNSResult();
2088 NS_WARNING_ASSERTION(
2089 !error.Failed(),
2090 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2091 error.SuppressException();
2093 // Scan through cells in row to do rowspan adjustments
2094 // Note that after we delete row, startRowIndex will point to the cells in
2095 // the next row to be deleted.
2097 // The list of cells we will change rowspan in and the new rowspan values
2098 // for each.
2099 struct MOZ_STACK_CLASS SpanCell final {
2100 RefPtr<Element> mElement;
2101 int32_t mNewRowSpanValue;
2103 SpanCell(Element* aSpanCellElement, int32_t aNewRowSpanValue)
2104 : mElement(aSpanCellElement), mNewRowSpanValue(aNewRowSpanValue) {}
2106 AutoTArray<SpanCell, 10> spanCellArray;
2107 RefPtr<Element> cellInDeleteRow;
2108 int32_t columnIndex = 0;
2109 while (aRowIndex < tableSize.mRowCount &&
2110 columnIndex < tableSize.mColumnCount) {
2111 const auto cellData = CellData::AtIndexInTableElement(
2112 *this, aTableElement, aRowIndex, columnIndex);
2113 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2114 return NS_ERROR_FAILURE;
2117 // XXX So, we should distinguish if CellDate returns error or just not
2118 // found later.
2119 if (!cellData.mElement) {
2120 break;
2123 // Compensate for cells that don't start or extend below the row we are
2124 // deleting.
2125 if (cellData.IsSpannedFromOtherRow()) {
2126 // If a cell starts in row above us, decrease its rowspan to keep table
2127 // rectangular but we don't need to do this if rowspan=0, since it will
2128 // be automatically adjusted.
2129 if (cellData.mRowSpan > 0) {
2130 // Build list of cells to change rowspan. We can't do it now since
2131 // it upsets cell map, so we will do it after deleting the row.
2132 int32_t newRowSpanValue = std::max(cellData.NumberOfPrecedingRows(),
2133 cellData.NumberOfFollowingRows());
2134 spanCellArray.AppendElement(
2135 SpanCell(cellData.mElement, newRowSpanValue));
2137 } else {
2138 if (cellData.mRowSpan > 1) {
2139 // Cell spans below row to delete, so we must insert new cells to
2140 // keep rows below. Note that we test "rowSpan" so we don't do this
2141 // if rowSpan = 0 (automatic readjustment).
2142 int32_t aboveRowToInsertNewCellInto =
2143 cellData.NumberOfPrecedingRows() + 1;
2144 nsresult rv = SplitCellIntoRows(
2145 &aTableElement, cellData.mFirst.mRow, cellData.mFirst.mColumn,
2146 aboveRowToInsertNewCellInto, cellData.NumberOfFollowingRows(),
2147 nullptr);
2148 if (NS_FAILED(rv)) {
2149 NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
2150 return rv;
2153 if (!cellInDeleteRow) {
2154 // Reference cell to find row to delete.
2155 cellInDeleteRow = std::move(cellData.mElement);
2158 // Skip over other columns spanned by this cell
2159 columnIndex += cellData.mEffectiveColSpan;
2162 // Things are messed up if we didn't find a cell in the row!
2163 if (!cellInDeleteRow) {
2164 NS_WARNING("There was no cell in deleting row");
2165 return NS_ERROR_FAILURE;
2168 // Delete the entire row.
2169 RefPtr<Element> parentRow =
2170 GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::tr, *cellInDeleteRow);
2171 if (parentRow) {
2172 nsresult rv = DeleteNodeWithTransaction(*parentRow);
2173 if (NS_FAILED(rv)) {
2174 NS_WARNING(
2175 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::tr) "
2176 "failed");
2177 return rv;
2181 // Now we can set new rowspans for cells stored above.
2182 for (SpanCell& spanCell : spanCellArray) {
2183 if (NS_WARN_IF(!spanCell.mElement)) {
2184 continue;
2186 nsresult rv =
2187 SetRowSpan(MOZ_KnownLive(spanCell.mElement), spanCell.mNewRowSpanValue);
2188 if (NS_FAILED(rv)) {
2189 NS_WARNING("HTMLEditor::SetRawSpan() failed");
2190 return rv;
2193 return NS_OK;
2196 NS_IMETHODIMP HTMLEditor::SelectTable() {
2197 AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTable);
2198 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2199 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2200 NS_WARNING("HTMLEditor::SelectTable() couldn't handle the job");
2201 return EditorBase::ToGenericNSResult(rv);
2204 RefPtr<Element> table =
2205 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
2206 if (!table) {
2207 NS_WARNING(
2208 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::table)"
2209 " failed");
2210 return NS_OK; // Don't fail if we didn't find a table.
2213 rv = ClearSelection();
2214 if (NS_FAILED(rv)) {
2215 NS_WARNING("HTMLEditor::ClearSelection() failed");
2216 return EditorBase::ToGenericNSResult(rv);
2218 rv = AppendContentToSelectionAsRange(*table);
2219 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2220 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2221 return EditorBase::ToGenericNSResult(rv);
2224 NS_IMETHODIMP HTMLEditor::SelectTableCell() {
2225 AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTableCell);
2226 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2227 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2228 NS_WARNING("HTMLEditor::SelectTableCell() couldn't handle the job");
2229 return EditorBase::ToGenericNSResult(rv);
2232 RefPtr<Element> cell =
2233 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2234 if (!cell) {
2235 NS_WARNING(
2236 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2237 "failed");
2238 // Don't fail if we didn't find a cell.
2239 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2242 rv = ClearSelection();
2243 if (NS_FAILED(rv)) {
2244 NS_WARNING("HTMLEditor::ClearSelection() failed");
2245 return EditorBase::ToGenericNSResult(rv);
2247 rv = AppendContentToSelectionAsRange(*cell);
2248 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2249 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2250 return EditorBase::ToGenericNSResult(rv);
2253 NS_IMETHODIMP HTMLEditor::SelectAllTableCells() {
2254 AutoEditActionDataSetter editActionData(*this,
2255 EditAction::eSelectAllTableCells);
2256 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2257 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2258 NS_WARNING("HTMLEditor::SelectAllTableCells() couldn't handle the job");
2259 return EditorBase::ToGenericNSResult(rv);
2262 RefPtr<Element> cell =
2263 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2264 if (!cell) {
2265 NS_WARNING(
2266 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2267 "failed");
2268 // Don't fail if we didn't find a cell.
2269 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2272 RefPtr<Element> startCell = cell;
2274 // Get parent table
2275 RefPtr<Element> table =
2276 GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *cell);
2277 if (!table) {
2278 NS_WARNING(
2279 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
2280 "failed");
2281 return NS_ERROR_FAILURE;
2284 const Result<TableSize, nsresult> tableSizeOrError =
2285 TableSize::Create(*this, *table);
2286 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2287 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
2289 const TableSize& tableSize = tableSizeOrError.inspect();
2291 // Suppress nsISelectionListener notification
2292 // until all selection changes are finished
2293 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
2295 // It is now safe to clear the selection
2296 // BE SURE TO RESET IT BEFORE LEAVING!
2297 rv = ClearSelection();
2298 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2299 NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
2300 return EditorBase::ToGenericNSResult(rv);
2302 NS_WARNING_ASSERTION(
2303 NS_SUCCEEDED(rv),
2304 "HTMLEditor::ClearSelection() failed, but might be ignored");
2306 // Select all cells in the same column as current cell
2307 bool cellSelected = false;
2308 // Safety code to select starting cell if nothing else was selected
2309 auto AppendContentToStartCell = [&]() MOZ_CAN_RUN_SCRIPT {
2310 MOZ_ASSERT(!cellSelected);
2311 // XXX In this case, we ignore `NS_ERROR_FAILURE` set by above inner
2312 // `for` loop.
2313 nsresult rv = AppendContentToSelectionAsRange(*startCell);
2314 NS_WARNING_ASSERTION(
2315 NS_SUCCEEDED(rv),
2316 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2317 return EditorBase::ToGenericNSResult(rv);
2319 for (int32_t row = 0; row < tableSize.mRowCount; row++) {
2320 for (int32_t col = 0; col < tableSize.mColumnCount;) {
2321 const auto cellData =
2322 CellData::AtIndexInTableElement(*this, *table, row, col);
2323 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2324 return !cellSelected ? AppendContentToStartCell() : NS_ERROR_FAILURE;
2327 // Skip cells that are spanned from previous rows or columns
2328 // XXX So, we should distinguish whether CellData returns error or just
2329 // not found later.
2330 if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
2331 nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
2332 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2333 NS_WARNING(
2334 "HTMLEditor::AppendContentToSelectionAsRange() caused "
2335 "destroying the editor");
2336 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2338 if (NS_FAILED(rv)) {
2339 NS_WARNING(
2340 "HTMLEditor::AppendContentToSelectionAsRange() failed, but "
2341 "might be ignored");
2342 return !cellSelected ? AppendContentToStartCell()
2343 : EditorBase::ToGenericNSResult(rv);
2345 cellSelected = true;
2347 MOZ_ASSERT(col < cellData.NextColumnIndex());
2348 col = cellData.NextColumnIndex();
2351 return EditorBase::ToGenericNSResult(rv);
2354 NS_IMETHODIMP HTMLEditor::SelectTableRow() {
2355 AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTableRow);
2356 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2357 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2358 NS_WARNING("HTMLEditor::SelectTableRow() couldn't handle the job");
2359 return EditorBase::ToGenericNSResult(rv);
2362 RefPtr<Element> cell =
2363 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2364 if (!cell) {
2365 NS_WARNING(
2366 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2367 "failed");
2368 // Don't fail if we didn't find a cell.
2369 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2372 RefPtr<Element> startCell = cell;
2374 // Get table and location of cell:
2375 RefPtr<Element> table;
2376 int32_t startRowIndex, startColIndex;
2378 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
2379 nullptr, &startRowIndex, &startColIndex);
2380 if (NS_FAILED(rv)) {
2381 NS_WARNING("HTMLEditor::GetCellContext() failed");
2382 return EditorBase::ToGenericNSResult(rv);
2384 if (!table) {
2385 NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
2386 return NS_ERROR_FAILURE;
2389 const Result<TableSize, nsresult> tableSizeOrError =
2390 TableSize::Create(*this, *table);
2391 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2392 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
2394 const TableSize& tableSize = tableSizeOrError.inspect();
2396 // Note: At this point, we could get first and last cells in row,
2397 // then call SelectBlockOfCells, but that would take just
2398 // a little less code, so the following is more efficient
2400 // Suppress nsISelectionListener notification
2401 // until all selection changes are finished
2402 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
2404 // It is now safe to clear the selection
2405 // BE SURE TO RESET IT BEFORE LEAVING!
2406 rv = ClearSelection();
2407 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2408 NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
2409 return EditorBase::ToGenericNSResult(rv);
2411 NS_WARNING_ASSERTION(
2412 NS_SUCCEEDED(rv),
2413 "HTMLEditor::ClearSelection() failed, but might be ignored");
2415 // Select all cells in the same row as current cell
2416 bool cellSelected = false;
2417 for (int32_t col = 0; col < tableSize.mColumnCount;) {
2418 const auto cellData =
2419 CellData::AtIndexInTableElement(*this, *table, startRowIndex, col);
2420 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2421 if (cellSelected) {
2422 return NS_ERROR_FAILURE;
2424 // Safety code to select starting cell if nothing else was selected
2425 nsresult rv = AppendContentToSelectionAsRange(*startCell);
2426 NS_WARNING_ASSERTION(
2427 NS_SUCCEEDED(rv),
2428 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2429 NS_WARNING_ASSERTION(
2430 cellData.isOk() || NS_SUCCEEDED(rv) ||
2431 NS_FAILED(EditorBase::ToGenericNSResult(rv)),
2432 "CellData::AtIndexInTableElement() failed, but ignored");
2433 return EditorBase::ToGenericNSResult(rv);
2436 // Skip cells that are spanned from previous rows or columns
2437 // XXX So, we should distinguish whether CellData returns error or just
2438 // not found later.
2439 if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
2440 nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
2441 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2442 NS_WARNING(
2443 "HTMLEditor::AppendContentToSelectionAsRange() caused destroying "
2444 "the editor");
2445 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2447 if (NS_FAILED(rv)) {
2448 if (cellSelected) {
2449 NS_WARNING("HTMLEditor::AppendContentToSelectionAsRange() failed");
2450 return EditorBase::ToGenericNSResult(rv);
2452 // Safety code to select starting cell if nothing else was selected
2453 nsresult rvTryAgain = AppendContentToSelectionAsRange(*startCell);
2454 NS_WARNING_ASSERTION(
2455 NS_SUCCEEDED(rv),
2456 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2457 NS_WARNING_ASSERTION(
2458 NS_SUCCEEDED(EditorBase::ToGenericNSResult(rv)) ||
2459 NS_SUCCEEDED(rvTryAgain) ||
2460 NS_FAILED(EditorBase::ToGenericNSResult(rvTryAgain)),
2461 "HTMLEditor::AppendContentToSelectionAsRange(*cellData.mElement) "
2462 "failed, but ignored");
2463 return EditorBase::ToGenericNSResult(rvTryAgain);
2465 cellSelected = true;
2467 MOZ_ASSERT(col < cellData.NextColumnIndex());
2468 col = cellData.NextColumnIndex();
2470 return EditorBase::ToGenericNSResult(rv);
2473 NS_IMETHODIMP HTMLEditor::SelectTableColumn() {
2474 AutoEditActionDataSetter editActionData(*this,
2475 EditAction::eSelectTableColumn);
2476 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2477 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2478 NS_WARNING("HTMLEditor::SelectTableColumn() couldn't handle the job");
2479 return EditorBase::ToGenericNSResult(rv);
2482 RefPtr<Element> cell =
2483 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2484 if (!cell) {
2485 NS_WARNING(
2486 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2487 "failed");
2488 // Don't fail if we didn't find a cell.
2489 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2492 RefPtr<Element> startCell = cell;
2494 // Get location of cell:
2495 RefPtr<Element> table;
2496 int32_t startRowIndex, startColIndex;
2498 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
2499 nullptr, &startRowIndex, &startColIndex);
2500 if (NS_FAILED(rv)) {
2501 NS_WARNING("HTMLEditor::GetCellContext() failed");
2502 return EditorBase::ToGenericNSResult(rv);
2504 if (!table) {
2505 NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
2506 return NS_ERROR_FAILURE;
2509 const Result<TableSize, nsresult> tableSizeOrError =
2510 TableSize::Create(*this, *table);
2511 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2512 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
2514 const TableSize& tableSize = tableSizeOrError.inspect();
2516 // Suppress nsISelectionListener notification
2517 // until all selection changes are finished
2518 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
2520 // It is now safe to clear the selection
2521 // BE SURE TO RESET IT BEFORE LEAVING!
2522 rv = ClearSelection();
2523 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2524 NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
2525 return EditorBase::ToGenericNSResult(rv);
2527 NS_WARNING_ASSERTION(
2528 NS_SUCCEEDED(rv),
2529 "HTMLEditor::ClearSelection() failed, but might be ignored");
2531 // Select all cells in the same column as current cell
2532 bool cellSelected = false;
2533 for (int32_t row = 0; row < tableSize.mRowCount;) {
2534 const auto cellData =
2535 CellData::AtIndexInTableElement(*this, *table, row, startColIndex);
2536 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2537 if (cellSelected) {
2538 return NS_ERROR_FAILURE;
2540 // Safety code to select starting cell if nothing else was selected
2541 nsresult rv = AppendContentToSelectionAsRange(*startCell);
2542 NS_WARNING_ASSERTION(
2543 NS_SUCCEEDED(rv),
2544 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2545 NS_WARNING_ASSERTION(
2546 cellData.isOk() || NS_SUCCEEDED(rv) ||
2547 NS_FAILED(EditorBase::ToGenericNSResult(rv)),
2548 "CellData::AtIndexInTableElement() failed, but ignored");
2549 return EditorBase::ToGenericNSResult(rv);
2552 // Skip cells that are spanned from previous rows or columns
2553 // XXX So, we should distinguish whether CellData returns error or just
2554 // not found later.
2555 if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
2556 nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
2557 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2558 NS_WARNING(
2559 "HTMLEditor::AppendContentToSelectionAsRange() caused destroying "
2560 "the editor");
2561 return EditorBase::ToGenericNSResult(rv);
2563 if (NS_FAILED(rv)) {
2564 if (cellSelected) {
2565 NS_WARNING("HTMLEditor::AppendContentToSelectionAsRange() failed");
2566 return EditorBase::ToGenericNSResult(rv);
2568 // Safety code to select starting cell if nothing else was selected
2569 nsresult rvTryAgain = AppendContentToSelectionAsRange(*startCell);
2570 NS_WARNING_ASSERTION(
2571 NS_SUCCEEDED(rv),
2572 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2573 NS_WARNING_ASSERTION(
2574 NS_SUCCEEDED(EditorBase::ToGenericNSResult(rv)) ||
2575 NS_SUCCEEDED(rvTryAgain) ||
2576 NS_FAILED(EditorBase::ToGenericNSResult(rvTryAgain)),
2577 "HTMLEditor::AppendContentToSelectionAsRange(*cellData.mElement) "
2578 "failed, but ignored");
2579 return EditorBase::ToGenericNSResult(rvTryAgain);
2581 cellSelected = true;
2583 MOZ_ASSERT(row < cellData.NextRowIndex());
2584 row = cellData.NextRowIndex();
2586 return EditorBase::ToGenericNSResult(rv);
2589 NS_IMETHODIMP HTMLEditor::SplitTableCell() {
2590 AutoEditActionDataSetter editActionData(*this,
2591 EditAction::eSplitTableCellElement);
2592 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2593 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2594 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2595 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2596 return EditorBase::ToGenericNSResult(rv);
2599 RefPtr<Element> table;
2600 RefPtr<Element> cell;
2601 int32_t startRowIndex, startColIndex, actualRowSpan, actualColSpan;
2602 // Get cell, table, etc. at selection anchor node
2603 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
2604 nullptr, &startRowIndex, &startColIndex);
2605 if (NS_FAILED(rv)) {
2606 NS_WARNING("HTMLEditor::GetCellContext() failed");
2607 return EditorBase::ToGenericNSResult(rv);
2609 if (!table || !cell) {
2610 NS_WARNING(
2611 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
2612 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2615 // We need rowspan and colspan data
2616 rv = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan,
2617 actualColSpan);
2618 if (NS_FAILED(rv)) {
2619 NS_WARNING("HTMLEditor::GetCellSpansAt() failed");
2620 return EditorBase::ToGenericNSResult(rv);
2623 // Must have some span to split
2624 if (actualRowSpan <= 1 && actualColSpan <= 1) {
2625 return NS_OK;
2628 AutoPlaceholderBatch treateAsOneTransaction(
2629 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2630 // Prevent auto insertion of BR in new cell until we're done
2631 IgnoredErrorResult ignoredError;
2632 AutoEditSubActionNotifier startToHandleEditSubAction(
2633 *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
2634 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2635 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
2637 NS_WARNING_ASSERTION(
2638 !ignoredError.Failed(),
2639 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2641 // We reset selection
2642 AutoSelectionSetterAfterTableEdit setCaret(
2643 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
2644 //...so suppress Rules System selection munging
2645 AutoTransactionsConserveSelection dontChangeSelection(*this);
2647 RefPtr<Element> newCell;
2648 int32_t rowIndex = startRowIndex;
2649 int32_t rowSpanBelow, colSpanAfter;
2651 // Split up cell row-wise first into rowspan=1 above, and the rest below,
2652 // whittling away at the cell below until no more extra span
2653 for (rowSpanBelow = actualRowSpan - 1; rowSpanBelow >= 0; rowSpanBelow--) {
2654 // We really split row-wise only if we had rowspan > 1
2655 if (rowSpanBelow > 0) {
2656 nsresult rv = SplitCellIntoRows(table, rowIndex, startColIndex, 1,
2657 rowSpanBelow, getter_AddRefs(newCell));
2658 if (NS_FAILED(rv)) {
2659 NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
2660 return EditorBase::ToGenericNSResult(rv);
2662 DebugOnly<nsresult> rvIgnored = CopyCellBackgroundColor(newCell, cell);
2663 NS_WARNING_ASSERTION(
2664 NS_SUCCEEDED(rvIgnored),
2665 "HTMLEditor::CopyCellBackgroundColor() failed, but ignored");
2667 int32_t colIndex = startColIndex;
2668 // Now split the cell with rowspan = 1 into cells if it has colSpan > 1
2669 for (colSpanAfter = actualColSpan - 1; colSpanAfter > 0; colSpanAfter--) {
2670 nsresult rv = SplitCellIntoColumns(table, rowIndex, colIndex, 1,
2671 colSpanAfter, getter_AddRefs(newCell));
2672 if (NS_FAILED(rv)) {
2673 NS_WARNING("HTMLEditor::SplitCellIntoColumns() failed");
2674 return EditorBase::ToGenericNSResult(rv);
2676 DebugOnly<nsresult> rvIgnored = CopyCellBackgroundColor(newCell, cell);
2677 NS_WARNING_ASSERTION(
2678 NS_SUCCEEDED(rv),
2679 "HTMLEditor::CopyCellBackgroundColor() failed, but ignored");
2680 colIndex++;
2682 // Point to the new cell and repeat
2683 rowIndex++;
2685 return NS_OK;
2688 nsresult HTMLEditor::CopyCellBackgroundColor(Element* aDestCell,
2689 Element* aSourceCell) {
2690 if (NS_WARN_IF(!aDestCell) || NS_WARN_IF(!aSourceCell)) {
2691 return NS_ERROR_INVALID_ARG;
2694 if (!aSourceCell->HasAttr(nsGkAtoms::bgcolor)) {
2695 return NS_OK;
2698 // Copy backgournd color to new cell.
2699 nsString backgroundColor;
2700 aSourceCell->GetAttr(nsGkAtoms::bgcolor, backgroundColor);
2701 nsresult rv = SetAttributeWithTransaction(*aDestCell, *nsGkAtoms::bgcolor,
2702 backgroundColor);
2703 NS_WARNING_ASSERTION(
2704 NS_SUCCEEDED(rv),
2705 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
2706 return rv;
2709 nsresult HTMLEditor::SplitCellIntoColumns(Element* aTable, int32_t aRowIndex,
2710 int32_t aColIndex,
2711 int32_t aColSpanLeft,
2712 int32_t aColSpanRight,
2713 Element** aNewCell) {
2714 if (NS_WARN_IF(!aTable)) {
2715 return NS_ERROR_INVALID_ARG;
2717 if (aNewCell) {
2718 *aNewCell = nullptr;
2721 const auto cellData =
2722 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, aColIndex);
2723 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2724 return NS_ERROR_FAILURE;
2727 // We can't split!
2728 if (cellData.mEffectiveColSpan <= 1 ||
2729 aColSpanLeft + aColSpanRight > cellData.mEffectiveColSpan) {
2730 return NS_OK;
2733 // Reduce colspan of cell to split
2734 nsresult rv = SetColSpan(cellData.mElement, aColSpanLeft);
2735 if (NS_FAILED(rv)) {
2736 NS_WARNING("HTMLEditor::SetColSpan() failed");
2737 return rv;
2740 // Insert new cell after using the remaining span
2741 // and always get the new cell so we can copy the background color;
2742 RefPtr<Element> newCellElement;
2743 rv = InsertCell(cellData.mElement, cellData.mEffectiveRowSpan, aColSpanRight,
2744 true, false, getter_AddRefs(newCellElement));
2745 if (NS_FAILED(rv)) {
2746 NS_WARNING("HTMLEditor::InsertCell() failed");
2747 return rv;
2749 if (!newCellElement) {
2750 return NS_OK;
2752 if (aNewCell) {
2753 *aNewCell = do_AddRef(newCellElement).take();
2755 rv = CopyCellBackgroundColor(newCellElement, cellData.mElement);
2756 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2757 "HTMLEditor::CopyCellBackgroundColor() failed");
2758 return rv;
2761 nsresult HTMLEditor::SplitCellIntoRows(Element* aTable, int32_t aRowIndex,
2762 int32_t aColIndex, int32_t aRowSpanAbove,
2763 int32_t aRowSpanBelow,
2764 Element** aNewCell) {
2765 if (NS_WARN_IF(!aTable)) {
2766 return NS_ERROR_INVALID_ARG;
2769 if (aNewCell) {
2770 *aNewCell = nullptr;
2773 const auto cellData =
2774 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, aColIndex);
2775 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2776 return NS_ERROR_FAILURE;
2779 // We can't split!
2780 if (cellData.mEffectiveRowSpan <= 1 ||
2781 aRowSpanAbove + aRowSpanBelow > cellData.mEffectiveRowSpan) {
2782 return NS_OK;
2785 const Result<TableSize, nsresult> tableSizeOrError =
2786 TableSize::Create(*this, *aTable);
2787 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2788 return tableSizeOrError.inspectErr();
2790 const TableSize& tableSize = tableSizeOrError.inspect();
2792 // Find a cell to insert before or after
2793 RefPtr<Element> cellElementAtInsertionPoint;
2794 RefPtr<Element> lastCellFound;
2795 bool insertAfter = (cellData.mFirst.mColumn > 0);
2796 for (int32_t colIndex = 0,
2797 rowBelowIndex = cellData.mFirst.mRow + aRowSpanAbove;
2798 colIndex <= tableSize.mColumnCount;) {
2799 const auto cellDataAtInsertionPoint = CellData::AtIndexInTableElement(
2800 *this, *aTable, rowBelowIndex, colIndex);
2801 // If we fail here, it could be because row has bad rowspan values,
2802 // such as all cells having rowspan > 1 (Call FixRowSpan first!).
2803 // XXX According to the comment, this does not assume that
2804 // FixRowSpan() doesn't work well and user can create non-rectangular
2805 // table. So, we should not return error when CellData cannot find
2806 // a cell.
2807 if (NS_WARN_IF(cellDataAtInsertionPoint.FailedOrNotFound())) {
2808 return NS_ERROR_FAILURE;
2811 // FYI: Don't use std::move() here since the following checks will use
2812 // utility methods of cellDataAtInsertionPoint, but some of them
2813 // check whether its mElement is not nullptr.
2814 cellElementAtInsertionPoint = cellDataAtInsertionPoint.mElement;
2816 // Skip over cells spanned from above (like the one we are splitting!)
2817 if (cellDataAtInsertionPoint.mElement &&
2818 !cellDataAtInsertionPoint.IsSpannedFromOtherRow()) {
2819 if (!insertAfter) {
2820 // Inserting before, so stop at first cell in row we want to insert
2821 // into.
2822 break;
2824 // New cell isn't first in row,
2825 // so stop after we find the cell just before new cell's column
2826 if (cellDataAtInsertionPoint.NextColumnIndex() ==
2827 cellData.mFirst.mColumn) {
2828 break;
2830 // If cell found is AFTER desired new cell colum,
2831 // we have multiple cells with rowspan > 1 that
2832 // prevented us from finding a cell to insert after...
2833 if (cellDataAtInsertionPoint.mFirst.mColumn > cellData.mFirst.mColumn) {
2834 // ... so instead insert before the cell we found
2835 insertAfter = false;
2836 break;
2838 // FYI: Don't use std::move() here since
2839 // cellDataAtInsertionPoint.NextColumnIndex() needs it.
2840 lastCellFound = cellDataAtInsertionPoint.mElement;
2842 MOZ_ASSERT(colIndex < cellDataAtInsertionPoint.NextColumnIndex());
2843 colIndex = cellDataAtInsertionPoint.NextColumnIndex();
2846 if (!cellElementAtInsertionPoint && lastCellFound) {
2847 // Edge case where we didn't find a cell to insert after
2848 // or before because column(s) before desired column
2849 // and all columns after it are spanned from above.
2850 // We can insert after the last cell we found
2851 cellElementAtInsertionPoint = std::move(lastCellFound);
2852 insertAfter = true; // Should always be true, but let's be sure
2855 // Reduce rowspan of cell to split
2856 nsresult rv = SetRowSpan(cellData.mElement, aRowSpanAbove);
2857 if (NS_FAILED(rv)) {
2858 NS_WARNING("HTMLEditor::SetRowSpan() failed");
2859 return rv;
2862 // Insert new cell after using the remaining span
2863 // and always get the new cell so we can copy the background color;
2864 RefPtr<Element> newCell;
2865 rv = InsertCell(cellElementAtInsertionPoint, aRowSpanBelow,
2866 cellData.mEffectiveColSpan, insertAfter, false,
2867 getter_AddRefs(newCell));
2868 if (NS_FAILED(rv)) {
2869 NS_WARNING("HTMLEditor::InsertCell() failed");
2870 return rv;
2872 if (!newCell) {
2873 return NS_OK;
2875 if (aNewCell) {
2876 *aNewCell = do_AddRef(newCell).take();
2878 rv = CopyCellBackgroundColor(newCell, cellElementAtInsertionPoint);
2879 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2880 "HTMLEditor::CopyCellBackgroundColor() failed");
2881 return rv;
2884 NS_IMETHODIMP HTMLEditor::SwitchTableCellHeaderType(Element* aSourceCell,
2885 Element** aNewCell) {
2886 if (NS_WARN_IF(!aSourceCell)) {
2887 return NS_ERROR_INVALID_ARG;
2890 AutoEditActionDataSetter editActionData(*this,
2891 EditAction::eSetTableCellElementType);
2892 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2893 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2894 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2895 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2896 return EditorBase::ToGenericNSResult(rv);
2899 AutoPlaceholderBatch treatAsOneTransaction(
2900 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2901 // Prevent auto insertion of BR in new cell created by
2902 // ReplaceContainerAndCloneAttributesWithTransaction().
2903 IgnoredErrorResult ignoredError;
2904 AutoEditSubActionNotifier startToHandleEditSubAction(
2905 *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
2906 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2907 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
2909 NS_WARNING_ASSERTION(
2910 !ignoredError.Failed(),
2911 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2913 // Save current selection to restore when done.
2914 // This is needed so ReplaceContainerAndCloneAttributesWithTransaction()
2915 // can monitor selection when replacing nodes.
2916 AutoSelectionRestorer restoreSelectionLater(*this);
2918 // Set to the opposite of current type
2919 nsAtom* newCellName =
2920 aSourceCell->IsHTMLElement(nsGkAtoms::td) ? nsGkAtoms::th : nsGkAtoms::td;
2922 // This creates new node, moves children, copies attributes (true)
2923 // and manages the selection!
2924 Result<CreateElementResult, nsresult> newCellElementOrError =
2925 ReplaceContainerAndCloneAttributesWithTransaction(
2926 *aSourceCell, MOZ_KnownLive(*newCellName));
2927 if (MOZ_UNLIKELY(newCellElementOrError.isErr())) {
2928 NS_WARNING(
2929 "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
2930 "failed");
2931 return newCellElementOrError.unwrapErr();
2933 // restoreSelectionLater will change selection
2934 newCellElementOrError.inspect().IgnoreCaretPointSuggestion();
2936 // Return the new cell
2937 if (aNewCell) {
2938 newCellElementOrError.unwrap().UnwrapNewNode().forget(aNewCell);
2941 return NS_OK;
2944 NS_IMETHODIMP HTMLEditor::JoinTableCells(bool aMergeNonContiguousContents) {
2945 AutoEditActionDataSetter editActionData(*this,
2946 EditAction::eJoinTableCellElements);
2947 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2948 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
2949 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2950 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2951 return EditorBase::ToGenericNSResult(rv);
2954 RefPtr<Element> table;
2955 RefPtr<Element> targetCell;
2956 int32_t startRowIndex, startColIndex;
2958 // Get cell, table, etc. at selection anchor node
2959 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(targetCell),
2960 nullptr, nullptr, &startRowIndex, &startColIndex);
2961 if (NS_FAILED(rv)) {
2962 NS_WARNING("HTMLEditor::GetCellContext() failed");
2963 return EditorBase::ToGenericNSResult(rv);
2965 if (!table || !targetCell) {
2966 NS_WARNING(
2967 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
2968 return NS_OK;
2971 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
2972 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
2975 AutoPlaceholderBatch treateAsOneTransaction(
2976 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2977 // Don't let Rules System change the selection
2978 AutoTransactionsConserveSelection dontChangeSelection(*this);
2980 // Note: We dont' use AutoSelectionSetterAfterTableEdit here so the selection
2981 // is retained after joining. This leaves the target cell selected
2982 // as well as the "non-contiguous" cells, so user can see what happened.
2984 SelectedTableCellScanner scanner(SelectionRef());
2986 // If only one cell is selected, join with cell to the right
2987 if (scanner.ElementsRef().Length() > 1) {
2988 // We have selected cells: Join just contiguous cells
2989 // and just merge contents if not contiguous
2990 Result<TableSize, nsresult> tableSizeOrError =
2991 TableSize::Create(*this, *table);
2992 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2993 return EditorBase::ToGenericNSResult(tableSizeOrError.unwrapErr());
2995 // FYI: Cannot be const because the row count will be updated
2996 TableSize tableSize = tableSizeOrError.unwrap();
2998 RefPtr<PresShell> presShell = GetPresShell();
2999 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
3000 // grabs it until it's destroyed later.
3001 const CellIndexes firstSelectedCellIndexes(
3002 MOZ_KnownLive(scanner.ElementsRef()[0]), presShell);
3003 if (NS_WARN_IF(firstSelectedCellIndexes.isErr())) {
3004 return NS_ERROR_FAILURE;
3007 // Get spans for cell we will merge into
3008 int32_t firstRowSpan, firstColSpan;
3009 nsresult rv = GetCellSpansAt(table, firstSelectedCellIndexes.mRow,
3010 firstSelectedCellIndexes.mColumn, firstRowSpan,
3011 firstColSpan);
3012 if (NS_FAILED(rv)) {
3013 NS_WARNING("HTMLEditor::GetCellSpansAt() failed");
3014 return EditorBase::ToGenericNSResult(rv);
3017 // This defines the last indexes along the "edges"
3018 // of the contiguous block of cells, telling us
3019 // that we can join adjacent cells to the block
3020 // Start with same as the first values,
3021 // then expand as we find adjacent selected cells
3022 int32_t lastRowIndex = firstSelectedCellIndexes.mRow;
3023 int32_t lastColIndex = firstSelectedCellIndexes.mColumn;
3025 // First pass: Determine boundaries of contiguous rectangular block that
3026 // we will join into one cell, favoring adjacent cells in the same row.
3027 for (int32_t rowIndex = firstSelectedCellIndexes.mRow;
3028 rowIndex <= lastRowIndex; rowIndex++) {
3029 int32_t currentRowCount = tableSize.mRowCount;
3030 // Be sure each row doesn't have rowspan errors
3031 rv = FixBadRowSpan(table, rowIndex, tableSize.mRowCount);
3032 if (NS_FAILED(rv)) {
3033 NS_WARNING("HTMLEditor::FixBadRowSpan() failed");
3034 return EditorBase::ToGenericNSResult(rv);
3036 // Adjust rowcount by number of rows we removed
3037 lastRowIndex -= currentRowCount - tableSize.mRowCount;
3039 bool cellFoundInRow = false;
3040 bool lastRowIsSet = false;
3041 int32_t lastColInRow = 0;
3042 int32_t firstColInRow = firstSelectedCellIndexes.mColumn;
3043 int32_t colIndex = firstSelectedCellIndexes.mColumn;
3044 for (; colIndex < tableSize.mColumnCount;) {
3045 const auto cellData =
3046 CellData::AtIndexInTableElement(*this, *table, rowIndex, colIndex);
3047 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3048 return NS_ERROR_FAILURE;
3051 if (cellData.mIsSelected) {
3052 if (!cellFoundInRow) {
3053 // We've just found the first selected cell in this row
3054 firstColInRow = cellData.mCurrent.mColumn;
3056 if (cellData.mCurrent.mRow > firstSelectedCellIndexes.mRow &&
3057 firstColInRow != firstSelectedCellIndexes.mColumn) {
3058 // We're in at least the second row,
3059 // but left boundary is "ragged" (not the same as 1st row's start)
3060 // Let's just end block on previous row
3061 // and keep previous lastColIndex
3062 // TODO: We could try to find the Maximum firstColInRow
3063 // so our block can still extend down more rows?
3064 lastRowIndex = std::max(0, cellData.mCurrent.mRow - 1);
3065 lastRowIsSet = true;
3066 break;
3068 // Save max selected column in this row, including extra colspan
3069 lastColInRow = cellData.LastColumnIndex();
3070 cellFoundInRow = true;
3071 } else if (cellFoundInRow) {
3072 // No cell or not selected, but at least one cell in row was found
3073 if (cellData.mCurrent.mRow > firstSelectedCellIndexes.mRow + 1 &&
3074 cellData.mCurrent.mColumn <= lastColIndex) {
3075 // Cell is in a column less than current right border in
3076 // the third or higher selected row, so stop block at the previous
3077 // row
3078 lastRowIndex = std::max(0, cellData.mCurrent.mRow - 1);
3079 lastRowIsSet = true;
3081 // We're done with this row
3082 break;
3084 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3085 colIndex = cellData.NextColumnIndex();
3086 } // End of column loop
3088 // Done with this row
3089 if (cellFoundInRow) {
3090 if (rowIndex == firstSelectedCellIndexes.mRow) {
3091 // First row always initializes the right boundary
3092 lastColIndex = lastColInRow;
3095 // If we didn't determine last row above...
3096 if (!lastRowIsSet) {
3097 if (colIndex < lastColIndex) {
3098 // (don't think we ever get here?)
3099 // Cell is in a column less than current right boundary,
3100 // so stop block at the previous row
3101 lastRowIndex = std::max(0, rowIndex - 1);
3102 } else {
3103 // Go on to examine next row
3104 lastRowIndex = rowIndex + 1;
3107 // Use the minimum col we found so far for right boundary
3108 lastColIndex = std::min(lastColIndex, lastColInRow);
3109 } else {
3110 // No selected cells in this row -- stop at row above
3111 // and leave last column at its previous value
3112 lastRowIndex = std::max(0, rowIndex - 1);
3116 // The list of cells we will delete after joining
3117 nsTArray<RefPtr<Element>> deleteList;
3119 // 2nd pass: Do the joining and merging
3120 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3121 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
3122 const auto cellData =
3123 CellData::AtIndexInTableElement(*this, *table, rowIndex, colIndex);
3124 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3125 return NS_ERROR_FAILURE;
3128 // If this is 0, we are past last cell in row, so exit the loop
3129 if (!cellData.mEffectiveColSpan) {
3130 break;
3133 // Merge only selected cells (skip cell we're merging into, of course)
3134 if (cellData.mIsSelected &&
3135 cellData.mElement != scanner.ElementsRef()[0]) {
3136 if (cellData.mCurrent.mRow >= firstSelectedCellIndexes.mRow &&
3137 cellData.mCurrent.mRow <= lastRowIndex &&
3138 cellData.mCurrent.mColumn >= firstSelectedCellIndexes.mColumn &&
3139 cellData.mCurrent.mColumn <= lastColIndex) {
3140 // We are within the join region
3141 // Problem: It is very tricky to delete cells as we merge,
3142 // since that will upset the cellmap
3143 // Instead, build a list of cells to delete and do it later
3144 NS_ASSERTION(!cellData.IsSpannedFromOtherRow(),
3145 "JoinTableCells: StartRowIndex is in row above");
3147 if (cellData.mEffectiveColSpan > 1) {
3148 // Check if cell "hangs" off the boundary because of colspan > 1
3149 // Use split methods to chop off excess
3150 int32_t extraColSpan = cellData.mFirst.mColumn +
3151 cellData.mEffectiveColSpan -
3152 (lastColIndex + 1);
3153 if (extraColSpan > 0) {
3154 nsresult rv = SplitCellIntoColumns(
3155 table, cellData.mFirst.mRow, cellData.mFirst.mColumn,
3156 cellData.mEffectiveColSpan - extraColSpan, extraColSpan,
3157 nullptr);
3158 if (NS_FAILED(rv)) {
3159 NS_WARNING("HTMLEditor::SplitCellIntoColumns() failed");
3160 return EditorBase::ToGenericNSResult(rv);
3165 nsresult rv =
3166 MergeCells(scanner.ElementsRef()[0], cellData.mElement, false);
3167 if (NS_FAILED(rv)) {
3168 NS_WARNING("HTMLEditor::MergeCells() failed");
3169 return EditorBase::ToGenericNSResult(rv);
3172 // Add cell to list to delete
3173 deleteList.AppendElement(cellData.mElement.get());
3174 } else if (aMergeNonContiguousContents) {
3175 // Cell is outside join region -- just merge the contents
3176 nsresult rv =
3177 MergeCells(scanner.ElementsRef()[0], cellData.mElement, false);
3178 if (NS_FAILED(rv)) {
3179 NS_WARNING("HTMLEditor::MergeCells() failed");
3180 return rv;
3184 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3185 colIndex = cellData.NextColumnIndex();
3189 // All cell contents are merged. Delete the empty cells we accumulated
3190 // Prevent rules testing until we're done
3191 IgnoredErrorResult error;
3192 AutoEditSubActionNotifier startToHandleEditSubAction(
3193 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
3194 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3195 return EditorBase::ToGenericNSResult(error.StealNSResult());
3197 NS_WARNING_ASSERTION(!error.Failed(),
3198 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() "
3199 "failed, but ignored");
3201 for (uint32_t i = 0, n = deleteList.Length(); i < n; i++) {
3202 RefPtr<Element> nodeToBeRemoved = deleteList[i];
3203 if (nodeToBeRemoved) {
3204 nsresult rv = DeleteNodeWithTransaction(*nodeToBeRemoved);
3205 if (NS_FAILED(rv)) {
3206 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3207 return EditorBase::ToGenericNSResult(rv);
3211 // Cleanup selection: remove ranges where cells were deleted
3212 uint32_t rangeCount = SelectionRef().RangeCount();
3214 // TODO: Rewriting this with reversed ranged-loop may make it simpler.
3215 RefPtr<nsRange> range;
3216 for (uint32_t i = 0; i < rangeCount; i++) {
3217 range = SelectionRef().GetRangeAt(i);
3218 if (NS_WARN_IF(!range)) {
3219 return NS_ERROR_FAILURE;
3222 Element* deletedCell =
3223 HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(*range);
3224 if (!deletedCell) {
3225 SelectionRef().RemoveRangeAndUnselectFramesAndNotifyListeners(*range,
3226 error);
3227 NS_WARNING_ASSERTION(
3228 !error.Failed(),
3229 "Selection::RemoveRangeAndUnselectFramesAndNotifyListeners() "
3230 "failed, but ignored");
3231 rangeCount--;
3232 i--;
3236 // Set spans for the cell everything merged into
3237 rv = SetRowSpan(MOZ_KnownLive(scanner.ElementsRef()[0]),
3238 lastRowIndex - firstSelectedCellIndexes.mRow + 1);
3239 if (NS_FAILED(rv)) {
3240 NS_WARNING("HTMLEditor::SetRowSpan() failed");
3241 return EditorBase::ToGenericNSResult(rv);
3243 rv = SetColSpan(MOZ_KnownLive(scanner.ElementsRef()[0]),
3244 lastColIndex - firstSelectedCellIndexes.mColumn + 1);
3245 if (NS_FAILED(rv)) {
3246 NS_WARNING("HTMLEditor::SetColSpan() failed");
3247 return EditorBase::ToGenericNSResult(rv);
3250 // Fixup disturbances in table layout
3251 DebugOnly<nsresult> rvIgnored = NormalizeTableInternal(*table);
3252 NS_WARNING_ASSERTION(
3253 NS_SUCCEEDED(rvIgnored),
3254 "HTMLEditor::NormalizeTableInternal() failed, but ignored");
3255 } else {
3256 // Joining with cell to the right -- get rowspan and colspan data of target
3257 // cell.
3258 const auto leftCellData = CellData::AtIndexInTableElement(
3259 *this, *table, startRowIndex, startColIndex);
3260 if (NS_WARN_IF(leftCellData.FailedOrNotFound())) {
3261 return NS_ERROR_FAILURE;
3264 // Get data for cell to the right.
3265 const auto rightCellData = CellData::AtIndexInTableElement(
3266 *this, *table, leftCellData.mFirst.mRow,
3267 leftCellData.mFirst.mColumn + leftCellData.mEffectiveColSpan);
3268 if (NS_WARN_IF(rightCellData.FailedOrNotFound())) {
3269 return NS_ERROR_FAILURE;
3272 // XXX So, this does not assume that CellData returns error when just not
3273 // found. We need to fix this later.
3274 if (!rightCellData.mElement) {
3275 return NS_OK; // Don't fail if there's no cell
3278 // sanity check
3279 NS_ASSERTION(
3280 rightCellData.mCurrent.mRow >= rightCellData.mFirst.mRow,
3281 "JoinCells: rightCellData.mCurrent.mRow < rightCellData.mFirst.mRow");
3283 // Figure out span of merged cell starting from target's starting row
3284 // to handle case of merged cell starting in a row above
3285 int32_t spanAboveMergedCell = rightCellData.NumberOfPrecedingRows();
3286 int32_t effectiveRowSpan2 =
3287 rightCellData.mEffectiveRowSpan - spanAboveMergedCell;
3288 if (effectiveRowSpan2 > leftCellData.mEffectiveRowSpan) {
3289 // Cell to the right spans into row below target
3290 // Split off portion below target cell's bottom-most row
3291 nsresult rv = SplitCellIntoRows(
3292 table, rightCellData.mFirst.mRow, rightCellData.mFirst.mColumn,
3293 spanAboveMergedCell + leftCellData.mEffectiveRowSpan,
3294 effectiveRowSpan2 - leftCellData.mEffectiveRowSpan, nullptr);
3295 if (NS_FAILED(rv)) {
3296 NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
3297 return EditorBase::ToGenericNSResult(rv);
3301 // Move contents from cell to the right
3302 // Delete the cell now only if it starts in the same row
3303 // and has enough row "height"
3304 nsresult rv =
3305 MergeCells(leftCellData.mElement, rightCellData.mElement,
3306 !rightCellData.IsSpannedFromOtherRow() &&
3307 effectiveRowSpan2 >= leftCellData.mEffectiveRowSpan);
3308 if (NS_FAILED(rv)) {
3309 NS_WARNING("HTMLEditor::MergeCells() failed");
3310 return EditorBase::ToGenericNSResult(rv);
3313 if (effectiveRowSpan2 < leftCellData.mEffectiveRowSpan) {
3314 // Merged cell is "shorter"
3315 // (there are cells(s) below it that are row-spanned by target cell)
3316 // We could try splitting those cells, but that's REAL messy,
3317 // so the safest thing to do is NOT really join the cells
3318 return NS_OK;
3321 if (spanAboveMergedCell > 0) {
3322 // Cell we merged started in a row above the target cell
3323 // Reduce rowspan to give room where target cell will extend its colspan
3324 nsresult rv = SetRowSpan(rightCellData.mElement, spanAboveMergedCell);
3325 if (NS_FAILED(rv)) {
3326 NS_WARNING("HTMLEditor::SetRowSpan() failed");
3327 return EditorBase::ToGenericNSResult(rv);
3331 // Reset target cell's colspan to encompass cell to the right
3332 rv = SetColSpan(leftCellData.mElement, leftCellData.mEffectiveColSpan +
3333 rightCellData.mEffectiveColSpan);
3334 if (NS_FAILED(rv)) {
3335 NS_WARNING("HTMLEditor::SetColSpan() failed");
3336 return EditorBase::ToGenericNSResult(rv);
3339 return NS_OK;
3342 nsresult HTMLEditor::MergeCells(RefPtr<Element> aTargetCell,
3343 RefPtr<Element> aCellToMerge,
3344 bool aDeleteCellToMerge) {
3345 MOZ_ASSERT(IsEditActionDataAvailable());
3347 if (NS_WARN_IF(!aTargetCell) || NS_WARN_IF(!aCellToMerge)) {
3348 return NS_ERROR_INVALID_ARG;
3351 // Prevent rules testing until we're done
3352 IgnoredErrorResult ignoredError;
3353 AutoEditSubActionNotifier startToHandleEditSubAction(
3354 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
3355 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3356 return ignoredError.StealNSResult();
3358 NS_WARNING_ASSERTION(
3359 !ignoredError.Failed(),
3360 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3362 // Don't need to merge if cell is empty
3363 if (!IsEmptyCell(aCellToMerge)) {
3364 // Get index of last child in target cell
3365 // If we fail or don't have children,
3366 // we insert at index 0
3367 int32_t insertIndex = 0;
3369 // Start inserting just after last child
3370 uint32_t len = aTargetCell->GetChildCount();
3371 if (len == 1 && IsEmptyCell(aTargetCell)) {
3372 // Delete the empty node
3373 nsCOMPtr<nsIContent> cellChild = aTargetCell->GetFirstChild();
3374 if (NS_WARN_IF(!cellChild)) {
3375 return NS_ERROR_FAILURE;
3377 nsresult rv = DeleteNodeWithTransaction(*cellChild);
3378 if (NS_FAILED(rv)) {
3379 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3380 return rv;
3382 insertIndex = 0;
3383 } else {
3384 insertIndex = (int32_t)len;
3387 // Move the contents
3388 EditorDOMPoint pointToPutCaret;
3389 while (aCellToMerge->HasChildren()) {
3390 nsCOMPtr<nsIContent> cellChild = aCellToMerge->GetLastChild();
3391 if (NS_WARN_IF(!cellChild)) {
3392 return NS_ERROR_FAILURE;
3394 nsresult rv = DeleteNodeWithTransaction(*cellChild);
3395 if (NS_FAILED(rv)) {
3396 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3397 return rv;
3399 Result<CreateContentResult, nsresult> insertChildContentResult =
3400 InsertNodeWithTransaction(*cellChild,
3401 EditorDOMPoint(aTargetCell, insertIndex));
3402 if (MOZ_UNLIKELY(insertChildContentResult.isErr())) {
3403 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3404 return insertChildContentResult.unwrapErr();
3406 CreateContentResult unwrappedInsertChildContentResult =
3407 insertChildContentResult.unwrap();
3408 unwrappedInsertChildContentResult.MoveCaretPointTo(
3409 pointToPutCaret, *this,
3410 {SuggestCaret::OnlyIfHasSuggestion,
3411 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
3413 if (pointToPutCaret.IsSet()) {
3414 nsresult rv = CollapseSelectionTo(pointToPutCaret);
3415 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
3416 NS_WARNING(
3417 "EditorBase::CollapseSelectionTo() caused destroying the editor");
3418 return NS_ERROR_EDITOR_DESTROYED;
3420 NS_WARNING_ASSERTION(
3421 NS_SUCCEEDED(rv),
3422 "EditorBase::CollapseSelectionTo() failed, but ignored");
3426 if (!aDeleteCellToMerge) {
3427 return NS_OK;
3430 // Delete cells whose contents were moved.
3431 nsresult rv = DeleteNodeWithTransaction(*aCellToMerge);
3432 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3433 "EditorBase::DeleteNodeWithTransaction() failed");
3434 return rv;
3437 nsresult HTMLEditor::FixBadRowSpan(Element* aTable, int32_t aRowIndex,
3438 int32_t& aNewRowCount) {
3439 if (NS_WARN_IF(!aTable)) {
3440 return NS_ERROR_INVALID_ARG;
3443 const Result<TableSize, nsresult> tableSizeOrError =
3444 TableSize::Create(*this, *aTable);
3445 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3446 return tableSizeOrError.inspectErr();
3448 const TableSize& tableSize = tableSizeOrError.inspect();
3450 int32_t minRowSpan = -1;
3451 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
3452 const auto cellData =
3453 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, colIndex);
3454 // NOTE: This is a *real* failure.
3455 // CellData passes if cell is missing from cellmap
3456 // XXX If <table> has large rowspan value or colspan value than actual
3457 // cells, we may hit error. So, this method is always failed to
3458 // "fix" the rowspan...
3459 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3460 return NS_ERROR_FAILURE;
3463 // XXX So, this does not assume that CellData returns error when just not
3464 // found. We need to fix this later.
3465 if (!cellData.mElement) {
3466 break;
3469 if (cellData.mRowSpan > 0 && !cellData.IsSpannedFromOtherRow() &&
3470 (cellData.mRowSpan < minRowSpan || minRowSpan == -1)) {
3471 minRowSpan = cellData.mRowSpan;
3473 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3474 colIndex = cellData.NextColumnIndex();
3477 if (minRowSpan > 1) {
3478 // The amount to reduce everyone's rowspan
3479 // so at least one cell has rowspan = 1
3480 int32_t rowsReduced = minRowSpan - 1;
3481 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
3482 const auto cellData =
3483 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, colIndex);
3484 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3485 return NS_ERROR_FAILURE;
3488 // Fixup rowspans only for cells starting in current row
3489 // XXX So, this does not assume that CellData returns error when just
3490 // not found a cell. Fix this later.
3491 if (cellData.mElement && cellData.mRowSpan > 0 &&
3492 !cellData.IsSpannedFromOtherRowOrColumn()) {
3493 nsresult rv =
3494 SetRowSpan(cellData.mElement, cellData.mRowSpan - rowsReduced);
3495 if (NS_FAILED(rv)) {
3496 NS_WARNING("HTMLEditor::SetRawSpan() failed");
3497 return rv;
3500 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3501 colIndex = cellData.NextColumnIndex();
3504 const Result<TableSize, nsresult> newTableSizeOrError =
3505 TableSize::Create(*this, *aTable);
3506 if (NS_WARN_IF(newTableSizeOrError.isErr())) {
3507 return newTableSizeOrError.inspectErr();
3509 aNewRowCount = newTableSizeOrError.inspect().mRowCount;
3510 return NS_OK;
3513 nsresult HTMLEditor::FixBadColSpan(Element* aTable, int32_t aColIndex,
3514 int32_t& aNewColCount) {
3515 if (NS_WARN_IF(!aTable)) {
3516 return NS_ERROR_INVALID_ARG;
3519 const Result<TableSize, nsresult> tableSizeOrError =
3520 TableSize::Create(*this, *aTable);
3521 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3522 return tableSizeOrError.inspectErr();
3524 const TableSize& tableSize = tableSizeOrError.inspect();
3526 int32_t minColSpan = -1;
3527 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount;) {
3528 const auto cellData =
3529 CellData::AtIndexInTableElement(*this, *aTable, rowIndex, aColIndex);
3530 // NOTE: This is a *real* failure.
3531 // CellData passes if cell is missing from cellmap
3532 // XXX If <table> has large rowspan value or colspan value than actual
3533 // cells, we may hit error. So, this method is always failed to
3534 // "fix" the colspan...
3535 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3536 return NS_ERROR_FAILURE;
3539 // XXX So, this does not assume that CellData returns error when just
3540 // not found a cell. Fix this later.
3541 if (!cellData.mElement) {
3542 break;
3544 if (cellData.mColSpan > 0 && !cellData.IsSpannedFromOtherColumn() &&
3545 (cellData.mColSpan < minColSpan || minColSpan == -1)) {
3546 minColSpan = cellData.mColSpan;
3548 MOZ_ASSERT(rowIndex < cellData.NextRowIndex());
3549 rowIndex = cellData.NextRowIndex();
3552 if (minColSpan > 1) {
3553 // The amount to reduce everyone's colspan
3554 // so at least one cell has colspan = 1
3555 int32_t colsReduced = minColSpan - 1;
3556 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount;) {
3557 const auto cellData =
3558 CellData::AtIndexInTableElement(*this, *aTable, rowIndex, aColIndex);
3559 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3560 return NS_ERROR_FAILURE;
3563 // Fixup colspans only for cells starting in current column
3564 // XXX So, this does not assume that CellData returns error when just
3565 // not found a cell. Fix this later.
3566 if (cellData.mElement && cellData.mColSpan > 0 &&
3567 !cellData.IsSpannedFromOtherRowOrColumn()) {
3568 nsresult rv =
3569 SetColSpan(cellData.mElement, cellData.mColSpan - colsReduced);
3570 if (NS_FAILED(rv)) {
3571 NS_WARNING("HTMLEditor::SetColSpan() failed");
3572 return rv;
3575 MOZ_ASSERT(rowIndex < cellData.NextRowIndex());
3576 rowIndex = cellData.NextRowIndex();
3579 const Result<TableSize, nsresult> newTableSizeOrError =
3580 TableSize::Create(*this, *aTable);
3581 if (NS_WARN_IF(newTableSizeOrError.isErr())) {
3582 return newTableSizeOrError.inspectErr();
3584 aNewColCount = newTableSizeOrError.inspect().mColumnCount;
3585 return NS_OK;
3588 NS_IMETHODIMP HTMLEditor::NormalizeTable(Element* aTableOrElementInTable) {
3589 AutoEditActionDataSetter editActionData(*this, EditAction::eNormalizeTable);
3590 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
3591 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
3592 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3593 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3594 return EditorBase::ToGenericNSResult(rv);
3597 if (!aTableOrElementInTable) {
3598 aTableOrElementInTable =
3599 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
3600 if (!aTableOrElementInTable) {
3601 NS_WARNING(
3602 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
3603 "table) failed");
3604 return NS_OK; // Don't throw error even if the element is not in <table>.
3607 rv = NormalizeTableInternal(*aTableOrElementInTable);
3608 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3609 "HTMLEditor::NormalizeTableInternal() failed");
3610 return EditorBase::ToGenericNSResult(rv);
3613 nsresult HTMLEditor::NormalizeTableInternal(Element& aTableOrElementInTable) {
3614 MOZ_ASSERT(IsEditActionDataAvailable());
3616 RefPtr<Element> tableElement;
3617 if (aTableOrElementInTable.NodeInfo()->NameAtom() == nsGkAtoms::table) {
3618 tableElement = &aTableOrElementInTable;
3619 } else {
3620 tableElement = GetInclusiveAncestorByTagNameInternal(
3621 *nsGkAtoms::table, aTableOrElementInTable);
3622 if (!tableElement) {
3623 NS_WARNING(
3624 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
3625 "failed");
3626 return NS_OK; // Don't throw error even if the element is not in <table>.
3630 Result<TableSize, nsresult> tableSizeOrError =
3631 TableSize::Create(*this, *tableElement);
3632 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3633 return tableSizeOrError.unwrapErr();
3635 // FYI: Cannot be const because the row/column count will be updated
3636 TableSize tableSize = tableSizeOrError.unwrap();
3638 // Save current selection
3639 AutoSelectionRestorer restoreSelectionLater(*this);
3641 AutoPlaceholderBatch treateAsOneTransaction(
3642 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3643 // Prevent auto insertion of BR in new cell until we're done
3644 IgnoredErrorResult error;
3645 AutoEditSubActionNotifier startToHandleEditSubAction(
3646 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
3647 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3648 return error.StealNSResult();
3650 NS_WARNING_ASSERTION(
3651 !error.Failed(),
3652 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3654 // XXX If there is a cell which has bigger or smaller "rowspan" or "colspan"
3655 // values, FixBadRowSpan() will return error. So, we can do nothing
3656 // if the table needs normalization...
3657 // Scan all cells in each row to detect bad rowspan values
3658 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3659 nsresult rv = FixBadRowSpan(tableElement, rowIndex, tableSize.mRowCount);
3660 if (NS_FAILED(rv)) {
3661 NS_WARNING("HTMLEditor::FixBadRowSpan() failed");
3662 return rv;
3665 // and same for colspans
3666 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
3667 nsresult rv = FixBadColSpan(tableElement, colIndex, tableSize.mColumnCount);
3668 if (NS_FAILED(rv)) {
3669 NS_WARNING("HTMLEditor::FixBadColSpan() failed");
3670 return rv;
3674 // Fill in missing cellmap locations with empty cells
3675 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3676 RefPtr<Element> previousCellElementInRow;
3677 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
3678 const auto cellData = CellData::AtIndexInTableElement(
3679 *this, *tableElement, rowIndex, colIndex);
3680 // NOTE: This is a *real* failure.
3681 // CellData passes if cell is missing from cellmap
3682 // XXX So, this method assumes that CellData won't return error when
3683 // just not found. Fix this later.
3684 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3685 return NS_ERROR_FAILURE;
3688 if (cellData.mElement) {
3689 // Save the last cell found in the same row we are scanning
3690 if (!cellData.IsSpannedFromOtherRow()) {
3691 previousCellElementInRow = std::move(cellData.mElement);
3693 continue;
3696 // We are missing a cell at a cellmap location.
3697 // Add a cell after the previous cell element in the current row.
3698 if (NS_WARN_IF(!previousCellElementInRow)) {
3699 // We don't have any cells in this row -- We are really messed up!
3700 return NS_ERROR_FAILURE;
3703 // Insert a new cell after (true), and return the new cell to us
3704 RefPtr<Element> newCellElement;
3705 nsresult rv = InsertCell(previousCellElementInRow, 1, 1, true, false,
3706 getter_AddRefs(newCellElement));
3707 if (NS_FAILED(rv)) {
3708 NS_WARNING("HTMLEditor::InsertCell() failed");
3709 return rv;
3712 if (newCellElement) {
3713 previousCellElementInRow = std::move(newCellElement);
3717 return NS_OK;
3720 NS_IMETHODIMP HTMLEditor::GetCellIndexes(Element* aCellElement,
3721 int32_t* aRowIndex,
3722 int32_t* aColumnIndex) {
3723 if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex)) {
3724 return NS_ERROR_INVALID_ARG;
3727 AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellIndexes);
3728 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3729 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
3730 NS_WARNING("HTMLEditor::GetCellIndexes() couldn't handle the job");
3731 return EditorBase::ToGenericNSResult(rv);
3734 *aRowIndex = 0;
3735 *aColumnIndex = 0;
3737 if (!aCellElement) {
3738 // Use cell element which contains anchor of Selection when aCellElement is
3739 // nullptr.
3740 const CellIndexes cellIndexes(*this, SelectionRef());
3741 if (NS_WARN_IF(cellIndexes.isErr())) {
3742 return NS_ERROR_FAILURE;
3744 *aRowIndex = cellIndexes.mRow;
3745 *aColumnIndex = cellIndexes.mColumn;
3746 return NS_OK;
3749 const RefPtr<PresShell> presShell{GetPresShell()};
3750 const CellIndexes cellIndexes(*aCellElement, presShell);
3751 if (NS_WARN_IF(cellIndexes.isErr())) {
3752 return NS_ERROR_FAILURE;
3754 *aRowIndex = cellIndexes.mRow;
3755 *aColumnIndex = cellIndexes.mColumn;
3756 return NS_OK;
3759 // static
3760 nsTableWrapperFrame* HTMLEditor::GetTableFrame(const Element* aTableElement) {
3761 if (NS_WARN_IF(!aTableElement)) {
3762 return nullptr;
3764 return do_QueryFrame(aTableElement->GetPrimaryFrame());
3767 // Return actual number of cells (a cell with colspan > 1 counts as just 1)
3768 int32_t HTMLEditor::GetNumberOfCellsInRow(Element& aTableElement,
3769 int32_t aRowIndex) {
3770 const Result<TableSize, nsresult> tableSizeOrError =
3771 TableSize::Create(*this, aTableElement);
3772 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3773 return -1;
3776 int32_t numberOfCells = 0;
3777 for (int32_t columnIndex = 0;
3778 columnIndex < tableSizeOrError.inspect().mColumnCount;) {
3779 const auto cellData = CellData::AtIndexInTableElement(
3780 *this, aTableElement, aRowIndex, columnIndex);
3781 // Failure means that there is no more cell in the row. In this case,
3782 // we shouldn't return error since we just reach the end of the row.
3783 // XXX So, this method assumes that CellData won't return error when
3784 // just not found. Fix this later.
3785 if (cellData.FailedOrNotFound()) {
3786 break;
3789 // Only count cells that start in row we are working with
3790 if (cellData.mElement && !cellData.IsSpannedFromOtherRow()) {
3791 numberOfCells++;
3793 MOZ_ASSERT(columnIndex < cellData.NextColumnIndex());
3794 columnIndex = cellData.NextColumnIndex();
3796 return numberOfCells;
3799 NS_IMETHODIMP HTMLEditor::GetTableSize(Element* aTableOrElementInTable,
3800 int32_t* aRowCount,
3801 int32_t* aColumnCount) {
3802 if (NS_WARN_IF(!aRowCount) || NS_WARN_IF(!aColumnCount)) {
3803 return NS_ERROR_INVALID_ARG;
3806 AutoEditActionDataSetter editActionData(*this, EditAction::eGetTableSize);
3807 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3808 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
3809 NS_WARNING("HTMLEditor::GetTableSize() couldn't handle the job");
3810 return EditorBase::ToGenericNSResult(rv);
3813 *aRowCount = 0;
3814 *aColumnCount = 0;
3816 Element* tableOrElementInTable = aTableOrElementInTable;
3817 if (!tableOrElementInTable) {
3818 tableOrElementInTable =
3819 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
3820 if (!tableOrElementInTable) {
3821 NS_WARNING(
3822 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
3823 "table) failed");
3824 return NS_ERROR_FAILURE;
3828 const Result<TableSize, nsresult> tableSizeOrError =
3829 TableSize::Create(*this, *tableOrElementInTable);
3830 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3831 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
3833 *aRowCount = tableSizeOrError.inspect().mRowCount;
3834 *aColumnCount = tableSizeOrError.inspect().mColumnCount;
3835 return NS_OK;
3838 NS_IMETHODIMP HTMLEditor::GetCellDataAt(
3839 Element* aTableElement, int32_t aRowIndex, int32_t aColumnIndex,
3840 Element** aCellElement, int32_t* aStartRowIndex, int32_t* aStartColumnIndex,
3841 int32_t* aRowSpan, int32_t* aColSpan, int32_t* aEffectiveRowSpan,
3842 int32_t* aEffectiveColSpan, bool* aIsSelected) {
3843 if (NS_WARN_IF(!aCellElement) || NS_WARN_IF(!aStartRowIndex) ||
3844 NS_WARN_IF(!aStartColumnIndex) || NS_WARN_IF(!aRowSpan) ||
3845 NS_WARN_IF(!aColSpan) || NS_WARN_IF(!aEffectiveRowSpan) ||
3846 NS_WARN_IF(!aEffectiveColSpan) || NS_WARN_IF(!aIsSelected)) {
3847 return NS_ERROR_INVALID_ARG;
3850 AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellDataAt);
3851 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3852 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
3853 NS_WARNING("HTMLEditor::GetCellDataAt() couldn't handle the job");
3854 return EditorBase::ToGenericNSResult(rv);
3857 *aStartRowIndex = 0;
3858 *aStartColumnIndex = 0;
3859 *aRowSpan = 0;
3860 *aColSpan = 0;
3861 *aEffectiveRowSpan = 0;
3862 *aEffectiveColSpan = 0;
3863 *aIsSelected = false;
3864 *aCellElement = nullptr;
3866 // Let's keep the table element with strong pointer since editor developers
3867 // may not handle layout code of <table>, however, this method depends on
3868 // them.
3869 RefPtr<Element> table = aTableElement;
3870 if (!table) {
3871 // Get the selected table or the table enclosing the selection anchor.
3872 table = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
3873 if (!table) {
3874 NS_WARNING(
3875 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
3876 "table) failed");
3877 return NS_ERROR_FAILURE;
3881 const CellData cellData =
3882 CellData::AtIndexInTableElement(*this, *table, aRowIndex, aColumnIndex);
3883 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3884 return NS_ERROR_FAILURE;
3886 NS_ADDREF(*aCellElement = cellData.mElement.get());
3887 *aIsSelected = cellData.mIsSelected;
3888 *aStartRowIndex = cellData.mFirst.mRow;
3889 *aStartColumnIndex = cellData.mFirst.mColumn;
3890 *aRowSpan = cellData.mRowSpan;
3891 *aColSpan = cellData.mColSpan;
3892 *aEffectiveRowSpan = cellData.mEffectiveRowSpan;
3893 *aEffectiveColSpan = cellData.mEffectiveColSpan;
3894 return NS_OK;
3897 NS_IMETHODIMP HTMLEditor::GetCellAt(Element* aTableElement, int32_t aRowIndex,
3898 int32_t aColumnIndex,
3899 Element** aCellElement) {
3900 if (NS_WARN_IF(!aCellElement)) {
3901 return NS_ERROR_INVALID_ARG;
3904 AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellAt);
3905 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3906 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
3907 NS_WARNING("HTMLEditor::GetCellAt() couldn't handle the job");
3908 return EditorBase::ToGenericNSResult(rv);
3911 *aCellElement = nullptr;
3913 Element* tableElement = aTableElement;
3914 if (!tableElement) {
3915 // Get the selected table or the table enclosing the selection anchor.
3916 tableElement = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
3917 if (!tableElement) {
3918 NS_WARNING(
3919 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
3920 "table) failed");
3921 return NS_ERROR_FAILURE;
3925 RefPtr<Element> cellElement =
3926 GetTableCellElementAt(*tableElement, aRowIndex, aColumnIndex);
3927 cellElement.forget(aCellElement);
3928 return NS_OK;
3931 Element* HTMLEditor::GetTableCellElementAt(Element& aTableElement,
3932 int32_t aRowIndex,
3933 int32_t aColumnIndex) const {
3934 // Let's grab the <table> element while we're retrieving layout API since
3935 // editor developers do not watch all layout API changes. So, it may
3936 // become unsafe.
3937 OwningNonNull<Element> tableElement(aTableElement);
3938 nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(tableElement);
3939 if (!tableFrame) {
3940 NS_WARNING("There was no table layout information");
3941 return nullptr;
3943 nsIContent* cell = tableFrame->GetCellAt(aRowIndex, aColumnIndex);
3944 return Element::FromNodeOrNull(cell);
3947 // When all you want are the rowspan and colspan (not exposed in nsITableEditor)
3948 nsresult HTMLEditor::GetCellSpansAt(Element* aTable, int32_t aRowIndex,
3949 int32_t aColIndex, int32_t& aActualRowSpan,
3950 int32_t& aActualColSpan) {
3951 nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(aTable);
3952 if (!tableFrame) {
3953 NS_WARNING("There was no table layout information");
3954 return NS_ERROR_FAILURE;
3956 aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
3957 aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex);
3959 return NS_OK;
3962 nsresult HTMLEditor::GetCellContext(Element** aTable, Element** aCell,
3963 nsINode** aCellParent, int32_t* aCellOffset,
3964 int32_t* aRowIndex, int32_t* aColumnIndex) {
3965 MOZ_ASSERT(IsEditActionDataAvailable());
3967 // Initialize return pointers
3968 if (aTable) {
3969 *aTable = nullptr;
3971 if (aCell) {
3972 *aCell = nullptr;
3974 if (aCellParent) {
3975 *aCellParent = nullptr;
3977 if (aCellOffset) {
3978 *aCellOffset = 0;
3980 if (aRowIndex) {
3981 *aRowIndex = 0;
3983 if (aColumnIndex) {
3984 *aColumnIndex = 0;
3987 RefPtr<Element> table;
3988 RefPtr<Element> cell;
3990 // Caller may supply the cell...
3991 if (aCell && *aCell) {
3992 cell = *aCell;
3995 // ...but if not supplied,
3996 // get cell if it's the child of selection anchor node,
3997 // or get the enclosing by a cell
3998 if (!cell) {
3999 // Find a selected or enclosing table element
4000 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
4001 GetSelectedOrParentTableElement();
4002 if (cellOrRowOrTableElementOrError.isErr()) {
4003 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
4004 return cellOrRowOrTableElementOrError.unwrapErr();
4006 if (!cellOrRowOrTableElementOrError.inspect()) {
4007 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
4009 if (HTMLEditUtils::IsTable(cellOrRowOrTableElementOrError.inspect())) {
4010 // We have a selected table, not a cell
4011 if (aTable) {
4012 cellOrRowOrTableElementOrError.unwrap().forget(aTable);
4014 return NS_OK;
4016 if (!HTMLEditUtils::IsTableCell(cellOrRowOrTableElementOrError.inspect())) {
4017 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
4020 // We found a cell
4021 cell = cellOrRowOrTableElementOrError.unwrap();
4023 if (aCell) {
4024 // we don't want to cell.forget() here, because we use it below.
4025 *aCell = do_AddRef(cell).take();
4028 // Get containing table
4029 table = GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *cell);
4030 if (!table) {
4031 NS_WARNING(
4032 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
4033 "failed");
4034 // Cell must be in a table, so fail if not found
4035 return NS_ERROR_FAILURE;
4037 if (aTable) {
4038 table.forget(aTable);
4041 // Get the rest of the related data only if requested
4042 if (aRowIndex || aColumnIndex) {
4043 const RefPtr<PresShell> presShell{GetPresShell()};
4044 const CellIndexes cellIndexes(*cell, presShell);
4045 if (NS_WARN_IF(cellIndexes.isErr())) {
4046 return NS_ERROR_FAILURE;
4048 if (aRowIndex) {
4049 *aRowIndex = cellIndexes.mRow;
4051 if (aColumnIndex) {
4052 *aColumnIndex = cellIndexes.mColumn;
4055 if (aCellParent) {
4056 // Get the immediate parent of the cell
4057 EditorRawDOMPoint atCellElement(cell);
4058 if (NS_WARN_IF(!atCellElement.IsSet())) {
4059 return NS_ERROR_FAILURE;
4062 if (aCellOffset) {
4063 *aCellOffset = atCellElement.Offset();
4066 // Now it's safe to hand over the reference to cellParent, since
4067 // we don't need it anymore.
4068 *aCellParent = do_AddRef(atCellElement.GetContainer()).take();
4071 return NS_OK;
4074 NS_IMETHODIMP HTMLEditor::GetSelectedCells(
4075 nsTArray<RefPtr<Element>>& aOutSelectedCellElements) {
4076 MOZ_ASSERT(aOutSelectedCellElements.IsEmpty());
4078 AutoEditActionDataSetter editActionData(*this, EditAction::eGetSelectedCells);
4079 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4080 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
4081 NS_WARNING("HTMLEditor::GetSelectedCells() couldn't handle the job");
4082 return EditorBase::ToGenericNSResult(rv);
4085 SelectedTableCellScanner scanner(SelectionRef());
4086 if (!scanner.IsInTableCellSelectionMode()) {
4087 return NS_OK;
4090 aOutSelectedCellElements.SetCapacity(scanner.ElementsRef().Length());
4091 for (const OwningNonNull<Element>& cellElement : scanner.ElementsRef()) {
4092 aOutSelectedCellElements.AppendElement(cellElement);
4094 return NS_OK;
4097 NS_IMETHODIMP HTMLEditor::GetFirstSelectedCellInTable(int32_t* aRowIndex,
4098 int32_t* aColumnIndex,
4099 Element** aCellElement) {
4100 if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex) ||
4101 NS_WARN_IF(!aCellElement)) {
4102 return NS_ERROR_INVALID_ARG;
4105 AutoEditActionDataSetter editActionData(
4106 *this, EditAction::eGetFirstSelectedCellInTable);
4107 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4108 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
4109 NS_WARNING(
4110 "HTMLEditor::GetFirstSelectedCellInTable() couldn't handle the job");
4111 return EditorBase::ToGenericNSResult(rv);
4114 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
4115 return NS_ERROR_FAILURE; // XXX Should return NS_OK?
4118 *aRowIndex = 0;
4119 *aColumnIndex = 0;
4120 *aCellElement = nullptr;
4121 RefPtr<Element> firstSelectedCellElement =
4122 HTMLEditUtils::GetFirstSelectedTableCellElement(SelectionRef());
4123 if (!firstSelectedCellElement) {
4124 return NS_OK;
4127 RefPtr<PresShell> presShell = GetPresShell();
4128 const CellIndexes indexes(*firstSelectedCellElement, presShell);
4129 if (NS_WARN_IF(indexes.isErr())) {
4130 return NS_ERROR_FAILURE;
4133 firstSelectedCellElement.forget(aCellElement);
4134 *aRowIndex = indexes.mRow;
4135 *aColumnIndex = indexes.mColumn;
4136 return NS_OK;
4139 void HTMLEditor::SetSelectionAfterTableEdit(Element* aTable, int32_t aRow,
4140 int32_t aCol, int32_t aDirection,
4141 bool aSelected) {
4142 MOZ_ASSERT(IsEditActionDataAvailable());
4144 if (NS_WARN_IF(!aTable) || NS_WARN_IF(Destroyed())) {
4145 return;
4148 RefPtr<Element> cell;
4149 bool done = false;
4150 do {
4151 cell = GetTableCellElementAt(*aTable, aRow, aCol);
4152 if (cell) {
4153 if (aSelected) {
4154 // Reselect the cell
4155 DebugOnly<nsresult> rv = SelectContentInternal(*cell);
4156 NS_WARNING_ASSERTION(
4157 NS_SUCCEEDED(rv),
4158 "HTMLEditor::SelectContentInternal() failed, but ignored");
4159 return;
4162 // Set the caret to deepest first child
4163 // but don't go into nested tables
4164 // TODO: Should we really be placing the caret at the END
4165 // of the cell content?
4166 CollapseSelectionToDeepestNonTableFirstChild(cell);
4167 return;
4170 // Setup index to find another cell in the
4171 // direction requested, but move in other direction if already at
4172 // beginning of row or column
4173 switch (aDirection) {
4174 case ePreviousColumn:
4175 if (!aCol) {
4176 if (aRow > 0) {
4177 aRow--;
4178 } else {
4179 done = true;
4181 } else {
4182 aCol--;
4184 break;
4185 case ePreviousRow:
4186 if (!aRow) {
4187 if (aCol > 0) {
4188 aCol--;
4189 } else {
4190 done = true;
4192 } else {
4193 aRow--;
4195 break;
4196 default:
4197 done = true;
4199 } while (!done);
4201 // We didn't find a cell
4202 // Set selection to just before the table
4203 if (aTable->GetParentNode()) {
4204 EditorRawDOMPoint atTable(aTable);
4205 if (NS_WARN_IF(!atTable.IsSetAndValid())) {
4206 return;
4208 DebugOnly<nsresult> rvIgnored = CollapseSelectionTo(atTable);
4209 NS_WARNING_ASSERTION(
4210 NS_SUCCEEDED(rvIgnored),
4211 "EditorBase::CollapseSelectionTo() failed, but ignored");
4212 return;
4214 // Last resort: Set selection to start of doc
4215 // (it's very bad to not have a valid selection!)
4216 DebugOnly<nsresult> rvIgnored = SetSelectionAtDocumentStart();
4217 NS_WARNING_ASSERTION(
4218 NS_SUCCEEDED(rvIgnored),
4219 "HTMLEditor::SetSelectionAtDocumentStart() failed, but ignored");
4222 NS_IMETHODIMP HTMLEditor::GetSelectedOrParentTableElement(
4223 nsAString& aTagName, int32_t* aSelectedCount,
4224 Element** aCellOrRowOrTableElement) {
4225 if (NS_WARN_IF(!aSelectedCount) || NS_WARN_IF(!aCellOrRowOrTableElement)) {
4226 return NS_ERROR_INVALID_ARG;
4229 aTagName.Truncate();
4230 *aCellOrRowOrTableElement = nullptr;
4231 *aSelectedCount = 0;
4233 AutoEditActionDataSetter editActionData(
4234 *this, EditAction::eGetSelectedOrParentTableElement);
4235 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4236 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
4237 NS_WARNING(
4238 "HTMLEditor::GetSelectedOrParentTableElement() couldn't handle the "
4239 "job");
4240 return EditorBase::ToGenericNSResult(rv);
4243 bool isCellSelected = false;
4244 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
4245 GetSelectedOrParentTableElement(&isCellSelected);
4246 if (cellOrRowOrTableElementOrError.isErr()) {
4247 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
4248 return EditorBase::ToGenericNSResult(
4249 cellOrRowOrTableElementOrError.unwrapErr());
4251 if (!cellOrRowOrTableElementOrError.inspect()) {
4252 return NS_OK;
4254 RefPtr<Element> cellOrRowOrTableElement =
4255 cellOrRowOrTableElementOrError.unwrap();
4257 if (isCellSelected) {
4258 aTagName.AssignLiteral("td");
4259 *aSelectedCount = SelectionRef().RangeCount();
4260 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4261 return NS_OK;
4264 if (HTMLEditUtils::IsTableCell(cellOrRowOrTableElement)) {
4265 aTagName.AssignLiteral("td");
4266 // Keep *aSelectedCount as 0.
4267 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4268 return NS_OK;
4271 if (HTMLEditUtils::IsTable(cellOrRowOrTableElement)) {
4272 aTagName.AssignLiteral("table");
4273 *aSelectedCount = 1;
4274 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4275 return NS_OK;
4278 if (HTMLEditUtils::IsTableRow(cellOrRowOrTableElement)) {
4279 aTagName.AssignLiteral("tr");
4280 *aSelectedCount = 1;
4281 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4282 return NS_OK;
4285 MOZ_ASSERT_UNREACHABLE("Which element was returned?");
4286 return NS_ERROR_UNEXPECTED;
4289 Result<RefPtr<Element>, nsresult> HTMLEditor::GetSelectedOrParentTableElement(
4290 bool* aIsCellSelected /* = nullptr */) const {
4291 MOZ_ASSERT(IsEditActionDataAvailable());
4293 if (aIsCellSelected) {
4294 *aIsCellSelected = false;
4297 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
4298 return Err(NS_ERROR_FAILURE); // XXX Shouldn't throw an exception?
4301 // Try to get the first selected cell, first.
4302 RefPtr<Element> cellElement =
4303 HTMLEditUtils::GetFirstSelectedTableCellElement(SelectionRef());
4304 if (cellElement) {
4305 if (aIsCellSelected) {
4306 *aIsCellSelected = true;
4308 return cellElement;
4311 const RangeBoundary& anchorRef = SelectionRef().AnchorRef();
4312 if (NS_WARN_IF(!anchorRef.IsSet())) {
4313 return Err(NS_ERROR_FAILURE);
4316 // If anchor selects a <td>, <table> or <tr>, return it.
4317 if (anchorRef.Container()->HasChildNodes()) {
4318 nsIContent* selectedContent = anchorRef.GetChildAtOffset();
4319 if (selectedContent) {
4320 // XXX Why do we ignore <th> element in this case?
4321 if (selectedContent->IsHTMLElement(nsGkAtoms::td)) {
4322 // FYI: If first range selects a <tr> element, but the other selects
4323 // a <td> element, you can reach here.
4324 // Each cell is in its own selection range in this case.
4325 // XXX Although, other ranges may not select cells, though.
4326 if (aIsCellSelected) {
4327 *aIsCellSelected = true;
4329 return RefPtr<Element>(selectedContent->AsElement());
4331 if (selectedContent->IsAnyOfHTMLElements(nsGkAtoms::table,
4332 nsGkAtoms::tr)) {
4333 return RefPtr<Element>(selectedContent->AsElement());
4338 if (NS_WARN_IF(!anchorRef.Container()->IsContent())) {
4339 return RefPtr<Element>();
4342 // Then, look for a cell element (either <td> or <th>) which contains
4343 // the anchor container.
4344 cellElement = GetInclusiveAncestorByTagNameInternal(
4345 *nsGkAtoms::td, *anchorRef.Container()->AsContent());
4346 if (!cellElement) {
4347 return RefPtr<Element>(); // Not in table.
4349 // Don't set *aIsCellSelected to true in this case because it does NOT
4350 // select a cell, just in a cell.
4351 return cellElement;
4354 Result<RefPtr<Element>, nsresult>
4355 HTMLEditor::GetFirstSelectedCellElementInTable() const {
4356 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
4357 GetSelectedOrParentTableElement();
4358 if (cellOrRowOrTableElementOrError.isErr()) {
4359 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
4360 return cellOrRowOrTableElementOrError;
4363 if (!cellOrRowOrTableElementOrError.inspect()) {
4364 return cellOrRowOrTableElementOrError;
4367 const RefPtr<Element>& element = cellOrRowOrTableElementOrError.inspect();
4368 if (!HTMLEditUtils::IsTableCell(element)) {
4369 return RefPtr<Element>();
4372 if (!HTMLEditUtils::IsTableRow(element->GetParentNode())) {
4373 NS_WARNING("There was no parent <tr> element for the found cell");
4374 return RefPtr<Element>();
4377 if (!HTMLEditUtils::GetClosestAncestorTableElement(*element)) {
4378 NS_WARNING("There was no ancestor <table> element for the found cell");
4379 return Err(NS_ERROR_FAILURE);
4381 return cellOrRowOrTableElementOrError;
4384 NS_IMETHODIMP HTMLEditor::GetSelectedCellsType(Element* aElement,
4385 uint32_t* aSelectionType) {
4386 if (NS_WARN_IF(!aSelectionType)) {
4387 return NS_ERROR_INVALID_ARG;
4389 *aSelectionType = 0;
4391 AutoEditActionDataSetter editActionData(*this,
4392 EditAction::eGetSelectedCellsType);
4393 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4394 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
4395 NS_WARNING("HTMLEditor::GetSelectedCellsType() couldn't handle the job");
4396 return EditorBase::ToGenericNSResult(rv);
4399 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
4400 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
4403 // Be sure we have a table element
4404 // (if aElement is null, this uses selection's anchor node)
4405 RefPtr<Element> table;
4406 if (aElement) {
4407 table = GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *aElement);
4408 if (!table) {
4409 NS_WARNING(
4410 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
4411 "failed");
4412 return NS_ERROR_FAILURE;
4414 } else {
4415 table = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
4416 if (!table) {
4417 NS_WARNING(
4418 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
4419 "table) failed");
4420 return NS_ERROR_FAILURE;
4424 const Result<TableSize, nsresult> tableSizeOrError =
4425 TableSize::Create(*this, *table);
4426 if (NS_WARN_IF(tableSizeOrError.isErr())) {
4427 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
4429 const TableSize& tableSize = tableSizeOrError.inspect();
4431 // Traverse all selected cells
4432 SelectedTableCellScanner scanner(SelectionRef());
4433 if (!scanner.IsInTableCellSelectionMode()) {
4434 return NS_OK;
4437 // We have at least one selected cell, so set return value
4438 *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Cell);
4440 // Store indexes of each row/col to avoid duplication of searches
4441 nsTArray<int32_t> indexArray;
4443 const RefPtr<PresShell> presShell{GetPresShell()};
4444 bool allCellsInRowAreSelected = false;
4445 for (const OwningNonNull<Element>& selectedCellElement :
4446 scanner.ElementsRef()) {
4447 // `MOZ_KnownLive(selectedCellElement)` is safe because `scanner` grabs
4448 // it until it's destroyed later.
4449 const CellIndexes selectedCellIndexes(MOZ_KnownLive(selectedCellElement),
4450 presShell);
4451 if (NS_WARN_IF(selectedCellIndexes.isErr())) {
4452 return NS_ERROR_FAILURE;
4454 if (!indexArray.Contains(selectedCellIndexes.mColumn)) {
4455 indexArray.AppendElement(selectedCellIndexes.mColumn);
4456 allCellsInRowAreSelected = AllCellsInRowSelected(
4457 table, selectedCellIndexes.mRow, tableSize.mColumnCount);
4458 // We're done as soon as we fail for any row
4459 if (!allCellsInRowAreSelected) {
4460 break;
4465 if (allCellsInRowAreSelected) {
4466 *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Row);
4467 return NS_OK;
4469 // Test for columns
4471 // Empty the indexArray
4472 indexArray.Clear();
4474 // Start at first cell again
4475 bool allCellsInColAreSelected = false;
4476 for (const OwningNonNull<Element>& selectedCellElement :
4477 scanner.ElementsRef()) {
4478 // `MOZ_KnownLive(selectedCellElement)` is safe because `scanner` grabs
4479 // it until it's destroyed later.
4480 const CellIndexes selectedCellIndexes(MOZ_KnownLive(selectedCellElement),
4481 presShell);
4482 if (NS_WARN_IF(selectedCellIndexes.isErr())) {
4483 return NS_ERROR_FAILURE;
4486 if (!indexArray.Contains(selectedCellIndexes.mRow)) {
4487 indexArray.AppendElement(selectedCellIndexes.mColumn);
4488 allCellsInColAreSelected = AllCellsInColumnSelected(
4489 table, selectedCellIndexes.mColumn, tableSize.mRowCount);
4490 // We're done as soon as we fail for any column
4491 if (!allCellsInRowAreSelected) {
4492 break;
4496 if (allCellsInColAreSelected) {
4497 *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Column);
4500 return NS_OK;
4503 bool HTMLEditor::AllCellsInRowSelected(Element* aTable, int32_t aRowIndex,
4504 int32_t aNumberOfColumns) {
4505 if (NS_WARN_IF(!aTable)) {
4506 return false;
4509 for (int32_t col = 0; col < aNumberOfColumns;) {
4510 const auto cellData =
4511 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, col);
4512 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
4513 return false;
4516 // If no cell, we may have a "ragged" right edge, so return TRUE only if
4517 // we already found a cell in the row.
4518 // XXX So, this does not assume that CellData returns error when just
4519 // not found a cell. Fix this later.
4520 if (!cellData.mElement) {
4521 NS_WARNING("CellData didn't set mElement");
4522 return cellData.mCurrent.mColumn > 0;
4525 // Return as soon as a non-selected cell is found.
4526 // XXX Odd, this is testing if each cell element is selected. Why do
4527 // we need to warn if it's false??
4528 if (!cellData.mIsSelected) {
4529 NS_WARNING("CellData didn't set mIsSelected");
4530 return false;
4533 MOZ_ASSERT(col < cellData.NextColumnIndex());
4534 col = cellData.NextColumnIndex();
4536 return true;
4539 bool HTMLEditor::AllCellsInColumnSelected(Element* aTable, int32_t aColIndex,
4540 int32_t aNumberOfRows) {
4541 if (NS_WARN_IF(!aTable)) {
4542 return false;
4545 for (int32_t row = 0; row < aNumberOfRows;) {
4546 const auto cellData =
4547 CellData::AtIndexInTableElement(*this, *aTable, row, aColIndex);
4548 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
4549 return false;
4552 // If no cell, we must have a "ragged" right edge on the last column so
4553 // return TRUE only if we already found a cell in the row.
4554 // XXX So, this does not assume that CellData returns error when just
4555 // not found a cell. Fix this later.
4556 if (!cellData.mElement) {
4557 NS_WARNING("CellData didn't set mElement");
4558 return cellData.mCurrent.mRow > 0;
4561 // Return as soon as a non-selected cell is found.
4562 // XXX Odd, this is testing if each cell element is selected. Why do
4563 // we need to warn if it's false??
4564 if (!cellData.mIsSelected) {
4565 NS_WARNING("CellData didn't set mIsSelected");
4566 return false;
4569 MOZ_ASSERT(row < cellData.NextRowIndex());
4570 row = cellData.NextRowIndex();
4572 return true;
4575 bool HTMLEditor::IsEmptyCell(dom::Element* aCell) {
4576 MOZ_ASSERT(aCell);
4578 // Check if target only contains empty text node or <br>
4579 nsCOMPtr<nsINode> cellChild = aCell->GetFirstChild();
4580 if (!cellChild) {
4581 return false;
4584 nsCOMPtr<nsINode> nextChild = cellChild->GetNextSibling();
4585 if (nextChild) {
4586 return false;
4589 // We insert a single break into a cell by default
4590 // to have some place to locate a cursor -- it is dispensable
4591 if (cellChild->IsHTMLElement(nsGkAtoms::br)) {
4592 return true;
4595 // Or check if no real content
4596 return HTMLEditUtils::IsEmptyNode(
4597 *cellChild, {EmptyCheckOption::TreatSingleBRElementAsVisible,
4598 EmptyCheckOption::TreatNonEditableContentAsInvisible});
4601 } // namespace mozilla