Bug 1860210 - Make `HTMLEditUtils::GetPreviousEditablePoint` and `HTMLEditUtils::GetNextEditablePoint` check ancestor limiter before getting a sibling of ancestor r=m_kato

They check ancestor limiter when they are climbing up the DOM tree if and only
if the ancestor does not have a sibling.  However, this causes moving into
a sibling of the ancestor limiter.

Differential Revision: https://phabricator.services.mozilla.com/D192528
This commit is contained in:
Masayuki Nakano 2023-11-10 01:35:33 +00:00
Родитель 86217c833b
Коммит 0348ca6476
2 изменённых файлов: 84 добавлений и 19 удалений

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

@ -1667,6 +1667,10 @@ EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint(
"HTMLEditUtils::GetPreviousEditablePoint() may return a point "
"between table structure elements");
if (&aContent == aAncestorLimiter) {
return EditorDOMPointType();
}
// First, look for previous content.
nsIContent* previousContent = aContent.GetPreviousSibling();
if (!previousContent) {
@ -1675,12 +1679,10 @@ EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint(
}
nsIContent* inclusiveAncestor = &aContent;
for (Element* parentElement : aContent.AncestorsOfType<Element>()) {
previousContent = parentElement->GetPreviousSibling();
if (!previousContent &&
(parentElement == aAncestorLimiter ||
if (parentElement == aAncestorLimiter ||
!HTMLEditUtils::IsSimplyEditableNode(*parentElement) ||
!HTMLEditUtils::CanCrossContentBoundary(*parentElement,
aHowToTreatTableBoundary))) {
aHowToTreatTableBoundary)) {
// If cannot cross the parent element boundary, return the point of
// last inclusive ancestor point.
return EditorDOMPointType(inclusiveAncestor);
@ -1693,6 +1695,7 @@ EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint(
inclusiveAncestor = parentElement;
}
previousContent = parentElement->GetPreviousSibling();
if (!previousContent) {
continue; // Keep looking for previous sibling of an ancestor.
}
@ -1777,6 +1780,10 @@ EditorDOMPointType HTMLEditUtils::GetNextEditablePoint(
"HTMLEditUtils::GetPreviousEditablePoint() may return a point "
"between table structure elements");
if (&aContent == aAncestorLimiter) {
return EditorDOMPointType();
}
// First, look for next content.
nsIContent* nextContent = aContent.GetNextSibling();
if (!nextContent) {
@ -1785,19 +1792,10 @@ EditorDOMPointType HTMLEditUtils::GetNextEditablePoint(
}
nsIContent* inclusiveAncestor = &aContent;
for (Element* parentElement : aContent.AncestorsOfType<Element>()) {
// End of the parent element is a next editable point if it's an
// element which is not a table structure element.
if (!HTMLEditUtils::IsAnyTableElement(parentElement) ||
HTMLEditUtils::IsTableCellOrCaption(*parentElement)) {
inclusiveAncestor = parentElement;
}
nextContent = parentElement->GetNextSibling();
if (!nextContent &&
(parentElement == aAncestorLimiter ||
if (parentElement == aAncestorLimiter ||
!HTMLEditUtils::IsSimplyEditableNode(*parentElement) ||
!HTMLEditUtils::CanCrossContentBoundary(*parentElement,
aHowToTreatTableBoundary))) {
aHowToTreatTableBoundary)) {
// If cannot cross the parent element boundary, return the point of
// last inclusive ancestor point.
return EditorDOMPointType(inclusiveAncestor);
@ -1810,6 +1808,7 @@ EditorDOMPointType HTMLEditUtils::GetNextEditablePoint(
inclusiveAncestor = parentElement;
}
nextContent = parentElement->GetNextSibling();
if (!nextContent) {
continue; // Keep looking for next sibling of an ancestor.
}

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

@ -2062,4 +2062,70 @@ promise_test(async (t) => {
checkGetTargetRangesOfInputOnDeleteSomething();
}, 'Meta + Backspace at "<p> abc[] def</p>"');
// If editing host is nested, editing in the nested one shouldn't cause
// target ranges extended to outside of it.
promise_test(async t => {
const innerHTML = "<div contenteditable=\"false\"><div contenteditable=\"\"><p><br></p></div></div>";
initializeTest(innerHTML);
const p = gEditor.querySelector("p");
const innerEditingHost = p.parentNode;
document.activeElement?.blur();
p.parentNode.focus();
gSelection.collapse(p, 0);
await sendBackspaceKey();
if (gEditor.innerHTML == innerHTML) {
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p,
startOffset: 0,
endContainer: p,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDoNothing();
} else {
checkEditorContentResultAsSubTest(
"<div contenteditable=\"false\"><div contenteditable=\"\"><br></div></div>",
t.name
);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: innerEditingHost,
startOffset: 0,
endContainer: p,
endOffset: 1,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}
}, 'Backspace at "<div contenteditable="false"><div contenteditable=""><p>{}<br></p></div></div>');
promise_test(async t => {
const innerHTML = "<div contenteditable=\"false\">\n <div contenteditable=\"\"><p><br></p></div></div>";
initializeTest(innerHTML);
const p = gEditor.querySelector("p");
const innerEditingHost = p.parentNode;
document.activeElement?.blur();
p.parentNode.focus();
gSelection.collapse(p, 0);
await sendBackspaceKey();
if (gEditor.innerHTML == innerHTML) {
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: p,
startOffset: 0,
endContainer: p,
endOffset: 0,
});
checkGetTargetRangesOfInputOnDoNothing();
} else {
checkEditorContentResultAsSubTest(
"<div contenteditable=\"false\">\n <div contenteditable=\"\"><br></div></div>",
t.name
);
checkGetTargetRangesOfBeforeinputOnDeleteSomething({
startContainer: innerEditingHost,
startOffset: 0,
endContainer: p,
endOffset: 1,
});
checkGetTargetRangesOfInputOnDeleteSomething();
}
}, 'Backspace at "<div contenteditable="false">\n <div contenteditable=""><p>{}<br></p></div></div>');
</script>