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();
};