diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h index d2a5b662141a..a2169f9cc5c3 100644 --- a/editor/libeditor/HTMLEditUtils.h +++ b/editor/libeditor/HTMLEditUtils.h @@ -12,10 +12,12 @@ #include "mozilla/dom/AbstractRange.h" #include "mozilla/dom/AncestorIterator.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/Selection.h" #include "mozilla/dom/Text.h" #include "nsCRT.h" #include "nsGkAtoms.h" #include "nsHTMLTags.h" +#include "nsTArray.h" class nsAtom; @@ -637,7 +639,7 @@ class HTMLEditUtils final { */ static Element* GetElementIfOnlyOneSelected( const dom::AbstractRange& aRange) { - if (!aRange.IsPositioned()) { + if (!aRange.IsPositioned() || aRange.Collapsed()) { return nullptr; } const RangeBoundary& start = aRange.StartRef(); @@ -669,6 +671,35 @@ class HTMLEditUtils final { return element && HTMLEditUtils::IsTableCell(element) ? element : nullptr; } + /** + * GetFirstSelectedTableCellElement() returns a table cell element (i.e., + * `` or `` if and only if first selection range selects only a + * table cell element. + */ + static Element* GetFirstSelectedTableCellElement( + const Selection& aSelection) { + if (!aSelection.RangeCount()) { + return nullptr; + } + const nsRange* firstRange = aSelection.GetRangeAt(0); + if (NS_WARN_IF(!firstRange) || NS_WARN_IF(!firstRange->IsPositioned())) { + return nullptr; + } + return GetTableCellElementIfOnlyOneSelected(*firstRange); + } + + /** + * IsInTableCellSelectionMode() returns true when Gecko's editor thinks that + * selection is in a table cell selection mode. + * Note that Gecko's editor traditionally treats selection as in table cell + * selection mode when first range selects a table cell element. I.e., even + * if `nsFrameSelection` is not in table cell selection mode, this may return + * true. + */ + static bool IsInTableCellSelectionMode(const Selection& aSelection) { + return GetFirstSelectedTableCellElement(aSelection) != nullptr; + } + static EditAction GetEditActionForInsert(const nsAtom& aTagName); static EditAction GetEditActionForRemoveList(const nsAtom& aTagName); static EditAction GetEditActionForInsert(const Element& aElement); @@ -850,6 +881,67 @@ class MOZ_STACK_CLASS DefinitionListItemScanner final { bool mDDFound = false; }; +/** + * SelectedTableCellScanner() scans all table cell elements which are selected + * by each selection range. Note that if 2nd or later ranges do not select + * only one table cell element, the ranges are just ignored. + */ +class MOZ_STACK_CLASS SelectedTableCellScanner final { + public: + SelectedTableCellScanner() = delete; + explicit SelectedTableCellScanner(const dom::Selection& aSelection) { + dom::Element* firstSelectedCellElement = + HTMLEditUtils::GetFirstSelectedTableCellElement(aSelection); + if (!firstSelectedCellElement) { + return; // We're not in table cell selection mode. + } + mSelectedCellElements.SetCapacity(aSelection.RangeCount()); + mSelectedCellElements.AppendElement(*firstSelectedCellElement); + for (uint32_t i = 1; i < aSelection.RangeCount(); i++) { + nsRange* range = aSelection.GetRangeAt(i); + if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) { + continue; // Shouldn't occur in normal conditions. + } + // Just ignore selection ranges which do not select only one table + // cell element. This is possible case if web apps sets multiple + // selections and first range selects a table cell element. + if (dom::Element* selectedCellElement = + HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(*range)) { + mSelectedCellElements.AppendElement(*selectedCellElement); + } + } + } + + bool IsInTableCellSelectionMode() const { + return !mSelectedCellElements.IsEmpty(); + } + + const nsTArray>& ElementsRef() const { + return mSelectedCellElements; + } + + /** + * GetFirstElement() and GetNextElement() are stateful iterator methods. + * This is useful to port legacy code which used old `nsITableEditor` API. + */ + dom::Element* GetFirstElement() const { + MOZ_ASSERT(!mSelectedCellElements.IsEmpty()); + mIndex = 0; + return !mSelectedCellElements.IsEmpty() ? mSelectedCellElements[0].get() + : nullptr; + } + dom::Element* GetNextElement() const { + MOZ_ASSERT(mIndex < mSelectedCellElements.Length()); + return ++mIndex < mSelectedCellElements.Length() + ? mSelectedCellElements[mIndex].get() + : nullptr; + } + + private: + AutoTArray, 16> mSelectedCellElements; + mutable size_t mIndex = 0; +}; + } // namespace mozilla #endif // #ifndef HTMLEditUtils_h diff --git a/editor/libeditor/HTMLTableEditor.cpp b/editor/libeditor/HTMLTableEditor.cpp index b5223d67d0f0..022519901825 100644 --- a/editor/libeditor/HTMLTableEditor.cpp +++ b/editor/libeditor/HTMLTableEditor.cpp @@ -3907,33 +3907,24 @@ nsresult HTMLEditor::GetCellContext(Element** aTable, Element** aCell, return NS_OK; } -NS_IMETHODIMP HTMLEditor::GetFirstSelectedCell( - Element** aFirstSelectedCellElement) { - if (NS_WARN_IF(!aFirstSelectedCellElement)) { - return NS_ERROR_INVALID_ARG; - } +NS_IMETHODIMP HTMLEditor::GetSelectedCells( + nsTArray>& aOutSelectedCellElements) { + MOZ_ASSERT(aOutSelectedCellElements.IsEmpty()); AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED; } - *aFirstSelectedCellElement = nullptr; - - ErrorResult error; - RefPtr firstSelectedCellElement = - GetFirstSelectedTableCellElement(error); - if (error.Failed()) { - NS_WARNING("HTMLEditor::GetFirstSelectedTableCellElement() failed"); - return EditorBase::ToGenericNSResult(error.StealNSResult()); - } - - if (!firstSelectedCellElement) { - // Just not found. Don't return error. + SelectedTableCellScanner scanner(*SelectionRefPtr()); + if (!scanner.IsInTableCellSelectionMode()) { return NS_OK; } - firstSelectedCellElement.forget(aFirstSelectedCellElement); + aOutSelectedCellElements.SetCapacity(scanner.ElementsRef().Length()); + for (const OwningNonNull& cellElement : scanner.ElementsRef()) { + aOutSelectedCellElements.AppendElement(cellElement); + } return NS_OK; } @@ -3970,36 +3961,6 @@ already_AddRefed HTMLEditor::GetFirstSelectedTableCellElement( return selectedCell.forget(); } -NS_IMETHODIMP HTMLEditor::GetNextSelectedCell( - Element** aNextSelectedCellElement) { - if (NS_WARN_IF(!aNextSelectedCellElement)) { - return NS_ERROR_INVALID_ARG; - } - - AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); - if (NS_WARN_IF(!editActionData.CanHandle())) { - return NS_ERROR_NOT_INITIALIZED; - } - - *aNextSelectedCellElement = nullptr; - - ErrorResult error; - RefPtr nextSelectedCellElement = - GetNextSelectedTableCellElement(error); - if (error.Failed()) { - NS_WARNING("HTMLEditor::GetNextSelectedTableCellElement() failed"); - return EditorBase::ToGenericNSResult(error.StealNSResult()); - } - - if (!nextSelectedCellElement) { - // not more range, or met a range which does not select nor . - return NS_OK; - } - - nextSelectedCellElement.forget(aNextSelectedCellElement); - return NS_OK; -} - already_AddRefed HTMLEditor::GetNextSelectedTableCellElement( ErrorResult& aRv) const { MOZ_ASSERT(IsEditActionDataAvailable()); diff --git a/editor/libeditor/tests/mochitest.ini b/editor/libeditor/tests/mochitest.ini index 01571a5040d8..341a8eb42a56 100644 --- a/editor/libeditor/tests/mochitest.ini +++ b/editor/libeditor/tests/mochitest.ini @@ -280,9 +280,8 @@ skip-if = headless [test_nsITableEditor_getCellDataAt.html] [test_nsITableEditor_getCellIndexes.html] [test_nsITableEditor_getFirstRow.html] -[test_nsITableEditor_getFirstSelectedCell.html] [test_nsITableEditor_getFirstSelectedCellInTable.html] -[test_nsITableEditor_getNextSelectedCell.html] +[test_nsITableEditor_getSelectedCells.html] [test_nsITableEditor_getSelectedOrParentTableElement.html] [test_nsITableEditor_getTableSize.html] [test_nsITableEditor_insertTableCell.html] diff --git a/editor/libeditor/tests/test_nsITableEditor_getFirstSelectedCell.html b/editor/libeditor/tests/test_nsITableEditor_getFirstSelectedCell.html deleted file mode 100644 index 1b42d663be26..000000000000 --- a/editor/libeditor/tests/test_nsITableEditor_getFirstSelectedCell.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - Test for nsITableEditor.getFirstSelectedCell() - - - - -
-
-
-
-
- - - - - diff --git a/editor/libeditor/tests/test_nsITableEditor_getNextSelectedCell.html b/editor/libeditor/tests/test_nsITableEditor_getNextSelectedCell.html deleted file mode 100644 index 90762045c02b..000000000000 --- a/editor/libeditor/tests/test_nsITableEditor_getNextSelectedCell.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - Test for nsITableEditor.getNextSelectedCell() - - - - -
-
-
-
-
- - - - - diff --git a/editor/libeditor/tests/test_nsITableEditor_getSelectedCells.html b/editor/libeditor/tests/test_nsITableEditor_getSelectedCells.html new file mode 100644 index 000000000000..cff75652dfc7 --- /dev/null +++ b/editor/libeditor/tests/test_nsITableEditor_getSelectedCells.html @@ -0,0 +1,295 @@ + + + + Test for nsITableEditor.getSelectedCells() + + + + +
+
+
+
+
+ + + + + diff --git a/editor/nsITableEditor.idl b/editor/nsITableEditor.idl index 50007e6cb920..2985fa6151d7 100644 --- a/editor/nsITableEditor.idl +++ b/editor/nsITableEditor.idl @@ -419,20 +419,6 @@ interface nsITableEditor : nsISupports [can_run_script] uint32_t getSelectedCellsType(in Element aElement); - /** - * getFirstSelectedCell() returns a or element if first range of - * Selection selects only one table cell element (i.e., startContainer and - * endContainer are same element and startOffset + 1 equals endOffset). - * If first range of Selection does not select a table cell element, this - * returns null. However, if Selection has no range, this throws an - * exception. - * - * @return A or element if first range of - * Selection selects only one table cell - * element. - */ - Element getFirstSelectedCell(); - /** * getFirstSelectedCellInTable() returns a cell element, its row index and * its column index if first range of Selection selects a cell. Note that @@ -455,22 +441,14 @@ interface nsITableEditor : nsISupports Element getFirstSelectedCellInTable(out long aRowIndex, out long aColIndex); /** - * getNextSelectedCell() is a stateful method to retrieve selected table - * cell elements which are selected by 2nd or later ranges of Selection. - * When you call getFirstSelectedCell(), it resets internal counter of - * this method. Then, following calls of getNextSelectedCell() scans the - * remaining ranges of Selection. If a range selects a or - * element, returns the cell element. If a range selects an element but - * neither nor element, this ignores the range. If a range is - * in a text node, returns null without throwing exception, but stops - * scanning the remaining ranges even you call this again. - * Note that this may cross boundaries since this method just - * scans all ranges of Selection. Therefore, returning cells which - * belong to different
elements. - * - * @return A
or element if one of remaining - * ranges selects a or element unless - * this does not meet a range in a text node. + * getSelectedCells() returns an array of `` and `` elements which + * are selected in **any** `` elements (i.e., some cells may be + * in different `
` element). + * If first range does not select a table cell element, this returns empty + * array because editor considers that selection is not in table cell + * selection mode. + * If second or later ranges do not select only a table cell element, this + * ignores the ranges. */ - Element getNextSelectedCell(); + Array getSelectedCells(); };