commit a50c3aba5d08153852bcbb996d637a60de52cd2d
parent 61f4fb3dadd7d5c24ecf8101426f927e5832d5a0
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date: Thu, 2 Oct 2025 01:46:05 +0000
Bug 1989861 - Make `HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak` consider a preceding `<br>` of a mailcite is necessary r=m_kato
The serializer needs to make each mailcite starts from head of a line.
However, mailcite may be a blocked `<span>`. So, its preceding `<br>`
is not required from the HTML point of view, but we need to preserve
it for the serializer.
If we need this hack in some other places, we should make
`HTMLEditUtils::GetFollowingUnnecessaryLineBreak()` aware of this
special handling in a follow up bug.
Differential Revision: https://phabricator.services.mozilla.com/D266751
Diffstat:
3 files changed, 92 insertions(+), 0 deletions(-)
diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp
@@ -4354,6 +4354,22 @@ nsresult HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak(
return NS_OK;
}
if (unnecessaryLineBreak->IsHTMLBRElement()) {
+ // If the found unnecessary <br> is a preceding one of a mailcite which is a
+ // <span> styled as block, we need to preserve the <br> element for the
+ // serializer to cause a line break before the mailcite.
+ if (IsPlaintextMailComposer()) {
+ const WSScanResult nextThing =
+ WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+ WSRunScanner::Scan::All,
+ unnecessaryLineBreak->After<EditorRawDOMPoint>(),
+ BlockInlineCheck::UseComputedDisplayOutsideStyle);
+ if (nextThing.ReachedOtherBlockElement() &&
+ HTMLEditUtils::IsMailCiteElement(*nextThing.ElementPtr()) &&
+ HTMLEditUtils::IsInlineContent(
+ *nextThing.ElementPtr(), BlockInlineCheck::UseHTMLDefaultStyle)) {
+ return NS_OK;
+ }
+ }
// If the invisible break is a placeholder of ancestor inline elements, we
// should not delete it to allow users to insert text with the format
// specified by them.
diff --git a/editor/libeditor/tests/mochitest.toml b/editor/libeditor/tests/mochitest.toml
@@ -480,6 +480,9 @@ skip-if = [
["test_mailcite_backspace_at_end_of_inline_reply.html"]
skip-if = ["xorigin"] # Testing internal API for comm-central
+["test_mailcite_keep_preceding_br_after_insert.html"]
+skip-if = ["xorigin"] # Testing internal API for comm-central
+
["test_mailcite_keep_trailing_br_after_delete.html"]
skip-if = ["xorigin"] # Testing internal API for comm-central
diff --git a/editor/libeditor/tests/test_mailcite_keep_preceding_br_after_insert.html b/editor/libeditor/tests/test_mailcite_keep_preceding_br_after_insert.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Preceding <br> of a mailcite which is a blocked <span></title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ const iframe = document.querySelector("iframe");
+ await new Promise(resolve => {
+ if (iframe.contentDocument?.readyState == "complete") {
+ resolve();
+ return;
+ }
+ iframe.addEventListener("load", resolve, {once: true});
+ });
+
+
+ const win = iframe.contentWindow;
+ getEditor(win).flags |=
+ SpecialPowers.Ci.nsIEditor.eEditorMailMask | SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
+ const doc = iframe.contentDocument;
+ const mailEditor = getEditorMailSupport(win);
+ win.focus();
+ doc.body.focus();
+ const mailciteStyle = "white-space: pre-wrap; display: block; width: 98vw;";
+ const mailcite = SpecialPowers.unwrap(
+ mailEditor.insertAsCitedQuotation("This is a mail cite", "", false)
+ );
+ is(
+ doc.body.innerHTML,
+ `<span style="${mailciteStyle}">> This is a mail cite<br><br></span><br><br>`,
+ "nsIEditorMailSupport.insertAsCitedQuotation() should insert a mailcite span"
+ );
+ // Split paragraph between "a " and "mail cite".
+ win.getSelection().collapse(mailcite.firstChild, "> This is a ".length);
+ doc.execCommand("insertParagraph");
+ is(
+ doc.body.innerHTML,
+ `<span style="${mailciteStyle}">> This is a <br></span><br><span style="${mailciteStyle}">mail cite<br><br></span><br><br>`,
+ "insertParagraph in a mailcite should split the mailcite and insert an empty line"
+ );
+ // Then, type a character. The <br> before the latter mailcite should be
+ // preserved for the serializer to insert a line break before the latter
+ // mailcite.
+ doc.execCommand("insertText", false, "X");
+ is(
+ doc.body.innerHTML,
+ `<span style="${mailciteStyle}">> This is a <br></span>X<br><span style="${mailciteStyle}">mail cite<br><br></span><br><br>`,
+ "Typing text into the empty line should preserve the preceding <br> of the latter mailcite"
+ );
+
+ SimpleTest.finish();
+});
+
+function getEditor(aWindow) {
+ const editingSession = SpecialPowers.wrap(aWindow).docShell.editingSession;
+ return editingSession.getEditorForWindow(aWindow);
+}
+
+function getEditorMailSupport(aWindow) {
+ return getEditor(aWindow).QueryInterface(SpecialPowers.Ci.nsIEditorMailSupport);
+}
+</script>
+</head>
+<body>
+ <iframe srcdoc="<body contenteditable><br><br></body>"></iframe>
+</body>
+</html>