Skip to content

[Gecko Bug 1921171] Add WPTs to check the editing behavior when contenteditable="plaintext-only" and contenteditable="true" are nested #51035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions editing/plaintext-only/nested-with-contenteditable-true.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="timeout" content="long">
<title>Testing contenteditable=plaintext-only which is nested with contenteditable=true</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 editingHost = document.createElement("div");
editingHost.setAttribute("contenteditable", "");
document.body.appendChild(editingHost);
editingHost.focus();
const utils = new EditorTestUtils(editingHost);
for (const trueOrEmpty of ["", "true"]) {
for (const explicitlySetFocus of [false, true]) {
for (const data of [
{
desc: `contenteditable="plaintext-only" in contenteditable="${trueOrEmpty}" should support style edit`,
init: `<div contenteditable="plaintext-only">[abc]</div>`,
run: function () {
document.execCommand("bold");
},
expected: `<div contenteditable="plaintext-only"><b>abc</b></div>`
},
{
desc: `contenteditable="plaintext-only" in contenteditable="${
trueOrEmpty
}" and contenteditable="false" should not support style edit`,
init: `<div contenteditable="false"><div contenteditable="plaintext-only">[abc]</div></div>`,
run: function () {
document.execCommand("bold");
},
expected: `<div contenteditable="false"><div contenteditable="plaintext-only">abc</div></div>`,
},
{
desc: `contenteditable="plaintext-only" in contenteditable="${trueOrEmpty}" should insert paragraph at typing Enter`,
init: `<div contenteditable="plaintext-only"><p>a[b]c</p></div>`,
run: function () {
return utils.sendEnterKey();
},
expected: `<div contenteditable="plaintext-only"><p>a</p><p>c</p></div>`
},
{
desc: `contenteditable="plaintext-only" in contenteditable="${
trueOrEmpty
}" and contenteditable="false" should insert line break at typing Enter`,
init: `<div contenteditable="false"><div contenteditable="plaintext-only"><p>a[b]c</p></div></div>`,
run: function () {
return utils.sendEnterKey();
},
expected: `<div contenteditable="false"><div contenteditable="plaintext-only"><p>a<br>c</p></div></div>`,
},
{
desc: `styling start boundary of contenteditable="plaintext-only" in contenteditable="${
trueOrEmpty
}" should apply the style to entire the range`,
init: `A[B<div contenteditable="plaintext-only">C]D</div>EF`,
run: function () {
document.execCommand("bold");
},
expected: `A<b>B</b><div contenteditable="plaintext-only"><b>C</b>D</div>EF`,
},
{
desc: `styling end boundary of contenteditable="plaintext-only" in contenteditable="${
trueOrEmpty
}" should apply the style to entire the range`,
init: `AB<div contenteditable="plaintext-only">C[D</div>E]F`,
run: function () {
document.execCommand("bold");
},
expected: `AB<div contenteditable="plaintext-only">C<b>D</b></div><b>E</b>F`,
},
{
desc: `even after moving selection into contenteditable="plaintext-only" in contenteditable="${
trueOrEmpty
}" and contenteditable="false" from parent editing host should not support style edit`,
init: `A[]B<div contenteditable="false"><div contenteditable="plaintext-only">CD</div></div>EF`,
run: function () {
getSelection().selectAllChildren(editingHost.querySelector("div[contenteditable=plaintext-only]"));
document.execCommand("bold");
},
expected: `AB<div contenteditable="false"><div contenteditable="plaintext-only">CD</div></div>EF`,
},
]) {
promise_test(async () => {
editingHost.setAttribute("contenteditable", trueOrEmpty);
utils.setupEditingHost(data.init);
if (explicitlySetFocus) {
editingHost.querySelector("[contenteditable=plaintext-only]").focus();
}
await data.run();
assert_equals(editingHost.outerHTML, `<div contenteditable="${trueOrEmpty}">${data.expected}</div>`);
}, data.desc + (explicitlySetFocus ? " (explicitly setting focus to the nested one)" : ""));
}

for (const data of [
{
desc: `contenteditable="${trueOrEmpty}" in contenteditable="plaintext-only" should not support style edit`,
init: `<div contenteditable="${trueOrEmpty}">[abc]</div>`,
run: function () {
document.execCommand("bold");
},
expected: `<div contenteditable="${trueOrEmpty}">abc</div>`
},
{
desc: `contenteditable="${
trueOrEmpty
}" in contenteditable="plaintext-only" and contenteditable="false" should support style edit`,
init: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}">[abc]</div></div>`,
run: function () {
document.execCommand("bold");
},
expected: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><b>abc</b></div></div>`,
},
{
desc: `contenteditable="${trueOrEmpty}" in contenteditable="plaintext-only" should insert line break at typing Enter`,
init: `<div contenteditable="${trueOrEmpty}"><p>a[b]c</p></div>`,
run: function () {
return utils.sendEnterKey();
},
expected: `<div contenteditable="${trueOrEmpty}"><p>a<br>c</p></div>`
},
{
desc: `contenteditable="${
trueOrEmpty
}" in contenteditable="plaintext-only" and contenteditable="false" should insert paragraph at typing Enter`,
init: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><p>a[b]c</p></div></div>`,
run: function () {
return utils.sendEnterKey();
},
expected: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><p>a</p><p>c</p></div></div>`,
},
{
desc: `styling start boundary of contenteditable="${
trueOrEmpty
}" in contenteditable="plaintext-only" should not apply the style`,
init: `A[B<div contenteditable="${trueOrEmpty}">C]D</div>EF`,
run: function () {
document.execCommand("bold");
},
expected: `AB<div contenteditable="${trueOrEmpty}">CD</div>EF`,
},
{
desc: `styling end boundary of contenteditable="${
trueOrEmpty
}" in contenteditable="plaintext-only" should not apply the style`,
init: `AB<div contenteditable="${trueOrEmpty}">C[D</div>E]F`,
run: function () {
document.execCommand("bold");
},
expected: `AB<div contenteditable="${trueOrEmpty}">CD</div>EF`,
},
{
desc: `even after moving selection into contenteditable="${
trueOrEmpty
}" in contenteditable="plaintext-only" and contenteditable="false" from parent editing host should support style edit`,
init: `A[]B<div contenteditable="false"><div contenteditable="${trueOrEmpty}">CD</div></div>EF`,
run: function () {
getSelection().selectAllChildren(editingHost.querySelector(`div[contenteditable="${trueOrEmpty}"]`));
document.execCommand("bold");
},
expected: `AB<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><b>CD</b></div></div>EF`,
},
]) {
promise_test(async () => {
editingHost.setAttribute("contenteditable", "plaintext-only");
utils.setupEditingHost(data.init);
if (explicitlySetFocus) {
editingHost.querySelector(`[contenteditable='${trueOrEmpty}']`).focus();
}
await data.run();
assert_equals(editingHost.outerHTML, `<div contenteditable="plaintext-only">${data.expected}</div>`);
}, data.desc + (explicitlySetFocus ? " (explicitly setting focus to the nested one)" : ""));
}
}
}
}, {once: true});
</script>
</head>
<body></body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="timeout" content="long">
<title>Delete editor in a shadow</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", () => {
promise_test(async () => {
const div = document.createElement("div");
div.contentEditable = "true";
div.innerHTML = "<b>abc</b>";
document.body.appendChild(div);
await test_driver.click(div);
getSelection().selectAllChildren(div);
await (new EditorTestUtils(div)).sendCopyShortcutKey();
assert_true(true);
}, `Initializing the clipboard with <b>abc</b>...`);

promise_test(async () => {
const editingHost = document.createElement("div");
document.body.appendChild(editingHost);
editingHost.focus();
const utils = new EditorTestUtils(editingHost);
let lastBeforeInputEvent;
editingHost.addEventListener("beforeinput", event => lastBeforeInputEvent = event);
for (const data of [
{
desc: `pasting in contenteditable="plaintext-only" in contenteditable="true"`,
init: `<div contenteditable="plaintext-only">[ABC]</div>`,
expected: `<div contenteditable="plaintext-only"><b>abc</b></div>`
},
{
desc: `pasting in contenteditable="plaintext-only" in contenteditable="true" and contenteditable="false"`,
init: `<div contenteditable="false"><div contenteditable="plaintext-only">[ABC]</div></div>`,
expected: `<div contenteditable="false"><div contenteditable="plaintext-only">abc</div></div>`,
},
]) {
promise_test(async t => {
editingHost.setAttribute("contenteditable", "true");
utils.setupEditingHost(data.init);
lastBeforeInputEvent = undefined;
await utils.sendPasteShortcutKey();
test(() => {
assert_equals(
lastBeforeInputEvent?.inputType,
"insertFromPaste",
"beforeinput.inputType should be insertFromPaste"
);
assert_equals(lastBeforeInputEvent?.data, null, "beforeinput.data should be null");
assert_true(
String(lastBeforeInputEvent?.dataTransfer?.getData("text/html")).includes("<b>abc</b>"),
"beforeinput.dataTransfer should have the styled text as text/html"
);
}, `${t.name}: beforeinput`);
test(() => {
assert_equals(editingHost.outerHTML, `<div contenteditable="true">${data.expected}</div>`);
}, `${t.name}: innerHTML`);
}, data.desc);
}
for (const data of [
{
desc: `pasting in contenteditable="true" in contenteditable="plaintext-only"`,
init: `<div contenteditable="true">[ABC]</div>`,
expected: `<div contenteditable="true">abc</div>`
},
{
desc: `pasting in contenteditable="true" in contenteditable="plaintext-only" and contenteditable="false"`,
init: `<div contenteditable="false"><div contenteditable="true">[ABC]</div></div>`,
expected: `<div contenteditable="false"><div contenteditable="true"><b>abc</b></div></div>`,
},
]) {
promise_test(async t => {
editingHost.setAttribute("contenteditable", "plaintext-only");
utils.setupEditingHost(data.init);
lastBeforeInputEvent = undefined;
await utils.sendPasteShortcutKey();
test(() => {
assert_equals(
lastBeforeInputEvent?.inputType,
"insertFromPaste",
"beforeinput.inputType should be insertFromPaste"
);
assert_equals(lastBeforeInputEvent?.data, null, "beforeinput.data should be null");
assert_true(
String(lastBeforeInputEvent?.dataTransfer?.getData("text/html")).includes("<b>abc</b>"),
"beforeinput.dataTransfer should have the styled text as text/html"
);
}, `${t.name}: beforeinput`);
test(() => {
assert_equals(editingHost.outerHTML, `<div contenteditable="plaintext-only">${data.expected}</div>`);
}, `${t.name}: innerHTML`);
}, data.desc);
}
}, "The result should depend on the outermost editing host in the innermost non-editable element whether pasting with or without format");
}, {once: true});
</script>
</head>
<body></body>
</html>
44 changes: 44 additions & 0 deletions editing/plaintext-only/plaintext-only-in-designMode.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="timeout" content="long">
<title>Delete editor in a shadow</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";

document.designMode = "on";

addEventListener("load", () => {
const div = document.querySelector("div[contenteditable=plaintext-only]");
const utils = new EditorTestUtils(div);
test(() => {
utils.setupEditingHost("[ABC]");
document.execCommand("bold");
assert_equals(div.outerHTML, `<div contenteditable="plaintext-only"><b>ABC</b></div>`);
}, "contenteditable=plaintext-only in the design mode should not prevent the style edit");

test(() => {
utils.setupEditingHost("A[B]C");
document.execCommand("insertHTML", false, "<b>b</b>");
assert_equals(div.outerHTML, `<div contenteditable="plaintext-only">A<b>b</b>C</div>`);
}, "contenteditable=plaintext-only in the design mode should not suppress style of inserting HTML");

promise_test(async () => {
utils.setupEditingHost("<p>A[]B</p>");
await utils.sendEnterKey();
assert_equals(div.outerHTML, `<div contenteditable="plaintext-only"><p>A</p><p>B</p></div>`);
}, "contenteditable=plaintext-only in the design mode should not cause inserting line break at typing Enter");
}, {once: true});
</script>
</head>
<body>
<div contenteditable="plaintext-only"></div>
</body>
</html>