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