<!doctype html> <html> <head> <meta chareset="utf-8"> <meta name="timeout" content="long"> <meta name="variant" content="?designMode=off&method=backspace"> <meta name="variant" content="?designMode=off&method=forwarddelete"> <meta name="variant" content="?designMode=on&method=backspace"> <meta name="variant" content="?designMode=on&method=forwarddelete"> <title>Join paragraphs in the head element</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> </head> <body> <iframe srcdoc=""></iframe> <script> "use strict"; const searchParams = new URLSearchParams(document.location.search); const testingBackspace = searchParams.get("method") == "backspace"; const commandName = testingBackspace ? "delete" : "forwarddelete"; const testingDesignMode = searchParams.get("designMode") == "on"; const iframe = document.querySelector("iframe"); const minimumSrcDoc = "<html>" + '<head style="display:block">' + "<title>iframe</title>" + "<script src='/resources/testdriver.js'></" + "script>" + "<script src='/resources/testdriver-vendor.js'></" + "script>" + "<script src='/resources/testdriver-actions.js'></" + "script>" + "</head>" + "<body><br></body>" + "</html>"; async function initializeAndWaitForLoad(iframeElement, srcDocValue) { const waitForLoad = new Promise( resolve => iframeElement.addEventListener("load", resolve, {once: true}) ); iframeElement.srcdoc = srcDocValue; await waitForLoad; if (testingDesignMode) { iframeElement.contentDocument.designMode = "on"; } else { iframeElement.contentDocument.documentElement.setAttribute("contenteditable", ""); } iframeElement.contentWindow.focus(); iframeElement.contentDocument.execCommand("defaultParagraphSeparator", false, "div"); } function removeResourceScriptElements(node) { node.querySelectorAll("script").forEach( element => { if (element.getAttribute("src")?.startsWith("/resources")) { element.remove() } } ); } // DO NOT USE multi-line comment in this file, then, you can comment out // unnecessary tests when you need to attach the browser with a debugger. // For backward compatibility, normal block elements in <head> should be // joined by deletion. promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const div1 = childDoc.createElement("div"); div1.innerHTML = "abc"; const div2 = childDoc.createElement("div"); div2.innerHTML = "def"; childDoc.head.appendChild(div1); childDoc.head.appendChild(div2); // Now: <head><title>...</title><div>abc</div><div>def</div></head>... childDoc.getSelection().collapse( testingBackspace ? div2.firstChild : div1.firstChild, testingBackspace ? 0 : div1.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); assert_in_array( childDoc.documentElement.innerHTML, [ '<head><title>iframe</title><div>abcdef</div></head><body><br></body>', '<head><title>iframe</title><div>abcdef<br></div></head><body><br></body>', ], "The <div> elements should be merged" ); assert_equals( div1.isConnected ^ div2.isConnected, 1, "One <div> element should be removed, and the other should stay" ); }, `${commandName} in <div> elements in <head> should join them`); // The following void elements shouldn't be deleted for avoiding various // affection to the document. for (const tag of ["meta", "title", "style", "script", "link", "base", "template"]) { promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const div1 = childDoc.createElement("div"); div1.innerHTML = "abc"; const div2 = childDoc.createElement("div"); div2.innerHTML = "def"; const element = childDoc.createElement(tag); childDoc.head.appendChild(div1); childDoc.head.appendChild(element); childDoc.head.appendChild(div2); // Now: <head><title>...</title><div>abc</div><tag/><div>def</div></head>... childDoc.getSelection().collapse( testingBackspace ? div2.firstChild : div1.firstChild, testingBackspace ? 0 : div1.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); if (["title", "style", "script", "template"].includes(tag)) { assert_in_array( childDoc.documentElement.innerHTML, [ `<head><title>iframe</title><div>abcdef</div><${tag}></${tag}></head><body><br></body>`, `<head><title>iframe</title><div>abcdef<br></div><${tag}></${tag}></head><body><br></body>`, `<head><title>iframe</title><${tag}></${tag}><div>abcdef</div></head><body><br></body>`, `<head><title>iframe</title><${tag}></${tag}><div>abcdef<br></div></head><body><br></body>`, ], `The <div> elements should be merged without deleting <${tag}>` ); } else { assert_in_array( childDoc.documentElement.innerHTML, [ `<head><title>iframe</title><div>abcdef</div><${tag}></head><body><br></body>`, `<head><title>iframe</title><div>abcdef<br></div><${tag}></head><body><br></body>`, `<head><title>iframe</title><${tag}><div>abcdef</div></head><body><br></body>`, `<head><title>iframe</title><${tag}><div>abcdef<br></div></head><body><br></body>`, ], `The <div> elements should be merged without deleting <${tag}>` ); } }, `${commandName} around invisible <${tag}> should not delete it at joining paragraphs`); } // Visible <script>, <style>, <title> elements shouldn't be joined promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const script1 = childDoc.createElement("script"); script1.innerHTML = "// abc"; script1.setAttribute("style", "display:block"); const script2 = childDoc.createElement("script"); script2.innerHTML = "// def"; script2.setAttribute("style", "display:block"); childDoc.head.appendChild(script1); childDoc.head.appendChild(script2); // Now: <head><title>...</title><script>// abc</ script><script>// def</ script></head>... childDoc.getSelection().collapse( testingBackspace ? script2.firstChild : script1.firstChild, testingBackspace ? 0 : script1.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); script1.removeAttribute("style"); script2.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><script>// abc</" + "script><script>// def</" + "script></head><body><br></body>", "Visible <script> elements shouldn't be merged" ); }, `${commandName} in visible <script> elements in <head> should not join them`); promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const style1 = childDoc.createElement("style"); style1.innerHTML = "abc"; style1.setAttribute("style", "display:block"); const style2 = childDoc.createElement("style"); style2.innerHTML = "def"; style2.setAttribute("style", "display:block"); childDoc.head.appendChild(style1); childDoc.head.appendChild(style2); // Now: <head><title>...</title><style>abc</style><style>def</style></head>... childDoc.getSelection().collapse( testingBackspace ? style2.firstChild : style1.firstChild, testingBackspace ? 0 : style1.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); style1.removeAttribute("style"); style2.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><style>abc</style><style>def</style></head><body><br></body>", "Visible <style> elements shouldn't be merged" ); }, `${commandName} in visible <style> elements in <head> should not join them`); promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const title1 = childDoc.createElement("title"); title1.innerHTML = "abc"; title1.setAttribute("style", "display:block"); const title2 = childDoc.createElement("title"); title2.innerHTML = "def"; title2.setAttribute("style", "display:block"); childDoc.head.appendChild(title1); childDoc.head.appendChild(title2); // Now: <head><title>...</title><title>abc</title><title>def</title></head>... childDoc.getSelection().collapse( testingBackspace ? title2.firstChild : title1.firstChild, testingBackspace ? 0 : title1.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); title1.removeAttribute("style"); title2.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><title>abc</title><title>def</title></head><body><br></body>", "Visible <title> elements shouldn't be merged" ); }, `${commandName} in visible <title> elements in <head> should not join them`); // Visible <script>, <style>, <title> shouldn't be joined with following <div> promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const script = childDoc.createElement("script"); script.innerHTML = "// abc"; script.setAttribute("style", "display:block"); const div = childDoc.createElement("div"); div.innerHTML = "// def"; childDoc.head.appendChild(script); childDoc.head.appendChild(div); // Now: <head><title>...</title><script>// abc</ script><div>// def</div></head>... childDoc.getSelection().collapse( testingBackspace ? div.firstChild : script.firstChild, testingBackspace ? 0 : script.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); script.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><script>// abc</" + "script><div>// def</div></head><body><br></body>", "Visible <script> and <div> shouldn't be merged" ); }, `${commandName} at boundary of <script> and <div> in <head> should not join them`); promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const style = childDoc.createElement("style"); style.innerHTML = "abc"; style.setAttribute("style", "display:block"); const div = childDoc.createElement("div"); div.innerHTML = "def"; childDoc.head.appendChild(style); childDoc.head.appendChild(div); // Now: <head><title>...</title><style>abc</style><div>def</div></head>... childDoc.getSelection().collapse( testingBackspace ? div.firstChild : style.firstChild, testingBackspace ? 0 : style.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); style.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><style>abc</style><div>def</div></head><body><br></body>", "Visible <style> and <div> shouldn't be merged" ); }, `${commandName} at boundary of <style> and <div> in <head> should not join them`); promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const title = childDoc.createElement("title"); title.innerHTML = "abc"; title.setAttribute("title", "display:block"); const div = childDoc.createElement("div"); div.innerHTML = "def"; childDoc.head.appendChild(title); childDoc.head.appendChild(div); // Now: <head><title>...</title><title>abc</title><div>def</div></head>... childDoc.getSelection().collapse( testingBackspace ? div.firstChild : title.firstChild, testingBackspace ? 0 : title.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); title.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><title>abc</title><div>def</div></head><body><br></body>", "Visible <title> and <div> shouldn't be merged" ); }, `${commandName} at boundary of <title> and <div> in <head> should not join them`); // Visible <script>, <style>, <title> shouldn't be joined with preceding <div> promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const div = childDoc.createElement("div"); div.innerHTML = "// abc"; const script = childDoc.createElement("script"); script.innerHTML = "// def"; script.setAttribute("style", "display:block"); childDoc.head.appendChild(div); childDoc.head.appendChild(script); // Now: <head><title>...</title><div>// abc</div><script>// def</ script></head>... childDoc.getSelection().collapse( testingBackspace ? script.firstChild : div.firstChild, testingBackspace ? 0 : div.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); script.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><div>// abc</div><script>// def</" + "script></head><body><br></body>", "<div> and visible <script> shouldn't be merged" ); }, `${commandName} at boundary of <div> and <script> in <head> should not join them`); promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const div = childDoc.createElement("div"); div.innerHTML = "abc"; const style = childDoc.createElement("style"); style.innerHTML = "def"; style.setAttribute("style", "display:block"); childDoc.head.appendChild(div); childDoc.head.appendChild(style); // Now: <head><title>...</title><div>abc</div><style>def</style></head>... childDoc.getSelection().collapse( testingBackspace ? style.firstChild : div.firstChild, testingBackspace ? 0 : div.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); style.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><div>abc</div><style>def</style></head><body><br></body>", "<div> and visible <style> shouldn't be merged" ); }, `${commandName} at boundary of <div> and <style> in <head> should not join them`); promise_test(async () => { await initializeAndWaitForLoad(iframe, minimumSrcDoc); const childDoc = iframe.contentDocument; const utils = new EditorTestUtils(childDoc.documentElement); const div = childDoc.createElement("div"); div.innerHTML = "abc"; const title = childDoc.createElement("title"); title.innerHTML = "def"; title.setAttribute("style", "display:block"); childDoc.head.appendChild(div); childDoc.head.appendChild(title); // Now: <head><title>...</title><div>abc</div><title>def</title></head>... childDoc.getSelection().collapse( testingBackspace ? title.firstChild : div.firstChild, testingBackspace ? 0 : div.firstChild.length ); await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); removeResourceScriptElements(childDoc); childDoc.head.removeAttribute("style"); title.removeAttribute("style"); assert_equals( childDoc.documentElement.innerHTML, "<head><title>iframe</title><div>abc</div><title>def</title></head><body><br></body>", "<div> and visible <title> shouldn't be merged" ); }, `${commandName} at boundary of <div> and <title> in <head> should not join them`); </script> </body> </html>