Bug 1671556 - part 2: Replace `nsITableEditor.getFirstSelectedCell()` and `nsITableEditor.getNextSelectedCell()` with `nsITableEditor.getSelectedCells()` r=m_kato

`nsITableEditor.getFirstSelectedCell()` and
`nsITableEditor.getNextSelectedCell()` use `HTMLEditor::mSelectedCellIndex`
via their internal methods.  That makes these API unstable if mutation happens
between the calls.  Therefore, it's really simpler and stabler that there is
an API to return selected table cell elements once.

Therefore this patch creates new API, `nsITableEditor.getSelectedCells()`,
which returns array of selected table cell elements.  And it stops taking
inconsistent behavior of the old API:
1. Even if there is no selection. it does not throw an exception.
https://searchfox.org/mozilla-central/rev/61728de8273c04fe2417c475fc0637e8b79210d7/editor/libeditor/HTMLTableEditor.cpp#3956-3961
2. Even if it meets selection range in a text node (i.e.,
`nsRange::GetChildAtStartOffset()` returns `nullptr`), this just ignores the
range.
https://searchfox.org/mozilla-central/rev/61728de8273c04fe2417c475fc0637e8b79210d7/editor/libeditor/HTMLTableEditor.cpp#4041-4047

The following patches will remove their internal methods too.

Differential Revision: https://phabricator.services.mozilla.com/D94228
This commit is contained in:
Masayuki Nakano 2020-10-24 04:12:38 +00:00
Родитель f450442d99
Коммит d57d81d139
7 изменённых файлов: 407 добавлений и 410 удалений

Просмотреть файл

@ -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.,
* `<td>` or `<th>` 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<OwningNonNull<dom::Element>>& 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<OwningNonNull<dom::Element>, 16> mSelectedCellElements;
mutable size_t mIndex = 0;
};
} // namespace mozilla
#endif // #ifndef HTMLEditUtils_h

Просмотреть файл

@ -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<RefPtr<Element>>& 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<Element> 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<Element>& cellElement : scanner.ElementsRef()) {
aOutSelectedCellElements.AppendElement(cellElement);
}
return NS_OK;
}
@ -3970,36 +3961,6 @@ already_AddRefed<Element> 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<Element> 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 <td> nor <th>.
return NS_OK;
}
nextSelectedCellElement.forget(aNextSelectedCellElement);
return NS_OK;
}
already_AddRefed<Element> HTMLEditor::GetNextSelectedTableCellElement(
ErrorResult& aRv) const {
MOZ_ASSERT(IsEditActionDataAvailable());

Просмотреть файл

@ -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]

Просмотреть файл

@ -1,125 +0,0 @@
<!DOCTYPE>
<html>
<head>
<title>Test for nsITableEditor.getFirstSelectedCell()</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<div id="display">
</div>
<div id="content" contenteditable></div>
<pre id="test">
</pre>
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
let editor = document.getElementById("content");
let selection = document.getSelection();
selection.collapse(editor, 0);
let cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, null,
"nsITableEditor.getFirstSelectedCell() should return null if Selection does not select cells");
editor.innerHTML =
'<table id="table">' +
'<tr id="r1"><td id="c1-1">cell1-1</td><td id="c1-2">cell1-2</td><td id="c1-3">cell1-3</td><td id="c1-4" colspan="2" rowspan="2">cell1-4</td></tr>' +
'<tr id="r2"><th id="c2-1" rowspan="2">cell2-1</th><td id="c2-2">cell2-2<td id="c2-3">cell2-3</td></tr>' +
'<tr id="r3"><td id="c3-2">cell3-2</td><td id="c3-3">cell3-3</td><td id="c3-4" colspan="2">cell3-4</td></tr>' +
'<tr id="r4"><td id="c4-1" rowspan="4">cell4-1</td><td id="c4-2">cell4-2</td><td id="c4-3">cell4-3</td><th id="c4-4">cell4-4</th><td id="c4-5">cell4-5</td></tr>' +
'<tr id="r5"><th id="c5-2">cell5-2</th><th id="c5-3" colspan="2">cell5-3</th><td id="c5-5">cell5-5</td></tr>' +
'<tr id="r6"><td id="c6-2">cell6-2</td><td id="c6-3">cell6-3</td><td id="c6-4"><p>cell6-4</p></td><td id="c6-5">cell6-5</td></tr>' +
'<tr id="r7"><td id="c7-2" colspan="4">cell7-2</td></tr>' +
"</table>";
let tr = document.getElementById("r1");
selection.setBaseAndExtent(tr, 0, tr, 1);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, document.getElementById("c1-1"),
"#1-1 nsITableEditor.getFirstSelectedCell() should return the first cell element in the first row");
tr = document.getElementById("r1");
selection.setBaseAndExtent(tr, 3, tr, 4);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, document.getElementById("c1-4"),
"#1-4 nsITableEditor.getFirstSelectedCell() should return the last cell element whose colspan and rowspan are 2 in the first row");
tr = document.getElementById("r2");
selection.setBaseAndExtent(tr, 0, tr, 1);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, document.getElementById("c2-1"),
"#2-1 nsITableEditor.getFirstSelectedCell() should return the first cell element in the second row");
tr = document.getElementById("r7");
selection.setBaseAndExtent(tr, 0, tr, 1);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, document.getElementById("c7-2"),
"#7-2 nsITableEditor.getFirstSelectedCell() should return the second cell element in the last row");
tr = document.getElementById("r2");
selection.removeAllRanges();
let range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, document.getElementById("c2-2"),
"#2-2 nsITableEditor.getFirstSelectedCell() should return the second cell element in the second row");
tr = document.getElementById("r3");
selection.removeAllRanges();
range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
range = document.createRange();
range.setStart(document.getElementById("r5"), 0);
range.setEnd(document.getElementById("r5"), 1);
selection.addRange(range);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, document.getElementById("c3-4"),
"#3-4 nsITableEditor.getFirstSelectedCell() should return the last cell element in the third row");
cell = document.getElementById("c6-4");
selection.selectAllChildren(cell);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, null,
"nsITableEditor.getFirstSelectedCell() should return null if neither <td> nor <th> element node is selected");
cell = document.getElementById("c6-5");
selection.setBaseAndExtent(cell.firstChild, 0, cell.firstChild, 0);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell, null,
"nsITableEditor.getFirstSelectedCell() should return null if a text node is selected");
// XXX If cell is not selected, nsITableEditor.getFirstSelectedCell() returns null
// without throwing exception, however, if there is no selection ranges,
// throwing an exception. This inconsistency is odd.
selection.removeAllRanges();
try {
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
ok(false, "nsITableEditor.getFirstSelectedCell() should throw an exception if there is no selection ranges");
} catch (e) {
ok(true, "nsITableEditor.getFirstSelectedCell() should throw an exception if there is no selection ranges");
}
SimpleTest.finish();
});
function getTableEditor() {
var Ci = SpecialPowers.Ci;
var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
}
</script>
</body>
</html>

Просмотреть файл

@ -1,203 +0,0 @@
<!DOCTYPE>
<html>
<head>
<title>Test for nsITableEditor.getNextSelectedCell()</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<div id="display">
</div>
<div id="content" contenteditable></div>
<pre id="test">
</pre>
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
let editor = document.getElementById("content");
let selection = document.getSelection();
editor.innerHTML =
'<table id="table">' +
'<tr id="r1"><td id="c1-1">cell1-1</td><td id="c1-2">cell1-2</td><td id="c1-3">cell1-3</td><td id="c1-4" colspan="2" rowspan="2">cell1-4</td></tr>' +
'<tr id="r2"><th id="c2-1" rowspan="2">cell2-1</th><td id="c2-2">cell2-2<td id="c2-3">cell2-3</td></tr>' +
'<tr id="r3"><td id="c3-2">cell3-2</td><td id="c3-3">cell3-3</td><td id="c3-4" colspan="2">cell3-4</td></tr>' +
'<tr id="r4"><td id="c4-1" rowspan="4">cell4-1</td><td id="c4-2">cell4-2</td><td id="c4-3">cell4-3</td><th id="c4-4">cell4-4</th><td id="c4-5">cell4-5</td></tr>' +
'<tr id="r5"><th id="c5-2">cell5-2</th><th id="c5-3" colspan="2">' +
'<table><tr id="r2-1"><td id="c2-1-1">cell2-1-1</td></tr></table>' +
'</th><td id="c5-5">cell5-5</td></tr>' +
'<tr id="r6"><td id="c6-2">cell6-2</td><td id="c6-3">cell6-3</td><td id="c6-4"><p>cell6-4</p></td><td id="c6-5">cell6-5</td></tr>' +
'<tr id="r7"><td id="c7-2" colspan="4">cell7-2</td></tr>' +
"</table>";
let tr = document.getElementById("r1");
selection.setBaseAndExtent(tr, 0, tr, 1);
let cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell.getAttribute("id"), "c1-1",
"#1-1 nsITableEditor.getFirstSelectedCell() should return the first cell element in the first row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell, null,
"Next of #1-1 nsITableEditor.getNextSelectedCell() should return null if there is only one selected range");
selection.removeAllRanges();
tr = document.getElementById("r1");
let range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 3);
range.setEnd(tr, 4);
selection.addRange(range);
tr = document.getElementById("r2");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell.getAttribute("id"), "c1-2",
"#1-2 nsITableEditor.getFirstSelectedCell() should return the second cell element in the first row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell.getAttribute("id"), "c1-3",
"#1-3 nsITableEditor.getNextSelectedCell() should return the third cell element in the first row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell.getAttribute("id"), "c1-4",
"#1-4 nsITableEditor.getNextSelectedCell() should return the forth cell element in the first row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell.getAttribute("id"), "c2-1",
"#2-1 nsITableEditor.getNextSelectedCell() should return the first cell element in the second row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell.getAttribute("id"), "c2-2",
"#2-2 nsITableEditor.getNextSelectedCell() should return the second cell element in the second row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell, null,
"Next of #2-2 nsITableEditor.getNextSelectedCell() should return null if we reached the last cell");
selection.removeAllRanges();
tr = document.getElementById("r6");
range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
range = document.createRange();
range.setStart(document.getElementById("c6-4").firstChild, 0);
range.setEnd(document.getElementById("c6-4").firstChild, 1);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 3);
range.setEnd(tr, 4);
selection.addRange(range);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell.getAttribute("id"), "c6-3",
"#6-3 nsITableEditor.getFirstSelectedCell() should return the second cell element in the sixth row");
// The <p> element in c6-4 is selected. In this case, the range is ignored
// by getNextSelectedCell(). So, next call should return the last range.
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell.getAttribute("id"), "c6-5",
"#6-5 nsITableEditor.getNextSelectedCell() should return the third cell element in the sixth row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell, null,
"Next of #6-5 nsITableEditor.getNextSelectedCell() should return null if we reached the last cell");
selection.removeAllRanges();
tr = document.getElementById("r2");
range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
range = document.createRange();
range.setStart(document.getElementById("c4-1").firstChild, 0);
range.setEnd(document.getElementById("c4-1").firstChild, 7);
selection.addRange(range);
tr = document.getElementById("r7");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell.getAttribute("id"), "c2-3",
"#2-3 nsITableEditor.getFirstSelectedCell() should return the third cell element in the second row");
// c4-1 is not selected even though it contains a range of Selection.
// In this case, getNextSelectedCell() returns null.
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell, null,
"#4-1 nsITableEditor.getNextSelectedCell() should return null if the range does not select <td> nor <th> element");
// Although c7-2 is selected, but if getNextSelectedCell() meets a range which
// does not select <td> nor <th>, it stops incrementing its internal counter.
// So, following getNextSelectedCell() should return null.
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell, null,
"#7-2 nsITableEditor.getNextSelectedCell() should return null if it reached a range which does not select <td> nor <th>");
selection.removeAllRanges();
tr = document.getElementById("r3");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
tr = document.getElementById("r2-1");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
tr = document.getElementById("r7");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCell());
is(cell.getAttribute("id"), "c3-2",
"#3-2 nsITableEditor.getFirstSelectedCell() should return the first cell element in the third row");
// c2-1-1 is in another <table>, however, getNextSelectedCell() returns it
// since it works only with ranges of Selection.
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell.getAttribute("id"), "c2-1-1",
"#2-1-1 nsITableEditor.getNextSelectedCell() should return the cell element in the child <table> element");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell.getAttribute("id"), "c7-2",
"#7-2 nsITableEditor.getNextSelectedCell() should return the cell element in the last row");
cell = SpecialPowers.unwrap(getTableEditor().getNextSelectedCell());
is(cell, null,
"Next of #7-2 nsITableEditor.getNextSelectedCell() should return null if we reached the last cell");
SimpleTest.finish();
});
function getTableEditor() {
var Ci = SpecialPowers.Ci;
var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,295 @@
<!DOCTYPE>
<html>
<head>
<title>Test for nsITableEditor.getSelectedCells()</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<div id="display">
</div>
<div id="content" contenteditable></div>
<pre id="test">
</pre>
<script class="testbody" type="application/javascript">
"use strict";
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(() => {
let editor = document.getElementById("content");
let selection = document.getSelection();
(function test_with_collapsed_selection() {
selection.collapse(editor, 0);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 0,
"nsITableEditor.getSelectedCells() should return empty array if Selection does not select cells");
})();
editor.innerHTML =
'<table id="table">' +
'<tr id="r1"><td id="c1-1">cell1-1</td><td id="c1-2">cell1-2</td><td id="c1-3">cell1-3</td><td id="c1-4" colspan="2" rowspan="2">cell1-4</td></tr>' +
'<tr id="r2"><th id="c2-1" rowspan="2">cell2-1</th><td id="c2-2">cell2-2</td><td id="c2-3">cell2-3</td></tr>' +
'<tr id="r3"><td id="c3-2">cell3-2</td><td id="c3-3">cell3-3</td><td id="c3-4" colspan="2">cell3-4</td></tr>' +
'<tr id="r4"><td id="c4-1" rowspan="4">cell4-1</td><td id="c4-2">cell4-2</td><td id="c4-3">cell4-3</td><th id="c4-4">cell4-4</th><td id="c4-5">cell4-5</td></tr>' +
'<tr id="r5"><th id="c5-2">cell5-2</th><th id="c5-3" colspan="2">' +
'<table><tr id="r2-1"><td id="c2-1-1">cell2-1-1</td></tr></table>' +
'</th><td id="c5-5">cell5-5</td></tr>' +
'<tr id="r6"><td id="c6-2">cell6-2</td><td id="c6-3">cell6-3</td><td id="c6-4"><p>cell6-4</p></td><td id="c6-5">cell6-5</td></tr>' +
'<tr id="r7"><td id="c7-2" colspan="4">cell7-2</td></tr>' +
"</table>";
(function test_with_selecting_1_1() {
let tr = document.getElementById("r1");
selection.setBaseAndExtent(tr, 0, tr, 1);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 1,
"#1-1 nsITableEditor.getSelectedCells() should return an array whose length is 1");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c1-1"),
"#1-1 nsITableEditor.getSelectedCells() should set the first item of the result to the first cell element in the first row");
})();
(function test_with_selecting_1_4() {
let tr = document.getElementById("r1");
selection.setBaseAndExtent(tr, 3, tr, 4);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 1,
"#1-4 nsITableEditor.getSelectedCells() should return an array whose length is 1");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c1-4"),
"#1-4 nsITableEditor.getSelectedCells() should set the first item of the result to the last cell element whose colspan and rowspan are 2 in the first row");
})();
(function test_with_selecting_2_1() {
let tr = document.getElementById("r2");
selection.setBaseAndExtent(tr, 0, tr, 1);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 1,
"#2-1 nsITableEditor.getSelectedCells() should return an array whose length is 1");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c2-1"),
"#2-1 nsITableEditor.getSelectedCells() should set the first item of the result to the first cell element in the second row");
})();
(function test_with_selecting_7_2() {
let tr = document.getElementById("r7");
selection.setBaseAndExtent(tr, 0, tr, 1);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 1,
"#7-2 nsITableEditor.getSelectedCells() should return an array whose length is 1");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c7-2"),
"#7-2 nsITableEditor.getSelectedCells() should set the first item of the result to the second cell element in the last row");
})();
(function test_with_selecting_2_2_and_2_3() {
let tr = document.getElementById("r2");
selection.removeAllRanges();
let range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 2,
"#2-2 nsITableEditor.getSelectedCells() should return an array whose length is 2");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c2-2"),
"#2-2 nsITableEditor.getSelectedCells() should set the first item of the result to the second cell element in the second row");
})();
(function test_with_selecting_3_4_and_5_2() {
let tr = document.getElementById("r3");
selection.removeAllRanges();
let range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
range = document.createRange();
range.setStart(document.getElementById("r5"), 0);
range.setEnd(document.getElementById("r5"), 1);
selection.addRange(range);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 2,
"#3-4, #5-2 nsITableEditor.getSelectedCells() should return an array whose length is 2");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c3-4"),
"#3-4, #5-2 nsITableEditor.getSelectedCells() should set the first item of the result to the last cell element in the third row");
is(SpecialPowers.unwrap(cells[1]), document.getElementById("c5-2"),
"#3-4, #5-2 nsITableEditor.getSelectedCells() should set the second item of the result to the first cell element in the fifth row");
})();
(function test_with_selecting_1_2_and_1_3_and_1_4_and_2_1_and_2_2() {
selection.removeAllRanges();
let tr = document.getElementById("r1");
let range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 3);
range.setEnd(tr, 4);
selection.addRange(range);
tr = document.getElementById("r2");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 5,
"#1-2, #1-3, #1-4, #2-1, #2-2 nsITableEditor.getSelectedCells() should return an array whose length is 5");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c1-2"),
"#1-2, #1-3, #1-4, #2-1, #2-2 nsITableEditor.getSelectedCells() should set the first item of the result to the second cell element in the first row");
is(SpecialPowers.unwrap(cells[1]), document.getElementById("c1-3"),
"#1-2, #1-3, #1-4, #2-1, #2-2 nsITableEditor.getSelectedCells() should set the second item of the result to the third cell element in the first row");
is(SpecialPowers.unwrap(cells[2]), document.getElementById("c1-4"),
"#1-2, #1-3, #1-4, #2-1, #2-2 nsITableEditor.getSelectedCells() should set the third item of the result to the forth cell element in the first row");
is(SpecialPowers.unwrap(cells[3]), document.getElementById("c2-1"),
"#1-2, #1-3, #1-4, #2-1, #2-2 nsITableEditor.getSelectedCells() should set the forth item of the result to the first cell element in the second row");
is(SpecialPowers.unwrap(cells[4]), document.getElementById("c2-2"),
"#1-2, #1-3, #1-4, #2-1, #2-2 nsITableEditor.getSelectedCells() should set the forth item of the result to the second cell element in the second row");
})();
(function test_with_selecting_6_3_and_paragraph_in_6_4_and_6_5() {
selection.removeAllRanges();
let tr = document.getElementById("r6");
let range = document.createRange();
range.setStart(tr, 1);
range.setEnd(tr, 2);
selection.addRange(range);
range = document.createRange();
range.setStart(document.getElementById("c6-4").firstChild, 0);
range.setEnd(document.getElementById("c6-4").firstChild, 1);
selection.addRange(range);
range = document.createRange();
range.setStart(tr, 3);
range.setEnd(tr, 4);
selection.addRange(range);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 2,
"#6-3, #6-5 nsITableEditor.getSelectedCells() should return an array whose length is 2");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c6-3"),
"#6-3, #6-5 nsITableEditor.getSelectedCells() should set the first item of the result to the second cell element in the sixth row");
// The <p> element in c6-4 is selected, this does not select the cell
// element so that it should be ignored.
is(SpecialPowers.unwrap(cells[1]), document.getElementById("c6-5"),
"#6-3, #6-5 nsITableEditor.getSelectedCells() should set the first item of the result to the forth cell element in the sixth row");
})();
(function test_with_selecting_2_3_and_text_in_4_1_and_7_2() {
selection.removeAllRanges();
let tr = document.getElementById("r2");
let range = document.createRange();
range.setStart(tr, 2);
range.setEnd(tr, 3);
selection.addRange(range);
range = document.createRange();
range.setStart(document.getElementById("c4-1").firstChild, 0);
range.setEnd(document.getElementById("c4-1").firstChild, 7);
selection.addRange(range);
tr = document.getElementById("r7");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 2,
"#2-3, #7-2 nsITableEditor.getSelectedCells() should return an array whose length is 2");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c2-3"),
"#2-3, #7-2 nsITableEditor.getSelectedCells() should set the first item of the result to the third cell element in the second row");
// Text in c4-1 is selected, this does not select the cell element so that
// it should be ignored. Note that we've ignored the following selected
// cell elements in old API, but it causes inconsistent behavior with the
// previous test case. Therefore, we take this behavior.
is(SpecialPowers.unwrap(cells[1]), document.getElementById("c7-2"),
"#2-3, #7-2 nsITableEditor.getSelectedCells() should set the second item of the result to the cell element in the seventh row");
})();
(function test_with_selecting_3_2_and_2_1_1_and_7_2() {
selection.removeAllRanges();
let tr = document.getElementById("r3");
let range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
tr = document.getElementById("r2-1");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
tr = document.getElementById("r7");
range = document.createRange();
range.setStart(tr, 0);
range.setEnd(tr, 1);
selection.addRange(range);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 3,
"#3-2, #2-1-1, #7-2 nsITableEditor.getSelectedCells() should return an array whose length is 3");
is(SpecialPowers.unwrap(cells[0]), document.getElementById("c3-2"),
"#3-2, #2-1-1, #7-2 nsITableEditor.getSelectedCells() should set the first item of the result to the first cell element in the third row");
// c2-1-1 is in another <table>, however, getSelectedCells() returns it
// since it works only with ranges of Selection.
is(SpecialPowers.unwrap(cells[1]), document.getElementById("c2-1-1"),
"#3-2, #2-1-1, #7-2 nsITableEditor.getSelectedCells() should set the second item of the result to the cell element in the child <table> element");
is(SpecialPowers.unwrap(cells[2]), document.getElementById("c7-2"),
"#3-2, #2-1-1, #7-2 nsITableEditor.getSelectedCells() should set the third item of the result to the cell element in the seventh row");
})();
(function test_with_selecting_all_children_of_cell() {
selection.selectAllChildren(document.getElementById("c6-4"));
let cells = getTableEditor().getSelectedCells();
is(cells.length, 0,
"nsITableEditor.getSelectedCells() should return an empty array when no cell element is selected");
})();
(function test_with_selecting_text_in_cell() {
let cell = document.getElementById("c6-5");
selection.collapse(cell.firstChild, 0);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 0,
"nsITableEditor.getSelectedCells() should return an empty array when selecting text in a cell element");
})();
(function test_with_selecting_text_in_1_1_and_1_2() {
let cell = document.getElementById("c1-1");
selection.setBaseAndExtent(cell.firstChild, 0, cell.firstChild, 3);
let range = document.createRange();
range.setStart(cell.parentNode, 1);
range.setEnd(cell.parentNode, 2);
selection.addRange(range);
let cells = getTableEditor().getSelectedCells();
is(cells.length, 0,
"nsITableEditor.getSelectedCells() should return an empty array when the first range does not select a cell element");
})();
(function test_without_selection_ranges() {
selection.removeAllRanges();
let cells = getTableEditor().getSelectedCells();
is(cells.length, 0,
"nsITableEditor.getSelectedCells() should return an empty array even when there is no selection range");
})();
SimpleTest.finish();
});
function getTableEditor() {
var Ci = SpecialPowers.Ci;
var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
}
</script>
</body>
</html>

Просмотреть файл

@ -419,20 +419,6 @@ interface nsITableEditor : nsISupports
[can_run_script]
uint32_t getSelectedCellsType(in Element aElement);
/**
* getFirstSelectedCell() returns a <td> or <th> element if first range of
* Selection selects only one table cell element (i.e., startContainer and
* endContainer are same <tr> 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 <td> or <th> 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 <td> or <th>
* element, returns the cell element. If a range selects an element but
* neither <td> nor <th> 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 <table> boundaries since this method just
* scans all ranges of Selection. Therefore, returning cells which
* belong to different <table> elements.
*
* @return A <td> or <th> element if one of remaining
* ranges selects a <td> or <th> element unless
* this does not meet a range in a text node.
* getSelectedCells() returns an array of `<td>` and `<th>` elements which
* are selected in **any** `<table>` elements (i.e., some cells may be
* in different `<table>` 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<Element> getSelectedCells();
};