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