commit 1eaf3a5aecce35d1cf9f519c4302a34f892241ba
parent 315371a4b053a73704bda05c728402c960f7285f
Author: Edgar Chen <echen@mozilla.com>
Date: Mon, 10 Nov 2025 20:21:45 +0000
Bug 1650720 - Part 1: Add plaintext serializer tests for linebreak; r=masayuki
Differential Revision: https://phabricator.services.mozilla.com/D270276
Diffstat:
3 files changed, 231 insertions(+), 0 deletions(-)
diff --git a/dom/serializers/tests/mochitest/mochitest.toml b/dom/serializers/tests/mochitest/mochitest.toml
@@ -123,3 +123,7 @@ skip-if = [
["test_htmlcopyencoder.html"]
["test_htmlcopyencoder.xhtml"]
+
+["test_plaintext_linebreak.html"]
+
+["test_plaintext_linebreak_compress.html"]
diff --git a/dom/serializers/tests/mochitest/test_plaintext_linebreak.html b/dom/serializers/tests/mochitest/test_plaintext_linebreak.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test line breaks for plaintext serializer</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1650720">Mozilla Bug 1650720</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="preformatted"></pre>
+<div id="container"></div>
+<script>
+
+const de = SpecialPowers.Ci.nsIDocumentEncoder;
+const platformLineBreak = navigator.platform.indexOf("Win") == 0 ? "\r\n" : "\n";
+const CASES = [
+ "\r",
+ "\n",
+ "\r\r",
+ "\r\n",
+ "\n\r",
+ "\n\n",
+ "\r\r\r",
+ "\r\r\n",
+ "\r\n\r",
+ "\r\n\n",
+ "\n\r\r",
+ "\n\r\n",
+ "\n\n\r",
+ "\n\n\n",
+ "\r \n",
+ "\n \r",
+];
+
+function convertLineBreaksForTestResult(aText) {
+ return aText.replace(/\r?\n|\r(?!\n)/g, platformLineBreak);
+}
+
+function selectAndEncode(aElement) {
+ // Select the element.
+ const selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(aElement);
+
+ const encoder = SpecialPowers.Cu.createHTMLCopyEncoder();
+ encoder.init(document, "text/plain", de.OutputSelectionOnly);
+ encoder.setSelection(selection);
+ return encoder.encodeToString();
+}
+
+CASES.forEach((lineBreaks) => {
+ const text = `${lineBreaks}First${lineBreaks}Second${lineBreaks}`;
+
+ add_task(async function test_pre_text() {
+ info(`Testing line breaks: ${JSON.stringify(lineBreaks)}`);
+
+ const pre = document.getElementById("preformatted");
+ pre.textContent = text;
+
+ is(selectAndEncode(pre), text.replace(/\r|\n/g, platformLineBreak),
+ `Encoded data for line breaks: ${JSON.stringify(lineBreaks)}`);
+ });
+
+ add_task(async function test_pre_img_alt() {
+ info(`Testing line breaks: ${JSON.stringify(lineBreaks)}`);
+
+ const pre = document.getElementById("preformatted");
+ const img = document.createElement("img");
+ img.alt = text;
+ pre.replaceChildren(img);
+
+ is(selectAndEncode(pre), convertLineBreaksForTestResult(text),
+ `Encoded data for line breaks: ${JSON.stringify(lineBreaks)}`);
+ });
+
+ add_task(async function test_pre_img_title() {
+ info(`Testing line breaks: ${JSON.stringify(lineBreaks)}`);
+
+ const pre = document.getElementById("preformatted");
+ const img = document.createElement("img");
+ img.title = text;
+ pre.replaceChildren(img);
+
+ is(selectAndEncode(pre), ` [${convertLineBreaksForTestResult(text)}] `,
+ `Encoded data for line breaks: ${JSON.stringify(lineBreaks)}`);
+ });
+
+ add_task(async function test_div_text() {
+ info(`Testing line breaks: ${JSON.stringify(lineBreaks)}`);
+
+ const div = document.getElementById("container");
+ div.textContent = text;
+
+ is(selectAndEncode(div), ` First Second `,
+ `Encoded data for line breaks: ${JSON.stringify(lineBreaks)}`);
+ });
+
+ add_task(async function test_div_img_alt() {
+ info(`Testing line breaks: ${JSON.stringify(lineBreaks)}`);
+
+ const div = document.getElementById("container");
+ const img = document.createElement("img");
+ img.alt = text;
+ div.replaceChildren(img);
+
+ // Our plain text serializer keep the first CR/LF.
+ is(selectAndEncode(div), `${lineBreaks[0]}First Second `,
+ `Encoded data for line breaks: ${JSON.stringify(lineBreaks)}`);
+ });
+
+ add_task(async function test_div_img_title() {
+ info(`Testing line breaks: ${JSON.stringify(lineBreaks)}`);
+
+ const div = document.getElementById("container");
+ const img = document.createElement("img");
+ img.title = text;
+ div.replaceChildren(img);
+
+ is(selectAndEncode(div), ` [ First Second ] `,
+ `Encoded data for line breaks: ${JSON.stringify(lineBreaks)}`);
+ });
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/serializers/tests/mochitest/test_plaintext_linebreak_compress.html b/dom/serializers/tests/mochitest/test_plaintext_linebreak_compress.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test line breaks for plaintext serializer</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1650720">Mozilla Bug 1650720</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="preformatted"></pre>
+<div id="container"></div>
+<script>
+
+const de = SpecialPowers.Ci.nsIDocumentEncoder;
+const platformLineBreak = navigator.platform.indexOf("Win") == 0 ? "\r\n" : "\n";
+// XXX: I'm not sure if the line break compression behavior entirely makes sense,
+// but this is what we have for now.
+const TESTS = [
+ {innerHTMLs: [`<pre>First <div>Second </div></pre><h3>Third</h3>`,
+ `<pre>First <div>Second </div></pre>\n<h3>Third</h3>`,
+ `<pre>First <div>Second </div></pre>\n\n<h3>Third</h3>`,
+ `<pre>First <div>Second </div></pre>\n\n\n<h3>Third</h3>`,
+ `<pre>First <div>Second </div></pre>\n\n\n\n<h3>Third</h3>`,
+ `<pre>First <div>Second </div>\n</pre><h3>Third</h3>`,
+ `<pre>First <div>Second </div>\n</pre>\n<h3>Third</h3>`,
+ `<pre>First <div>Second </div>\n</pre>\n\n<h3>Third</h3>`,
+ `<pre>First <div>Second </div>\n</pre>\n\n\n<h3>Third</h3>`,
+ `<pre>First <div>Second </div>\n</pre>\n\n\n\n<h3>Third</h3>`,
+ `<pre>First \n<div>Second </div></pre><h3>Third</h3>`],
+ expectedResult: `First ${platformLineBreak}Second ${platformLineBreak}${platformLineBreak}Third`},
+ {innerHTMLs: [`<pre>First <pre>Second </pre></pre>Third`,
+ `<pre>First <pre>Second </pre></pre>\nThird`,
+ `<pre>First <pre>Second </pre></pre>\n\nThird`,
+ `<pre>First <pre>Second </pre></pre>\n\n\nThird`,
+ `<pre>First <pre>Second </pre></pre>\n\n\n\nThird`,
+ `<pre>First \n<pre>Second </pre></pre>Third`,
+ `<pre>First \n<pre>Second </pre></pre>\nThird`,
+ // XXX: Not sure below two cases should have another line break
+ // before the "Second". However, <pre> might have some special
+ // rules against normal elements with white-space:pre.
+ `<pre>First \n\n<pre>Second </pre></pre>Third`,
+ `<pre>First \n\n<pre>Second </pre></pre>\nThird`],
+ expectedResult: `First ${platformLineBreak}${platformLineBreak}Second ${platformLineBreak}${platformLineBreak}Third`},
+ {innerHTMLs: [`<pre>First </pre><pre>Second</pre>`,
+ `<pre>First </pre>\n<pre>Second</pre>`,
+ `<pre>First </pre>\n\n<pre>Second</pre>`,
+ `<pre>First </pre>\n\n\n<pre>Second</pre>`,
+ `<pre>First </pre>\n\n\n\n<pre>Second</pre>`,
+ `<pre>First \n</pre><pre>Second</pre>`,
+ `<pre>First \n</pre>\n<pre>Second</pre>`,
+ `<pre>First \n</pre>\n\n<pre>Second</pre>`,
+ `<pre>First \n</pre>\n\n\n<pre>Second</pre>`,
+ `<pre>First \n</pre>\n\n\n\n<pre>Second</pre>`,
+ // XXX: Not sure below five cases should have another line break
+ // before the "Second". However, <pre> might have some special
+ // rules against normal elements with white-space:pre.
+ `<pre>First \n\n</pre><pre>Second</pre>`,
+ `<pre>First \n\n</pre>\n<pre>Second</pre>`,
+ `<pre>First \n\n</pre>\n\n<pre>Second</pre>`,
+ `<pre>First \n\n</pre>\n\n\n<pre>Second</pre>`,
+ `<pre>First \n\n</pre>\n\n\n\n<pre>Second</pre>`],
+ expectedResult: `First ${platformLineBreak}${platformLineBreak}Second`},
+];
+
+function selectAndEncode(aElement) {
+ // Select the element.
+ const selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.selectAllChildren(aElement);
+
+ const encoder = SpecialPowers.Cu.createHTMLCopyEncoder();
+ encoder.init(document, "text/plain", de.OutputSelectionOnly);
+ encoder.setSelection(selection);
+ return encoder.encodeToString();
+}
+
+TESTS.forEach((test) => {
+ test.innerHTMLs.forEach((innerHTML) => {
+ add_task(async function test_line_break_compress() {
+ info(`Testing ${JSON.stringify(innerHTML)}`);
+
+ const div = document.getElementById("container");
+ div.innerHTML = innerHTML;
+
+ is(selectAndEncode(div), test.expectedResult,
+ `Encoded data for ${JSON.stringify(innerHTML)}`);
+ });
+ });
+});
+
+</script>
+</body>
+</html>