Bug 1881989 - Make AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete handle it with the closest editable ancestor block or inline editing host r=m_kato

It's currently handling its job with the closest ancestor block which may be
non-editable and editing host which is either inline or block.  However, the
closest block is required for check whether the range won't be extended outside
the closest block of the common ancestor of the range and the range is
guaranteed that they are in an editing host.  Therefore, it's not required if
it's outside the editing host.  So, comparisons which check whether a node is
either/neither editing host or/nor ancestor block can get same result with
comparing with the closest one of the editing host or the closest editable
block.

Differential Revision: https://phabricator.services.mozilla.com/D202697
This commit is contained in:
Masayuki Nakano 2024-02-27 23:15:36 +00:00
parent 20495a80d2
commit b4501d1622
2 changed files with 92 additions and 24 deletions

View file

@ -6666,23 +6666,31 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
return Err(NS_ERROR_FAILURE);
}
// Look for the common ancestor's block element. It's fine that we get
// non-editable block element which is ancestor of inline editing host
// because the following code checks editing host too.
const Element* const maybeNonEditableBlockElement =
HTMLEditUtils::GetInclusiveAncestorElement(
*commonAncestor, HTMLEditUtils::ClosestBlockElement,
BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (NS_WARN_IF(!maybeNonEditableBlockElement)) {
// Editing host may be nested and outer one could have focus. Let's use
// the closest editing host instead.
const RefPtr<Element> closestEditingHost =
aHTMLEditor.ComputeEditingHost(*commonAncestor, LimitInBodyElement::No);
if (NS_WARN_IF(!closestEditingHost)) {
return Err(NS_ERROR_FAILURE);
}
// Look for the common ancestor's block element in the editing host. It's
// fine that we get non-editable block element which is ancestor of inline
// editing host because the following code checks editing host too.
const RefPtr<Element> closestBlockAncestorOrInlineEditingHost = [&]() {
// Note that if non-closest editing host has focus, found block may be
// non-editable.
if (Element* const maybeEditableBlockElement =
HTMLEditUtils::GetInclusiveAncestorElement(
*commonAncestor, HTMLEditUtils::ClosestBlockElement,
BlockInlineCheck::UseComputedDisplayOutsideStyle,
closestEditingHost)) {
return maybeEditableBlockElement;
}
return closestEditingHost.get();
}();
// Set up for loops and cache our root element
RefPtr<Element> editingHost = aHTMLEditor.ComputeEditingHost();
if (NS_WARN_IF(!editingHost)) {
return Err(NS_ERROR_FAILURE);
}
// If only one list element is selected, and if the list element is empty,
// we should delete only the list element. Or if the list element is not
// empty, we should make the list has only one empty list item element.
@ -6712,18 +6720,18 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
// Find previous visible things before start of selection
EditorRawDOMRange rangeToDelete(aRangeToDelete);
if (rangeToDelete.StartRef().GetContainer() != maybeNonEditableBlockElement &&
rangeToDelete.StartRef().GetContainer() != editingHost) {
if (rangeToDelete.StartRef().GetContainer() !=
closestBlockAncestorOrInlineEditingHost) {
for (;;) {
WSScanResult backwardScanFromStartResult =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
editingHost, rangeToDelete.StartRef(),
closestEditingHost, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary()) {
break;
}
MOZ_ASSERT(backwardScanFromStartResult.GetContent() ==
WSRunScanner(editingHost, rangeToDelete.StartRef(),
WSRunScanner(closestEditingHost, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle)
.GetStartReasonContent());
// We want to keep looking up. But stop if we are crossing table
@ -6731,8 +6739,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
if (HTMLEditUtils::IsAnyTableElement(
backwardScanFromStartResult.GetContent()) ||
backwardScanFromStartResult.GetContent() ==
maybeNonEditableBlockElement ||
backwardScanFromStartResult.GetContent() == editingHost) {
closestBlockAncestorOrInlineEditingHost ||
backwardScanFromStartResult.GetContent() == closestEditingHost) {
break;
}
// Don't cross list element boundary because we don't want to delete list
@ -6767,11 +6775,11 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
// Find next visible things after end of selection
EditorDOMPoint atFirstInvisibleBRElement;
if (rangeToDelete.EndRef().GetContainer() != maybeNonEditableBlockElement &&
rangeToDelete.EndRef().GetContainer() != editingHost) {
if (rangeToDelete.EndRef().GetContainer() !=
closestBlockAncestorOrInlineEditingHost) {
for (;;) {
WSRunScanner wsScannerAtEnd(
editingHost, rangeToDelete.EndRef(),
closestEditingHost, rangeToDelete.EndRef(),
BlockInlineCheck::UseComputedDisplayOutsideStyle);
WSScanResult forwardScanFromEndResult =
wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom(
@ -6806,8 +6814,7 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
if (HTMLEditUtils::IsAnyTableElement(
forwardScanFromEndResult.GetContent()) ||
forwardScanFromEndResult.GetContent() ==
maybeNonEditableBlockElement ||
forwardScanFromEndResult.GetContent() == editingHost) {
closestBlockAncestorOrInlineEditingHost) {
break;
}
// Don't cross flex-item/grid-item boundary to make new content inserted

View file

@ -0,0 +1,61 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Backspace/Delete in inline editing host which is a shadow root</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="../include/editor-test-utils.js"></script>
<script>
"use strict";
addEventListener("load", () => {
const shadowRoot = document.body.firstChild.attachShadow({mode: "open"});
const editingHost = document.createElement("span");
editingHost.setAttribute("contenteditable", "");
shadowRoot.appendChild(editingHost);
const utils = new EditorTestUtils(editingHost);
promise_test(async t => {
utils.setupEditingHost("ab[]c");
await utils.sendBackspaceKey();
assert_equals(
editingHost.textContent,
"ac"
);
}, "Backspace at <span contenteditable>ab[]c</span>");
promise_test(async t => {
utils.setupEditingHost("a[]bc");
await utils.sendDeleteKey();
assert_equals(
editingHost.textContent,
"ac"
);
}, "Delete at <span contenteditable>a[]bc</span>");
promise_test(async t => {
utils.setupEditingHost("a[b]c");
await utils.sendBackspaceKey();
assert_equals(
editingHost.textContent,
"ac"
);
}, "Backspace at <span contenteditable>a[b]c</span>");
promise_test(async t => {
utils.setupEditingHost("a[b]c");
await utils.sendDeleteKey();
assert_equals(
editingHost.textContent,
"ac"
);
}, "Delete at <span contenteditable>a[b]c</span>");
}, {once: true});
</script>
</head>
<body><div></div></body>
</html>