Bug 1705519 - Make `Document::ExecCommand` not stop handling `selectall` command when there is no editable content r=smaug

When `document.execCommand("selectall")` is called, it should behave exactly
same as "Select All" in the UI for compatibility with the other browsers.

And also this patch fixes a bug of the WPT.  `selectionchange` event should
be fired, but asynchronously.  Therefore, Chrome also fails the check.

Differential Revision: https://phabricator.services.mozilla.com/D112307
This commit is contained in:
Masayuki Nakano 2021-04-19 23:03:54 +00:00
Родитель 5508a33216
Коммит 7ce105e3db
4 изменённых файлов: 54 добавлений и 37 удалений

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

@ -5289,7 +5289,8 @@ bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
return false;
}
MOZ_ASSERT(commandData.IsPasteCommand());
MOZ_ASSERT(commandData.IsPasteCommand() ||
commandData.mCommand == Command::SelectAll);
nsresult rv =
commandManager->DoCommand(commandData.mXULCommandName, nullptr, window);
return NS_SUCCEEDED(rv) && rv != NS_SUCCESS_DOM_NO_OPERATION;

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

@ -4157,7 +4157,8 @@ class Document : public nsINode,
mCommand != mozilla::Command::Copy &&
mCommand != mozilla::Command::Paste &&
mCommand != mozilla::Command::SetDocumentReadOnly &&
mCommand != mozilla::Command::GetHTML;
mCommand != mozilla::Command::GetHTML &&
mCommand != mozilla::Command::SelectAll;
}
bool IsCutOrCopyCommand() const {
return mCommand == mozilla::Command::Cut ||

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

@ -11,15 +11,6 @@
[ChildDocument.execCommand(copy, false, null) with a\[b\]c: checking event on executed document]
expected: FAIL
[ChildDocument.execCommand(selectall, false, null) with a\[b\]c: calling execCommand]
expected: FAIL
[ChildDocument.execCommand(selectall, false, null) with a\[b\]c: checking event on executed document]
expected: FAIL
[ChildDocument.execCommand(selectall, false, null) with a\[b\]c: checking result content in executed document]
expected: FAIL
[ParentDocument.execCommand(cut, false, null) with ab\[\]c: checking event on executed document]
expected: FAIL
@ -32,11 +23,3 @@
[ParentDocument.execCommand(copy, false, null) with a\[b\]c: checking event on executed document]
expected: FAIL
[ParentDocument.execCommand(selectall, false, null) with a\[b\]c: calling execCommand]
expected: FAIL
[ParentDocument.execCommand(selectall, false, null) with a\[b\]c: checking event on executed document]
expected: FAIL
[ParentDocument.execCommand(selectall, false, null) with a\[b\]c: checking result content in executed document]
expected: FAIL

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

@ -6,8 +6,6 @@
<script src=../include/tests.js></script>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<div contenteditable id="editor">abc</div>
<iframe srcdoc="<div contenteditable id='editor'>def</div><span>ghi</span>"></iframe>
<script>
"use strict";
@ -19,7 +17,7 @@ setup({explicit_done: true});
// "cut", "copy", "paste" and "selectall" commands should work without DOM tree
// modification for making web apps can implement their own editor without
// editable element.
function runTests() {
async function runTests() {
let parentWindow = window;
let parentDocument = document;
let parentSelection = parentDocument.getSelection();
@ -34,19 +32,19 @@ function runTests() {
// execCommand() in child document shouldn't affect to focused parent
// document.
doTest(parentWindow, parentDocument, parentSelection, parentEditor,
childWindow, childDocument, childSelection, childEditor, false);
await doTest(parentWindow, parentDocument, parentSelection, parentEditor,
childWindow, childDocument, childSelection, childEditor, false);
// execCommand() in parent document shouldn't affect to focused child
// document but "cut" and "copy" may affect the focused child document.
doTest(childWindow, childDocument, childSelection, childEditor,
parentWindow, parentDocument, parentSelection, parentEditor, true);
await doTest(childWindow, childDocument, childSelection, childEditor,
parentWindow, parentDocument, parentSelection, parentEditor, true);
done();
}
function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
aExecWindow, aExecDocument, aExecSelection, aExecEditor,
aExecInParent) {
async function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
aExecWindow, aExecDocument, aExecSelection, aExecEditor,
aExecInParent) {
const kTests = [
/**
* command: The command which you test.
@ -152,7 +150,7 @@ function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
// "selectall" command should be available without editable content.
{command: "selectall", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "[abc",
expectedFocusContent: "a[b]c", expectedExecContent: undefined,
event: "selectionchange", expectedFiredInFocus: false, expectedFiredInExec: true,
expectedResult: true,
},
@ -387,6 +385,16 @@ function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
},
];
async function waitForCondition(aCheckFunc) {
let retry = 60;
while (retry--) {
if (aCheckFunc()) {
return;
}
await new Promise(resolve => requestAnimationFrame(resolve));
}
}
for (const kTest of kTests) {
// Skip unsupported command since it's not purpose of this tests whether
// each command is supported on the browser.
@ -419,10 +427,22 @@ function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
let ret = aExecDocument.execCommand(kTest.command, false, kTest.value);
assert_equals(ret, kTest.expectedResult, `execCommand should return ${kTest.expectedResult}`);
}, `${kDescription}: calling execCommand`);
test(function () {
assert_equals(eventFiredOnFocusDocument, kTest.expectedFiredInFocus,
`"${kTest.event}" event should${kTest.expectedFiredInFocus ? "" : " not"} be fired`);
}, `${kDescription}: checking event on focused document`);
if (kTest.event === "selectionchange") {
test(function () {
assert_false(eventFiredOnFocusDocument,
`"${kTest.event}" event should not be fired synchronously on focused document`);
assert_false(eventFiredOnExecDocument,
`"${kTest.event}" event should not be fired synchronously on executed document`);
}, `${kDescription}: checking unexpected synchronous event`);
await waitForCondition(() => eventFiredOnFocusDocument && eventFiredOnExecDocument);
// TODO: Whether select all changes selection in the focused document depends on the
// implementation of "Select All".
} else {
test(function () {
assert_equals(eventFiredOnFocusDocument, kTest.expectedFiredInFocus,
`"${kTest.event}" event should${kTest.expectedFiredInFocus ? "" : " not"} be fired`);
}, `${kDescription}: checking event on focused document`);
}
test(function () {
assert_equals(eventFiredOnExecDocument, kTest.expectedFiredInExec,
`"${kTest.event}" event should${kTest.expectedFiredInExec ? "" : " not"} be fired`);
@ -434,10 +454,18 @@ function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
assert_equals(aFocusEditor.innerHTML, kTest.expectedFocusContent);
}, `${kDescription}: checking result content in focused document`);
test(function () {
if (aExecSelection.rangeCount) {
addBrackets(aExecSelection.getRangeAt(0));
if (kTest.command === "selectall") {
assert_true(aExecSelection.rangeCount > 0);
assert_equals(
aExecSelection.toString().replace(/[\r\n]/g, ""),
aExecDocument.body.textContent.replace(/[\r\n]/g, "")
);
} else {
if (aExecSelection.rangeCount) {
addBrackets(aExecSelection.getRangeAt(0));
}
assert_equals(aExecEditor.innerHTML, kTest.expectedExecContent);
}
assert_equals(aExecEditor.innerHTML, kTest.expectedExecContent);
}, `${kDescription}: checking result content in executed document`);
aFocusDocument.removeEventListener(kTest.event, handlerOnFocusDocument, {capture: true});
aExecDocument.removeEventListener(kTest.event, handlerOnExecDocument, {capture: true});
@ -492,3 +520,7 @@ function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
window.addEventListener("load", runTests, {once: true});
</script>
<body>
<div contenteditable id="editor">abc</div>
<iframe srcdoc="<div contenteditable id='editor'>def</div><span>ghi</span>"></iframe>
</body>