tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Meditor/libeditor/HTMLEditor.cpp | 16++++++++++++++++
Meditor/libeditor/tests/mochitest.toml | 3+++
Aeditor/libeditor/tests/test_mailcite_keep_preceding_br_after_insert.html | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 &lt;br&gt; of a mailcite which is a blocked &lt;span&gt;</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}">&gt; 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}">&gt; 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}">&gt; 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>