Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!doctype html>
<meta charset=utf-8>
<title>Test that execCommand without editable element</title>
<script src=../include/implementation.js></script>
<script>var testsJsLibraryOnly = true</script>
<script src=../include/tests.js></script>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
"use strict";
setup({explicit_done: true});
// This test calls execCommand() without editable element in the document,
// but its parent or child document has editable element and it has focus.
// In most cases, execCommand() should do nothing and return false. However,
// "cut", "copy", "paste" and "selectall" commands should work without DOM tree
// modification for making web apps can implement their own editor without
// editable element.
async function runTests() {
let parentWindow = window;
let parentDocument = document;
let parentSelection = parentDocument.getSelection();
let parentEditor = parentDocument.getElementById("editor");
parentEditor.focus();
let iframe = document.getElementsByTagName("iframe")[0];
let childWindow = iframe.contentWindow;
let childDocument = iframe.contentDocument;
let childSelection = childDocument.getSelection();
let childEditor = childDocument.getElementById("editor");
childEditor.focus();
// execCommand() in child document shouldn't affect to focused parent
// document.
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.
await doTest(childWindow, childDocument, childSelection, childEditor,
parentWindow, parentDocument, parentSelection, parentEditor, true);
done();
}
async function doTest(aFocusWindow, aFocusDocument, aFocusSelection, aFocusEditor,
aExecWindow, aExecDocument, aExecSelection, aExecEditor,
aExecInParent) {
const kTests = [
/**
* command: The command which you test.
* focusContent: Will be set to innerHTML of div#editor element in focused
* document.
* execContent: Will be set to innerHTML of div#editor element in the
* document whose execCommand() will be called.
* initFunc: [optional] If you need to do something before running the
* test, you can do it with a function.
* expectedFocusContent: Expected content and selection in div#editor in
* focused document after calling execCommand().
* expectedExecContent: Expected content and selection in div#editor in
* the document whose execCommand() is called.
* event: The event which you need to check whether it's fired or not.
* expectedFiredInFocus: true if the event should be fired on the focused
* document node.
* expectedFiredInExec: true if the event should be fired on the document
* node whose execCommand() is called.
* expectedResult: Expected result of execCommand().
*/
{command: "bold", value: "bold",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "italic", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "underline", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "strikethrough", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "subscript", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "superscript", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
// "cut", "copy" and "paste" command should cause firing corresponding
// events to make web apps be able to implement their own editor even
// if there is no editor and selection is collapsed.
{command: "cut", value: null,
focusContent: "a[b]c", execContent: "ab[]c",
expectedFocusContent: "a[b]c", expectedExecContent: "ab[]c",
event: "cut", expectedFiredInFocus: false, expectedFiredInExec: true,
expectedResult: false,
},
{command: "cut", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "cut", expectedFiredInFocus: false, expectedFiredInExec: true,
expectedResult: false,
},
{command: "copy", value: null,
focusContent: "a[b]c", execContent: "ab[]c",
expectedFocusContent: "a[b]c", expectedExecContent: "ab[]c",
event: "copy", expectedFiredInFocus: false, expectedFiredInExec: true,
expectedResult: false,
},
{command: "copy", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "copy", expectedFiredInFocus: false, expectedFiredInExec: true,
expectedResult: false,
},
{command: "paste", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
initFunc: () => { aFocusDocument.execCommand("copy", false, "b"); },
event: "paste", expectedFiredInFocus: false, expectedFiredInExec: true,
expectedResult: false,
},
{command: "delete", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "forwarddelete", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
// "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: undefined,
event: "selectionchange", expectedFiredInFocus: false, expectedFiredInExec: true,
expectedResult: true,
},
{command: "undo", value: null,
focusContent: "a[]c", execContent: "a[b]c",
initFunc: () => { aFocusDocument.execCommand("insertText", false, "b"); },
expectedFocusContent: "ab[]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "redo", value: null,
focusContent: "a[]c", execContent: "a[b]c",
initFunc: () => {
aFocusDocument.execCommand("insertText", false, "b");
aFocusDocument.execCommand("undo", false, null);
},
expectedFocusContent: "a[]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "indent", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "outdent", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "backcolor", value: "#000000",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "forecolor", value: "#F0F0F0",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "hilitecolor", value: "#FFFF00",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "fontname", value: "DummyFont",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "fontsize", value: "5",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "increasefontsize", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "decreasefontsize", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "inserthorizontalrule", value: null,
focusContent: "a[]bc", execContent: "a[]bc",
expectedFocusContent: "a[]bc", expectedExecContent: "a[]bc",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "createlink", value: "foo.html",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "insertimage", value: "no-image.png",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "inserthtml", value: "<b>inserted</b>",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "inserttext", value: "**inserted**",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "justifyleft", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "justifyright", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "justifycenter", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "justifyfull", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "removeformat", value: null,
focusContent: "<b>a[b]c</b>", execContent: "<b>a[b]c</b>",
expectedFocusContent: "<b>a[b]c</b>", expectedExecContent: "<b>a[b]c</b>",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "unlink", value: null,
focusContent: "<a href=\"foo.html\">a[b]c</a>", execContent: "<a href=\"foo.html\">a[b]c</a>",
expectedFocusContent: "<a href=\"foo.html\">a[b]c</a>", expectedExecContent: "<a href=\"foo.html\">a[b]c</a>",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "insertorderedlist", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "insertunorderedlist", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "insertparagraph", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "insertlinebreak", value: null,
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "formatblock", value: "div",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
{command: "heading", value: "h1",
focusContent: "a[b]c", execContent: "a[b]c",
expectedFocusContent: "a[b]c", expectedExecContent: "a[b]c",
event: "input", expectedFiredInFocus: false, expectedFiredInExec: false,
expectedResult: false,
},
/**
* command: The command which you test.
* state: The state which is used with execCommand().
* initState: The state which should be set with execCommand() first.
* focusContent: Will be set to innerHTML of div#editor element in focused
* document.
* execContent: Will be set to innerHTML of div#editor element in the
* document whose execCommand() will be called.
* initFunc: [optional] If you need to do something before running the
* test, you can do it with a function.
* expectedSetStateInFocus: Expected queryCommandState() result in focused
* document.
* expectedSetStateInExec: Expected queryCommandState() result in document
* whose execCommand() is called.
* expectedResult: Expected result of execCommand().
*/
{command: "styleWithCSS", state: "true", initState: "false",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
{command: "contentReadOnly", state: "true", initState: "false",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
{command: "insertBrOnReturn", state: "true", initState: "false",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
{command: "defaultParagraphSeparator", state: "div", initState: "p",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
{command: "defaultParagraphSeparator", state: "p", initState: "div",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
{command: "enableObjectResizing", state: "true", initState: "false",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
{command: "enableInlineTableEditing", state: "true", initState: "false",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
{command: "enableAbsolutePositionEditing", state: "true", initState: "false",
focusContent: "a[b]c", execContent: "a[b]c",
expectedSetStateInFocus: false, expectedSetStateInExec: false,
expectedResult: false,
},
];
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.
if (!aExecDocument.queryCommandSupported(kTest.command)) {
continue;
}
aExecEditor.removeAttribute("contenteditable"); // Disable commands in the exec document.
let points = setupDiv(aFocusEditor, kTest.focusContent);
aFocusSelection.setBaseAndExtent(points[0], points[1], points[2], points[3]);
points = setupDiv(aExecEditor, kTest.execContent);
aExecSelection.setBaseAndExtent(points[0], points[1], points[2], points[3]);
aFocusWindow.focus();
aFocusEditor.focus();
if (kTest.initFunc) {
kTest.initFunc();
}
if (kTest.state === undefined) {
let eventFiredOnFocusDocument = false;
function handlerOnFocusDocument() {
eventFiredOnFocusDocument = true;
}
aFocusDocument.addEventListener(kTest.event, handlerOnFocusDocument, {capture: true});
let eventFiredOnExecDocument = false;
function handlerOnExecDocument() {
eventFiredOnExecDocument = true;
}
aExecDocument.addEventListener(kTest.event, handlerOnExecDocument, {capture: true});
const kDescription = `${aExecInParent ? "Parent" : "Child"}Document.execCommand(${kTest.command}, false, ${kTest.value}) with ${kTest.execContent}`;
test(function () {
let ret = aExecDocument.execCommand(kTest.command, false, kTest.value);
assert_equals(ret, kTest.expectedResult, `execCommand should return ${kTest.expectedResult}`);
}, `${kDescription}: calling execCommand`);
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`);
}, `${kDescription}: checking event on executed document`);
test(function () {
if (aFocusSelection.rangeCount) {
addBrackets(aFocusSelection.getRangeAt(0));
}
assert_equals(aFocusEditor.innerHTML, kTest.expectedFocusContent);
}, `${kDescription}: checking result content in focused document`);
test(function () {
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);
}
}, `${kDescription}: checking result content in executed document`);
aFocusDocument.removeEventListener(kTest.event, handlerOnFocusDocument, {capture: true});
aExecDocument.removeEventListener(kTest.event, handlerOnExecDocument, {capture: true});
aExecEditor.setAttribute("contenteditable", "");
} else {
const kDescription = `${aExecInParent ? "Parent" : "Child"}Document.execCommand(${kTest.command}, false, ${kTest.state})`;
test(function () {
let ret = aExecDocument.execCommand(kTest.command, false, kTest.initState);
assert_equals(ret, kTest.expectedResult, `execCommand should return ${kTest.expectedResult}`);
}, `${kDescription}: calling execCommand to initialize`);
let hasSetState = false;
test(function () {
hasSetState = aExecDocument.queryCommandState(kTest.command);
assert_equals(hasSetState, kTest.expectedSetStateInExec, `queryCommandState on executed document should return ${kTest.expectedSetState}`);
}, `${kDescription}: calling queryCommandState on executed document after initializing`);
test(function () {
let ret = aFocusDocument.queryCommandState(kTest.command);
assert_equals(ret, kTest.expectedSetStateInFocus, `queryCommandState on focus document should return ${kTest.expectedSetState}`);
}, `${kDescription}: calling queryCommandState on focus document after initializing`);
if (hasSetState) {
test(function () {
let ret = aExecDocument.queryCommandValue(kTest.command);
assert_equals(ret, kTest.initState, `queryCommandValue on executed document should return ${kTest.initState}`);
}, `${kDescription}: calling queryCommandValue on executed document after initializing`);
}
test(function () {
let ret = aExecDocument.execCommand(kTest.command, false, kTest.state);
assert_equals(ret, kTest.expectedResult, `execCommand should return ${kTest.expectedResult}`);
}, `${kDescription}: calling execCommand to set state`);
test(function () {
hasSetState = aExecDocument.queryCommandState(kTest.command);
assert_equals(hasSetState, kTest.expectedSetStateInExec, `queryCommandState should return ${kTest.expectedSetState}`);
}, `${kDescription}: calling queryCommandState on executed document`);
test(function () {
let ret = aFocusDocument.queryCommandState(kTest.command);
assert_equals(ret, kTest.expectedSetStateInFocus, `queryCommandState should return ${kTest.expectedSetState}`);
}, `${kDescription}: calling queryCommandState on focused document`);
if (hasSetState) {
test(function () {
let ret = aExecDocument.queryCommandValue(kTest.command);
assert_equals(ret, kTest.state, `queryCommandValue should return ${kTest.initState}`);
}, `${kDescription}: calling queryCommandValue on executed document`);
}
aExecEditor.setAttribute("contenteditable", "");
test(function () {
let ret = aExecDocument.queryCommandState(kTest.command);
assert_equals(ret, kTest.expectedSetStateInExec, `queryCommandState should return ${kTest.expectedSetState}`);
}, `${kDescription}: calling queryCommandState on executed document after making executed document editable`);
}
}
}
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>