Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / editor / libeditor / HTMLTableEditor.cpp
bloba31abae9e9100d74dede2c510fdabbc7a28c4df4
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 "AutoSelectionRestorer.h"
12 #include "EditAction.h"
13 #include "EditorDOMPoint.h"
14 #include "EditorUtils.h"
15 #include "HTMLEditUtils.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/FlushType.h"
19 #include "mozilla/IntegerRange.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/dom/Selection.h"
22 #include "mozilla/dom/Element.h"
23 #include "mozilla/dom/ElementInlines.h"
24 #include "nsAString.h"
25 #include "nsCOMPtr.h"
26 #include "nsDebug.h"
27 #include "nsError.h"
28 #include "nsFrameSelection.h"
29 #include "nsGkAtoms.h"
30 #include "nsAtom.h"
31 #include "nsIContent.h"
32 #include "nsIFrame.h"
33 #include "nsINode.h"
34 #include "nsISupportsUtils.h"
35 #include "nsITableCellLayout.h" // For efficient access to table cell
36 #include "nsLiteralString.h"
37 #include "nsQueryFrame.h"
38 #include "nsRange.h"
39 #include "nsString.h"
40 #include "nsTArray.h"
41 #include "nsTableCellFrame.h"
42 #include "nsTableWrapperFrame.h"
43 #include "nscore.h"
44 #include <algorithm>
46 namespace mozilla {
48 using namespace dom;
49 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
51 /**
52 * Stack based helper class for restoring selection after table edit.
54 class MOZ_STACK_CLASS AutoSelectionSetterAfterTableEdit final {
55 private:
56 const RefPtr<HTMLEditor> mHTMLEditor;
57 const RefPtr<Element> mTable;
58 int32_t mCol, mRow, mDirection, mSelected;
60 public:
61 AutoSelectionSetterAfterTableEdit(HTMLEditor& aHTMLEditor, Element* aTable,
62 int32_t aRow, int32_t aCol,
63 int32_t aDirection, bool aSelected)
64 : mHTMLEditor(&aHTMLEditor),
65 mTable(aTable),
66 mCol(aCol),
67 mRow(aRow),
68 mDirection(aDirection),
69 mSelected(aSelected) {}
71 MOZ_CAN_RUN_SCRIPT ~AutoSelectionSetterAfterTableEdit() {
72 if (mHTMLEditor) {
73 mHTMLEditor->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection,
74 mSelected);
79 /******************************************************************************
80 * HTMLEditor::CellIndexes
81 ******************************************************************************/
83 void HTMLEditor::CellIndexes::Update(HTMLEditor& aHTMLEditor,
84 Selection& aSelection) {
85 // Guarantee the life time of the cell element since Init() will access
86 // layout methods.
87 RefPtr<Element> cellElement =
88 aHTMLEditor.GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
89 if (!cellElement) {
90 NS_WARNING(
91 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
92 "failed");
93 return;
96 RefPtr<PresShell> presShell{aHTMLEditor.GetPresShell()};
97 Update(*cellElement, presShell);
100 void HTMLEditor::CellIndexes::Update(Element& aCellElement,
101 PresShell* aPresShell) {
102 // If the table cell is created immediately before this call, e.g., using
103 // innerHTML, frames have not been created yet. Hence, flush layout to create
104 // them.
105 if (NS_WARN_IF(!aPresShell)) {
106 return;
109 aPresShell->FlushPendingNotifications(FlushType::Frames);
111 nsIFrame* frameOfCell = aCellElement.GetPrimaryFrame();
112 if (!frameOfCell) {
113 NS_WARNING("There was no layout information of aCellElement");
114 return;
117 nsITableCellLayout* tableCellLayout = do_QueryFrame(frameOfCell);
118 if (!tableCellLayout) {
119 NS_WARNING("aCellElement was not a table cell");
120 return;
123 if (NS_FAILED(tableCellLayout->GetCellIndexes(mRow, mColumn))) {
124 NS_WARNING("nsITableCellLayout::GetCellIndexes() failed");
125 mRow = mColumn = -1;
126 return;
129 MOZ_ASSERT(!isErr());
132 /******************************************************************************
133 * HTMLEditor::CellData
134 ******************************************************************************/
136 // static
137 HTMLEditor::CellData HTMLEditor::CellData::AtIndexInTableElement(
138 const HTMLEditor& aHTMLEditor, const Element& aTableElement,
139 int32_t aRowIndex, int32_t aColumnIndex) {
140 nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(&aTableElement);
141 if (!tableFrame) {
142 NS_WARNING("There was no layout information of the table");
143 return CellData::Error(aRowIndex, aColumnIndex);
146 // If there is no cell at the indexes. Don't set the error state to the new
147 // instance.
148 nsTableCellFrame* cellFrame =
149 tableFrame->GetCellFrameAt(aRowIndex, aColumnIndex);
150 if (!cellFrame) {
151 return CellData::NotFound(aRowIndex, aColumnIndex);
154 Element* cellElement = Element::FromNodeOrNull(cellFrame->GetContent());
155 if (!cellElement) {
156 return CellData::Error(aRowIndex, aColumnIndex);
158 return CellData(*cellElement, aRowIndex, aColumnIndex, *cellFrame,
159 *tableFrame);
162 HTMLEditor::CellData::CellData(Element& aElement, int32_t aRowIndex,
163 int32_t aColumnIndex,
164 nsTableCellFrame& aTableCellFrame,
165 nsTableWrapperFrame& aTableWrapperFrame)
166 : mElement(&aElement),
167 mCurrent(aRowIndex, aColumnIndex),
168 mFirst(aTableCellFrame.RowIndex(), aTableCellFrame.ColIndex()),
169 mRowSpan(aTableCellFrame.GetRowSpan()),
170 mColSpan(aTableCellFrame.GetColSpan()),
171 mEffectiveRowSpan(
172 aTableWrapperFrame.GetEffectiveRowSpanAt(aRowIndex, aColumnIndex)),
173 mEffectiveColSpan(
174 aTableWrapperFrame.GetEffectiveColSpanAt(aRowIndex, aColumnIndex)),
175 mIsSelected(aTableCellFrame.IsSelected()) {
176 MOZ_ASSERT(!mCurrent.isErr());
179 /******************************************************************************
180 * HTMLEditor::TableSize
181 ******************************************************************************/
183 // static
184 Result<HTMLEditor::TableSize, nsresult> HTMLEditor::TableSize::Create(
185 HTMLEditor& aHTMLEditor, Element& aTableOrElementInTable) {
186 // Currently, nsTableWrapperFrame::GetRowCount() and
187 // nsTableWrapperFrame::GetColCount() are safe to use without grabbing
188 // <table> element. However, editor developers may not watch layout API
189 // changes. So, for keeping us safer, we should use RefPtr here.
190 RefPtr<Element> tableElement =
191 aHTMLEditor.GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table,
192 aTableOrElementInTable);
193 if (!tableElement) {
194 NS_WARNING(
195 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
196 "failed");
197 return Err(NS_ERROR_FAILURE);
199 nsTableWrapperFrame* tableFrame =
200 do_QueryFrame(tableElement->GetPrimaryFrame());
201 if (!tableFrame) {
202 NS_WARNING("There was no layout information of the <table> element");
203 return Err(NS_ERROR_FAILURE);
205 const int32_t rowCount = tableFrame->GetRowCount();
206 const int32_t columnCount = tableFrame->GetColCount();
207 if (NS_WARN_IF(rowCount < 0) || NS_WARN_IF(columnCount < 0)) {
208 return Err(NS_ERROR_FAILURE);
210 return TableSize(rowCount, columnCount);
213 /******************************************************************************
214 * HTMLEditor
215 ******************************************************************************/
217 nsresult HTMLEditor::InsertCell(Element* aCell, int32_t aRowSpan,
218 int32_t aColSpan, bool aAfter, bool aIsHeader,
219 Element** aNewCell) {
220 if (aNewCell) {
221 *aNewCell = nullptr;
224 if (NS_WARN_IF(!aCell)) {
225 return NS_ERROR_INVALID_ARG;
228 // And the parent and offsets needed to do an insert
229 EditorDOMPoint pointToInsert(aCell);
230 if (NS_WARN_IF(!pointToInsert.IsSet())) {
231 return NS_ERROR_INVALID_ARG;
234 RefPtr<Element> newCell =
235 CreateElementWithDefaults(aIsHeader ? *nsGkAtoms::th : *nsGkAtoms::td);
236 if (!newCell) {
237 NS_WARNING(
238 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::th or td) failed");
239 return NS_ERROR_FAILURE;
242 // Optional: return new cell created
243 if (aNewCell) {
244 *aNewCell = do_AddRef(newCell).take();
247 if (aRowSpan > 1) {
248 // Note: Do NOT use editor transaction for this
249 nsAutoString newRowSpan;
250 newRowSpan.AppendInt(aRowSpan, 10);
251 DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
252 kNameSpaceID_None, nsGkAtoms::rowspan, newRowSpan, true);
253 NS_WARNING_ASSERTION(
254 NS_SUCCEEDED(rvIgnored),
255 "Element::SetAttr(nsGkAtoms::rawspan) failed, but ignored");
257 if (aColSpan > 1) {
258 // Note: Do NOT use editor transaction for this
259 nsAutoString newColSpan;
260 newColSpan.AppendInt(aColSpan, 10);
261 DebugOnly<nsresult> rvIgnored = newCell->SetAttr(
262 kNameSpaceID_None, nsGkAtoms::colspan, newColSpan, true);
263 NS_WARNING_ASSERTION(
264 NS_SUCCEEDED(rvIgnored),
265 "Element::SetAttr(nsGkAtoms::colspan) failed, but ignored");
267 if (aAfter) {
268 DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
269 NS_WARNING_ASSERTION(advanced,
270 "Failed to advance offset to after the old cell");
273 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
274 // in normal cases. However, it may be required for nested edit
275 // actions which may be caused by legacy mutation event listeners or
276 // chrome script.
277 AutoTransactionsConserveSelection dontChangeSelection(*this);
278 Result<CreateElementResult, nsresult> insertNewCellResult =
279 InsertNodeWithTransaction<Element>(*newCell, pointToInsert);
280 if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
281 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
282 return insertNewCellResult.unwrapErr();
284 // Because of dontChangeSelection, we've never allowed to transactions to
285 // update selection here.
286 insertNewCellResult.inspect().IgnoreCaretPointSuggestion();
287 return NS_OK;
290 nsresult HTMLEditor::SetColSpan(Element* aCell, int32_t aColSpan) {
291 if (NS_WARN_IF(!aCell)) {
292 return NS_ERROR_INVALID_ARG;
294 nsAutoString newSpan;
295 newSpan.AppendInt(aColSpan, 10);
296 nsresult rv =
297 SetAttributeWithTransaction(*aCell, *nsGkAtoms::colspan, newSpan);
298 NS_WARNING_ASSERTION(
299 NS_SUCCEEDED(rv),
300 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::colspan) failed");
301 return rv;
304 nsresult HTMLEditor::SetRowSpan(Element* aCell, int32_t aRowSpan) {
305 if (NS_WARN_IF(!aCell)) {
306 return NS_ERROR_INVALID_ARG;
308 nsAutoString newSpan;
309 newSpan.AppendInt(aRowSpan, 10);
310 nsresult rv =
311 SetAttributeWithTransaction(*aCell, *nsGkAtoms::rowspan, newSpan);
312 NS_WARNING_ASSERTION(
313 NS_SUCCEEDED(rv),
314 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::rowspan) failed");
315 return rv;
318 NS_IMETHODIMP HTMLEditor::InsertTableCell(int32_t aNumberOfCellsToInsert,
319 bool aInsertAfterSelectedCell) {
320 if (aNumberOfCellsToInsert <= 0) {
321 return NS_OK; // Just do nothing.
324 AutoEditActionDataSetter editActionData(*this,
325 EditAction::eInsertTableCellElement);
326 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
327 if (NS_FAILED(rv)) {
328 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
329 return EditorBase::ToGenericNSResult(rv);
331 const RefPtr<Element> editingHost =
332 ComputeEditingHost(LimitInBodyElement::No);
333 if (NS_WARN_IF(editingHost &&
334 editingHost->IsContentEditablePlainTextOnly())) {
335 return NS_ERROR_NOT_AVAILABLE;
337 rv = editActionData.MaybeDispatchBeforeInputEvent();
338 if (NS_FAILED(rv)) {
339 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
340 "MaybeDispatchBeforeInputEvent(), failed");
341 return EditorBase::ToGenericNSResult(rv);
344 Result<RefPtr<Element>, nsresult> cellElementOrError =
345 GetFirstSelectedCellElementInTable();
346 if (cellElementOrError.isErr()) {
347 NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
348 return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
351 if (!cellElementOrError.inspect()) {
352 return NS_OK;
355 EditorDOMPoint pointToInsert(cellElementOrError.inspect());
356 if (!pointToInsert.IsSet()) {
357 NS_WARNING("Found an orphan cell element");
358 return NS_ERROR_FAILURE;
360 if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
361 DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
362 NS_WARNING_ASSERTION(
363 advanced,
364 "Failed to set insertion point after current cell, but ignored");
366 Result<CreateElementResult, nsresult> insertCellElementResult =
367 InsertTableCellsWithTransaction(pointToInsert, aNumberOfCellsToInsert);
368 if (MOZ_UNLIKELY(insertCellElementResult.isErr())) {
369 NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
370 return EditorBase::ToGenericNSResult(insertCellElementResult.unwrapErr());
372 // We don't need to modify selection here.
373 insertCellElementResult.inspect().IgnoreCaretPointSuggestion();
374 return NS_OK;
377 Result<CreateElementResult, nsresult>
378 HTMLEditor::InsertTableCellsWithTransaction(
379 const EditorDOMPoint& aPointToInsert, int32_t aNumberOfCellsToInsert) {
380 MOZ_ASSERT(IsEditActionDataAvailable());
381 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
382 MOZ_ASSERT(aNumberOfCellsToInsert > 0);
384 if (!HTMLEditUtils::IsTableRow(aPointToInsert.GetContainer())) {
385 NS_WARNING("Tried to insert cell elements to non-<tr> element");
386 return Err(NS_ERROR_FAILURE);
389 AutoPlaceholderBatch treateAsOneTransaction(
390 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
391 // Prevent auto insertion of BR in new cell until we're done
392 // XXX Why? I think that we should insert <br> element for every cell
393 // **before** inserting new cell into the <tr> element.
394 IgnoredErrorResult error;
395 AutoEditSubActionNotifier startToHandleEditSubAction(
396 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
397 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
398 return Err(error.StealNSResult());
400 NS_WARNING_ASSERTION(
401 !error.Failed(),
402 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
403 error.SuppressException();
405 // Put caret into the cell before the first inserting cell, or the first
406 // table cell in the row.
407 RefPtr<Element> cellToPutCaret =
408 aPointToInsert.IsEndOfContainer()
409 ? nullptr
410 : HTMLEditUtils::GetPreviousTableCellElementSibling(
411 *aPointToInsert.GetChild());
413 RefPtr<Element> firstCellElement, lastCellElement;
414 nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT {
415 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
416 // in normal cases. However, it may be required for nested edit
417 // actions which may be caused by legacy mutation event listeners or
418 // chrome script.
419 AutoTransactionsConserveSelection dontChangeSelection(*this);
421 // Block legacy mutation events for making this job simpler.
422 nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
424 // If there is a child to put a cell, we need to put all cell elements
425 // before it. Therefore, creating `EditorDOMPoint` with the child element
426 // is safe. Otherwise, we need to try to append cell elements in the row.
427 // Therefore, using `EditorDOMPoint::AtEndOf()` is safe. Note that it's
428 // not safe to creat it once because the offset and child relation in the
429 // point becomes invalid after inserting a cell element.
430 nsIContent* referenceContent = aPointToInsert.GetChild();
431 for ([[maybe_unused]] const auto i :
432 IntegerRange<uint32_t>(aNumberOfCellsToInsert)) {
433 RefPtr<Element> newCell = CreateElementWithDefaults(*nsGkAtoms::td);
434 if (!newCell) {
435 NS_WARNING(
436 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
437 return NS_ERROR_FAILURE;
439 Result<CreateElementResult, nsresult> insertNewCellResult =
440 InsertNodeWithTransaction(
441 *newCell, referenceContent
442 ? EditorDOMPoint(referenceContent)
443 : EditorDOMPoint::AtEndOf(
444 *aPointToInsert.ContainerAs<Element>()));
445 if (MOZ_UNLIKELY(insertNewCellResult.isErr())) {
446 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
447 return insertNewCellResult.unwrapErr();
449 CreateElementResult unwrappedInsertNewCellResult =
450 insertNewCellResult.unwrap();
451 lastCellElement = unwrappedInsertNewCellResult.UnwrapNewNode();
452 if (!firstCellElement) {
453 firstCellElement = lastCellElement;
455 // Because of dontChangeSelection, we've never allowed to transactions
456 // to update selection here.
457 unwrappedInsertNewCellResult.IgnoreCaretPointSuggestion();
458 if (!cellToPutCaret) {
459 cellToPutCaret = std::move(newCell); // This is first cell in the row.
463 // TODO: Stop touching selection here.
464 MOZ_ASSERT(cellToPutCaret);
465 MOZ_ASSERT(cellToPutCaret->GetParent());
466 CollapseSelectionToDeepestNonTableFirstChild(cellToPutCaret);
467 return NS_OK;
468 }();
469 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED ||
470 NS_WARN_IF(Destroyed()))) {
471 return Err(NS_ERROR_EDITOR_DESTROYED);
473 if (NS_FAILED(rv)) {
474 return Err(rv);
476 MOZ_ASSERT(firstCellElement);
477 MOZ_ASSERT(lastCellElement);
478 return CreateElementResult(std::move(firstCellElement),
479 EditorDOMPoint(lastCellElement, 0u));
482 NS_IMETHODIMP HTMLEditor::GetFirstRow(Element* aTableOrElementInTable,
483 Element** aFirstRowElement) {
484 if (NS_WARN_IF(!aTableOrElementInTable) || NS_WARN_IF(!aFirstRowElement)) {
485 return NS_ERROR_INVALID_ARG;
488 AutoEditActionDataSetter editActionData(*this, EditAction::eGetFirstRow);
489 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
490 if (NS_FAILED(rv)) {
491 NS_WARNING("HTMLEditor::GetFirstRow() couldn't handle the job");
492 return EditorBase::ToGenericNSResult(rv);
495 Result<RefPtr<Element>, nsresult> firstRowElementOrError =
496 GetFirstTableRowElement(*aTableOrElementInTable);
497 NS_WARNING_ASSERTION(!firstRowElementOrError.isErr(),
498 "HTMLEditor::GetFirstTableRowElement() failed");
499 if (firstRowElementOrError.isErr()) {
500 NS_WARNING("HTMLEditor::GetFirstTableRowElement() failed");
501 return EditorBase::ToGenericNSResult(firstRowElementOrError.unwrapErr());
503 firstRowElementOrError.unwrap().forget(aFirstRowElement);
504 return NS_OK;
507 Result<RefPtr<Element>, nsresult> HTMLEditor::GetFirstTableRowElement(
508 const Element& aTableOrElementInTable) const {
509 MOZ_ASSERT(IsEditActionDataAvailable());
511 Element* tableElement = GetInclusiveAncestorByTagNameInternal(
512 *nsGkAtoms::table, aTableOrElementInTable);
513 // If the element is not in <table>, return error.
514 if (!tableElement) {
515 NS_WARNING(
516 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
517 "failed");
518 return Err(NS_ERROR_FAILURE);
521 for (nsIContent* tableChild = tableElement->GetFirstChild(); tableChild;
522 tableChild = tableChild->GetNextSibling()) {
523 if (tableChild->IsHTMLElement(nsGkAtoms::tr)) {
524 // Found a row directly under <table>
525 return RefPtr<Element>(tableChild->AsElement());
527 // <table> can have table section elements like <tbody>. <tr> elements
528 // may be children of them.
529 if (tableChild->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::thead,
530 nsGkAtoms::tfoot)) {
531 for (nsIContent* tableSectionChild = tableChild->GetFirstChild();
532 tableSectionChild;
533 tableSectionChild = tableSectionChild->GetNextSibling()) {
534 if (tableSectionChild->IsHTMLElement(nsGkAtoms::tr)) {
535 return RefPtr<Element>(tableSectionChild->AsElement());
540 // Don't return error when there is no <tr> element in the <table>.
541 return RefPtr<Element>();
544 Result<RefPtr<Element>, nsresult> HTMLEditor::GetNextTableRowElement(
545 const Element& aTableRowElement) const {
546 if (NS_WARN_IF(!aTableRowElement.IsHTMLElement(nsGkAtoms::tr))) {
547 return Err(NS_ERROR_INVALID_ARG);
550 for (nsIContent* maybeNextRow = aTableRowElement.GetNextSibling();
551 maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
552 if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
553 return RefPtr<Element>(maybeNextRow->AsElement());
557 // In current table section (e.g., <tbody>), there is no <tr> element.
558 // Then, check the following table sections.
559 Element* parentElementOfRow = aTableRowElement.GetParentElement();
560 if (!parentElementOfRow) {
561 NS_WARNING("aTableRowElement was an orphan node");
562 return Err(NS_ERROR_FAILURE);
565 // Basically, <tr> elements should be in table section elements even if
566 // they are not written in the source explicitly. However, for preventing
567 // cross table boundary, check it now.
568 if (parentElementOfRow->IsHTMLElement(nsGkAtoms::table)) {
569 // Don't return error since this means just not found.
570 return RefPtr<Element>();
573 for (nsIContent* maybeNextTableSection = parentElementOfRow->GetNextSibling();
574 maybeNextTableSection;
575 maybeNextTableSection = maybeNextTableSection->GetNextSibling()) {
576 // If the sibling of parent of given <tr> is a table section element,
577 // check its children.
578 if (maybeNextTableSection->IsAnyOfHTMLElements(
579 nsGkAtoms::tbody, nsGkAtoms::thead, nsGkAtoms::tfoot)) {
580 for (nsIContent* maybeNextRow = maybeNextTableSection->GetFirstChild();
581 maybeNextRow; maybeNextRow = maybeNextRow->GetNextSibling()) {
582 if (maybeNextRow->IsHTMLElement(nsGkAtoms::tr)) {
583 return RefPtr<Element>(maybeNextRow->AsElement());
587 // I'm not sure whether this is a possible case since table section
588 // elements are created automatically. However, DOM API may create
589 // <tr> elements without table section elements. So, let's check it.
590 else if (maybeNextTableSection->IsHTMLElement(nsGkAtoms::tr)) {
591 return RefPtr<Element>(maybeNextTableSection->AsElement());
594 // Don't return error when the given <tr> element is the last <tr> element in
595 // the <table>.
596 return RefPtr<Element>();
599 NS_IMETHODIMP HTMLEditor::InsertTableColumn(int32_t aNumberOfColumnsToInsert,
600 bool aInsertAfterSelectedCell) {
601 if (aNumberOfColumnsToInsert <= 0) {
602 return NS_OK; // XXX Traditional behavior
605 AutoEditActionDataSetter editActionData(*this,
606 EditAction::eInsertTableColumn);
607 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
608 if (NS_FAILED(rv)) {
609 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
610 return EditorBase::ToGenericNSResult(rv);
612 const RefPtr<Element> editingHost =
613 ComputeEditingHost(LimitInBodyElement::No);
614 if (NS_WARN_IF(editingHost &&
615 editingHost->IsContentEditablePlainTextOnly())) {
616 return NS_ERROR_NOT_AVAILABLE;
618 rv = editActionData.MaybeDispatchBeforeInputEvent();
619 if (NS_FAILED(rv)) {
620 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
621 "MaybeDispatchBeforeInputEvent(), failed");
622 return EditorBase::ToGenericNSResult(rv);
625 Result<RefPtr<Element>, nsresult> cellElementOrError =
626 GetFirstSelectedCellElementInTable();
627 if (cellElementOrError.isErr()) {
628 NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
629 return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
632 if (!cellElementOrError.inspect()) {
633 return NS_OK;
636 EditorDOMPoint pointToInsert(cellElementOrError.inspect());
637 if (!pointToInsert.IsSet()) {
638 NS_WARNING("Found an orphan cell element");
639 return NS_ERROR_FAILURE;
641 if (aInsertAfterSelectedCell && !pointToInsert.IsEndOfContainer()) {
642 DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
643 NS_WARNING_ASSERTION(
644 advanced,
645 "Failed to set insertion point after current cell, but ignored");
647 rv = InsertTableColumnsWithTransaction(pointToInsert,
648 aNumberOfColumnsToInsert);
649 NS_WARNING_ASSERTION(
650 NS_SUCCEEDED(rv),
651 "HTMLEditor::InsertTableColumnsWithTransaction() failed");
652 return EditorBase::ToGenericNSResult(rv);
655 nsresult HTMLEditor::InsertTableColumnsWithTransaction(
656 const EditorDOMPoint& aPointToInsert, int32_t aNumberOfColumnsToInsert) {
657 MOZ_ASSERT(IsEditActionDataAvailable());
658 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
659 MOZ_ASSERT(aNumberOfColumnsToInsert > 0);
661 const RefPtr<PresShell> presShell = GetPresShell();
662 if (NS_WARN_IF(!presShell)) {
663 return NS_ERROR_FAILURE;
666 if (!HTMLEditUtils::IsTableRow(aPointToInsert.GetContainer())) {
667 NS_WARNING("Tried to insert columns to non-<tr> element");
668 return NS_ERROR_FAILURE;
671 const RefPtr<Element> tableElement =
672 HTMLEditUtils::GetClosestAncestorTableElement(
673 *aPointToInsert.ContainerAs<Element>());
674 if (!tableElement) {
675 NS_WARNING("There was no ancestor <table> element");
676 return NS_ERROR_FAILURE;
679 const Result<TableSize, nsresult> tableSizeOrError =
680 TableSize::Create(*this, *tableElement);
681 if (NS_WARN_IF(tableSizeOrError.isErr())) {
682 return tableSizeOrError.inspectErr();
684 const TableSize& tableSize = tableSizeOrError.inspect();
685 if (NS_WARN_IF(tableSize.IsEmpty())) {
686 return NS_ERROR_FAILURE; // We cannot handle it in an empty table
689 // If aPointToInsert points non-cell element or end of the row, it means that
690 // the caller wants to insert column immediately after the last cell of
691 // the pointing cell element or in the raw.
692 const bool insertAfterPreviousCell = [&]() {
693 if (!aPointToInsert.IsEndOfContainer() &&
694 HTMLEditUtils::IsTableCell(aPointToInsert.GetChild())) {
695 return false; // Insert before the cell element.
697 // There is a previous cell element, we should add a column after it.
698 Element* previousCellElement =
699 aPointToInsert.IsEndOfContainer()
700 ? HTMLEditUtils::GetLastTableCellElementChild(
701 *aPointToInsert.ContainerAs<Element>())
702 : HTMLEditUtils::GetPreviousTableCellElementSibling(
703 *aPointToInsert.GetChild());
704 return previousCellElement != nullptr;
705 }();
707 // Consider the column index in the table from given point and direction.
708 auto referenceColumnIndexOrError =
709 [&]() MOZ_CAN_RUN_SCRIPT -> Result<int32_t, nsresult> {
710 if (!insertAfterPreviousCell) {
711 if (aPointToInsert.IsEndOfContainer()) {
712 return tableSize.mColumnCount; // Empty row, append columns to the end
714 // Insert columns immediately before current column.
715 const OwningNonNull<Element> tableCellElement =
716 *aPointToInsert.GetChild()->AsElement();
717 MOZ_ASSERT(HTMLEditUtils::IsTableCell(tableCellElement));
718 CellIndexes cellIndexes(*tableCellElement, presShell);
719 if (NS_WARN_IF(cellIndexes.isErr())) {
720 return Err(NS_ERROR_FAILURE);
722 return cellIndexes.mColumn;
725 // Otherwise, insert columns immediately after the previous column.
726 Element* previousCellElement =
727 aPointToInsert.IsEndOfContainer()
728 ? HTMLEditUtils::GetLastTableCellElementChild(
729 *aPointToInsert.ContainerAs<Element>())
730 : HTMLEditUtils::GetPreviousTableCellElementSibling(
731 *aPointToInsert.GetChild());
732 MOZ_ASSERT(previousCellElement);
733 CellIndexes cellIndexes(*previousCellElement, presShell);
734 if (NS_WARN_IF(cellIndexes.isErr())) {
735 return Err(NS_ERROR_FAILURE);
737 return cellIndexes.mColumn;
738 }();
739 if (MOZ_UNLIKELY(referenceColumnIndexOrError.isErr())) {
740 return referenceColumnIndexOrError.unwrapErr();
743 AutoPlaceholderBatch treateAsOneTransaction(
744 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
745 // Prevent auto insertion of <br> element in new cell until we're done.
746 // XXX Why? We should put <br> element to every cell element before inserting
747 // the cells into the tree.
748 IgnoredErrorResult error;
749 AutoEditSubActionNotifier startToHandleEditSubAction(
750 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
751 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
752 return error.StealNSResult();
754 NS_WARNING_ASSERTION(
755 !error.Failed(),
756 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
757 error.SuppressException();
759 // Suppress Rules System selection munging.
760 AutoTransactionsConserveSelection dontChangeSelection(*this);
762 // If we are inserting after all existing columns, make sure table is
763 // "well formed" before appending new column.
764 // XXX As far as I've tested, NormalizeTableInternal() always fails to
765 // normalize non-rectangular table. So, the following CellData will
766 // fail if the table is not rectangle.
767 if (referenceColumnIndexOrError.inspect() >= tableSize.mColumnCount) {
768 DebugOnly<nsresult> rv = NormalizeTableInternal(*tableElement);
769 if (MOZ_UNLIKELY(Destroyed())) {
770 NS_WARNING(
771 "HTMLEditor::NormalizeTableInternal() caused destroying the editor");
772 return NS_ERROR_EDITOR_DESTROYED;
774 NS_WARNING_ASSERTION(
775 NS_SUCCEEDED(rv),
776 "HTMLEditor::NormalizeTableInternal() failed, but ignored");
779 // First, we should collect all reference nodes to insert new table cells.
780 AutoTArray<CellData, 32> arrayOfCellData;
782 arrayOfCellData.SetCapacity(tableSize.mRowCount);
783 for (const int32_t rowIndex : IntegerRange(tableSize.mRowCount)) {
784 const auto cellData = CellData::AtIndexInTableElement(
785 *this, *tableElement, rowIndex,
786 referenceColumnIndexOrError.inspect());
787 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
788 return NS_ERROR_FAILURE;
790 arrayOfCellData.AppendElement(cellData);
794 // Note that checking whether the editor destroyed or not should be done
795 // after inserting all cell elements. Otherwise, the table is left as
796 // not a rectangle.
797 auto cellElementToPutCaretOrError =
798 [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
799 // Block legacy mutation events for making this job simpler.
800 nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
801 RefPtr<Element> cellElementToPutCaret;
802 for (const CellData& cellData : arrayOfCellData) {
803 // Don't fail entire process if we fail to find a cell (may fail just in
804 // particular rows with < adequate cells per row).
805 // XXX So, here wants to know whether the CellData actually failed
806 // above. Fix this later.
807 if (!cellData.mElement) {
808 continue;
811 if ((!insertAfterPreviousCell && cellData.IsSpannedFromOtherColumn()) ||
812 (insertAfterPreviousCell &&
813 cellData.IsNextColumnSpannedFromOtherColumn())) {
814 // If we have a cell spanning this location, simply increase its
815 // colspan to keep table rectangular.
816 if (cellData.mColSpan > 0) {
817 DebugOnly<nsresult> rvIgnored = SetColSpan(
818 cellData.mElement, cellData.mColSpan + aNumberOfColumnsToInsert);
819 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
820 "HTMLEditor::SetColSpan() failed, but ignored");
822 continue;
825 EditorDOMPoint pointToInsert = [&]() {
826 if (!insertAfterPreviousCell) {
827 // Insert before the reference cell.
828 return EditorDOMPoint(cellData.mElement);
830 if (!cellData.mElement->GetNextSibling()) {
831 // Insert after the reference cell, but nothing follows it, append
832 // to the end of the row.
833 return EditorDOMPoint::AtEndOf(*cellData.mElement->GetParentNode());
835 // Otherwise, returns immediately before the next sibling. Note that
836 // the next sibling may not be a table cell element. E.g., it may be
837 // a text node containing only white-spaces in most cases.
838 return EditorDOMPoint(cellData.mElement->GetNextSibling());
839 }();
840 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
841 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
843 Result<CreateElementResult, nsresult> insertCellElementsResult =
844 InsertTableCellsWithTransaction(pointToInsert,
845 aNumberOfColumnsToInsert);
846 if (MOZ_UNLIKELY(insertCellElementsResult.isErr())) {
847 NS_WARNING("HTMLEditor::InsertTableCellsWithTransaction() failed");
848 return insertCellElementsResult.propagateErr();
850 CreateElementResult unwrappedInsertCellElementsResult =
851 insertCellElementsResult.unwrap();
852 // We'll update selection later into the first inserted cell element in
853 // the current row.
854 unwrappedInsertCellElementsResult.IgnoreCaretPointSuggestion();
855 if (pointToInsert.ContainerAs<Element>() ==
856 aPointToInsert.ContainerAs<Element>()) {
857 cellElementToPutCaret =
858 unwrappedInsertCellElementsResult.UnwrapNewNode();
859 MOZ_ASSERT(cellElementToPutCaret);
860 MOZ_ASSERT(HTMLEditUtils::IsTableCell(cellElementToPutCaret));
863 return cellElementToPutCaret;
864 }();
865 if (MOZ_UNLIKELY(cellElementToPutCaretOrError.isErr())) {
866 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
867 : cellElementToPutCaretOrError.unwrapErr();
869 const RefPtr<Element> cellElementToPutCaret =
870 cellElementToPutCaretOrError.unwrap();
871 NS_WARNING_ASSERTION(
872 cellElementToPutCaret,
873 "Didn't find the first inserted cell element in the specified row");
874 if (MOZ_LIKELY(cellElementToPutCaret)) {
875 CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
877 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
880 NS_IMETHODIMP HTMLEditor::InsertTableRow(int32_t aNumberOfRowsToInsert,
881 bool aInsertAfterSelectedCell) {
882 if (aNumberOfRowsToInsert <= 0) {
883 return NS_OK;
886 AutoEditActionDataSetter editActionData(*this,
887 EditAction::eInsertTableRowElement);
888 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
889 if (NS_FAILED(rv)) {
890 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
891 return EditorBase::ToGenericNSResult(rv);
893 const RefPtr<Element> editingHost =
894 ComputeEditingHost(LimitInBodyElement::No);
895 if (NS_WARN_IF(editingHost &&
896 editingHost->IsContentEditablePlainTextOnly())) {
897 return NS_ERROR_NOT_AVAILABLE;
899 rv = editActionData.MaybeDispatchBeforeInputEvent();
900 if (NS_FAILED(rv)) {
901 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
902 "MaybeDispatchBeforeInputEvent(), failed");
903 return EditorBase::ToGenericNSResult(rv);
906 Result<RefPtr<Element>, nsresult> cellElementOrError =
907 GetFirstSelectedCellElementInTable();
908 if (cellElementOrError.isErr()) {
909 NS_WARNING("HTMLEditor::GetFirstSelectedCellElementInTable() failed");
910 return EditorBase::ToGenericNSResult(cellElementOrError.unwrapErr());
913 if (!cellElementOrError.inspect()) {
914 return NS_OK;
917 rv = InsertTableRowsWithTransaction(
918 MOZ_KnownLive(*cellElementOrError.inspect()), aNumberOfRowsToInsert,
919 aInsertAfterSelectedCell ? InsertPosition::eAfterSelectedCell
920 : InsertPosition::eBeforeSelectedCell);
921 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
922 "HTMLEditor::InsertTableRowsWithTransaction() failed");
923 return EditorBase::ToGenericNSResult(rv);
926 nsresult HTMLEditor::InsertTableRowsWithTransaction(
927 Element& aCellElement, int32_t aNumberOfRowsToInsert,
928 InsertPosition aInsertPosition) {
929 MOZ_ASSERT(IsEditActionDataAvailable());
930 MOZ_ASSERT(HTMLEditUtils::IsTableCell(&aCellElement));
932 const RefPtr<PresShell> presShell = GetPresShell();
933 if (MOZ_UNLIKELY(NS_WARN_IF(!presShell))) {
934 return NS_ERROR_FAILURE;
937 if (MOZ_UNLIKELY(
938 !HTMLEditUtils::IsTableRow(aCellElement.GetParentElement()))) {
939 NS_WARNING("Tried to insert columns to non-<tr> element");
940 return NS_ERROR_FAILURE;
943 const RefPtr<Element> tableElement =
944 HTMLEditUtils::GetClosestAncestorTableElement(aCellElement);
945 if (MOZ_UNLIKELY(!tableElement)) {
946 return NS_OK;
949 const Result<TableSize, nsresult> tableSizeOrError =
950 TableSize::Create(*this, *tableElement);
951 if (NS_WARN_IF(tableSizeOrError.isErr())) {
952 return tableSizeOrError.inspectErr();
954 const TableSize& tableSize = tableSizeOrError.inspect();
955 // Should not be empty since we've already found a cell.
956 MOZ_ASSERT(!tableSize.IsEmpty());
958 const CellIndexes cellIndexes(aCellElement, presShell);
959 if (NS_WARN_IF(cellIndexes.isErr())) {
960 return NS_ERROR_FAILURE;
963 // Get more data for current cell in row we are inserting at because we need
964 // rowspan.
965 const auto cellData =
966 CellData::AtIndexInTableElement(*this, *tableElement, cellIndexes);
967 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
968 return NS_ERROR_FAILURE;
970 MOZ_ASSERT(&aCellElement == cellData.mElement);
972 AutoPlaceholderBatch treateAsOneTransaction(
973 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
974 // Prevent auto insertion of BR in new cell until we're done
975 IgnoredErrorResult error;
976 AutoEditSubActionNotifier startToHandleEditSubAction(
977 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
978 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
979 return error.StealNSResult();
981 NS_WARNING_ASSERTION(
982 !error.Failed(),
983 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
985 struct ElementWithNewRowSpan final {
986 const OwningNonNull<Element> mCellElement;
987 const int32_t mNewRowSpan;
989 ElementWithNewRowSpan(Element& aCellElement, int32_t aNewRowSpan)
990 : mCellElement(aCellElement), mNewRowSpan(aNewRowSpan) {}
992 AutoTArray<ElementWithNewRowSpan, 16> cellElementsToModifyRowSpan;
993 if (aInsertPosition == InsertPosition::eAfterSelectedCell &&
994 !cellData.mRowSpan) {
995 // Detect when user is adding after a rowspan=0 case.
996 // Assume they want to stop the "0" behavior and really add a new row.
997 // Thus we set the rowspan to its true value.
998 cellElementsToModifyRowSpan.AppendElement(
999 ElementWithNewRowSpan(aCellElement, cellData.mEffectiveRowSpan));
1002 struct MOZ_STACK_CLASS TableRowData {
1003 RefPtr<Element> mElement;
1004 int32_t mNumberOfCellsInStartRow;
1005 int32_t mOffsetInTRElementToPutCaret;
1007 const auto referenceRowDataOrError = [&]() -> Result<TableRowData, nsresult> {
1008 const int32_t startRowIndex =
1009 aInsertPosition == InsertPosition::eBeforeSelectedCell
1010 ? cellData.mCurrent.mRow
1011 : cellData.mCurrent.mRow + cellData.mEffectiveRowSpan;
1012 if (startRowIndex < tableSize.mRowCount) {
1013 // We are inserting above an existing row. Get each cell in the insert
1014 // row to adjust for rowspan effects while we count how many cells are
1015 // needed.
1016 RefPtr<Element> referenceRowElement;
1017 int32_t numberOfCellsInStartRow = 0;
1018 int32_t offsetInTRElementToPutCaret = 0;
1019 for (int32_t colIndex = 0;;) {
1020 const auto cellDataInStartRow = CellData::AtIndexInTableElement(
1021 *this, *tableElement, startRowIndex, colIndex);
1022 if (cellDataInStartRow.FailedOrNotFound()) {
1023 break; // Perhaps, we reach end of the row.
1026 // XXX So, this is impossible case. Will be removed.
1027 if (!cellDataInStartRow.mElement) {
1028 NS_WARNING("CellData::Update() succeeded, but didn't set mElement");
1029 break;
1032 if (cellDataInStartRow.IsSpannedFromOtherRow()) {
1033 // We have a cell spanning this location. Increase its rowspan.
1034 // Note that if rowspan is 0, we do nothing since that cell should
1035 // automatically extend into the new row.
1036 if (cellDataInStartRow.mRowSpan > 0) {
1037 cellElementsToModifyRowSpan.AppendElement(ElementWithNewRowSpan(
1038 *cellDataInStartRow.mElement,
1039 cellDataInStartRow.mRowSpan + aNumberOfRowsToInsert));
1041 colIndex = cellDataInStartRow.NextColumnIndex();
1042 continue;
1045 if (colIndex < cellDataInStartRow.mCurrent.mColumn) {
1046 offsetInTRElementToPutCaret++;
1049 numberOfCellsInStartRow += cellDataInStartRow.mEffectiveColSpan;
1050 if (!referenceRowElement) {
1051 if (Element* maybeTableRowElement =
1052 cellDataInStartRow.mElement->GetParentElement()) {
1053 if (HTMLEditUtils::IsTableRow(maybeTableRowElement)) {
1054 referenceRowElement = maybeTableRowElement;
1058 MOZ_ASSERT(colIndex < cellDataInStartRow.NextColumnIndex());
1059 colIndex = cellDataInStartRow.NextColumnIndex();
1061 if (MOZ_UNLIKELY(!referenceRowElement)) {
1062 NS_WARNING(
1063 "Reference row element to insert new row elements was not found");
1064 return Err(NS_ERROR_FAILURE);
1066 return TableRowData{std::move(referenceRowElement),
1067 numberOfCellsInStartRow, offsetInTRElementToPutCaret};
1070 // We are adding a new row after all others. If it weren't for colspan=0
1071 // effect, we could simply use tableSize.mColumnCount for number of new
1072 // cells...
1073 // XXX colspan=0 support has now been removed in table layout so maybe this
1074 // can be cleaned up now? (bug 1243183)
1075 int32_t numberOfCellsInStartRow = tableSize.mColumnCount;
1076 int32_t offsetInTRElementToPutCaret = 0;
1078 // but we must compensate for all cells with rowspan = 0 in the last row.
1079 const int32_t lastRowIndex = tableSize.mRowCount - 1;
1080 for (int32_t colIndex = 0;;) {
1081 const auto cellDataInLastRow = CellData::AtIndexInTableElement(
1082 *this, *tableElement, lastRowIndex, colIndex);
1083 if (cellDataInLastRow.FailedOrNotFound()) {
1084 break; // Perhaps, we reach end of the row.
1087 if (!cellDataInLastRow.mRowSpan) {
1088 MOZ_ASSERT(numberOfCellsInStartRow >=
1089 cellDataInLastRow.mEffectiveColSpan);
1090 numberOfCellsInStartRow -= cellDataInLastRow.mEffectiveColSpan;
1091 } else if (colIndex < cellDataInLastRow.mCurrent.mColumn) {
1092 offsetInTRElementToPutCaret++;
1094 MOZ_ASSERT(colIndex < cellDataInLastRow.NextColumnIndex());
1095 colIndex = cellDataInLastRow.NextColumnIndex();
1097 return TableRowData{nullptr, numberOfCellsInStartRow,
1098 offsetInTRElementToPutCaret};
1099 }();
1100 if (MOZ_UNLIKELY(referenceRowDataOrError.isErr())) {
1101 return referenceRowDataOrError.inspectErr();
1104 const TableRowData& referenceRowData = referenceRowDataOrError.inspect();
1105 if (MOZ_UNLIKELY(!referenceRowData.mNumberOfCellsInStartRow)) {
1106 NS_WARNING("There was no cell element in the row");
1107 return NS_OK;
1110 MOZ_ASSERT_IF(referenceRowData.mElement,
1111 HTMLEditUtils::IsTableRow(referenceRowData.mElement));
1112 if (NS_WARN_IF(!HTMLEditUtils::IsTableRow(aCellElement.GetParentElement()))) {
1113 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
1116 // The row parent and offset where we will insert new row.
1117 EditorDOMPoint pointToInsert = [&]() {
1118 if (aInsertPosition == InsertPosition::eBeforeSelectedCell) {
1119 MOZ_ASSERT(referenceRowData.mElement);
1120 return EditorDOMPoint(referenceRowData.mElement);
1122 // Look for the last row element in the same table section or immediately
1123 // before the reference row element. Then, we can insert new rows
1124 // immediately after the given row element.
1125 Element* lastRowElement = nullptr;
1126 for (Element* rowElement = aCellElement.GetParentElement();
1127 rowElement && rowElement != referenceRowData.mElement;) {
1128 lastRowElement = rowElement;
1129 const Result<RefPtr<Element>, nsresult> nextRowElementOrError =
1130 GetNextTableRowElement(*rowElement);
1131 if (MOZ_UNLIKELY(nextRowElementOrError.isErr())) {
1132 NS_WARNING("HTMLEditor::GetNextTableRowElement() failed");
1133 return EditorDOMPoint();
1135 rowElement = nextRowElementOrError.inspect();
1137 MOZ_ASSERT(lastRowElement);
1138 return EditorDOMPoint::After(*lastRowElement);
1139 }();
1140 if (NS_WARN_IF(!pointToInsert.IsSet())) {
1141 return NS_ERROR_FAILURE;
1143 // Note that checking whether the editor destroyed or not should be done
1144 // after inserting all cell elements. Otherwise, the table is left as
1145 // not a rectangle.
1146 auto firstInsertedTRElementOrError =
1147 [&]() MOZ_CAN_RUN_SCRIPT -> Result<RefPtr<Element>, nsresult> {
1148 // Block legacy mutation events for making this job simpler.
1149 nsAutoScriptBlockerSuppressNodeRemoved blockToRunScript;
1151 // Suppress Rules System selection munging.
1152 AutoTransactionsConserveSelection dontChangeSelection(*this);
1154 for (const ElementWithNewRowSpan& cellElementAndNewRowSpan :
1155 cellElementsToModifyRowSpan) {
1156 DebugOnly<nsresult> rvIgnored =
1157 SetRowSpan(MOZ_KnownLive(cellElementAndNewRowSpan.mCellElement),
1158 cellElementAndNewRowSpan.mNewRowSpan);
1159 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1160 "HTMLEditor::SetRowSpan() failed, but ignored");
1163 RefPtr<Element> firstInsertedTRElement;
1164 IgnoredErrorResult error;
1165 for ([[maybe_unused]] const int32_t rowIndex :
1166 Reversed(IntegerRange(aNumberOfRowsToInsert))) {
1167 // Create a new row
1168 RefPtr<Element> newRowElement = CreateElementWithDefaults(*nsGkAtoms::tr);
1169 if (!newRowElement) {
1170 NS_WARNING(
1171 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::tr) failed");
1172 return Err(NS_ERROR_FAILURE);
1175 for ([[maybe_unused]] const int32_t i :
1176 IntegerRange(referenceRowData.mNumberOfCellsInStartRow)) {
1177 const RefPtr<Element> newCellElement =
1178 CreateElementWithDefaults(*nsGkAtoms::td);
1179 if (!newCellElement) {
1180 NS_WARNING(
1181 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::td) failed");
1182 return Err(NS_ERROR_FAILURE);
1184 newRowElement->AppendChild(*newCellElement, error);
1185 if (error.Failed()) {
1186 NS_WARNING("nsINode::AppendChild() failed");
1187 return Err(error.StealNSResult());
1191 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
1192 Result<CreateElementResult, nsresult> insertNewRowResult =
1193 InsertNodeWithTransaction<Element>(*newRowElement, pointToInsert);
1194 if (MOZ_UNLIKELY(insertNewRowResult.isErr())) {
1195 if (insertNewRowResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
1196 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
1197 return insertNewRowResult.propagateErr();
1199 NS_WARNING(
1200 "EditorBase::InsertNodeWithTransaction() failed, but ignored");
1202 firstInsertedTRElement = std::move(newRowElement);
1203 // We'll update selection later.
1204 insertNewRowResult.inspect().IgnoreCaretPointSuggestion();
1206 return firstInsertedTRElement;
1207 }();
1208 if (NS_WARN_IF(Destroyed())) {
1209 return NS_ERROR_EDITOR_DESTROYED;
1211 if (MOZ_UNLIKELY(firstInsertedTRElementOrError.isErr())) {
1212 return firstInsertedTRElementOrError.unwrapErr();
1215 const OwningNonNull<Element> cellElementToPutCaret = [&]() {
1216 if (MOZ_LIKELY(firstInsertedTRElementOrError.inspect())) {
1217 EditorRawDOMPoint point(firstInsertedTRElementOrError.inspect(),
1218 referenceRowData.mOffsetInTRElementToPutCaret);
1219 if (MOZ_LIKELY(point.IsSetAndValid()) &&
1220 MOZ_LIKELY(!point.IsEndOfContainer()) &&
1221 MOZ_LIKELY(HTMLEditUtils::IsTableCell(point.GetChild()))) {
1222 return OwningNonNull<Element>(*point.GetChild()->AsElement());
1225 return OwningNonNull<Element>(aCellElement);
1226 }();
1227 CollapseSelectionToDeepestNonTableFirstChild(cellElementToPutCaret);
1228 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
1231 nsresult HTMLEditor::DeleteTableElementAndChildrenWithTransaction(
1232 Element& aTableElement) {
1233 MOZ_ASSERT(IsEditActionDataAvailable());
1235 // Block selectionchange event. It's enough to dispatch selectionchange
1236 // event immediately after removing the table element.
1238 AutoHideSelectionChanges hideSelection(SelectionRef());
1240 // Select the <table> element after clear current selection.
1241 if (SelectionRef().RangeCount()) {
1242 ErrorResult error;
1243 SelectionRef().RemoveAllRanges(error);
1244 if (error.Failed()) {
1245 NS_WARNING("Selection::RemoveAllRanges() failed");
1246 return error.StealNSResult();
1250 RefPtr<nsRange> range = nsRange::Create(&aTableElement);
1251 ErrorResult error;
1252 range->SelectNode(aTableElement, error);
1253 if (error.Failed()) {
1254 NS_WARNING("nsRange::SelectNode() failed");
1255 return error.StealNSResult();
1257 SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*range, error);
1258 if (error.Failed()) {
1259 NS_WARNING(
1260 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
1261 return error.StealNSResult();
1264 #ifdef DEBUG
1265 range = SelectionRef().GetRangeAt(0);
1266 MOZ_ASSERT(range);
1267 MOZ_ASSERT(range->GetStartContainer() == aTableElement.GetParent());
1268 MOZ_ASSERT(range->GetEndContainer() == aTableElement.GetParent());
1269 MOZ_ASSERT(range->GetChildAtStartOffset() == &aTableElement);
1270 MOZ_ASSERT(range->GetChildAtEndOffset() == aTableElement.GetNextSibling());
1271 #endif // #ifdef DEBUG
1274 nsresult rv = DeleteSelectionAsSubAction(eNext, eStrip);
1275 NS_WARNING_ASSERTION(
1276 NS_SUCCEEDED(rv),
1277 "EditorBase::DeleteSelectionAsSubAction(eNext, eStrip) failed");
1278 return rv;
1281 NS_IMETHODIMP HTMLEditor::DeleteTable() {
1282 AutoEditActionDataSetter editActionData(*this,
1283 EditAction::eRemoveTableElement);
1284 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
1285 if (NS_FAILED(rv)) {
1286 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
1287 return EditorBase::ToGenericNSResult(rv);
1289 const RefPtr<Element> editingHost =
1290 ComputeEditingHost(LimitInBodyElement::No);
1291 if (NS_WARN_IF(editingHost &&
1292 editingHost->IsContentEditablePlainTextOnly())) {
1293 return NS_ERROR_NOT_AVAILABLE;
1295 rv = editActionData.MaybeDispatchBeforeInputEvent();
1296 if (NS_FAILED(rv)) {
1297 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1298 "MaybeDispatchBeforeInputEvent(), failed");
1299 return EditorBase::ToGenericNSResult(rv);
1302 RefPtr<Element> table;
1303 rv = GetCellContext(getter_AddRefs(table), nullptr, nullptr, nullptr, nullptr,
1304 nullptr);
1305 if (NS_FAILED(rv)) {
1306 NS_WARNING("HTMLEditor::GetCellContext() failed");
1307 return EditorBase::ToGenericNSResult(rv);
1309 if (!table) {
1310 NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
1311 return NS_ERROR_FAILURE;
1314 AutoPlaceholderBatch treateAsOneTransaction(
1315 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1316 rv = DeleteTableElementAndChildrenWithTransaction(*table);
1317 NS_WARNING_ASSERTION(
1318 NS_SUCCEEDED(rv),
1319 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
1320 return EditorBase::ToGenericNSResult(rv);
1323 NS_IMETHODIMP HTMLEditor::DeleteTableCell(int32_t aNumberOfCellsToDelete) {
1324 AutoEditActionDataSetter editActionData(*this,
1325 EditAction::eRemoveTableCellElement);
1326 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
1327 if (NS_FAILED(rv)) {
1328 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
1329 return EditorBase::ToGenericNSResult(rv);
1331 const RefPtr<Element> editingHost =
1332 ComputeEditingHost(LimitInBodyElement::No);
1333 if (NS_WARN_IF(editingHost &&
1334 editingHost->IsContentEditablePlainTextOnly())) {
1335 return NS_ERROR_NOT_AVAILABLE;
1337 rv = editActionData.MaybeDispatchBeforeInputEvent();
1338 if (NS_FAILED(rv)) {
1339 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1340 "MaybeDispatchBeforeInputEvent(), failed");
1341 return EditorBase::ToGenericNSResult(rv);
1344 rv = DeleteTableCellWithTransaction(aNumberOfCellsToDelete);
1345 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1346 "HTMLEditor::DeleteTableCellWithTransaction() failed");
1347 return EditorBase::ToGenericNSResult(rv);
1350 nsresult HTMLEditor::DeleteTableCellWithTransaction(
1351 int32_t aNumberOfCellsToDelete) {
1352 MOZ_ASSERT(IsEditActionDataAvailable());
1354 RefPtr<Element> table;
1355 RefPtr<Element> cell;
1356 int32_t startRowIndex, startColIndex;
1358 nsresult rv =
1359 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1360 nullptr, &startRowIndex, &startColIndex);
1361 if (NS_FAILED(rv)) {
1362 NS_WARNING("HTMLEditor::GetCellContext() failed");
1363 return rv;
1365 if (!table || !cell) {
1366 NS_WARNING(
1367 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
1368 // Don't fail if we didn't find a table or cell.
1369 return NS_OK;
1372 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
1373 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
1376 AutoPlaceholderBatch treateAsOneTransaction(
1377 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1378 // Prevent rules testing until we're done
1379 IgnoredErrorResult ignoredError;
1380 AutoEditSubActionNotifier startToHandleEditSubAction(
1381 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
1382 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1383 return ignoredError.StealNSResult();
1385 NS_WARNING_ASSERTION(
1386 !ignoredError.Failed(),
1387 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1389 MOZ_ASSERT(SelectionRef().RangeCount());
1391 SelectedTableCellScanner scanner(SelectionRef());
1393 Result<TableSize, nsresult> tableSizeOrError =
1394 TableSize::Create(*this, *table);
1395 if (NS_WARN_IF(tableSizeOrError.isErr())) {
1396 return tableSizeOrError.unwrapErr();
1398 // FYI: Cannot be a const reference because the row count will be updated
1399 TableSize tableSize = tableSizeOrError.unwrap();
1400 MOZ_ASSERT(!tableSize.IsEmpty());
1402 // If only one cell is selected or no cell is selected, remove cells
1403 // starting from the first selected cell or a cell containing first
1404 // selection range.
1405 if (!scanner.IsInTableCellSelectionMode() ||
1406 SelectionRef().RangeCount() == 1) {
1407 for (int32_t i = 0; i < aNumberOfCellsToDelete; i++) {
1408 nsresult rv =
1409 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1410 nullptr, &startRowIndex, &startColIndex);
1411 if (NS_FAILED(rv)) {
1412 NS_WARNING("HTMLEditor::GetCellContext() failed");
1413 return rv;
1415 if (!table || !cell) {
1416 NS_WARNING(
1417 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
1418 // Don't fail if no cell found
1419 return NS_OK;
1422 int32_t numberOfCellsInRow = GetNumberOfCellsInRow(*table, startRowIndex);
1423 NS_WARNING_ASSERTION(
1424 numberOfCellsInRow >= 0,
1425 "HTMLEditor::GetNumberOfCellsInRow() failed, but ignored");
1427 if (numberOfCellsInRow == 1) {
1428 // Remove <tr> or <table> if we're removing all cells in the row or
1429 // the table.
1430 if (tableSize.mRowCount == 1) {
1431 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
1432 NS_WARNING_ASSERTION(
1433 NS_SUCCEEDED(rv),
1434 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
1435 "failed");
1436 return rv;
1439 // We need to call DeleteSelectedTableRowsWithTransaction() to handle
1440 // cells with rowspan attribute.
1441 rv = DeleteSelectedTableRowsWithTransaction(1);
1442 if (NS_FAILED(rv)) {
1443 NS_WARNING(
1444 "HTMLEditor::DeleteSelectedTableRowsWithTransaction(1) failed");
1445 return rv;
1448 // Adjust table rows simply. In strictly speaking, we should
1449 // recompute table size with the latest layout information since
1450 // mutation event listener may have changed the DOM tree. However,
1451 // this is not in usual path of Firefox. So, we can assume that
1452 // there are no mutation event listeners.
1453 MOZ_ASSERT(tableSize.mRowCount);
1454 tableSize.mRowCount--;
1455 continue;
1458 // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
1459 // destructor
1460 AutoSelectionSetterAfterTableEdit setCaret(
1461 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
1462 AutoTransactionsConserveSelection dontChangeSelection(*this);
1464 // XXX Removing cell element causes not adjusting colspan.
1465 rv = DeleteNodeWithTransaction(*cell);
1466 // If we fail, don't try to delete any more cells???
1467 if (NS_FAILED(rv)) {
1468 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1469 return rv;
1471 // Note that we don't refer column number in this loop. So, it must
1472 // be safe not to recompute table size since number of row is synced
1473 // above.
1475 return NS_OK;
1478 // When 2 or more cells are selected, ignore aNumberOfCellsToRemove and
1479 // remove all selected cells.
1480 const RefPtr<PresShell> presShell{GetPresShell()};
1481 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because scanner grabs
1482 // it until it's destroyed later.
1483 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
1484 presShell);
1485 if (NS_WARN_IF(firstCellIndexes.isErr())) {
1486 return NS_ERROR_FAILURE;
1488 startRowIndex = firstCellIndexes.mRow;
1489 startColIndex = firstCellIndexes.mColumn;
1491 // The setCaret object will call AutoSelectionSetterAfterTableEdit in its
1492 // destructor
1493 AutoSelectionSetterAfterTableEdit setCaret(
1494 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
1495 AutoTransactionsConserveSelection dontChangeSelection(*this);
1497 bool checkToDeleteRow = true;
1498 bool checkToDeleteColumn = true;
1499 for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
1500 selectedCellElement;) {
1501 if (checkToDeleteRow) {
1502 // Optimize to delete an entire row
1503 // Clear so we don't repeat AllCellsInRowSelected within the same row
1504 checkToDeleteRow = false;
1505 if (AllCellsInRowSelected(table, startRowIndex, tableSize.mColumnCount)) {
1506 // First, find the next cell in a different row to continue after we
1507 // delete this row.
1508 int32_t nextRow = startRowIndex;
1509 while (nextRow == startRowIndex) {
1510 selectedCellElement = scanner.GetNextElement();
1511 if (!selectedCellElement) {
1512 break;
1514 const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
1515 presShell);
1516 if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
1517 return NS_ERROR_FAILURE;
1519 nextRow = nextSelectedCellIndexes.mRow;
1520 startColIndex = nextSelectedCellIndexes.mColumn;
1522 if (tableSize.mRowCount == 1) {
1523 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
1524 NS_WARNING_ASSERTION(
1525 NS_SUCCEEDED(rv),
1526 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() "
1527 "failed");
1528 return rv;
1530 nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
1531 if (NS_FAILED(rv)) {
1532 NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
1533 return rv;
1535 // Adjust table rows simply. In strictly speaking, we should
1536 // recompute table size with the latest layout information since
1537 // mutation event listener may have changed the DOM tree. However,
1538 // this is not in usual path of Firefox. So, we can assume that
1539 // there are no mutation event listeners.
1540 MOZ_ASSERT(tableSize.mRowCount);
1541 tableSize.mRowCount--;
1542 if (!selectedCellElement) {
1543 break; // XXX Seems like a dead path
1545 // For the next cell: Subtract 1 for row we deleted
1546 startRowIndex = nextRow - 1;
1547 // Set true since we know we will look at a new row next
1548 checkToDeleteRow = true;
1549 continue;
1553 if (checkToDeleteColumn) {
1554 // Optimize to delete an entire column
1555 // Clear this so we don't repeat AllCellsInColSelected within the same Col
1556 checkToDeleteColumn = false;
1557 if (AllCellsInColumnSelected(table, startColIndex,
1558 tableSize.mColumnCount)) {
1559 // First, find the next cell in a different column to continue after
1560 // we delete this column.
1561 int32_t nextCol = startColIndex;
1562 while (nextCol == startColIndex) {
1563 selectedCellElement = scanner.GetNextElement();
1564 if (!selectedCellElement) {
1565 break;
1567 const CellIndexes nextSelectedCellIndexes(*selectedCellElement,
1568 presShell);
1569 if (NS_WARN_IF(nextSelectedCellIndexes.isErr())) {
1570 return NS_ERROR_FAILURE;
1572 startRowIndex = nextSelectedCellIndexes.mRow;
1573 nextCol = nextSelectedCellIndexes.mColumn;
1575 // Delete all cells which belong to the column.
1576 nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1577 if (NS_FAILED(rv)) {
1578 NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
1579 return rv;
1581 // Adjust table columns simply. In strictly speaking, we should
1582 // recompute table size with the latest layout information since
1583 // mutation event listener may have changed the DOM tree. However,
1584 // this is not in usual path of Firefox. So, we can assume that
1585 // there are no mutation event listeners.
1586 MOZ_ASSERT(tableSize.mColumnCount);
1587 tableSize.mColumnCount--;
1588 if (!selectedCellElement) {
1589 break;
1591 // For the next cell, subtract 1 for col. deleted
1592 startColIndex = nextCol - 1;
1593 // Set true since we know we will look at a new column next
1594 checkToDeleteColumn = true;
1595 continue;
1599 nsresult rv = DeleteNodeWithTransaction(*selectedCellElement);
1600 if (NS_FAILED(rv)) {
1601 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1602 return rv;
1605 selectedCellElement = scanner.GetNextElement();
1606 if (!selectedCellElement) {
1607 return NS_OK;
1610 const CellIndexes nextCellIndexes(*selectedCellElement, presShell);
1611 if (NS_WARN_IF(nextCellIndexes.isErr())) {
1612 return NS_ERROR_FAILURE;
1614 startRowIndex = nextCellIndexes.mRow;
1615 startColIndex = nextCellIndexes.mColumn;
1616 // When table cell is removed, table size of column may be changed.
1617 // For example, if there are 2 rows, one has 2 cells, the other has
1618 // 3 cells, tableSize.mColumnCount is 3. When this removes a cell
1619 // in the latter row, mColumnCount should be come 2. However, we
1620 // don't use mColumnCount in this loop, so, this must be okay for now.
1622 return NS_OK;
1625 NS_IMETHODIMP HTMLEditor::DeleteTableCellContents() {
1626 AutoEditActionDataSetter editActionData(*this,
1627 EditAction::eDeleteTableCellContents);
1628 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
1629 if (NS_FAILED(rv)) {
1630 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
1631 return EditorBase::ToGenericNSResult(rv);
1633 const RefPtr<Element> editingHost =
1634 ComputeEditingHost(LimitInBodyElement::No);
1635 if (NS_WARN_IF(editingHost &&
1636 editingHost->IsContentEditablePlainTextOnly())) {
1637 return NS_ERROR_NOT_AVAILABLE;
1639 rv = editActionData.MaybeDispatchBeforeInputEvent();
1640 if (NS_FAILED(rv)) {
1641 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1642 "MaybeDispatchBeforeInputEvent(), failed");
1643 return EditorBase::ToGenericNSResult(rv);
1646 rv = DeleteTableCellContentsWithTransaction();
1647 NS_WARNING_ASSERTION(
1648 NS_SUCCEEDED(rv),
1649 "HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
1650 return EditorBase::ToGenericNSResult(rv);
1653 nsresult HTMLEditor::DeleteTableCellContentsWithTransaction() {
1654 MOZ_ASSERT(IsEditActionDataAvailable());
1656 RefPtr<Element> table;
1657 RefPtr<Element> cell;
1658 int32_t startRowIndex, startColIndex;
1659 nsresult rv =
1660 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1661 nullptr, &startRowIndex, &startColIndex);
1662 if (NS_FAILED(rv)) {
1663 NS_WARNING("HTMLEditor::GetCellContext() failed");
1664 return rv;
1666 if (!cell) {
1667 NS_WARNING("HTMLEditor::GetCellContext() didn't return cell element");
1668 // Don't fail if no cell found.
1669 return NS_OK;
1672 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
1673 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
1676 AutoPlaceholderBatch treateAsOneTransaction(
1677 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1678 // Prevent rules testing until we're done
1679 IgnoredErrorResult ignoredError;
1680 AutoEditSubActionNotifier startToHandleEditSubAction(
1681 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
1682 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1683 return ignoredError.StealNSResult();
1685 NS_WARNING_ASSERTION(
1686 !ignoredError.Failed(),
1687 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1689 // Don't let Rules System change the selection
1690 AutoTransactionsConserveSelection dontChangeSelection(*this);
1692 SelectedTableCellScanner scanner(SelectionRef());
1693 if (scanner.IsInTableCellSelectionMode()) {
1694 const RefPtr<PresShell> presShell{GetPresShell()};
1695 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because scanner
1696 // grabs it until it's destroyed later.
1697 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
1698 presShell);
1699 if (NS_WARN_IF(firstCellIndexes.isErr())) {
1700 return NS_ERROR_FAILURE;
1702 cell = scanner.ElementsRef()[0];
1703 startRowIndex = firstCellIndexes.mRow;
1704 startColIndex = firstCellIndexes.mColumn;
1707 AutoSelectionSetterAfterTableEdit setCaret(
1708 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
1710 for (RefPtr<Element> selectedCellElement = std::move(cell);
1711 selectedCellElement; selectedCellElement = scanner.GetNextElement()) {
1712 DebugOnly<nsresult> rvIgnored =
1713 DeleteAllChildrenWithTransaction(*selectedCellElement);
1714 if (NS_WARN_IF(Destroyed())) {
1715 return NS_ERROR_EDITOR_DESTROYED;
1717 NS_WARNING_ASSERTION(
1718 NS_SUCCEEDED(rv),
1719 "HTMLEditor::DeleteAllChildrenWithTransaction() failed, but ignored");
1720 if (!scanner.IsInTableCellSelectionMode()) {
1721 break;
1724 return NS_OK;
1727 NS_IMETHODIMP HTMLEditor::DeleteTableColumn(int32_t aNumberOfColumnsToDelete) {
1728 AutoEditActionDataSetter editActionData(*this,
1729 EditAction::eRemoveTableColumn);
1730 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
1731 if (NS_FAILED(rv)) {
1732 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
1733 return EditorBase::ToGenericNSResult(rv);
1735 const RefPtr<Element> editingHost =
1736 ComputeEditingHost(LimitInBodyElement::No);
1737 if (NS_WARN_IF(editingHost &&
1738 editingHost->IsContentEditablePlainTextOnly())) {
1739 return NS_ERROR_NOT_AVAILABLE;
1741 rv = editActionData.MaybeDispatchBeforeInputEvent();
1742 if (NS_FAILED(rv)) {
1743 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1744 "MaybeDispatchBeforeInputEvent(), failed");
1745 return EditorBase::ToGenericNSResult(rv);
1748 rv = DeleteSelectedTableColumnsWithTransaction(aNumberOfColumnsToDelete);
1749 NS_WARNING_ASSERTION(
1750 NS_SUCCEEDED(rv),
1751 "HTMLEditor::DeleteSelectedTableColumnsWithTransaction() failed");
1752 return EditorBase::ToGenericNSResult(rv);
1755 nsresult HTMLEditor::DeleteSelectedTableColumnsWithTransaction(
1756 int32_t aNumberOfColumnsToDelete) {
1757 MOZ_ASSERT(IsEditActionDataAvailable());
1759 RefPtr<Element> table;
1760 RefPtr<Element> cell;
1761 int32_t startRowIndex, startColIndex;
1762 nsresult rv =
1763 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
1764 nullptr, &startRowIndex, &startColIndex);
1765 if (NS_FAILED(rv)) {
1766 NS_WARNING("HTMLEditor::GetCellContext() failed");
1767 return rv;
1769 if (!table || !cell) {
1770 NS_WARNING(
1771 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
1772 // Don't fail if no cell found.
1773 return NS_OK;
1776 const Result<TableSize, nsresult> tableSizeOrError =
1777 TableSize::Create(*this, *table);
1778 if (NS_WARN_IF(tableSizeOrError.isErr())) {
1779 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
1781 const TableSize& tableSize = tableSizeOrError.inspect();
1783 AutoPlaceholderBatch treateAsOneTransaction(
1784 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1786 // Prevent rules testing until we're done
1787 IgnoredErrorResult error;
1788 AutoEditSubActionNotifier startToHandleEditSubAction(
1789 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
1790 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1791 return error.StealNSResult();
1793 NS_WARNING_ASSERTION(
1794 !error.Failed(),
1795 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1797 // Shortcut the case of deleting all columns in table
1798 if (!startColIndex && aNumberOfColumnsToDelete >= tableSize.mColumnCount) {
1799 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
1800 NS_WARNING_ASSERTION(
1801 NS_SUCCEEDED(rv),
1802 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
1803 return rv;
1806 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
1807 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
1810 SelectedTableCellScanner scanner(SelectionRef());
1811 if (scanner.IsInTableCellSelectionMode() && SelectionRef().RangeCount() > 1) {
1812 const RefPtr<PresShell> presShell{GetPresShell()};
1813 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
1814 // grabs it until it's destroyed later.
1815 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
1816 presShell);
1817 if (NS_WARN_IF(firstCellIndexes.isErr())) {
1818 return NS_ERROR_FAILURE;
1820 startRowIndex = firstCellIndexes.mRow;
1821 startColIndex = firstCellIndexes.mColumn;
1824 // We control selection resetting after the insert...
1825 AutoSelectionSetterAfterTableEdit setCaret(
1826 *this, table, startRowIndex, startColIndex, ePreviousRow, false);
1828 // If 2 or more cells are not selected, removing columns starting from
1829 // a column which contains first selection range.
1830 if (!scanner.IsInTableCellSelectionMode() ||
1831 SelectionRef().RangeCount() == 1) {
1832 int32_t columnCountToRemove = std::min(
1833 aNumberOfColumnsToDelete, tableSize.mColumnCount - startColIndex);
1834 for (int32_t i = 0; i < columnCountToRemove; i++) {
1835 nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1836 if (NS_FAILED(rv)) {
1837 NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
1838 return rv;
1841 return NS_OK;
1844 // If 2 or more cells are selected, remove all columns which contain selected
1845 // cells. I.e., we ignore aNumberOfColumnsToDelete in this case.
1846 const RefPtr<PresShell> presShell{GetPresShell()};
1847 for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
1848 selectedCellElement;) {
1849 if (selectedCellElement != scanner.ElementsRef()[0]) {
1850 const CellIndexes cellIndexes(*selectedCellElement, presShell);
1851 if (NS_WARN_IF(cellIndexes.isErr())) {
1852 return NS_ERROR_FAILURE;
1854 startRowIndex = cellIndexes.mRow;
1855 startColIndex = cellIndexes.mColumn;
1857 // Find the next cell in a different column
1858 // to continue after we delete this column
1859 int32_t nextCol = startColIndex;
1860 while (nextCol == startColIndex) {
1861 selectedCellElement = scanner.GetNextElement();
1862 if (!selectedCellElement) {
1863 break;
1865 const CellIndexes cellIndexes(*selectedCellElement, presShell);
1866 if (NS_WARN_IF(cellIndexes.isErr())) {
1867 return NS_ERROR_FAILURE;
1869 startRowIndex = cellIndexes.mRow;
1870 nextCol = cellIndexes.mColumn;
1872 nsresult rv = DeleteTableColumnWithTransaction(*table, startColIndex);
1873 if (NS_FAILED(rv)) {
1874 NS_WARNING("HTMLEditor::DeleteTableColumnWithTransaction() failed");
1875 return rv;
1878 return NS_OK;
1881 nsresult HTMLEditor::DeleteTableColumnWithTransaction(Element& aTableElement,
1882 int32_t aColumnIndex) {
1883 MOZ_ASSERT(IsEditActionDataAvailable());
1885 for (int32_t rowIndex = 0;; rowIndex++) {
1886 const auto cellData = CellData::AtIndexInTableElement(
1887 *this, aTableElement, rowIndex, aColumnIndex);
1888 // Failure means that there is no more row in the table. In this case,
1889 // we shouldn't return error since we just reach the end of the table.
1890 // XXX Should distinguish whether CellData returns error or just not found
1891 // later.
1892 if (cellData.FailedOrNotFound()) {
1893 return NS_OK;
1896 // Find cells that don't start in column we are deleting.
1897 MOZ_ASSERT(cellData.mColSpan >= 0);
1898 if (cellData.IsSpannedFromOtherColumn() || cellData.mColSpan != 1) {
1899 // If we have a cell spanning this location, decrease its colspan to
1900 // keep table rectangular, but if colspan is 0, it'll be adjusted
1901 // automatically.
1902 if (cellData.mColSpan > 0) {
1903 NS_WARNING_ASSERTION(cellData.mColSpan > 1,
1904 "colspan should be 2 or larger");
1905 DebugOnly<nsresult> rvIgnored =
1906 SetColSpan(cellData.mElement, cellData.mColSpan - 1);
1907 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1908 "HTMLEditor::SetColSpan() failed, but ignored");
1910 if (!cellData.IsSpannedFromOtherColumn()) {
1911 // Cell is in column to be deleted, but must have colspan > 1,
1912 // so delete contents of cell instead of cell itself (We must have
1913 // reset colspan above).
1914 DebugOnly<nsresult> rvIgnored =
1915 DeleteAllChildrenWithTransaction(*cellData.mElement);
1916 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1917 "HTMLEditor::DeleteAllChildrenWithTransaction() "
1918 "failed, but ignored");
1920 // Skip rows which the removed cell spanned.
1921 rowIndex += cellData.NumberOfFollowingRows();
1922 continue;
1925 // Delete the cell
1926 int32_t numberOfCellsInRow =
1927 GetNumberOfCellsInRow(aTableElement, cellData.mCurrent.mRow);
1928 NS_WARNING_ASSERTION(
1929 numberOfCellsInRow > 0,
1930 "HTMLEditor::GetNumberOfCellsInRow() failed, but ignored");
1931 if (numberOfCellsInRow != 1) {
1932 // If removing cell is not the last cell of the row, we can just remove
1933 // it.
1934 nsresult rv = DeleteNodeWithTransaction(*cellData.mElement);
1935 if (NS_FAILED(rv)) {
1936 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1937 return rv;
1939 // Skip rows which the removed cell spanned.
1940 rowIndex += cellData.NumberOfFollowingRows();
1941 continue;
1944 // When the cell is the last cell in the row, remove the row instead.
1945 Element* parentRow = GetInclusiveAncestorByTagNameInternal(
1946 *nsGkAtoms::tr, *cellData.mElement);
1947 if (!parentRow) {
1948 NS_WARNING(
1949 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::tr) "
1950 "failed");
1951 return NS_ERROR_FAILURE;
1954 // Check if its the only row left in the table. If so, we can delete
1955 // the table instead.
1956 const Result<TableSize, nsresult> tableSizeOrError =
1957 TableSize::Create(*this, aTableElement);
1958 if (NS_WARN_IF(tableSizeOrError.isErr())) {
1959 return tableSizeOrError.inspectErr();
1961 const TableSize& tableSize = tableSizeOrError.inspect();
1963 if (tableSize.mRowCount == 1) {
1964 // We're deleting the last row. So, let's remove the <table> now.
1965 nsresult rv = DeleteTableElementAndChildrenWithTransaction(aTableElement);
1966 NS_WARNING_ASSERTION(
1967 NS_SUCCEEDED(rv),
1968 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
1969 return rv;
1972 // Delete the row by placing caret in cell we were to delete. We need
1973 // to call DeleteTableRowWithTransaction() to handle cells with rowspan.
1974 nsresult rv =
1975 DeleteTableRowWithTransaction(aTableElement, cellData.mFirst.mRow);
1976 if (NS_FAILED(rv)) {
1977 NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
1978 return rv;
1981 // Note that we decrement rowIndex since a row was deleted.
1982 rowIndex--;
1985 // Not reached because for (;;) loop never breaks.
1988 NS_IMETHODIMP HTMLEditor::DeleteTableRow(int32_t aNumberOfRowsToDelete) {
1989 AutoEditActionDataSetter editActionData(*this,
1990 EditAction::eRemoveTableRowElement);
1991 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
1992 if (NS_FAILED(rv)) {
1993 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
1994 return EditorBase::ToGenericNSResult(rv);
1996 const RefPtr<Element> editingHost =
1997 ComputeEditingHost(LimitInBodyElement::No);
1998 if (NS_WARN_IF(editingHost &&
1999 editingHost->IsContentEditablePlainTextOnly())) {
2000 return NS_ERROR_NOT_AVAILABLE;
2002 rv = editActionData.MaybeDispatchBeforeInputEvent();
2003 if (NS_FAILED(rv)) {
2004 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2005 "MaybeDispatchBeforeInputEvent(), failed");
2006 return EditorBase::ToGenericNSResult(rv);
2009 rv = DeleteSelectedTableRowsWithTransaction(aNumberOfRowsToDelete);
2010 NS_WARNING_ASSERTION(
2011 NS_SUCCEEDED(rv),
2012 "HTMLEditor::DeleteSelectedTableRowsWithTransaction() failed");
2013 return EditorBase::ToGenericNSResult(rv);
2016 nsresult HTMLEditor::DeleteSelectedTableRowsWithTransaction(
2017 int32_t aNumberOfRowsToDelete) {
2018 MOZ_ASSERT(IsEditActionDataAvailable());
2020 RefPtr<Element> table;
2021 RefPtr<Element> cell;
2022 int32_t startRowIndex, startColIndex;
2023 nsresult rv =
2024 GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
2025 nullptr, &startRowIndex, &startColIndex);
2026 if (NS_FAILED(rv)) {
2027 NS_WARNING("HTMLEditor::GetCellContext() failed");
2028 return rv;
2030 if (!table || !cell) {
2031 NS_WARNING(
2032 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
2033 // Don't fail if no cell found.
2034 return NS_OK;
2037 const Result<TableSize, nsresult> tableSizeOrError =
2038 TableSize::Create(*this, *table);
2039 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2040 return tableSizeOrError.inspectErr();
2042 const TableSize& tableSize = tableSizeOrError.inspect();
2044 AutoPlaceholderBatch treateAsOneTransaction(
2045 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2047 // Prevent rules testing until we're done
2048 IgnoredErrorResult error;
2049 AutoEditSubActionNotifier startToHandleEditSubAction(
2050 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
2051 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2052 return error.StealNSResult();
2054 NS_WARNING_ASSERTION(
2055 !error.Failed(),
2056 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2058 // Shortcut the case of deleting all rows in table
2059 if (!startRowIndex && aNumberOfRowsToDelete >= tableSize.mRowCount) {
2060 nsresult rv = DeleteTableElementAndChildrenWithTransaction(*table);
2061 NS_WARNING_ASSERTION(
2062 NS_SUCCEEDED(rv),
2063 "HTMLEditor::DeleteTableElementAndChildrenWithTransaction() failed");
2064 return rv;
2067 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
2068 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
2071 SelectedTableCellScanner scanner(SelectionRef());
2072 if (scanner.IsInTableCellSelectionMode() && SelectionRef().RangeCount() > 1) {
2073 // Fetch indexes again - may be different for selected cells
2074 const RefPtr<PresShell> presShell{GetPresShell()};
2075 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
2076 // grabs it until it's destroyed later.
2077 const CellIndexes firstCellIndexes(MOZ_KnownLive(scanner.ElementsRef()[0]),
2078 presShell);
2079 if (NS_WARN_IF(firstCellIndexes.isErr())) {
2080 return NS_ERROR_FAILURE;
2082 startRowIndex = firstCellIndexes.mRow;
2083 startColIndex = firstCellIndexes.mColumn;
2086 // We control selection resetting after the insert...
2087 AutoSelectionSetterAfterTableEdit setCaret(
2088 *this, table, startRowIndex, startColIndex, ePreviousRow, false);
2089 // Don't change selection during deletions
2090 AutoTransactionsConserveSelection dontChangeSelection(*this);
2092 // XXX Perhaps, the following loops should collect <tr> elements to remove
2093 // first, then, remove them from the DOM tree since mutation event
2094 // listener may change the DOM tree during the loops.
2096 // If 2 or more cells are not selected, removing rows starting from
2097 // a row which contains first selection range.
2098 if (!scanner.IsInTableCellSelectionMode() ||
2099 SelectionRef().RangeCount() == 1) {
2100 int32_t rowCountToRemove =
2101 std::min(aNumberOfRowsToDelete, tableSize.mRowCount - startRowIndex);
2102 for (int32_t i = 0; i < rowCountToRemove; i++) {
2103 nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
2104 // If failed in current row, try the next
2105 if (NS_FAILED(rv)) {
2106 NS_WARNING(
2107 "HTMLEditor::DeleteTableRowWithTransaction() failed, but trying "
2108 "next...");
2109 startRowIndex++;
2111 // Check if there's a cell in the "next" row.
2112 cell = GetTableCellElementAt(*table, startRowIndex, startColIndex);
2113 if (!cell) {
2114 return NS_OK;
2117 return NS_OK;
2120 // If 2 or more cells are selected, remove all rows which contain selected
2121 // cells. I.e., we ignore aNumberOfRowsToDelete in this case.
2122 const RefPtr<PresShell> presShell{GetPresShell()};
2123 for (RefPtr<Element> selectedCellElement = scanner.GetFirstElement();
2124 selectedCellElement;) {
2125 if (selectedCellElement != scanner.ElementsRef()[0]) {
2126 const CellIndexes cellIndexes(*selectedCellElement, presShell);
2127 if (NS_WARN_IF(cellIndexes.isErr())) {
2128 return NS_ERROR_FAILURE;
2130 startRowIndex = cellIndexes.mRow;
2131 startColIndex = cellIndexes.mColumn;
2133 // Find the next cell in a different row
2134 // to continue after we delete this row
2135 int32_t nextRow = startRowIndex;
2136 while (nextRow == startRowIndex) {
2137 selectedCellElement = scanner.GetNextElement();
2138 if (!selectedCellElement) {
2139 break;
2141 const CellIndexes cellIndexes(*selectedCellElement, presShell);
2142 if (NS_WARN_IF(cellIndexes.isErr())) {
2143 return NS_ERROR_FAILURE;
2145 nextRow = cellIndexes.mRow;
2146 startColIndex = cellIndexes.mColumn;
2148 // Delete the row containing selected cell(s).
2149 nsresult rv = DeleteTableRowWithTransaction(*table, startRowIndex);
2150 if (NS_FAILED(rv)) {
2151 NS_WARNING("HTMLEditor::DeleteTableRowWithTransaction() failed");
2152 return rv;
2155 return NS_OK;
2158 // Helper that doesn't batch or change the selection
2159 nsresult HTMLEditor::DeleteTableRowWithTransaction(Element& aTableElement,
2160 int32_t aRowIndex) {
2161 MOZ_ASSERT(IsEditActionDataAvailable());
2163 const Result<TableSize, nsresult> tableSizeOrError =
2164 TableSize::Create(*this, aTableElement);
2165 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2166 return tableSizeOrError.inspectErr();
2168 const TableSize& tableSize = tableSizeOrError.inspect();
2170 // Prevent rules testing until we're done
2171 IgnoredErrorResult error;
2172 AutoEditSubActionNotifier startToHandleEditSubAction(
2173 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
2174 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2175 return error.StealNSResult();
2177 NS_WARNING_ASSERTION(
2178 !error.Failed(),
2179 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2180 error.SuppressException();
2182 // Scan through cells in row to do rowspan adjustments
2183 // Note that after we delete row, startRowIndex will point to the cells in
2184 // the next row to be deleted.
2186 // The list of cells we will change rowspan in and the new rowspan values
2187 // for each.
2188 struct MOZ_STACK_CLASS SpanCell final {
2189 RefPtr<Element> mElement;
2190 int32_t mNewRowSpanValue;
2192 SpanCell(Element* aSpanCellElement, int32_t aNewRowSpanValue)
2193 : mElement(aSpanCellElement), mNewRowSpanValue(aNewRowSpanValue) {}
2195 AutoTArray<SpanCell, 10> spanCellArray;
2196 RefPtr<Element> cellInDeleteRow;
2197 int32_t columnIndex = 0;
2198 while (aRowIndex < tableSize.mRowCount &&
2199 columnIndex < tableSize.mColumnCount) {
2200 const auto cellData = CellData::AtIndexInTableElement(
2201 *this, aTableElement, aRowIndex, columnIndex);
2202 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2203 return NS_ERROR_FAILURE;
2206 // XXX So, we should distinguish if CellDate returns error or just not
2207 // found later.
2208 if (!cellData.mElement) {
2209 break;
2212 // Compensate for cells that don't start or extend below the row we are
2213 // deleting.
2214 if (cellData.IsSpannedFromOtherRow()) {
2215 // If a cell starts in row above us, decrease its rowspan to keep table
2216 // rectangular but we don't need to do this if rowspan=0, since it will
2217 // be automatically adjusted.
2218 if (cellData.mRowSpan > 0) {
2219 // Build list of cells to change rowspan. We can't do it now since
2220 // it upsets cell map, so we will do it after deleting the row.
2221 int32_t newRowSpanValue = std::max(cellData.NumberOfPrecedingRows(),
2222 cellData.NumberOfFollowingRows());
2223 spanCellArray.AppendElement(
2224 SpanCell(cellData.mElement, newRowSpanValue));
2226 } else {
2227 if (cellData.mRowSpan > 1) {
2228 // Cell spans below row to delete, so we must insert new cells to
2229 // keep rows below. Note that we test "rowSpan" so we don't do this
2230 // if rowSpan = 0 (automatic readjustment).
2231 int32_t aboveRowToInsertNewCellInto =
2232 cellData.NumberOfPrecedingRows() + 1;
2233 nsresult rv = SplitCellIntoRows(
2234 &aTableElement, cellData.mFirst.mRow, cellData.mFirst.mColumn,
2235 aboveRowToInsertNewCellInto, cellData.NumberOfFollowingRows(),
2236 nullptr);
2237 if (NS_FAILED(rv)) {
2238 NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
2239 return rv;
2242 if (!cellInDeleteRow) {
2243 // Reference cell to find row to delete.
2244 cellInDeleteRow = std::move(cellData.mElement);
2247 // Skip over other columns spanned by this cell
2248 columnIndex += cellData.mEffectiveColSpan;
2251 // Things are messed up if we didn't find a cell in the row!
2252 if (!cellInDeleteRow) {
2253 NS_WARNING("There was no cell in deleting row");
2254 return NS_ERROR_FAILURE;
2257 // Delete the entire row.
2258 RefPtr<Element> parentRow =
2259 GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::tr, *cellInDeleteRow);
2260 if (parentRow) {
2261 nsresult rv = DeleteNodeWithTransaction(*parentRow);
2262 if (NS_FAILED(rv)) {
2263 NS_WARNING(
2264 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::tr) "
2265 "failed");
2266 return rv;
2270 // Now we can set new rowspans for cells stored above.
2271 for (SpanCell& spanCell : spanCellArray) {
2272 if (NS_WARN_IF(!spanCell.mElement)) {
2273 continue;
2275 nsresult rv =
2276 SetRowSpan(MOZ_KnownLive(spanCell.mElement), spanCell.mNewRowSpanValue);
2277 if (NS_FAILED(rv)) {
2278 NS_WARNING("HTMLEditor::SetRawSpan() failed");
2279 return rv;
2282 return NS_OK;
2285 NS_IMETHODIMP HTMLEditor::SelectTable() {
2286 AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTable);
2287 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2288 if (NS_FAILED(rv)) {
2289 NS_WARNING("HTMLEditor::SelectTable() couldn't handle the job");
2290 return EditorBase::ToGenericNSResult(rv);
2293 RefPtr<Element> table =
2294 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
2295 if (!table) {
2296 NS_WARNING(
2297 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::table)"
2298 " failed");
2299 return NS_OK; // Don't fail if we didn't find a table.
2302 rv = ClearSelection();
2303 if (NS_FAILED(rv)) {
2304 NS_WARNING("HTMLEditor::ClearSelection() failed");
2305 return EditorBase::ToGenericNSResult(rv);
2307 rv = AppendContentToSelectionAsRange(*table);
2308 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2309 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2310 return EditorBase::ToGenericNSResult(rv);
2313 NS_IMETHODIMP HTMLEditor::SelectTableCell() {
2314 AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTableCell);
2315 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2316 if (NS_FAILED(rv)) {
2317 NS_WARNING("HTMLEditor::SelectTableCell() couldn't handle the job");
2318 return EditorBase::ToGenericNSResult(rv);
2321 RefPtr<Element> cell =
2322 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2323 if (!cell) {
2324 NS_WARNING(
2325 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2326 "failed");
2327 // Don't fail if we didn't find a cell.
2328 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2331 rv = ClearSelection();
2332 if (NS_FAILED(rv)) {
2333 NS_WARNING("HTMLEditor::ClearSelection() failed");
2334 return EditorBase::ToGenericNSResult(rv);
2336 rv = AppendContentToSelectionAsRange(*cell);
2337 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2338 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2339 return EditorBase::ToGenericNSResult(rv);
2342 NS_IMETHODIMP HTMLEditor::SelectAllTableCells() {
2343 AutoEditActionDataSetter editActionData(*this,
2344 EditAction::eSelectAllTableCells);
2345 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2346 if (NS_FAILED(rv)) {
2347 NS_WARNING("HTMLEditor::SelectAllTableCells() couldn't handle the job");
2348 return EditorBase::ToGenericNSResult(rv);
2351 RefPtr<Element> cell =
2352 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2353 if (!cell) {
2354 NS_WARNING(
2355 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2356 "failed");
2357 // Don't fail if we didn't find a cell.
2358 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2361 RefPtr<Element> startCell = cell;
2363 // Get parent table
2364 RefPtr<Element> table =
2365 GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *cell);
2366 if (!table) {
2367 NS_WARNING(
2368 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
2369 "failed");
2370 return NS_ERROR_FAILURE;
2373 const Result<TableSize, nsresult> tableSizeOrError =
2374 TableSize::Create(*this, *table);
2375 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2376 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
2378 const TableSize& tableSize = tableSizeOrError.inspect();
2380 // Suppress nsISelectionListener notification
2381 // until all selection changes are finished
2382 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
2384 // It is now safe to clear the selection
2385 // BE SURE TO RESET IT BEFORE LEAVING!
2386 rv = ClearSelection();
2387 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2388 NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
2389 return EditorBase::ToGenericNSResult(rv);
2391 NS_WARNING_ASSERTION(
2392 NS_SUCCEEDED(rv),
2393 "HTMLEditor::ClearSelection() failed, but might be ignored");
2395 // Select all cells in the same column as current cell
2396 bool cellSelected = false;
2397 // Safety code to select starting cell if nothing else was selected
2398 auto AppendContentToStartCell = [&]() MOZ_CAN_RUN_SCRIPT {
2399 MOZ_ASSERT(!cellSelected);
2400 // XXX In this case, we ignore `NS_ERROR_FAILURE` set by above inner
2401 // `for` loop.
2402 nsresult rv = AppendContentToSelectionAsRange(*startCell);
2403 NS_WARNING_ASSERTION(
2404 NS_SUCCEEDED(rv),
2405 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2406 return EditorBase::ToGenericNSResult(rv);
2408 for (int32_t row = 0; row < tableSize.mRowCount; row++) {
2409 for (int32_t col = 0; col < tableSize.mColumnCount;) {
2410 const auto cellData =
2411 CellData::AtIndexInTableElement(*this, *table, row, col);
2412 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2413 return !cellSelected ? AppendContentToStartCell() : NS_ERROR_FAILURE;
2416 // Skip cells that are spanned from previous rows or columns
2417 // XXX So, we should distinguish whether CellData returns error or just
2418 // not found later.
2419 if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
2420 nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
2421 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2422 NS_WARNING(
2423 "HTMLEditor::AppendContentToSelectionAsRange() caused "
2424 "destroying the editor");
2425 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2427 if (NS_FAILED(rv)) {
2428 NS_WARNING(
2429 "HTMLEditor::AppendContentToSelectionAsRange() failed, but "
2430 "might be ignored");
2431 return !cellSelected ? AppendContentToStartCell()
2432 : EditorBase::ToGenericNSResult(rv);
2434 cellSelected = true;
2436 MOZ_ASSERT(col < cellData.NextColumnIndex());
2437 col = cellData.NextColumnIndex();
2440 return EditorBase::ToGenericNSResult(rv);
2443 NS_IMETHODIMP HTMLEditor::SelectTableRow() {
2444 AutoEditActionDataSetter editActionData(*this, EditAction::eSelectTableRow);
2445 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2446 if (NS_FAILED(rv)) {
2447 NS_WARNING("HTMLEditor::SelectTableRow() couldn't handle the job");
2448 return EditorBase::ToGenericNSResult(rv);
2451 RefPtr<Element> cell =
2452 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2453 if (!cell) {
2454 NS_WARNING(
2455 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2456 "failed");
2457 // Don't fail if we didn't find a cell.
2458 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2461 RefPtr<Element> startCell = cell;
2463 // Get table and location of cell:
2464 RefPtr<Element> table;
2465 int32_t startRowIndex, startColIndex;
2467 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
2468 nullptr, &startRowIndex, &startColIndex);
2469 if (NS_FAILED(rv)) {
2470 NS_WARNING("HTMLEditor::GetCellContext() failed");
2471 return EditorBase::ToGenericNSResult(rv);
2473 if (!table) {
2474 NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
2475 return NS_ERROR_FAILURE;
2478 const Result<TableSize, nsresult> tableSizeOrError =
2479 TableSize::Create(*this, *table);
2480 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2481 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
2483 const TableSize& tableSize = tableSizeOrError.inspect();
2485 // Note: At this point, we could get first and last cells in row,
2486 // then call SelectBlockOfCells, but that would take just
2487 // a little less code, so the following is more efficient
2489 // Suppress nsISelectionListener notification
2490 // until all selection changes are finished
2491 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
2493 // It is now safe to clear the selection
2494 // BE SURE TO RESET IT BEFORE LEAVING!
2495 rv = ClearSelection();
2496 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2497 NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
2498 return EditorBase::ToGenericNSResult(rv);
2500 NS_WARNING_ASSERTION(
2501 NS_SUCCEEDED(rv),
2502 "HTMLEditor::ClearSelection() failed, but might be ignored");
2504 // Select all cells in the same row as current cell
2505 bool cellSelected = false;
2506 for (int32_t col = 0; col < tableSize.mColumnCount;) {
2507 const auto cellData =
2508 CellData::AtIndexInTableElement(*this, *table, startRowIndex, col);
2509 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2510 if (cellSelected) {
2511 return NS_ERROR_FAILURE;
2513 // Safety code to select starting cell if nothing else was selected
2514 nsresult rv = AppendContentToSelectionAsRange(*startCell);
2515 NS_WARNING_ASSERTION(
2516 NS_SUCCEEDED(rv),
2517 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2518 NS_WARNING_ASSERTION(
2519 cellData.isOk() || NS_SUCCEEDED(rv) ||
2520 NS_FAILED(EditorBase::ToGenericNSResult(rv)),
2521 "CellData::AtIndexInTableElement() failed, but ignored");
2522 return EditorBase::ToGenericNSResult(rv);
2525 // Skip cells that are spanned from previous rows or columns
2526 // XXX So, we should distinguish whether CellData returns error or just
2527 // not found later.
2528 if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
2529 nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
2530 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2531 NS_WARNING(
2532 "HTMLEditor::AppendContentToSelectionAsRange() caused destroying "
2533 "the editor");
2534 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2536 if (NS_FAILED(rv)) {
2537 if (cellSelected) {
2538 NS_WARNING("HTMLEditor::AppendContentToSelectionAsRange() failed");
2539 return EditorBase::ToGenericNSResult(rv);
2541 // Safety code to select starting cell if nothing else was selected
2542 nsresult rvTryAgain = AppendContentToSelectionAsRange(*startCell);
2543 NS_WARNING_ASSERTION(
2544 NS_SUCCEEDED(rv),
2545 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2546 NS_WARNING_ASSERTION(
2547 NS_SUCCEEDED(EditorBase::ToGenericNSResult(rv)) ||
2548 NS_SUCCEEDED(rvTryAgain) ||
2549 NS_FAILED(EditorBase::ToGenericNSResult(rvTryAgain)),
2550 "HTMLEditor::AppendContentToSelectionAsRange(*cellData.mElement) "
2551 "failed, but ignored");
2552 return EditorBase::ToGenericNSResult(rvTryAgain);
2554 cellSelected = true;
2556 MOZ_ASSERT(col < cellData.NextColumnIndex());
2557 col = cellData.NextColumnIndex();
2559 return EditorBase::ToGenericNSResult(rv);
2562 NS_IMETHODIMP HTMLEditor::SelectTableColumn() {
2563 AutoEditActionDataSetter editActionData(*this,
2564 EditAction::eSelectTableColumn);
2565 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2566 if (NS_FAILED(rv)) {
2567 NS_WARNING("HTMLEditor::SelectTableColumn() couldn't handle the job");
2568 return EditorBase::ToGenericNSResult(rv);
2571 RefPtr<Element> cell =
2572 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
2573 if (!cell) {
2574 NS_WARNING(
2575 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::td) "
2576 "failed");
2577 // Don't fail if we didn't find a cell.
2578 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2581 RefPtr<Element> startCell = cell;
2583 // Get location of cell:
2584 RefPtr<Element> table;
2585 int32_t startRowIndex, startColIndex;
2587 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
2588 nullptr, &startRowIndex, &startColIndex);
2589 if (NS_FAILED(rv)) {
2590 NS_WARNING("HTMLEditor::GetCellContext() failed");
2591 return EditorBase::ToGenericNSResult(rv);
2593 if (!table) {
2594 NS_WARNING("HTMLEditor::GetCellContext() didn't return <table> element");
2595 return NS_ERROR_FAILURE;
2598 const Result<TableSize, nsresult> tableSizeOrError =
2599 TableSize::Create(*this, *table);
2600 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2601 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
2603 const TableSize& tableSize = tableSizeOrError.inspect();
2605 // Suppress nsISelectionListener notification
2606 // until all selection changes are finished
2607 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
2609 // It is now safe to clear the selection
2610 // BE SURE TO RESET IT BEFORE LEAVING!
2611 rv = ClearSelection();
2612 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2613 NS_WARNING("HTMLEditor::ClearSelection() caused destroying the editor");
2614 return EditorBase::ToGenericNSResult(rv);
2616 NS_WARNING_ASSERTION(
2617 NS_SUCCEEDED(rv),
2618 "HTMLEditor::ClearSelection() failed, but might be ignored");
2620 // Select all cells in the same column as current cell
2621 bool cellSelected = false;
2622 for (int32_t row = 0; row < tableSize.mRowCount;) {
2623 const auto cellData =
2624 CellData::AtIndexInTableElement(*this, *table, row, startColIndex);
2625 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2626 if (cellSelected) {
2627 return NS_ERROR_FAILURE;
2629 // Safety code to select starting cell if nothing else was selected
2630 nsresult rv = AppendContentToSelectionAsRange(*startCell);
2631 NS_WARNING_ASSERTION(
2632 NS_SUCCEEDED(rv),
2633 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2634 NS_WARNING_ASSERTION(
2635 cellData.isOk() || NS_SUCCEEDED(rv) ||
2636 NS_FAILED(EditorBase::ToGenericNSResult(rv)),
2637 "CellData::AtIndexInTableElement() failed, but ignored");
2638 return EditorBase::ToGenericNSResult(rv);
2641 // Skip cells that are spanned from previous rows or columns
2642 // XXX So, we should distinguish whether CellData returns error or just
2643 // not found later.
2644 if (cellData.mElement && !cellData.IsSpannedFromOtherRowOrColumn()) {
2645 nsresult rv = AppendContentToSelectionAsRange(*cellData.mElement);
2646 if (rv == NS_ERROR_EDITOR_DESTROYED) {
2647 NS_WARNING(
2648 "HTMLEditor::AppendContentToSelectionAsRange() caused destroying "
2649 "the editor");
2650 return EditorBase::ToGenericNSResult(rv);
2652 if (NS_FAILED(rv)) {
2653 if (cellSelected) {
2654 NS_WARNING("HTMLEditor::AppendContentToSelectionAsRange() failed");
2655 return EditorBase::ToGenericNSResult(rv);
2657 // Safety code to select starting cell if nothing else was selected
2658 nsresult rvTryAgain = AppendContentToSelectionAsRange(*startCell);
2659 NS_WARNING_ASSERTION(
2660 NS_SUCCEEDED(rv),
2661 "HTMLEditor::AppendContentToSelectionAsRange() failed");
2662 NS_WARNING_ASSERTION(
2663 NS_SUCCEEDED(EditorBase::ToGenericNSResult(rv)) ||
2664 NS_SUCCEEDED(rvTryAgain) ||
2665 NS_FAILED(EditorBase::ToGenericNSResult(rvTryAgain)),
2666 "HTMLEditor::AppendContentToSelectionAsRange(*cellData.mElement) "
2667 "failed, but ignored");
2668 return EditorBase::ToGenericNSResult(rvTryAgain);
2670 cellSelected = true;
2672 MOZ_ASSERT(row < cellData.NextRowIndex());
2673 row = cellData.NextRowIndex();
2675 return EditorBase::ToGenericNSResult(rv);
2678 NS_IMETHODIMP HTMLEditor::SplitTableCell() {
2679 AutoEditActionDataSetter editActionData(*this,
2680 EditAction::eSplitTableCellElement);
2681 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2682 if (NS_FAILED(rv)) {
2683 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
2684 return EditorBase::ToGenericNSResult(rv);
2686 const RefPtr<Element> editingHost =
2687 ComputeEditingHost(LimitInBodyElement::No);
2688 if (NS_WARN_IF(editingHost &&
2689 editingHost->IsContentEditablePlainTextOnly())) {
2690 return NS_ERROR_NOT_AVAILABLE;
2692 rv = editActionData.MaybeDispatchBeforeInputEvent();
2693 if (NS_FAILED(rv)) {
2694 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2695 "MaybeDispatchBeforeInputEvent(), failed");
2696 return EditorBase::ToGenericNSResult(rv);
2699 RefPtr<Element> table;
2700 RefPtr<Element> cell;
2701 int32_t startRowIndex, startColIndex, actualRowSpan, actualColSpan;
2702 // Get cell, table, etc. at selection anchor node
2703 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(cell), nullptr,
2704 nullptr, &startRowIndex, &startColIndex);
2705 if (NS_FAILED(rv)) {
2706 NS_WARNING("HTMLEditor::GetCellContext() failed");
2707 return EditorBase::ToGenericNSResult(rv);
2709 if (!table || !cell) {
2710 NS_WARNING(
2711 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
2712 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2715 // We need rowspan and colspan data
2716 rv = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan,
2717 actualColSpan);
2718 if (NS_FAILED(rv)) {
2719 NS_WARNING("HTMLEditor::GetCellSpansAt() failed");
2720 return EditorBase::ToGenericNSResult(rv);
2723 // Must have some span to split
2724 if (actualRowSpan <= 1 && actualColSpan <= 1) {
2725 return NS_OK;
2728 AutoPlaceholderBatch treateAsOneTransaction(
2729 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2730 // Prevent auto insertion of BR in new cell until we're done
2731 IgnoredErrorResult ignoredError;
2732 AutoEditSubActionNotifier startToHandleEditSubAction(
2733 *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
2734 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2735 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
2737 NS_WARNING_ASSERTION(
2738 !ignoredError.Failed(),
2739 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2741 // We reset selection
2742 AutoSelectionSetterAfterTableEdit setCaret(
2743 *this, table, startRowIndex, startColIndex, ePreviousColumn, false);
2744 //...so suppress Rules System selection munging
2745 AutoTransactionsConserveSelection dontChangeSelection(*this);
2747 RefPtr<Element> newCell;
2748 int32_t rowIndex = startRowIndex;
2749 int32_t rowSpanBelow, colSpanAfter;
2751 // Split up cell row-wise first into rowspan=1 above, and the rest below,
2752 // whittling away at the cell below until no more extra span
2753 for (rowSpanBelow = actualRowSpan - 1; rowSpanBelow >= 0; rowSpanBelow--) {
2754 // We really split row-wise only if we had rowspan > 1
2755 if (rowSpanBelow > 0) {
2756 nsresult rv = SplitCellIntoRows(table, rowIndex, startColIndex, 1,
2757 rowSpanBelow, getter_AddRefs(newCell));
2758 if (NS_FAILED(rv)) {
2759 NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
2760 return EditorBase::ToGenericNSResult(rv);
2762 DebugOnly<nsresult> rvIgnored = CopyCellBackgroundColor(newCell, cell);
2763 NS_WARNING_ASSERTION(
2764 NS_SUCCEEDED(rvIgnored),
2765 "HTMLEditor::CopyCellBackgroundColor() failed, but ignored");
2767 int32_t colIndex = startColIndex;
2768 // Now split the cell with rowspan = 1 into cells if it has colSpan > 1
2769 for (colSpanAfter = actualColSpan - 1; colSpanAfter > 0; colSpanAfter--) {
2770 nsresult rv = SplitCellIntoColumns(table, rowIndex, colIndex, 1,
2771 colSpanAfter, getter_AddRefs(newCell));
2772 if (NS_FAILED(rv)) {
2773 NS_WARNING("HTMLEditor::SplitCellIntoColumns() failed");
2774 return EditorBase::ToGenericNSResult(rv);
2776 DebugOnly<nsresult> rvIgnored = CopyCellBackgroundColor(newCell, cell);
2777 NS_WARNING_ASSERTION(
2778 NS_SUCCEEDED(rv),
2779 "HTMLEditor::CopyCellBackgroundColor() failed, but ignored");
2780 colIndex++;
2782 // Point to the new cell and repeat
2783 rowIndex++;
2785 return NS_OK;
2788 nsresult HTMLEditor::CopyCellBackgroundColor(Element* aDestCell,
2789 Element* aSourceCell) {
2790 if (NS_WARN_IF(!aDestCell) || NS_WARN_IF(!aSourceCell)) {
2791 return NS_ERROR_INVALID_ARG;
2794 if (!aSourceCell->HasAttr(nsGkAtoms::bgcolor)) {
2795 return NS_OK;
2798 // Copy backgournd color to new cell.
2799 nsString backgroundColor;
2800 aSourceCell->GetAttr(nsGkAtoms::bgcolor, backgroundColor);
2801 nsresult rv = SetAttributeWithTransaction(*aDestCell, *nsGkAtoms::bgcolor,
2802 backgroundColor);
2803 NS_WARNING_ASSERTION(
2804 NS_SUCCEEDED(rv),
2805 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
2806 return rv;
2809 nsresult HTMLEditor::SplitCellIntoColumns(Element* aTable, int32_t aRowIndex,
2810 int32_t aColIndex,
2811 int32_t aColSpanLeft,
2812 int32_t aColSpanRight,
2813 Element** aNewCell) {
2814 if (NS_WARN_IF(!aTable)) {
2815 return NS_ERROR_INVALID_ARG;
2817 if (aNewCell) {
2818 *aNewCell = nullptr;
2821 const auto cellData =
2822 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, aColIndex);
2823 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2824 return NS_ERROR_FAILURE;
2827 // We can't split!
2828 if (cellData.mEffectiveColSpan <= 1 ||
2829 aColSpanLeft + aColSpanRight > cellData.mEffectiveColSpan) {
2830 return NS_OK;
2833 // Reduce colspan of cell to split
2834 nsresult rv = SetColSpan(cellData.mElement, aColSpanLeft);
2835 if (NS_FAILED(rv)) {
2836 NS_WARNING("HTMLEditor::SetColSpan() failed");
2837 return rv;
2840 // Insert new cell after using the remaining span
2841 // and always get the new cell so we can copy the background color;
2842 RefPtr<Element> newCellElement;
2843 rv = InsertCell(cellData.mElement, cellData.mEffectiveRowSpan, aColSpanRight,
2844 true, false, getter_AddRefs(newCellElement));
2845 if (NS_FAILED(rv)) {
2846 NS_WARNING("HTMLEditor::InsertCell() failed");
2847 return rv;
2849 if (!newCellElement) {
2850 return NS_OK;
2852 if (aNewCell) {
2853 *aNewCell = do_AddRef(newCellElement).take();
2855 rv = CopyCellBackgroundColor(newCellElement, cellData.mElement);
2856 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2857 "HTMLEditor::CopyCellBackgroundColor() failed");
2858 return rv;
2861 nsresult HTMLEditor::SplitCellIntoRows(Element* aTable, int32_t aRowIndex,
2862 int32_t aColIndex, int32_t aRowSpanAbove,
2863 int32_t aRowSpanBelow,
2864 Element** aNewCell) {
2865 if (NS_WARN_IF(!aTable)) {
2866 return NS_ERROR_INVALID_ARG;
2869 if (aNewCell) {
2870 *aNewCell = nullptr;
2873 const auto cellData =
2874 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, aColIndex);
2875 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
2876 return NS_ERROR_FAILURE;
2879 // We can't split!
2880 if (cellData.mEffectiveRowSpan <= 1 ||
2881 aRowSpanAbove + aRowSpanBelow > cellData.mEffectiveRowSpan) {
2882 return NS_OK;
2885 const Result<TableSize, nsresult> tableSizeOrError =
2886 TableSize::Create(*this, *aTable);
2887 if (NS_WARN_IF(tableSizeOrError.isErr())) {
2888 return tableSizeOrError.inspectErr();
2890 const TableSize& tableSize = tableSizeOrError.inspect();
2892 // Find a cell to insert before or after
2893 RefPtr<Element> cellElementAtInsertionPoint;
2894 RefPtr<Element> lastCellFound;
2895 bool insertAfter = (cellData.mFirst.mColumn > 0);
2896 for (int32_t colIndex = 0,
2897 rowBelowIndex = cellData.mFirst.mRow + aRowSpanAbove;
2898 colIndex <= tableSize.mColumnCount;) {
2899 const auto cellDataAtInsertionPoint = CellData::AtIndexInTableElement(
2900 *this, *aTable, rowBelowIndex, colIndex);
2901 // If we fail here, it could be because row has bad rowspan values,
2902 // such as all cells having rowspan > 1 (Call FixRowSpan first!).
2903 // XXX According to the comment, this does not assume that
2904 // FixRowSpan() doesn't work well and user can create non-rectangular
2905 // table. So, we should not return error when CellData cannot find
2906 // a cell.
2907 if (NS_WARN_IF(cellDataAtInsertionPoint.FailedOrNotFound())) {
2908 return NS_ERROR_FAILURE;
2911 // FYI: Don't use std::move() here since the following checks will use
2912 // utility methods of cellDataAtInsertionPoint, but some of them
2913 // check whether its mElement is not nullptr.
2914 cellElementAtInsertionPoint = cellDataAtInsertionPoint.mElement;
2916 // Skip over cells spanned from above (like the one we are splitting!)
2917 if (cellDataAtInsertionPoint.mElement &&
2918 !cellDataAtInsertionPoint.IsSpannedFromOtherRow()) {
2919 if (!insertAfter) {
2920 // Inserting before, so stop at first cell in row we want to insert
2921 // into.
2922 break;
2924 // New cell isn't first in row,
2925 // so stop after we find the cell just before new cell's column
2926 if (cellDataAtInsertionPoint.NextColumnIndex() ==
2927 cellData.mFirst.mColumn) {
2928 break;
2930 // If cell found is AFTER desired new cell colum,
2931 // we have multiple cells with rowspan > 1 that
2932 // prevented us from finding a cell to insert after...
2933 if (cellDataAtInsertionPoint.mFirst.mColumn > cellData.mFirst.mColumn) {
2934 // ... so instead insert before the cell we found
2935 insertAfter = false;
2936 break;
2938 // FYI: Don't use std::move() here since
2939 // cellDataAtInsertionPoint.NextColumnIndex() needs it.
2940 lastCellFound = cellDataAtInsertionPoint.mElement;
2942 MOZ_ASSERT(colIndex < cellDataAtInsertionPoint.NextColumnIndex());
2943 colIndex = cellDataAtInsertionPoint.NextColumnIndex();
2946 if (!cellElementAtInsertionPoint && lastCellFound) {
2947 // Edge case where we didn't find a cell to insert after
2948 // or before because column(s) before desired column
2949 // and all columns after it are spanned from above.
2950 // We can insert after the last cell we found
2951 cellElementAtInsertionPoint = std::move(lastCellFound);
2952 insertAfter = true; // Should always be true, but let's be sure
2955 // Reduce rowspan of cell to split
2956 nsresult rv = SetRowSpan(cellData.mElement, aRowSpanAbove);
2957 if (NS_FAILED(rv)) {
2958 NS_WARNING("HTMLEditor::SetRowSpan() failed");
2959 return rv;
2962 // Insert new cell after using the remaining span
2963 // and always get the new cell so we can copy the background color;
2964 RefPtr<Element> newCell;
2965 rv = InsertCell(cellElementAtInsertionPoint, aRowSpanBelow,
2966 cellData.mEffectiveColSpan, insertAfter, false,
2967 getter_AddRefs(newCell));
2968 if (NS_FAILED(rv)) {
2969 NS_WARNING("HTMLEditor::InsertCell() failed");
2970 return rv;
2972 if (!newCell) {
2973 return NS_OK;
2975 if (aNewCell) {
2976 *aNewCell = do_AddRef(newCell).take();
2978 rv = CopyCellBackgroundColor(newCell, cellElementAtInsertionPoint);
2979 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2980 "HTMLEditor::CopyCellBackgroundColor() failed");
2981 return rv;
2984 NS_IMETHODIMP HTMLEditor::SwitchTableCellHeaderType(Element* aSourceCell,
2985 Element** aNewCell) {
2986 if (NS_WARN_IF(!aSourceCell)) {
2987 return NS_ERROR_INVALID_ARG;
2990 AutoEditActionDataSetter editActionData(*this,
2991 EditAction::eSetTableCellElementType);
2992 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
2993 if (NS_FAILED(rv)) {
2994 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
2995 return EditorBase::ToGenericNSResult(rv);
2997 const RefPtr<Element> editingHost =
2998 ComputeEditingHost(LimitInBodyElement::No);
2999 if (NS_WARN_IF(editingHost &&
3000 editingHost->IsContentEditablePlainTextOnly())) {
3001 return NS_ERROR_NOT_AVAILABLE;
3003 rv = editActionData.MaybeDispatchBeforeInputEvent();
3004 if (NS_FAILED(rv)) {
3005 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3006 "MaybeDispatchBeforeInputEvent(), failed");
3007 return EditorBase::ToGenericNSResult(rv);
3010 AutoPlaceholderBatch treatAsOneTransaction(
3011 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3012 // Prevent auto insertion of BR in new cell created by
3013 // ReplaceContainerAndCloneAttributesWithTransaction().
3014 IgnoredErrorResult ignoredError;
3015 AutoEditSubActionNotifier startToHandleEditSubAction(
3016 *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
3017 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3018 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
3020 NS_WARNING_ASSERTION(
3021 !ignoredError.Failed(),
3022 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3024 // Save current selection to restore when done.
3025 // This is needed so ReplaceContainerAndCloneAttributesWithTransaction()
3026 // can monitor selection when replacing nodes.
3027 AutoSelectionRestorer restoreSelectionLater(this);
3029 // Set to the opposite of current type
3030 nsAtom* newCellName =
3031 aSourceCell->IsHTMLElement(nsGkAtoms::td) ? nsGkAtoms::th : nsGkAtoms::td;
3033 // This creates new node, moves children, copies attributes (true)
3034 // and manages the selection!
3035 Result<CreateElementResult, nsresult> newCellElementOrError =
3036 ReplaceContainerAndCloneAttributesWithTransaction(
3037 *aSourceCell, MOZ_KnownLive(*newCellName));
3038 if (MOZ_UNLIKELY(newCellElementOrError.isErr())) {
3039 NS_WARNING(
3040 "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
3041 "failed");
3042 return newCellElementOrError.unwrapErr();
3044 // restoreSelectionLater will change selection
3045 newCellElementOrError.inspect().IgnoreCaretPointSuggestion();
3047 // Return the new cell
3048 if (aNewCell) {
3049 newCellElementOrError.unwrap().UnwrapNewNode().forget(aNewCell);
3052 return NS_OK;
3055 NS_IMETHODIMP HTMLEditor::JoinTableCells(bool aMergeNonContiguousContents) {
3056 AutoEditActionDataSetter editActionData(*this,
3057 EditAction::eJoinTableCellElements);
3058 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3059 if (NS_FAILED(rv)) {
3060 NS_WARNING("CanHandleAndFlushPendingNotifications() failed");
3061 return EditorBase::ToGenericNSResult(rv);
3063 const RefPtr<Element> editingHost =
3064 ComputeEditingHost(LimitInBodyElement::No);
3065 if (NS_WARN_IF(editingHost &&
3066 editingHost->IsContentEditablePlainTextOnly())) {
3067 return NS_ERROR_NOT_AVAILABLE;
3069 rv = editActionData.MaybeDispatchBeforeInputEvent();
3070 if (NS_FAILED(rv)) {
3071 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3072 "MaybeDispatchBeforeInputEvent(), failed");
3073 return EditorBase::ToGenericNSResult(rv);
3076 RefPtr<Element> table;
3077 RefPtr<Element> targetCell;
3078 int32_t startRowIndex, startColIndex;
3080 // Get cell, table, etc. at selection anchor node
3081 rv = GetCellContext(getter_AddRefs(table), getter_AddRefs(targetCell),
3082 nullptr, nullptr, &startRowIndex, &startColIndex);
3083 if (NS_FAILED(rv)) {
3084 NS_WARNING("HTMLEditor::GetCellContext() failed");
3085 return EditorBase::ToGenericNSResult(rv);
3087 if (!table || !targetCell) {
3088 NS_WARNING(
3089 "HTMLEditor::GetCellContext() didn't return <table> and/or cell");
3090 return NS_OK;
3093 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
3094 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
3097 AutoPlaceholderBatch treateAsOneTransaction(
3098 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3099 // Don't let Rules System change the selection
3100 AutoTransactionsConserveSelection dontChangeSelection(*this);
3102 // Note: We dont' use AutoSelectionSetterAfterTableEdit here so the selection
3103 // is retained after joining. This leaves the target cell selected
3104 // as well as the "non-contiguous" cells, so user can see what happened.
3106 SelectedTableCellScanner scanner(SelectionRef());
3108 // If only one cell is selected, join with cell to the right
3109 if (scanner.ElementsRef().Length() > 1) {
3110 // We have selected cells: Join just contiguous cells
3111 // and just merge contents if not contiguous
3112 Result<TableSize, nsresult> tableSizeOrError =
3113 TableSize::Create(*this, *table);
3114 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3115 return EditorBase::ToGenericNSResult(tableSizeOrError.unwrapErr());
3117 // FYI: Cannot be const because the row count will be updated
3118 TableSize tableSize = tableSizeOrError.unwrap();
3120 RefPtr<PresShell> presShell = GetPresShell();
3121 // `MOZ_KnownLive(scanner.ElementsRef()[0])` is safe because `scanner`
3122 // grabs it until it's destroyed later.
3123 const CellIndexes firstSelectedCellIndexes(
3124 MOZ_KnownLive(scanner.ElementsRef()[0]), presShell);
3125 if (NS_WARN_IF(firstSelectedCellIndexes.isErr())) {
3126 return NS_ERROR_FAILURE;
3129 // Get spans for cell we will merge into
3130 int32_t firstRowSpan, firstColSpan;
3131 nsresult rv = GetCellSpansAt(table, firstSelectedCellIndexes.mRow,
3132 firstSelectedCellIndexes.mColumn, firstRowSpan,
3133 firstColSpan);
3134 if (NS_FAILED(rv)) {
3135 NS_WARNING("HTMLEditor::GetCellSpansAt() failed");
3136 return EditorBase::ToGenericNSResult(rv);
3139 // This defines the last indexes along the "edges"
3140 // of the contiguous block of cells, telling us
3141 // that we can join adjacent cells to the block
3142 // Start with same as the first values,
3143 // then expand as we find adjacent selected cells
3144 int32_t lastRowIndex = firstSelectedCellIndexes.mRow;
3145 int32_t lastColIndex = firstSelectedCellIndexes.mColumn;
3147 // First pass: Determine boundaries of contiguous rectangular block that
3148 // we will join into one cell, favoring adjacent cells in the same row.
3149 for (int32_t rowIndex = firstSelectedCellIndexes.mRow;
3150 rowIndex <= lastRowIndex; rowIndex++) {
3151 int32_t currentRowCount = tableSize.mRowCount;
3152 // Be sure each row doesn't have rowspan errors
3153 rv = FixBadRowSpan(table, rowIndex, tableSize.mRowCount);
3154 if (NS_FAILED(rv)) {
3155 NS_WARNING("HTMLEditor::FixBadRowSpan() failed");
3156 return EditorBase::ToGenericNSResult(rv);
3158 // Adjust rowcount by number of rows we removed
3159 lastRowIndex -= currentRowCount - tableSize.mRowCount;
3161 bool cellFoundInRow = false;
3162 bool lastRowIsSet = false;
3163 int32_t lastColInRow = 0;
3164 int32_t firstColInRow = firstSelectedCellIndexes.mColumn;
3165 int32_t colIndex = firstSelectedCellIndexes.mColumn;
3166 for (; colIndex < tableSize.mColumnCount;) {
3167 const auto cellData =
3168 CellData::AtIndexInTableElement(*this, *table, rowIndex, colIndex);
3169 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3170 return NS_ERROR_FAILURE;
3173 if (cellData.mIsSelected) {
3174 if (!cellFoundInRow) {
3175 // We've just found the first selected cell in this row
3176 firstColInRow = cellData.mCurrent.mColumn;
3178 if (cellData.mCurrent.mRow > firstSelectedCellIndexes.mRow &&
3179 firstColInRow != firstSelectedCellIndexes.mColumn) {
3180 // We're in at least the second row,
3181 // but left boundary is "ragged" (not the same as 1st row's start)
3182 // Let's just end block on previous row
3183 // and keep previous lastColIndex
3184 // TODO: We could try to find the Maximum firstColInRow
3185 // so our block can still extend down more rows?
3186 lastRowIndex = std::max(0, cellData.mCurrent.mRow - 1);
3187 lastRowIsSet = true;
3188 break;
3190 // Save max selected column in this row, including extra colspan
3191 lastColInRow = cellData.LastColumnIndex();
3192 cellFoundInRow = true;
3193 } else if (cellFoundInRow) {
3194 // No cell or not selected, but at least one cell in row was found
3195 if (cellData.mCurrent.mRow > firstSelectedCellIndexes.mRow + 1 &&
3196 cellData.mCurrent.mColumn <= lastColIndex) {
3197 // Cell is in a column less than current right border in
3198 // the third or higher selected row, so stop block at the previous
3199 // row
3200 lastRowIndex = std::max(0, cellData.mCurrent.mRow - 1);
3201 lastRowIsSet = true;
3203 // We're done with this row
3204 break;
3206 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3207 colIndex = cellData.NextColumnIndex();
3208 } // End of column loop
3210 // Done with this row
3211 if (cellFoundInRow) {
3212 if (rowIndex == firstSelectedCellIndexes.mRow) {
3213 // First row always initializes the right boundary
3214 lastColIndex = lastColInRow;
3217 // If we didn't determine last row above...
3218 if (!lastRowIsSet) {
3219 if (colIndex < lastColIndex) {
3220 // (don't think we ever get here?)
3221 // Cell is in a column less than current right boundary,
3222 // so stop block at the previous row
3223 lastRowIndex = std::max(0, rowIndex - 1);
3224 } else {
3225 // Go on to examine next row
3226 lastRowIndex = rowIndex + 1;
3229 // Use the minimum col we found so far for right boundary
3230 lastColIndex = std::min(lastColIndex, lastColInRow);
3231 } else {
3232 // No selected cells in this row -- stop at row above
3233 // and leave last column at its previous value
3234 lastRowIndex = std::max(0, rowIndex - 1);
3238 // The list of cells we will delete after joining
3239 nsTArray<RefPtr<Element>> deleteList;
3241 // 2nd pass: Do the joining and merging
3242 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3243 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
3244 const auto cellData =
3245 CellData::AtIndexInTableElement(*this, *table, rowIndex, colIndex);
3246 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3247 return NS_ERROR_FAILURE;
3250 // If this is 0, we are past last cell in row, so exit the loop
3251 if (!cellData.mEffectiveColSpan) {
3252 break;
3255 // Merge only selected cells (skip cell we're merging into, of course)
3256 if (cellData.mIsSelected &&
3257 cellData.mElement != scanner.ElementsRef()[0]) {
3258 if (cellData.mCurrent.mRow >= firstSelectedCellIndexes.mRow &&
3259 cellData.mCurrent.mRow <= lastRowIndex &&
3260 cellData.mCurrent.mColumn >= firstSelectedCellIndexes.mColumn &&
3261 cellData.mCurrent.mColumn <= lastColIndex) {
3262 // We are within the join region
3263 // Problem: It is very tricky to delete cells as we merge,
3264 // since that will upset the cellmap
3265 // Instead, build a list of cells to delete and do it later
3266 NS_ASSERTION(!cellData.IsSpannedFromOtherRow(),
3267 "JoinTableCells: StartRowIndex is in row above");
3269 if (cellData.mEffectiveColSpan > 1) {
3270 // Check if cell "hangs" off the boundary because of colspan > 1
3271 // Use split methods to chop off excess
3272 int32_t extraColSpan = cellData.mFirst.mColumn +
3273 cellData.mEffectiveColSpan -
3274 (lastColIndex + 1);
3275 if (extraColSpan > 0) {
3276 nsresult rv = SplitCellIntoColumns(
3277 table, cellData.mFirst.mRow, cellData.mFirst.mColumn,
3278 cellData.mEffectiveColSpan - extraColSpan, extraColSpan,
3279 nullptr);
3280 if (NS_FAILED(rv)) {
3281 NS_WARNING("HTMLEditor::SplitCellIntoColumns() failed");
3282 return EditorBase::ToGenericNSResult(rv);
3287 nsresult rv =
3288 MergeCells(scanner.ElementsRef()[0], cellData.mElement, false);
3289 if (NS_FAILED(rv)) {
3290 NS_WARNING("HTMLEditor::MergeCells() failed");
3291 return EditorBase::ToGenericNSResult(rv);
3294 // Add cell to list to delete
3295 deleteList.AppendElement(cellData.mElement.get());
3296 } else if (aMergeNonContiguousContents) {
3297 // Cell is outside join region -- just merge the contents
3298 nsresult rv =
3299 MergeCells(scanner.ElementsRef()[0], cellData.mElement, false);
3300 if (NS_FAILED(rv)) {
3301 NS_WARNING("HTMLEditor::MergeCells() failed");
3302 return rv;
3306 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3307 colIndex = cellData.NextColumnIndex();
3311 // All cell contents are merged. Delete the empty cells we accumulated
3312 // Prevent rules testing until we're done
3313 IgnoredErrorResult error;
3314 AutoEditSubActionNotifier startToHandleEditSubAction(
3315 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, error);
3316 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3317 return EditorBase::ToGenericNSResult(error.StealNSResult());
3319 NS_WARNING_ASSERTION(!error.Failed(),
3320 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() "
3321 "failed, but ignored");
3323 for (uint32_t i = 0, n = deleteList.Length(); i < n; i++) {
3324 RefPtr<Element> nodeToBeRemoved = deleteList[i];
3325 if (nodeToBeRemoved) {
3326 nsresult rv = DeleteNodeWithTransaction(*nodeToBeRemoved);
3327 if (NS_FAILED(rv)) {
3328 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3329 return EditorBase::ToGenericNSResult(rv);
3333 // Cleanup selection: remove ranges where cells were deleted
3334 uint32_t rangeCount = SelectionRef().RangeCount();
3336 // TODO: Rewriting this with reversed ranged-loop may make it simpler.
3337 RefPtr<nsRange> range;
3338 for (uint32_t i = 0; i < rangeCount; i++) {
3339 range = SelectionRef().GetRangeAt(i);
3340 if (NS_WARN_IF(!range)) {
3341 return NS_ERROR_FAILURE;
3344 Element* deletedCell =
3345 HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(*range);
3346 if (!deletedCell) {
3347 SelectionRef().RemoveRangeAndUnselectFramesAndNotifyListeners(*range,
3348 error);
3349 NS_WARNING_ASSERTION(
3350 !error.Failed(),
3351 "Selection::RemoveRangeAndUnselectFramesAndNotifyListeners() "
3352 "failed, but ignored");
3353 rangeCount--;
3354 i--;
3358 // Set spans for the cell everything merged into
3359 rv = SetRowSpan(MOZ_KnownLive(scanner.ElementsRef()[0]),
3360 lastRowIndex - firstSelectedCellIndexes.mRow + 1);
3361 if (NS_FAILED(rv)) {
3362 NS_WARNING("HTMLEditor::SetRowSpan() failed");
3363 return EditorBase::ToGenericNSResult(rv);
3365 rv = SetColSpan(MOZ_KnownLive(scanner.ElementsRef()[0]),
3366 lastColIndex - firstSelectedCellIndexes.mColumn + 1);
3367 if (NS_FAILED(rv)) {
3368 NS_WARNING("HTMLEditor::SetColSpan() failed");
3369 return EditorBase::ToGenericNSResult(rv);
3372 // Fixup disturbances in table layout
3373 DebugOnly<nsresult> rvIgnored = NormalizeTableInternal(*table);
3374 NS_WARNING_ASSERTION(
3375 NS_SUCCEEDED(rvIgnored),
3376 "HTMLEditor::NormalizeTableInternal() failed, but ignored");
3377 } else {
3378 // Joining with cell to the right -- get rowspan and colspan data of target
3379 // cell.
3380 const auto leftCellData = CellData::AtIndexInTableElement(
3381 *this, *table, startRowIndex, startColIndex);
3382 if (NS_WARN_IF(leftCellData.FailedOrNotFound())) {
3383 return NS_ERROR_FAILURE;
3386 // Get data for cell to the right.
3387 const auto rightCellData = CellData::AtIndexInTableElement(
3388 *this, *table, leftCellData.mFirst.mRow,
3389 leftCellData.mFirst.mColumn + leftCellData.mEffectiveColSpan);
3390 if (NS_WARN_IF(rightCellData.FailedOrNotFound())) {
3391 return NS_ERROR_FAILURE;
3394 // XXX So, this does not assume that CellData returns error when just not
3395 // found. We need to fix this later.
3396 if (!rightCellData.mElement) {
3397 return NS_OK; // Don't fail if there's no cell
3400 // sanity check
3401 NS_ASSERTION(
3402 rightCellData.mCurrent.mRow >= rightCellData.mFirst.mRow,
3403 "JoinCells: rightCellData.mCurrent.mRow < rightCellData.mFirst.mRow");
3405 // Figure out span of merged cell starting from target's starting row
3406 // to handle case of merged cell starting in a row above
3407 int32_t spanAboveMergedCell = rightCellData.NumberOfPrecedingRows();
3408 int32_t effectiveRowSpan2 =
3409 rightCellData.mEffectiveRowSpan - spanAboveMergedCell;
3410 if (effectiveRowSpan2 > leftCellData.mEffectiveRowSpan) {
3411 // Cell to the right spans into row below target
3412 // Split off portion below target cell's bottom-most row
3413 nsresult rv = SplitCellIntoRows(
3414 table, rightCellData.mFirst.mRow, rightCellData.mFirst.mColumn,
3415 spanAboveMergedCell + leftCellData.mEffectiveRowSpan,
3416 effectiveRowSpan2 - leftCellData.mEffectiveRowSpan, nullptr);
3417 if (NS_FAILED(rv)) {
3418 NS_WARNING("HTMLEditor::SplitCellIntoRows() failed");
3419 return EditorBase::ToGenericNSResult(rv);
3423 // Move contents from cell to the right
3424 // Delete the cell now only if it starts in the same row
3425 // and has enough row "height"
3426 nsresult rv =
3427 MergeCells(leftCellData.mElement, rightCellData.mElement,
3428 !rightCellData.IsSpannedFromOtherRow() &&
3429 effectiveRowSpan2 >= leftCellData.mEffectiveRowSpan);
3430 if (NS_FAILED(rv)) {
3431 NS_WARNING("HTMLEditor::MergeCells() failed");
3432 return EditorBase::ToGenericNSResult(rv);
3435 if (effectiveRowSpan2 < leftCellData.mEffectiveRowSpan) {
3436 // Merged cell is "shorter"
3437 // (there are cells(s) below it that are row-spanned by target cell)
3438 // We could try splitting those cells, but that's REAL messy,
3439 // so the safest thing to do is NOT really join the cells
3440 return NS_OK;
3443 if (spanAboveMergedCell > 0) {
3444 // Cell we merged started in a row above the target cell
3445 // Reduce rowspan to give room where target cell will extend its colspan
3446 nsresult rv = SetRowSpan(rightCellData.mElement, spanAboveMergedCell);
3447 if (NS_FAILED(rv)) {
3448 NS_WARNING("HTMLEditor::SetRowSpan() failed");
3449 return EditorBase::ToGenericNSResult(rv);
3453 // Reset target cell's colspan to encompass cell to the right
3454 rv = SetColSpan(leftCellData.mElement, leftCellData.mEffectiveColSpan +
3455 rightCellData.mEffectiveColSpan);
3456 if (NS_FAILED(rv)) {
3457 NS_WARNING("HTMLEditor::SetColSpan() failed");
3458 return EditorBase::ToGenericNSResult(rv);
3461 return NS_OK;
3464 nsresult HTMLEditor::MergeCells(RefPtr<Element> aTargetCell,
3465 RefPtr<Element> aCellToMerge,
3466 bool aDeleteCellToMerge) {
3467 MOZ_ASSERT(IsEditActionDataAvailable());
3469 if (NS_WARN_IF(!aTargetCell) || NS_WARN_IF(!aCellToMerge)) {
3470 return NS_ERROR_INVALID_ARG;
3473 // Prevent rules testing until we're done
3474 IgnoredErrorResult ignoredError;
3475 AutoEditSubActionNotifier startToHandleEditSubAction(
3476 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
3477 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3478 return ignoredError.StealNSResult();
3480 NS_WARNING_ASSERTION(
3481 !ignoredError.Failed(),
3482 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3484 // Don't need to merge if cell is empty
3485 if (!IsEmptyCell(aCellToMerge)) {
3486 // Get index of last child in target cell
3487 // If we fail or don't have children,
3488 // we insert at index 0
3489 int32_t insertIndex = 0;
3491 // Start inserting just after last child
3492 uint32_t len = aTargetCell->GetChildCount();
3493 if (len == 1 && IsEmptyCell(aTargetCell)) {
3494 // Delete the empty node
3495 nsCOMPtr<nsIContent> cellChild = aTargetCell->GetFirstChild();
3496 if (NS_WARN_IF(!cellChild)) {
3497 return NS_ERROR_FAILURE;
3499 nsresult rv = DeleteNodeWithTransaction(*cellChild);
3500 if (NS_FAILED(rv)) {
3501 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3502 return rv;
3504 insertIndex = 0;
3505 } else {
3506 insertIndex = (int32_t)len;
3509 // Move the contents
3510 EditorDOMPoint pointToPutCaret;
3511 while (aCellToMerge->HasChildren()) {
3512 nsCOMPtr<nsIContent> cellChild = aCellToMerge->GetLastChild();
3513 if (NS_WARN_IF(!cellChild)) {
3514 return NS_ERROR_FAILURE;
3516 nsresult rv = DeleteNodeWithTransaction(*cellChild);
3517 if (NS_FAILED(rv)) {
3518 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3519 return rv;
3521 Result<CreateContentResult, nsresult> insertChildContentResult =
3522 InsertNodeWithTransaction(*cellChild,
3523 EditorDOMPoint(aTargetCell, insertIndex));
3524 if (MOZ_UNLIKELY(insertChildContentResult.isErr())) {
3525 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3526 return insertChildContentResult.unwrapErr();
3528 CreateContentResult unwrappedInsertChildContentResult =
3529 insertChildContentResult.unwrap();
3530 unwrappedInsertChildContentResult.MoveCaretPointTo(
3531 pointToPutCaret, *this,
3532 {SuggestCaret::OnlyIfHasSuggestion,
3533 SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
3535 if (pointToPutCaret.IsSet()) {
3536 nsresult rv = CollapseSelectionTo(pointToPutCaret);
3537 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
3538 NS_WARNING(
3539 "EditorBase::CollapseSelectionTo() caused destroying the editor");
3540 return NS_ERROR_EDITOR_DESTROYED;
3542 NS_WARNING_ASSERTION(
3543 NS_SUCCEEDED(rv),
3544 "EditorBase::CollapseSelectionTo() failed, but ignored");
3548 if (!aDeleteCellToMerge) {
3549 return NS_OK;
3552 // Delete cells whose contents were moved.
3553 nsresult rv = DeleteNodeWithTransaction(*aCellToMerge);
3554 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3555 "EditorBase::DeleteNodeWithTransaction() failed");
3556 return rv;
3559 nsresult HTMLEditor::FixBadRowSpan(Element* aTable, int32_t aRowIndex,
3560 int32_t& aNewRowCount) {
3561 if (NS_WARN_IF(!aTable)) {
3562 return NS_ERROR_INVALID_ARG;
3565 const Result<TableSize, nsresult> tableSizeOrError =
3566 TableSize::Create(*this, *aTable);
3567 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3568 return tableSizeOrError.inspectErr();
3570 const TableSize& tableSize = tableSizeOrError.inspect();
3572 int32_t minRowSpan = -1;
3573 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
3574 const auto cellData =
3575 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, colIndex);
3576 // NOTE: This is a *real* failure.
3577 // CellData passes if cell is missing from cellmap
3578 // XXX If <table> has large rowspan value or colspan value than actual
3579 // cells, we may hit error. So, this method is always failed to
3580 // "fix" the rowspan...
3581 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3582 return NS_ERROR_FAILURE;
3585 // XXX So, this does not assume that CellData returns error when just not
3586 // found. We need to fix this later.
3587 if (!cellData.mElement) {
3588 break;
3591 if (cellData.mRowSpan > 0 && !cellData.IsSpannedFromOtherRow() &&
3592 (cellData.mRowSpan < minRowSpan || minRowSpan == -1)) {
3593 minRowSpan = cellData.mRowSpan;
3595 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3596 colIndex = cellData.NextColumnIndex();
3599 if (minRowSpan > 1) {
3600 // The amount to reduce everyone's rowspan
3601 // so at least one cell has rowspan = 1
3602 int32_t rowsReduced = minRowSpan - 1;
3603 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount;) {
3604 const auto cellData =
3605 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, colIndex);
3606 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3607 return NS_ERROR_FAILURE;
3610 // Fixup rowspans only for cells starting in current row
3611 // XXX So, this does not assume that CellData returns error when just
3612 // not found a cell. Fix this later.
3613 if (cellData.mElement && cellData.mRowSpan > 0 &&
3614 !cellData.IsSpannedFromOtherRowOrColumn()) {
3615 nsresult rv =
3616 SetRowSpan(cellData.mElement, cellData.mRowSpan - rowsReduced);
3617 if (NS_FAILED(rv)) {
3618 NS_WARNING("HTMLEditor::SetRawSpan() failed");
3619 return rv;
3622 MOZ_ASSERT(colIndex < cellData.NextColumnIndex());
3623 colIndex = cellData.NextColumnIndex();
3626 const Result<TableSize, nsresult> newTableSizeOrError =
3627 TableSize::Create(*this, *aTable);
3628 if (NS_WARN_IF(newTableSizeOrError.isErr())) {
3629 return newTableSizeOrError.inspectErr();
3631 aNewRowCount = newTableSizeOrError.inspect().mRowCount;
3632 return NS_OK;
3635 nsresult HTMLEditor::FixBadColSpan(Element* aTable, int32_t aColIndex,
3636 int32_t& aNewColCount) {
3637 if (NS_WARN_IF(!aTable)) {
3638 return NS_ERROR_INVALID_ARG;
3641 const Result<TableSize, nsresult> tableSizeOrError =
3642 TableSize::Create(*this, *aTable);
3643 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3644 return tableSizeOrError.inspectErr();
3646 const TableSize& tableSize = tableSizeOrError.inspect();
3648 int32_t minColSpan = -1;
3649 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount;) {
3650 const auto cellData =
3651 CellData::AtIndexInTableElement(*this, *aTable, rowIndex, aColIndex);
3652 // NOTE: This is a *real* failure.
3653 // CellData passes if cell is missing from cellmap
3654 // XXX If <table> has large rowspan value or colspan value than actual
3655 // cells, we may hit error. So, this method is always failed to
3656 // "fix" the colspan...
3657 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3658 return NS_ERROR_FAILURE;
3661 // XXX So, this does not assume that CellData returns error when just
3662 // not found a cell. Fix this later.
3663 if (!cellData.mElement) {
3664 break;
3666 if (cellData.mColSpan > 0 && !cellData.IsSpannedFromOtherColumn() &&
3667 (cellData.mColSpan < minColSpan || minColSpan == -1)) {
3668 minColSpan = cellData.mColSpan;
3670 MOZ_ASSERT(rowIndex < cellData.NextRowIndex());
3671 rowIndex = cellData.NextRowIndex();
3674 if (minColSpan > 1) {
3675 // The amount to reduce everyone's colspan
3676 // so at least one cell has colspan = 1
3677 int32_t colsReduced = minColSpan - 1;
3678 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount;) {
3679 const auto cellData =
3680 CellData::AtIndexInTableElement(*this, *aTable, rowIndex, aColIndex);
3681 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3682 return NS_ERROR_FAILURE;
3685 // Fixup colspans only for cells starting in current column
3686 // XXX So, this does not assume that CellData returns error when just
3687 // not found a cell. Fix this later.
3688 if (cellData.mElement && cellData.mColSpan > 0 &&
3689 !cellData.IsSpannedFromOtherRowOrColumn()) {
3690 nsresult rv =
3691 SetColSpan(cellData.mElement, cellData.mColSpan - colsReduced);
3692 if (NS_FAILED(rv)) {
3693 NS_WARNING("HTMLEditor::SetColSpan() failed");
3694 return rv;
3697 MOZ_ASSERT(rowIndex < cellData.NextRowIndex());
3698 rowIndex = cellData.NextRowIndex();
3701 const Result<TableSize, nsresult> newTableSizeOrError =
3702 TableSize::Create(*this, *aTable);
3703 if (NS_WARN_IF(newTableSizeOrError.isErr())) {
3704 return newTableSizeOrError.inspectErr();
3706 aNewColCount = newTableSizeOrError.inspect().mColumnCount;
3707 return NS_OK;
3710 NS_IMETHODIMP HTMLEditor::NormalizeTable(Element* aTableOrElementInTable) {
3711 AutoEditActionDataSetter editActionData(*this, EditAction::eNormalizeTable);
3712 if (NS_WARN_IF(!editActionData.CanHandle())) {
3713 return NS_ERROR_NOT_INITIALIZED;
3715 const RefPtr<Element> editingHost =
3716 ComputeEditingHost(LimitInBodyElement::No);
3717 if (NS_WARN_IF(editingHost &&
3718 editingHost->IsContentEditablePlainTextOnly())) {
3719 return NS_ERROR_NOT_AVAILABLE;
3721 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
3722 if (NS_FAILED(rv)) {
3723 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3724 "MaybeDispatchBeforeInputEvent(), failed");
3725 return EditorBase::ToGenericNSResult(rv);
3728 if (!aTableOrElementInTable) {
3729 aTableOrElementInTable =
3730 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
3731 if (!aTableOrElementInTable) {
3732 NS_WARNING(
3733 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
3734 "table) failed");
3735 return NS_OK; // Don't throw error even if the element is not in <table>.
3738 rv = NormalizeTableInternal(*aTableOrElementInTable);
3739 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3740 "HTMLEditor::NormalizeTableInternal() failed");
3741 return EditorBase::ToGenericNSResult(rv);
3744 nsresult HTMLEditor::NormalizeTableInternal(Element& aTableOrElementInTable) {
3745 MOZ_ASSERT(IsEditActionDataAvailable());
3747 RefPtr<Element> tableElement;
3748 if (aTableOrElementInTable.NodeInfo()->NameAtom() == nsGkAtoms::table) {
3749 tableElement = &aTableOrElementInTable;
3750 } else {
3751 tableElement = GetInclusiveAncestorByTagNameInternal(
3752 *nsGkAtoms::table, aTableOrElementInTable);
3753 if (!tableElement) {
3754 NS_WARNING(
3755 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
3756 "failed");
3757 return NS_OK; // Don't throw error even if the element is not in <table>.
3761 Result<TableSize, nsresult> tableSizeOrError =
3762 TableSize::Create(*this, *tableElement);
3763 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3764 return tableSizeOrError.unwrapErr();
3766 // FYI: Cannot be const because the row/column count will be updated
3767 TableSize tableSize = tableSizeOrError.unwrap();
3769 // Save current selection
3770 AutoSelectionRestorer restoreSelectionLater(this);
3772 AutoPlaceholderBatch treateAsOneTransaction(
3773 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3774 // Prevent auto insertion of BR in new cell until we're done
3775 IgnoredErrorResult error;
3776 AutoEditSubActionNotifier startToHandleEditSubAction(
3777 *this, EditSubAction::eInsertNode, nsIEditor::eNext, error);
3778 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3779 return error.StealNSResult();
3781 NS_WARNING_ASSERTION(
3782 !error.Failed(),
3783 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3785 // XXX If there is a cell which has bigger or smaller "rowspan" or "colspan"
3786 // values, FixBadRowSpan() will return error. So, we can do nothing
3787 // if the table needs normalization...
3788 // Scan all cells in each row to detect bad rowspan values
3789 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3790 nsresult rv = FixBadRowSpan(tableElement, rowIndex, tableSize.mRowCount);
3791 if (NS_FAILED(rv)) {
3792 NS_WARNING("HTMLEditor::FixBadRowSpan() failed");
3793 return rv;
3796 // and same for colspans
3797 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
3798 nsresult rv = FixBadColSpan(tableElement, colIndex, tableSize.mColumnCount);
3799 if (NS_FAILED(rv)) {
3800 NS_WARNING("HTMLEditor::FixBadColSpan() failed");
3801 return rv;
3805 // Fill in missing cellmap locations with empty cells
3806 for (int32_t rowIndex = 0; rowIndex < tableSize.mRowCount; rowIndex++) {
3807 RefPtr<Element> previousCellElementInRow;
3808 for (int32_t colIndex = 0; colIndex < tableSize.mColumnCount; colIndex++) {
3809 const auto cellData = CellData::AtIndexInTableElement(
3810 *this, *tableElement, rowIndex, colIndex);
3811 // NOTE: This is a *real* failure.
3812 // CellData passes if cell is missing from cellmap
3813 // XXX So, this method assumes that CellData won't return error when
3814 // just not found. Fix this later.
3815 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
3816 return NS_ERROR_FAILURE;
3819 if (cellData.mElement) {
3820 // Save the last cell found in the same row we are scanning
3821 if (!cellData.IsSpannedFromOtherRow()) {
3822 previousCellElementInRow = std::move(cellData.mElement);
3824 continue;
3827 // We are missing a cell at a cellmap location.
3828 // Add a cell after the previous cell element in the current row.
3829 if (NS_WARN_IF(!previousCellElementInRow)) {
3830 // We don't have any cells in this row -- We are really messed up!
3831 return NS_ERROR_FAILURE;
3834 // Insert a new cell after (true), and return the new cell to us
3835 RefPtr<Element> newCellElement;
3836 nsresult rv = InsertCell(previousCellElementInRow, 1, 1, true, false,
3837 getter_AddRefs(newCellElement));
3838 if (NS_FAILED(rv)) {
3839 NS_WARNING("HTMLEditor::InsertCell() failed");
3840 return rv;
3843 if (newCellElement) {
3844 previousCellElementInRow = std::move(newCellElement);
3848 return NS_OK;
3851 NS_IMETHODIMP HTMLEditor::GetCellIndexes(Element* aCellElement,
3852 int32_t* aRowIndex,
3853 int32_t* aColumnIndex) {
3854 if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex)) {
3855 return NS_ERROR_INVALID_ARG;
3858 AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellIndexes);
3859 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3860 if (NS_FAILED(rv)) {
3861 NS_WARNING("HTMLEditor::GetCellIndexes() couldn't handle the job");
3862 return EditorBase::ToGenericNSResult(rv);
3865 *aRowIndex = 0;
3866 *aColumnIndex = 0;
3868 if (!aCellElement) {
3869 // Use cell element which contains anchor of Selection when aCellElement is
3870 // nullptr.
3871 const CellIndexes cellIndexes(*this, SelectionRef());
3872 if (NS_WARN_IF(cellIndexes.isErr())) {
3873 return NS_ERROR_FAILURE;
3875 *aRowIndex = cellIndexes.mRow;
3876 *aColumnIndex = cellIndexes.mColumn;
3877 return NS_OK;
3880 const RefPtr<PresShell> presShell{GetPresShell()};
3881 const CellIndexes cellIndexes(*aCellElement, presShell);
3882 if (NS_WARN_IF(cellIndexes.isErr())) {
3883 return NS_ERROR_FAILURE;
3885 *aRowIndex = cellIndexes.mRow;
3886 *aColumnIndex = cellIndexes.mColumn;
3887 return NS_OK;
3890 // static
3891 nsTableWrapperFrame* HTMLEditor::GetTableFrame(const Element* aTableElement) {
3892 if (NS_WARN_IF(!aTableElement)) {
3893 return nullptr;
3895 return do_QueryFrame(aTableElement->GetPrimaryFrame());
3898 // Return actual number of cells (a cell with colspan > 1 counts as just 1)
3899 int32_t HTMLEditor::GetNumberOfCellsInRow(Element& aTableElement,
3900 int32_t aRowIndex) {
3901 const Result<TableSize, nsresult> tableSizeOrError =
3902 TableSize::Create(*this, aTableElement);
3903 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3904 return -1;
3907 int32_t numberOfCells = 0;
3908 for (int32_t columnIndex = 0;
3909 columnIndex < tableSizeOrError.inspect().mColumnCount;) {
3910 const auto cellData = CellData::AtIndexInTableElement(
3911 *this, aTableElement, aRowIndex, columnIndex);
3912 // Failure means that there is no more cell in the row. In this case,
3913 // we shouldn't return error since we just reach the end of the row.
3914 // XXX So, this method assumes that CellData won't return error when
3915 // just not found. Fix this later.
3916 if (cellData.FailedOrNotFound()) {
3917 break;
3920 // Only count cells that start in row we are working with
3921 if (cellData.mElement && !cellData.IsSpannedFromOtherRow()) {
3922 numberOfCells++;
3924 MOZ_ASSERT(columnIndex < cellData.NextColumnIndex());
3925 columnIndex = cellData.NextColumnIndex();
3927 return numberOfCells;
3930 NS_IMETHODIMP HTMLEditor::GetTableSize(Element* aTableOrElementInTable,
3931 int32_t* aRowCount,
3932 int32_t* aColumnCount) {
3933 if (NS_WARN_IF(!aRowCount) || NS_WARN_IF(!aColumnCount)) {
3934 return NS_ERROR_INVALID_ARG;
3937 AutoEditActionDataSetter editActionData(*this, EditAction::eGetTableSize);
3938 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3939 if (NS_FAILED(rv)) {
3940 NS_WARNING("HTMLEditor::GetTableSize() couldn't handle the job");
3941 return EditorBase::ToGenericNSResult(rv);
3944 *aRowCount = 0;
3945 *aColumnCount = 0;
3947 Element* tableOrElementInTable = aTableOrElementInTable;
3948 if (!tableOrElementInTable) {
3949 tableOrElementInTable =
3950 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
3951 if (!tableOrElementInTable) {
3952 NS_WARNING(
3953 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
3954 "table) failed");
3955 return NS_ERROR_FAILURE;
3959 const Result<TableSize, nsresult> tableSizeOrError =
3960 TableSize::Create(*this, *tableOrElementInTable);
3961 if (NS_WARN_IF(tableSizeOrError.isErr())) {
3962 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
3964 *aRowCount = tableSizeOrError.inspect().mRowCount;
3965 *aColumnCount = tableSizeOrError.inspect().mColumnCount;
3966 return NS_OK;
3969 NS_IMETHODIMP HTMLEditor::GetCellDataAt(
3970 Element* aTableElement, int32_t aRowIndex, int32_t aColumnIndex,
3971 Element** aCellElement, int32_t* aStartRowIndex, int32_t* aStartColumnIndex,
3972 int32_t* aRowSpan, int32_t* aColSpan, int32_t* aEffectiveRowSpan,
3973 int32_t* aEffectiveColSpan, bool* aIsSelected) {
3974 if (NS_WARN_IF(!aCellElement) || NS_WARN_IF(!aStartRowIndex) ||
3975 NS_WARN_IF(!aStartColumnIndex) || NS_WARN_IF(!aRowSpan) ||
3976 NS_WARN_IF(!aColSpan) || NS_WARN_IF(!aEffectiveRowSpan) ||
3977 NS_WARN_IF(!aEffectiveColSpan) || NS_WARN_IF(!aIsSelected)) {
3978 return NS_ERROR_INVALID_ARG;
3981 AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellDataAt);
3982 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
3983 if (NS_FAILED(rv)) {
3984 NS_WARNING("HTMLEditor::GetCellDataAt() couldn't handle the job");
3985 return EditorBase::ToGenericNSResult(rv);
3988 *aStartRowIndex = 0;
3989 *aStartColumnIndex = 0;
3990 *aRowSpan = 0;
3991 *aColSpan = 0;
3992 *aEffectiveRowSpan = 0;
3993 *aEffectiveColSpan = 0;
3994 *aIsSelected = false;
3995 *aCellElement = nullptr;
3997 // Let's keep the table element with strong pointer since editor developers
3998 // may not handle layout code of <table>, however, this method depends on
3999 // them.
4000 RefPtr<Element> table = aTableElement;
4001 if (!table) {
4002 // Get the selected table or the table enclosing the selection anchor.
4003 table = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
4004 if (!table) {
4005 NS_WARNING(
4006 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
4007 "table) failed");
4008 return NS_ERROR_FAILURE;
4012 const CellData cellData =
4013 CellData::AtIndexInTableElement(*this, *table, aRowIndex, aColumnIndex);
4014 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
4015 return NS_ERROR_FAILURE;
4017 NS_ADDREF(*aCellElement = cellData.mElement.get());
4018 *aIsSelected = cellData.mIsSelected;
4019 *aStartRowIndex = cellData.mFirst.mRow;
4020 *aStartColumnIndex = cellData.mFirst.mColumn;
4021 *aRowSpan = cellData.mRowSpan;
4022 *aColSpan = cellData.mColSpan;
4023 *aEffectiveRowSpan = cellData.mEffectiveRowSpan;
4024 *aEffectiveColSpan = cellData.mEffectiveColSpan;
4025 return NS_OK;
4028 NS_IMETHODIMP HTMLEditor::GetCellAt(Element* aTableElement, int32_t aRowIndex,
4029 int32_t aColumnIndex,
4030 Element** aCellElement) {
4031 if (NS_WARN_IF(!aCellElement)) {
4032 return NS_ERROR_INVALID_ARG;
4035 AutoEditActionDataSetter editActionData(*this, EditAction::eGetCellAt);
4036 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4037 if (NS_FAILED(rv)) {
4038 NS_WARNING("HTMLEditor::GetCellAt() couldn't handle the job");
4039 return EditorBase::ToGenericNSResult(rv);
4042 *aCellElement = nullptr;
4044 Element* tableElement = aTableElement;
4045 if (!tableElement) {
4046 // Get the selected table or the table enclosing the selection anchor.
4047 tableElement = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
4048 if (!tableElement) {
4049 NS_WARNING(
4050 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
4051 "table) failed");
4052 return NS_ERROR_FAILURE;
4056 RefPtr<Element> cellElement =
4057 GetTableCellElementAt(*tableElement, aRowIndex, aColumnIndex);
4058 cellElement.forget(aCellElement);
4059 return NS_OK;
4062 Element* HTMLEditor::GetTableCellElementAt(Element& aTableElement,
4063 int32_t aRowIndex,
4064 int32_t aColumnIndex) const {
4065 // Let's grab the <table> element while we're retrieving layout API since
4066 // editor developers do not watch all layout API changes. So, it may
4067 // become unsafe.
4068 OwningNonNull<Element> tableElement(aTableElement);
4069 nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(tableElement);
4070 if (!tableFrame) {
4071 NS_WARNING("There was no table layout information");
4072 return nullptr;
4074 nsIContent* cell = tableFrame->GetCellAt(aRowIndex, aColumnIndex);
4075 return Element::FromNodeOrNull(cell);
4078 // When all you want are the rowspan and colspan (not exposed in nsITableEditor)
4079 nsresult HTMLEditor::GetCellSpansAt(Element* aTable, int32_t aRowIndex,
4080 int32_t aColIndex, int32_t& aActualRowSpan,
4081 int32_t& aActualColSpan) {
4082 nsTableWrapperFrame* tableFrame = HTMLEditor::GetTableFrame(aTable);
4083 if (!tableFrame) {
4084 NS_WARNING("There was no table layout information");
4085 return NS_ERROR_FAILURE;
4087 aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex);
4088 aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex);
4090 return NS_OK;
4093 nsresult HTMLEditor::GetCellContext(Element** aTable, Element** aCell,
4094 nsINode** aCellParent, int32_t* aCellOffset,
4095 int32_t* aRowIndex, int32_t* aColumnIndex) {
4096 MOZ_ASSERT(IsEditActionDataAvailable());
4098 // Initialize return pointers
4099 if (aTable) {
4100 *aTable = nullptr;
4102 if (aCell) {
4103 *aCell = nullptr;
4105 if (aCellParent) {
4106 *aCellParent = nullptr;
4108 if (aCellOffset) {
4109 *aCellOffset = 0;
4111 if (aRowIndex) {
4112 *aRowIndex = 0;
4114 if (aColumnIndex) {
4115 *aColumnIndex = 0;
4118 RefPtr<Element> table;
4119 RefPtr<Element> cell;
4121 // Caller may supply the cell...
4122 if (aCell && *aCell) {
4123 cell = *aCell;
4126 // ...but if not supplied,
4127 // get cell if it's the child of selection anchor node,
4128 // or get the enclosing by a cell
4129 if (!cell) {
4130 // Find a selected or enclosing table element
4131 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
4132 GetSelectedOrParentTableElement();
4133 if (cellOrRowOrTableElementOrError.isErr()) {
4134 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
4135 return cellOrRowOrTableElementOrError.unwrapErr();
4137 if (!cellOrRowOrTableElementOrError.inspect()) {
4138 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
4140 if (HTMLEditUtils::IsTable(cellOrRowOrTableElementOrError.inspect())) {
4141 // We have a selected table, not a cell
4142 if (aTable) {
4143 cellOrRowOrTableElementOrError.unwrap().forget(aTable);
4145 return NS_OK;
4147 if (!HTMLEditUtils::IsTableCell(cellOrRowOrTableElementOrError.inspect())) {
4148 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
4151 // We found a cell
4152 cell = cellOrRowOrTableElementOrError.unwrap();
4154 if (aCell) {
4155 // we don't want to cell.forget() here, because we use it below.
4156 *aCell = do_AddRef(cell).take();
4159 // Get containing table
4160 table = GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *cell);
4161 if (!table) {
4162 NS_WARNING(
4163 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
4164 "failed");
4165 // Cell must be in a table, so fail if not found
4166 return NS_ERROR_FAILURE;
4168 if (aTable) {
4169 table.forget(aTable);
4172 // Get the rest of the related data only if requested
4173 if (aRowIndex || aColumnIndex) {
4174 const RefPtr<PresShell> presShell{GetPresShell()};
4175 const CellIndexes cellIndexes(*cell, presShell);
4176 if (NS_WARN_IF(cellIndexes.isErr())) {
4177 return NS_ERROR_FAILURE;
4179 if (aRowIndex) {
4180 *aRowIndex = cellIndexes.mRow;
4182 if (aColumnIndex) {
4183 *aColumnIndex = cellIndexes.mColumn;
4186 if (aCellParent) {
4187 // Get the immediate parent of the cell
4188 EditorRawDOMPoint atCellElement(cell);
4189 if (NS_WARN_IF(!atCellElement.IsSet())) {
4190 return NS_ERROR_FAILURE;
4193 if (aCellOffset) {
4194 *aCellOffset = atCellElement.Offset();
4197 // Now it's safe to hand over the reference to cellParent, since
4198 // we don't need it anymore.
4199 *aCellParent = do_AddRef(atCellElement.GetContainer()).take();
4202 return NS_OK;
4205 NS_IMETHODIMP HTMLEditor::GetSelectedCells(
4206 nsTArray<RefPtr<Element>>& aOutSelectedCellElements) {
4207 MOZ_ASSERT(aOutSelectedCellElements.IsEmpty());
4209 AutoEditActionDataSetter editActionData(*this, EditAction::eGetSelectedCells);
4210 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4211 if (NS_FAILED(rv)) {
4212 NS_WARNING("HTMLEditor::GetSelectedCells() couldn't handle the job");
4213 return EditorBase::ToGenericNSResult(rv);
4216 SelectedTableCellScanner scanner(SelectionRef());
4217 if (!scanner.IsInTableCellSelectionMode()) {
4218 return NS_OK;
4221 aOutSelectedCellElements.SetCapacity(scanner.ElementsRef().Length());
4222 for (const OwningNonNull<Element>& cellElement : scanner.ElementsRef()) {
4223 aOutSelectedCellElements.AppendElement(cellElement);
4225 return NS_OK;
4228 NS_IMETHODIMP HTMLEditor::GetFirstSelectedCellInTable(int32_t* aRowIndex,
4229 int32_t* aColumnIndex,
4230 Element** aCellElement) {
4231 if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex) ||
4232 NS_WARN_IF(!aCellElement)) {
4233 return NS_ERROR_INVALID_ARG;
4236 AutoEditActionDataSetter editActionData(
4237 *this, EditAction::eGetFirstSelectedCellInTable);
4238 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4239 if (NS_FAILED(rv)) {
4240 NS_WARNING(
4241 "HTMLEditor::GetFirstSelectedCellInTable() couldn't handle the job");
4242 return EditorBase::ToGenericNSResult(rv);
4245 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
4246 return NS_ERROR_FAILURE; // XXX Should return NS_OK?
4249 *aRowIndex = 0;
4250 *aColumnIndex = 0;
4251 *aCellElement = nullptr;
4252 RefPtr<Element> firstSelectedCellElement =
4253 HTMLEditUtils::GetFirstSelectedTableCellElement(SelectionRef());
4254 if (!firstSelectedCellElement) {
4255 return NS_OK;
4258 RefPtr<PresShell> presShell = GetPresShell();
4259 const CellIndexes indexes(*firstSelectedCellElement, presShell);
4260 if (NS_WARN_IF(indexes.isErr())) {
4261 return NS_ERROR_FAILURE;
4264 firstSelectedCellElement.forget(aCellElement);
4265 *aRowIndex = indexes.mRow;
4266 *aColumnIndex = indexes.mColumn;
4267 return NS_OK;
4270 void HTMLEditor::SetSelectionAfterTableEdit(Element* aTable, int32_t aRow,
4271 int32_t aCol, int32_t aDirection,
4272 bool aSelected) {
4273 MOZ_ASSERT(IsEditActionDataAvailable());
4275 if (NS_WARN_IF(!aTable) || NS_WARN_IF(Destroyed())) {
4276 return;
4279 RefPtr<Element> cell;
4280 bool done = false;
4281 do {
4282 cell = GetTableCellElementAt(*aTable, aRow, aCol);
4283 if (cell) {
4284 if (aSelected) {
4285 // Reselect the cell
4286 DebugOnly<nsresult> rv = SelectContentInternal(*cell);
4287 NS_WARNING_ASSERTION(
4288 NS_SUCCEEDED(rv),
4289 "HTMLEditor::SelectContentInternal() failed, but ignored");
4290 return;
4293 // Set the caret to deepest first child
4294 // but don't go into nested tables
4295 // TODO: Should we really be placing the caret at the END
4296 // of the cell content?
4297 CollapseSelectionToDeepestNonTableFirstChild(cell);
4298 return;
4301 // Setup index to find another cell in the
4302 // direction requested, but move in other direction if already at
4303 // beginning of row or column
4304 switch (aDirection) {
4305 case ePreviousColumn:
4306 if (!aCol) {
4307 if (aRow > 0) {
4308 aRow--;
4309 } else {
4310 done = true;
4312 } else {
4313 aCol--;
4315 break;
4316 case ePreviousRow:
4317 if (!aRow) {
4318 if (aCol > 0) {
4319 aCol--;
4320 } else {
4321 done = true;
4323 } else {
4324 aRow--;
4326 break;
4327 default:
4328 done = true;
4330 } while (!done);
4332 // We didn't find a cell
4333 // Set selection to just before the table
4334 if (aTable->GetParentNode()) {
4335 EditorRawDOMPoint atTable(aTable);
4336 if (NS_WARN_IF(!atTable.IsSetAndValid())) {
4337 return;
4339 DebugOnly<nsresult> rvIgnored = CollapseSelectionTo(atTable);
4340 NS_WARNING_ASSERTION(
4341 NS_SUCCEEDED(rvIgnored),
4342 "EditorBase::CollapseSelectionTo() failed, but ignored");
4343 return;
4345 // Last resort: Set selection to start of doc
4346 // (it's very bad to not have a valid selection!)
4347 DebugOnly<nsresult> rvIgnored = SetSelectionAtDocumentStart();
4348 NS_WARNING_ASSERTION(
4349 NS_SUCCEEDED(rvIgnored),
4350 "HTMLEditor::SetSelectionAtDocumentStart() failed, but ignored");
4353 NS_IMETHODIMP HTMLEditor::GetSelectedOrParentTableElement(
4354 nsAString& aTagName, int32_t* aSelectedCount,
4355 Element** aCellOrRowOrTableElement) {
4356 if (NS_WARN_IF(!aSelectedCount) || NS_WARN_IF(!aCellOrRowOrTableElement)) {
4357 return NS_ERROR_INVALID_ARG;
4360 aTagName.Truncate();
4361 *aCellOrRowOrTableElement = nullptr;
4362 *aSelectedCount = 0;
4364 AutoEditActionDataSetter editActionData(
4365 *this, EditAction::eGetSelectedOrParentTableElement);
4366 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4367 if (NS_FAILED(rv)) {
4368 NS_WARNING(
4369 "HTMLEditor::GetSelectedOrParentTableElement() couldn't handle the "
4370 "job");
4371 return EditorBase::ToGenericNSResult(rv);
4374 bool isCellSelected = false;
4375 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
4376 GetSelectedOrParentTableElement(&isCellSelected);
4377 if (cellOrRowOrTableElementOrError.isErr()) {
4378 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
4379 return EditorBase::ToGenericNSResult(
4380 cellOrRowOrTableElementOrError.unwrapErr());
4382 if (!cellOrRowOrTableElementOrError.inspect()) {
4383 return NS_OK;
4385 RefPtr<Element> cellOrRowOrTableElement =
4386 cellOrRowOrTableElementOrError.unwrap();
4388 if (isCellSelected) {
4389 aTagName.AssignLiteral("td");
4390 *aSelectedCount = SelectionRef().RangeCount();
4391 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4392 return NS_OK;
4395 if (HTMLEditUtils::IsTableCell(cellOrRowOrTableElement)) {
4396 aTagName.AssignLiteral("td");
4397 // Keep *aSelectedCount as 0.
4398 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4399 return NS_OK;
4402 if (HTMLEditUtils::IsTable(cellOrRowOrTableElement)) {
4403 aTagName.AssignLiteral("table");
4404 *aSelectedCount = 1;
4405 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4406 return NS_OK;
4409 if (HTMLEditUtils::IsTableRow(cellOrRowOrTableElement)) {
4410 aTagName.AssignLiteral("tr");
4411 *aSelectedCount = 1;
4412 cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
4413 return NS_OK;
4416 MOZ_ASSERT_UNREACHABLE("Which element was returned?");
4417 return NS_ERROR_UNEXPECTED;
4420 Result<RefPtr<Element>, nsresult> HTMLEditor::GetSelectedOrParentTableElement(
4421 bool* aIsCellSelected /* = nullptr */) const {
4422 MOZ_ASSERT(IsEditActionDataAvailable());
4424 if (aIsCellSelected) {
4425 *aIsCellSelected = false;
4428 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
4429 return Err(NS_ERROR_FAILURE); // XXX Shouldn't throw an exception?
4432 // Try to get the first selected cell, first.
4433 RefPtr<Element> cellElement =
4434 HTMLEditUtils::GetFirstSelectedTableCellElement(SelectionRef());
4435 if (cellElement) {
4436 if (aIsCellSelected) {
4437 *aIsCellSelected = true;
4439 return cellElement;
4442 const RangeBoundary& anchorRef = SelectionRef().AnchorRef();
4443 if (NS_WARN_IF(!anchorRef.IsSet())) {
4444 return Err(NS_ERROR_FAILURE);
4447 // If anchor selects a <td>, <table> or <tr>, return it.
4448 if (anchorRef.Container()->HasChildNodes()) {
4449 nsIContent* selectedContent = anchorRef.GetChildAtOffset();
4450 if (selectedContent) {
4451 // XXX Why do we ignore <th> element in this case?
4452 if (selectedContent->IsHTMLElement(nsGkAtoms::td)) {
4453 // FYI: If first range selects a <tr> element, but the other selects
4454 // a <td> element, you can reach here.
4455 // Each cell is in its own selection range in this case.
4456 // XXX Although, other ranges may not select cells, though.
4457 if (aIsCellSelected) {
4458 *aIsCellSelected = true;
4460 return RefPtr<Element>(selectedContent->AsElement());
4462 if (selectedContent->IsAnyOfHTMLElements(nsGkAtoms::table,
4463 nsGkAtoms::tr)) {
4464 return RefPtr<Element>(selectedContent->AsElement());
4469 if (NS_WARN_IF(!anchorRef.Container()->IsContent())) {
4470 return RefPtr<Element>();
4473 // Then, look for a cell element (either <td> or <th>) which contains
4474 // the anchor container.
4475 cellElement = GetInclusiveAncestorByTagNameInternal(
4476 *nsGkAtoms::td, *anchorRef.Container()->AsContent());
4477 if (!cellElement) {
4478 return RefPtr<Element>(); // Not in table.
4480 // Don't set *aIsCellSelected to true in this case because it does NOT
4481 // select a cell, just in a cell.
4482 return cellElement;
4485 Result<RefPtr<Element>, nsresult>
4486 HTMLEditor::GetFirstSelectedCellElementInTable() const {
4487 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
4488 GetSelectedOrParentTableElement();
4489 if (cellOrRowOrTableElementOrError.isErr()) {
4490 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
4491 return cellOrRowOrTableElementOrError;
4494 if (!cellOrRowOrTableElementOrError.inspect()) {
4495 return cellOrRowOrTableElementOrError;
4498 const RefPtr<Element>& element = cellOrRowOrTableElementOrError.inspect();
4499 if (!HTMLEditUtils::IsTableCell(element)) {
4500 return RefPtr<Element>();
4503 if (!HTMLEditUtils::IsTableRow(element->GetParentNode())) {
4504 NS_WARNING("There was no parent <tr> element for the found cell");
4505 return RefPtr<Element>();
4508 if (!HTMLEditUtils::GetClosestAncestorTableElement(*element)) {
4509 NS_WARNING("There was no ancestor <table> element for the found cell");
4510 return Err(NS_ERROR_FAILURE);
4512 return cellOrRowOrTableElementOrError;
4515 NS_IMETHODIMP HTMLEditor::GetSelectedCellsType(Element* aElement,
4516 uint32_t* aSelectionType) {
4517 if (NS_WARN_IF(!aSelectionType)) {
4518 return NS_ERROR_INVALID_ARG;
4520 *aSelectionType = 0;
4522 AutoEditActionDataSetter editActionData(*this,
4523 EditAction::eGetSelectedCellsType);
4524 nsresult rv = editActionData.CanHandleAndFlushPendingNotifications();
4525 if (NS_FAILED(rv)) {
4526 NS_WARNING("HTMLEditor::GetSelectedCellsType() couldn't handle the job");
4527 return EditorBase::ToGenericNSResult(rv);
4530 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
4531 return NS_ERROR_FAILURE; // XXX Should we just return NS_OK?
4534 // Be sure we have a table element
4535 // (if aElement is null, this uses selection's anchor node)
4536 RefPtr<Element> table;
4537 if (aElement) {
4538 table = GetInclusiveAncestorByTagNameInternal(*nsGkAtoms::table, *aElement);
4539 if (!table) {
4540 NS_WARNING(
4541 "HTMLEditor::GetInclusiveAncestorByTagNameInternal(nsGkAtoms::table) "
4542 "failed");
4543 return NS_ERROR_FAILURE;
4545 } else {
4546 table = GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::table);
4547 if (!table) {
4548 NS_WARNING(
4549 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(nsGkAtoms::"
4550 "table) failed");
4551 return NS_ERROR_FAILURE;
4555 const Result<TableSize, nsresult> tableSizeOrError =
4556 TableSize::Create(*this, *table);
4557 if (NS_WARN_IF(tableSizeOrError.isErr())) {
4558 return EditorBase::ToGenericNSResult(tableSizeOrError.inspectErr());
4560 const TableSize& tableSize = tableSizeOrError.inspect();
4562 // Traverse all selected cells
4563 SelectedTableCellScanner scanner(SelectionRef());
4564 if (!scanner.IsInTableCellSelectionMode()) {
4565 return NS_OK;
4568 // We have at least one selected cell, so set return value
4569 *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Cell);
4571 // Store indexes of each row/col to avoid duplication of searches
4572 nsTArray<int32_t> indexArray;
4574 const RefPtr<PresShell> presShell{GetPresShell()};
4575 bool allCellsInRowAreSelected = false;
4576 for (const OwningNonNull<Element>& selectedCellElement :
4577 scanner.ElementsRef()) {
4578 // `MOZ_KnownLive(selectedCellElement)` is safe because `scanner` grabs
4579 // it until it's destroyed later.
4580 const CellIndexes selectedCellIndexes(MOZ_KnownLive(selectedCellElement),
4581 presShell);
4582 if (NS_WARN_IF(selectedCellIndexes.isErr())) {
4583 return NS_ERROR_FAILURE;
4585 if (!indexArray.Contains(selectedCellIndexes.mColumn)) {
4586 indexArray.AppendElement(selectedCellIndexes.mColumn);
4587 allCellsInRowAreSelected = AllCellsInRowSelected(
4588 table, selectedCellIndexes.mRow, tableSize.mColumnCount);
4589 // We're done as soon as we fail for any row
4590 if (!allCellsInRowAreSelected) {
4591 break;
4596 if (allCellsInRowAreSelected) {
4597 *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Row);
4598 return NS_OK;
4600 // Test for columns
4602 // Empty the indexArray
4603 indexArray.Clear();
4605 // Start at first cell again
4606 bool allCellsInColAreSelected = false;
4607 for (const OwningNonNull<Element>& selectedCellElement :
4608 scanner.ElementsRef()) {
4609 // `MOZ_KnownLive(selectedCellElement)` is safe because `scanner` grabs
4610 // it until it's destroyed later.
4611 const CellIndexes selectedCellIndexes(MOZ_KnownLive(selectedCellElement),
4612 presShell);
4613 if (NS_WARN_IF(selectedCellIndexes.isErr())) {
4614 return NS_ERROR_FAILURE;
4617 if (!indexArray.Contains(selectedCellIndexes.mRow)) {
4618 indexArray.AppendElement(selectedCellIndexes.mColumn);
4619 allCellsInColAreSelected = AllCellsInColumnSelected(
4620 table, selectedCellIndexes.mColumn, tableSize.mRowCount);
4621 // We're done as soon as we fail for any column
4622 if (!allCellsInRowAreSelected) {
4623 break;
4627 if (allCellsInColAreSelected) {
4628 *aSelectionType = static_cast<uint32_t>(TableSelectionMode::Column);
4631 return NS_OK;
4634 bool HTMLEditor::AllCellsInRowSelected(Element* aTable, int32_t aRowIndex,
4635 int32_t aNumberOfColumns) {
4636 if (NS_WARN_IF(!aTable)) {
4637 return false;
4640 for (int32_t col = 0; col < aNumberOfColumns;) {
4641 const auto cellData =
4642 CellData::AtIndexInTableElement(*this, *aTable, aRowIndex, col);
4643 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
4644 return false;
4647 // If no cell, we may have a "ragged" right edge, so return TRUE only if
4648 // we already found a cell in the row.
4649 // XXX So, this does not assume that CellData returns error when just
4650 // not found a cell. Fix this later.
4651 if (!cellData.mElement) {
4652 NS_WARNING("CellData didn't set mElement");
4653 return cellData.mCurrent.mColumn > 0;
4656 // Return as soon as a non-selected cell is found.
4657 // XXX Odd, this is testing if each cell element is selected. Why do
4658 // we need to warn if it's false??
4659 if (!cellData.mIsSelected) {
4660 NS_WARNING("CellData didn't set mIsSelected");
4661 return false;
4664 MOZ_ASSERT(col < cellData.NextColumnIndex());
4665 col = cellData.NextColumnIndex();
4667 return true;
4670 bool HTMLEditor::AllCellsInColumnSelected(Element* aTable, int32_t aColIndex,
4671 int32_t aNumberOfRows) {
4672 if (NS_WARN_IF(!aTable)) {
4673 return false;
4676 for (int32_t row = 0; row < aNumberOfRows;) {
4677 const auto cellData =
4678 CellData::AtIndexInTableElement(*this, *aTable, row, aColIndex);
4679 if (NS_WARN_IF(cellData.FailedOrNotFound())) {
4680 return false;
4683 // If no cell, we must have a "ragged" right edge on the last column so
4684 // return TRUE only if we already found a cell in the row.
4685 // XXX So, this does not assume that CellData returns error when just
4686 // not found a cell. Fix this later.
4687 if (!cellData.mElement) {
4688 NS_WARNING("CellData didn't set mElement");
4689 return cellData.mCurrent.mRow > 0;
4692 // Return as soon as a non-selected cell is found.
4693 // XXX Odd, this is testing if each cell element is selected. Why do
4694 // we need to warn if it's false??
4695 if (!cellData.mIsSelected) {
4696 NS_WARNING("CellData didn't set mIsSelected");
4697 return false;
4700 MOZ_ASSERT(row < cellData.NextRowIndex());
4701 row = cellData.NextRowIndex();
4703 return true;
4706 bool HTMLEditor::IsEmptyCell(dom::Element* aCell) {
4707 MOZ_ASSERT(aCell);
4709 // Check if target only contains empty text node or <br>
4710 nsCOMPtr<nsINode> cellChild = aCell->GetFirstChild();
4711 if (!cellChild) {
4712 return false;
4715 nsCOMPtr<nsINode> nextChild = cellChild->GetNextSibling();
4716 if (nextChild) {
4717 return false;
4720 // We insert a single break into a cell by default
4721 // to have some place to locate a cursor -- it is dispensable
4722 if (cellChild->IsHTMLElement(nsGkAtoms::br)) {
4723 return true;
4726 // Or check if no real content
4727 return HTMLEditUtils::IsEmptyNode(
4728 *cellChild, {EmptyCheckOption::TreatSingleBRElementAsVisible,
4729 EmptyCheckOption::TreatNonEditableContentAsInvisible});
4732 } // namespace mozilla