Bug 1671197 - Make `HTMLEditor::SelectAllInternal()` select all children of `<body>` element if computed selection root is an ancestor of the `<body>` element r=m_kato

In strictly speaking, we should shrink selection ranges at very first time
of edit action handling.  However, we support multiple selection ranges and
it makes the check cost really expensive, and the code would be really
complicated since ranges cannot be overlapped.  I.e., changing one range
could affect some of the others.

Therefore, this patch changes `HTMLEditor::SelectAllInternal()` instead.
If computed selection root is an ancestor of `<body>` element in HTML document,
it use the `<body>` element instead.

Note that, in HTML document, there should be only one `<body>` element and
only its content should be editable at least for now.  (Note that in XHTML
document, no `<body>` is allowed, multiple `<body>` elements allowed.)

Differential Revision: https://phabricator.services.mozilla.com/D93712
This commit is contained in:
Masayuki Nakano 2020-10-16 09:38:56 +00:00
Родитель 6ecb24d4d2
Коммит 049c973fbf
3 изменённых файлов: 203 добавлений и 4 удалений

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

@ -3851,13 +3851,28 @@ nsresult HTMLEditor::SelectAllInternal() {
if (anchorContent->HasIndependentSelection()) {
SelectionRefPtr()->SetAncestorLimiter(nullptr);
rootContent = mRootElement;
if (NS_WARN_IF(!rootContent)) {
return NS_ERROR_UNEXPECTED;
}
} else {
RefPtr<PresShell> presShell = GetPresShell();
rootContent = anchorContent->GetSelectionRootContent(presShell);
}
if (NS_WARN_IF(!rootContent)) {
return NS_ERROR_UNEXPECTED;
if (NS_WARN_IF(!rootContent)) {
return NS_ERROR_UNEXPECTED;
}
// If the document is HTML document (not XHTML document), we should
// select all children of the `<body>` element instead of `<html>`
// element.
if (Document* document = GetDocument()) {
if (document->IsHTMLDocument()) {
if (HTMLBodyElement* bodyElement = document->GetBodyElement()) {
if (nsContentUtils::ContentIsFlattenedTreeDescendantOf(bodyElement,
rootContent)) {
rootContent = bodyElement;
}
}
}
}
}
Maybe<Selection::AutoUserInitiated> userSelection;

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

@ -0,0 +1,33 @@
[select-all-and-delete-in-html-element-having-contenteditable.html]
[Select All, then, Backspace]
expected:
if (os == "linux"): FAIL
PASS
[Select All, then, Delete]
expected:
if (os == "linux"): FAIL
PASS
[Select All, then, execCommand("forwarddelete")]
expected:
if (os == "linux"): FAIL
PASS
[Select All, then, execCommand("delete")]
expected:
if (os == "linux"): FAIL
PASS
[getSelection().selectAllChildren(document.documentElement), then, Backspace]
expected: FAIL
[getSelection().selectAllChildren(document.documentElement), then, Delete]
expected: FAIL
[getSelection().selectAllChildren(document.documentElement), then, execCommand("forwarddelete")]
expected: FAIL
[getSelection().selectAllChildren(document.documentElement), then, execCommand("delete")]
expected: FAIL

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

@ -0,0 +1,151 @@
<!doctype html>
<html contenteditable>
<head>
<meta charset=utf-8>
<title>Test "Select all" and deletion work with &lt;html contenteditable&gt;</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>
</head>
<body>
<script>
"use strict";
const kBackspaceKey = "\uE003";
const kDeleteKey = "\uE017";
const kMeta = "\uE03d";
const kControl = "\uE009";
async function selectAllWithKey(elementToSelectAll) {
if (elementToSelectAll.length === 0) {
throw "element to select all must not be empty";
}
getSelection().collapse(elementToSelectAll, 0);
try {
await new test_driver.Actions()
.keyDown(kControl)
.keyDown("a")
.keyUp("a")
.keyUp(kControl)
.send();
if (!getSelection().isCollapsed) {
return;
}
await new test_driver.Actions()
.keyDown(kMeta)
.keyDown("a")
.keyUp("a")
.keyUp(kMeta)
.send();
if (!getSelection().isCollapsed) {
return;
}
} catch (ex) {
throw ex;
}
throw "Neither Control-A nor Meta-A does not select all contents";
}
function deleteWithBackspaceKey() {
return new test_driver.Actions()
.keyDown(kBackspaceKey)
.keyUp(kBackspaceKey)
.send();
}
function deleteWithDeleteKey() {
return new test_driver.Actions()
.keyDown(kDeleteKey)
.keyUp(kDeleteKey)
.send();
}
promise_test(async () => {
document.body.innerHTML = "abc";
await selectAllWithKey(document.body);
await deleteWithBackspaceKey();
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, "Select All, then, Backspace");
promise_test(async () => {
document.body.innerHTML = "abc";
await selectAllWithKey(document.body);
await deleteWithDeleteKey();
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, "Select All, then, Delete");
promise_test(async () => {
document.body.innerHTML = "abc";
document.execCommand("selectall");
await deleteWithBackspaceKey();
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'execCommand("selectall"), then, Backspace');
promise_test(async () => {
document.body.innerHTML = "abc";
document.execCommand("selectall");
await deleteWithDeleteKey();
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'execCommand("selectall"), then, Delete');
promise_test(async () => {
document.body.innerHTML = "abc";
await selectAllWithKey(document.body);
document.execCommand("forwarddelete", false, false);
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'Select All, then, execCommand("forwarddelete")');
promise_test(async () => {
document.body.innerHTML = "abc";
await selectAllWithKey(document.body);
document.execCommand("delete", false, false);
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'Select All, then, execCommand("delete")');
test(() => {
document.body.innerHTML = "abc";
document.execCommand("selectall");
document.execCommand("forwarddelete", false, false);
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'execCommand("selectall"), then, execCommand("forwarddelete")');
test(() => {
document.body.innerHTML = "abc";
document.execCommand("selectall");
document.execCommand("delete", false, false);
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'execCommand("selectall"), then, execCommand("delete")');
promise_test(async () => {
document.body.innerHTML = "abc";
getSelection().selectAllChildren(document.documentElement);
await deleteWithBackspaceKey();
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'getSelection().selectAllChildren(document.documentElement), then, Backspace');
promise_test(async () => {
document.body.innerHTML = "abc";
getSelection().selectAllChildren(document.documentElement);
await deleteWithDeleteKey();
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'getSelection().selectAllChildren(document.documentElement), then, Delete');
test(() => {
document.body.innerHTML = "abc";
getSelection().selectAllChildren(document.documentElement);
document.execCommand("forwarddelete", false, false);
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'getSelection().selectAllChildren(document.documentElement), then, execCommand("forwarddelete")');
test(() => {
document.body.innerHTML = "abc";
getSelection().selectAllChildren(document.documentElement);
document.execCommand("delete", false, false);
assert_in_array(document.body.innerHTML, ["", "<br>"]);
}, 'getSelection().selectAllChildren(document.documentElement), then, execCommand("delete")');
</script>
</body>
</html>