commit 62c170d960bae034b8527ab3d08420c346986d74
parent ddcc5e3d24ac27707e57c3a38833de2bcb0e90dc
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Fri, 3 Oct 2025 17:24:10 +0000
Bug 1568313 - Fix <textarea wrap=hard>. r=smaug
Move the handling only to form submission which is where it belongs.
Differential Revision: https://phabricator.services.mozilla.com/D267385
Diffstat:
10 files changed, 186 insertions(+), 192 deletions(-)
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
@@ -1667,7 +1667,7 @@ void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
case VALUE_MODE_VALUE:
if (IsSingleLineTextControl(false)) {
if (mInputData.mState) {
- mInputData.mState->GetValue(aValue, true, /* aForDisplay = */ false);
+ mInputData.mState->GetValue(aValue, /* aForDisplay = */ false);
} else {
// Value hasn't been set yet.
aValue.Truncate();
@@ -7065,7 +7065,7 @@ bool HTMLInputElement::ValueChanged() const { return mValueChanged; }
void HTMLInputElement::GetTextEditorValue(nsAString& aValue) const {
if (TextControlState* state = GetEditorState()) {
- state->GetValue(aValue, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
+ state->GetValue(aValue, /* aForDisplay = */ true);
}
}
diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp
@@ -29,6 +29,7 @@
#include "nsIMutationObserver.h"
#include "nsLayoutUtils.h"
#include "nsLinebreakConverter.h"
+#include "nsPlainTextSerializer.h"
#include "nsPresContext.h"
#include "nsReadableUtils.h"
#include "nsStyleConsts.h"
@@ -128,6 +129,26 @@ void HTMLTextAreaElement::SelectAll() {
}
}
+enum class Wrap {
+ Off,
+ Hard,
+ Soft,
+};
+
+static Wrap WrapValue(const HTMLTextAreaElement& aElement) {
+ static mozilla::dom::Element::AttrValuesArray strings[] = {
+ nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr};
+ switch (aElement.FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::wrap, strings,
+ eIgnoreCase)) {
+ case 0:
+ return Wrap::Hard;
+ case 1:
+ return Wrap::Off;
+ default:
+ return Wrap::Soft;
+ }
+}
+
bool HTMLTextAreaElement::IsHTMLFocusable(IsFocusableFlags aFlags,
bool* aIsFocusable,
int32_t* aTabIndex) {
@@ -148,14 +169,13 @@ void HTMLTextAreaElement::GetType(nsAString& aType) {
}
void HTMLTextAreaElement::GetValue(nsAString& aValue) {
- GetValueInternal(aValue, true);
+ GetValueInternal(aValue);
MOZ_ASSERT(aValue.FindChar(static_cast<char16_t>('\r')) == -1);
}
-void HTMLTextAreaElement::GetValueInternal(nsAString& aValue,
- bool aIgnoreWrap) const {
+void HTMLTextAreaElement::GetValueInternal(nsAString& aValue) const {
MOZ_ASSERT(mState);
- mState->GetValue(aValue, aIgnoreWrap, /* aForDisplay = */ true);
+ mState->GetValue(aValue, /* aForDisplay = */ true);
}
nsIEditor* HTMLTextAreaElement::GetEditorForBindings() {
@@ -254,7 +274,7 @@ void HTMLTextAreaElement::SetValue(const nsAString& aValue,
// NOTE: this is currently quite expensive work (too much string
// manipulation). We should probably optimize that.
nsAutoString currentValue;
- GetValueInternal(currentValue, true);
+ GetValueInternal(currentValue);
nsresult rv = SetValueInternal(
aValue,
@@ -266,7 +286,7 @@ void HTMLTextAreaElement::SetValue(const nsAString& aValue,
}
if (mFocusedValue.Equals(currentValue)) {
- GetValueInternal(mFocusedValue, true);
+ GetValueInternal(mFocusedValue);
}
}
@@ -448,7 +468,7 @@ nsresult HTMLTextAreaElement::PreHandleEvent(EventChainVisitor& aVisitor) {
void HTMLTextAreaElement::FireChangeEventIfNeeded() {
nsString value;
- GetValueInternal(value, true);
+ GetValueInternal(value);
// NOTE(emilio): This is not quite on the spec, but matches <input>, see
// https://github.com/whatwg/html/issues/10011 and
@@ -472,7 +492,7 @@ nsresult HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
mHandlingSelect = false;
}
if (aVisitor.mEvent->mMessage == eFocus) {
- GetValueInternal(mFocusedValue, true);
+ GetValueInternal(mFocusedValue);
}
return NS_OK;
}
@@ -606,7 +626,7 @@ void HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement,
}
void HTMLTextAreaElement::GetValueFromSetRangeText(nsAString& aValue) {
- GetValueInternal(aValue, false);
+ GetValueInternal(aValue);
}
nsresult HTMLTextAreaElement::SetValueFromSetRangeText(
@@ -639,15 +659,21 @@ HTMLTextAreaElement::SubmitNamesValues(FormData* aFormData) {
return NS_OK;
}
- //
// Get the value
- //
nsAutoString value;
- GetValueInternal(value, false);
+ GetValueInternal(value);
+ if (WrapValue(*this) == Wrap::Hard) {
+ if (auto cols = GetWrapCols(); cols > 0) {
+ int32_t flags = nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputPreformatted |
+ nsIDocumentEncoder::OutputPersistNBSP |
+ nsIDocumentEncoder::OutputBodyOnly |
+ nsIDocumentEncoder::OutputWrap;
+ nsPlainTextSerializer::HardWrapString(value, cols, flags);
+ }
+ }
- //
// Submit name=value
- //
const nsresult rv = aFormData->AddNameValuePair(name, value);
if (NS_FAILED(rv)) {
return rv;
@@ -664,7 +690,7 @@ void HTMLTextAreaElement::SaveState() {
state = GetPrimaryPresState();
if (state) {
nsAutoString value;
- GetValueInternal(value, true);
+ GetValueInternal(value);
if (NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
value, nsLinebreakConverter::eLinebreakPlatform,
@@ -887,7 +913,7 @@ nsresult HTMLTextAreaElement::CopyInnerTo(Element* aDest) {
auto* dest = static_cast<HTMLTextAreaElement*>(aDest);
nsAutoString value;
- GetValueInternal(value, true);
+ GetValueInternal(value);
// SetValueInternal handles setting mValueChanged for us. dest is a fresh
// element so setting its value can't really run script.
@@ -1035,13 +1061,9 @@ Maybe<int32_t> HTMLTextAreaElement::GetCols() {
}
int32_t HTMLTextAreaElement::GetWrapCols() {
- nsHTMLTextWrap wrapProp;
- TextControlElement::GetWrapPropertyEnum(this, wrapProp);
- if (wrapProp == TextControlElement::eHTMLTextWrap_Off) {
- // do not wrap when wrap=off
+ if (WrapValue(*this) == Wrap::Off) {
return 0;
}
-
// Otherwise we just wrap at the given number of columns
return GetColsOrDefault();
}
@@ -1064,7 +1086,7 @@ bool HTMLTextAreaElement::ValueChanged() const { return mValueChanged; }
void HTMLTextAreaElement::GetTextEditorValue(nsAString& aValue) const {
MOZ_ASSERT(mState);
- mState->GetValue(aValue, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
+ mState->GetValue(aValue, /* aForDisplay = */ true);
}
void HTMLTextAreaElement::InitializeKeyboardEventListeners() {
diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h
@@ -334,11 +334,8 @@ class HTMLTextAreaElement final : public TextControlElement,
/**
* Get the value, whether it is from the content or the frame.
* @param aValue the value [out]
- * @param aIgnoreWrap whether to ignore the wrap attribute when getting the
- * value. If this is true, linebreaks will not be inserted even if
- * wrap=hard.
*/
- void GetValueInternal(nsAString& aValue, bool aIgnoreWrap) const;
+ void GetValueInternal(nsAString& aValue) const;
/**
* Setting the value.
diff --git a/dom/html/TextControlElement.h b/dom/html/TextControlElement.h
@@ -222,16 +222,6 @@ class TextControlElement : public nsGenericHTMLFormControlElementWithState {
inline static constexpr int32_t DEFAULT_ROWS_TEXTAREA = 2;
inline static constexpr int32_t DEFAULT_UNDO_CAP = 1000;
- // wrap can be one of these three values.
- typedef enum {
- eHTMLTextWrap_Off = 1, // "off"
- eHTMLTextWrap_Hard = 2, // "hard"
- eHTMLTextWrap_Soft = 3 // the default
- } nsHTMLTextWrap;
-
- static bool GetWrapPropertyEnum(nsIContent* aContent,
- nsHTMLTextWrap& aWrapProp);
-
/**
* Does the editor have a selection cache?
*
diff --git a/dom/html/TextControlState.cpp b/dom/html/TextControlState.cpp
@@ -79,34 +79,6 @@ NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
TextControlElement, nsGenericHTMLFormControlElementWithState)
/*static*/
-bool TextControlElement::GetWrapPropertyEnum(
- nsIContent* aContent, TextControlElement::nsHTMLTextWrap& aWrapProp) {
- // soft is the default; "physical" defaults to soft as well because all other
- // browsers treat it that way and there is no real reason to maintain physical
- // and virtual as separate entities if no one else does. Only hard and off
- // do anything different.
- aWrapProp = eHTMLTextWrap_Soft; // the default
-
- if (!aContent->IsHTMLElement()) {
- return false;
- }
-
- static mozilla::dom::Element::AttrValuesArray strings[] = {
- nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr};
- switch (aContent->AsElement()->FindAttrValueIn(
- kNameSpaceID_None, nsGkAtoms::wrap, strings, eIgnoreCase)) {
- case 0:
- aWrapProp = eHTMLTextWrap_Hard;
- break;
- case 1:
- aWrapProp = eHTMLTextWrap_Off;
- break;
- }
-
- return true;
-}
-
-/*static*/
already_AddRefed<TextControlElement>
TextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) {
if (!aHost) {
@@ -1613,7 +1585,7 @@ nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
// binding to the frame.
nsAutoString currentValue;
if (mTextEditor) {
- GetValue(currentValue, true, /* aForDisplay = */ false);
+ GetValue(currentValue, /* aForDisplay = */ false);
}
mBoundFrame = aFrame;
@@ -1781,7 +1753,7 @@ nsresult TextControlState::PrepareEditor(const nsAString* aValue) {
if (aValue) {
defaultValue = *aValue;
} else {
- GetValue(defaultValue, true, /* aForDisplay = */ true);
+ GetValue(defaultValue, /* aForDisplay = */ true);
}
if (!mEditorInitialized) {
@@ -2099,7 +2071,7 @@ void TextControlState::SetSelectionRange(uint32_t aStart, uint32_t aEnd,
if (!props.HasMaxLength()) {
// A clone without a dirty value flag may not have a max length yet
nsAutoString value;
- GetValue(value, false, /* aForDisplay = */ true);
+ GetValue(value, /* aForDisplay = */ true);
props.SetMaxLength(value.Length());
}
@@ -2388,7 +2360,7 @@ void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
// We need to start storing the value outside of the editor if we're not
// going to use it anymore, so retrieve it for now.
nsAutoString value;
- GetValue(value, true, /* aForDisplay = */ false);
+ GetValue(value, /* aForDisplay = */ false);
if (mRestoringSelection) {
mRestoringSelection->Revoke();
@@ -2489,8 +2461,7 @@ void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
}
}
-void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
- bool aForDisplay) const {
+void TextControlState::GetValue(nsAString& aValue, bool aForDisplay) const {
// While SetValue() is being called and requesting to commit composition to
// IME, GetValue() may be called for appending text or something. Then, we
// need to return the latest aValue of SetValue() since the value hasn't
@@ -2507,7 +2478,7 @@ void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
if (mTextEditor && mBoundFrame &&
(mEditorInitialized || !IsSingleLineTextControl())) {
- if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
+ if (!mBoundFrame->CachedValue().IsVoid()) {
aValue = mBoundFrame->CachedValue();
MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
return;
@@ -2515,19 +2486,10 @@ void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
aValue.Truncate(); // initialize out param
- uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
- nsIDocumentEncoder::OutputPreformatted |
- nsIDocumentEncoder::OutputPersistNBSP |
- nsIDocumentEncoder::OutputBodyOnly);
- if (!aIgnoreWrap) {
- TextControlElement::nsHTMLTextWrap wrapProp;
- if (mTextCtrlElement &&
- TextControlElement::GetWrapPropertyEnum(mTextCtrlElement, wrapProp) &&
- wrapProp == TextControlElement::eHTMLTextWrap_Hard) {
- flags |= nsIDocumentEncoder::OutputWrap;
- }
- }
-
+ uint32_t flags = nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputPreformatted |
+ nsIDocumentEncoder::OutputPersistNBSP |
+ nsIDocumentEncoder::OutputBodyOnly;
// What follows is a bit of a hack. The problem is that we could be in
// this method because we're being destroyed for whatever reason while
// script is executing. If that happens, editor will run with the
@@ -2540,22 +2502,13 @@ void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
// XXXbz if we could just get the textContent of our anonymous content (eg
// if plaintext editor didn't create <br> nodes all over), we wouldn't need
// this.
- // XXX If mTextEditor has not been initialized yet, ComputeTextValue()
- // anyway returns empty string. Is this always expected here?
if (mEditorInitialized) {
AutoNoJSAPI nojsapi;
-
DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
}
- // Only when the result doesn't include line breaks caused by hard-wrap,
- // mCacheValue should cache the value.
- if (!(flags & nsIDocumentEncoder::OutputWrap)) {
- mBoundFrame->CacheValue(aValue);
- } else {
- mBoundFrame->ClearCachedValue();
- }
+ mBoundFrame->CacheValue(aValue);
} else if (!mTextCtrlElement->ValueChanged() || mValue.IsVoid()) {
// Use nsString to avoid copying string buffer at setting aValue.
nsString value;
@@ -2571,7 +2524,7 @@ void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
bool TextControlState::ValueEquals(const nsAString& aValue) const {
nsAutoString value;
- GetValue(value, true, /* aForDisplay = */ true);
+ GetValue(value, /* aForDisplay = */ true);
return aValue.Equals(value);
}
@@ -2634,8 +2587,7 @@ bool TextControlState::SetValue(const nsAString& aValue,
// away.
if (auto* input = HTMLInputElement::FromNode(mTextCtrlElement)) {
if (input->LastValueChangeWasInteractive()) {
- GetValue(mLastInteractiveValue, /* aIgnoreWrap = */ true,
- /* aForDisplay = */ true);
+ GetValue(mLastInteractiveValue, /* aForDisplay = */ true);
}
}
}
diff --git a/dom/html/TextControlState.h b/dom/html/TextControlState.h
@@ -292,7 +292,7 @@ class TextControlState final : public SupportsWeakPtr {
* GetValue() returns current value either with or without TextEditor.
* The result never includes \r.
*/
- void GetValue(nsAString& aValue, bool aIgnoreWrap, bool aForDisplay) const;
+ void GetValue(nsAString& aValue, bool aForDisplay) const;
/**
* ValueEquals() is designed for internal use so that aValue shouldn't
diff --git a/dom/serializers/nsPlainTextSerializer.cpp b/dom/serializers/nsPlainTextSerializer.cpp
@@ -32,6 +32,7 @@
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsGkAtoms.h"
+#include "nsIDocumentEncoder.h"
#include "nsNameSpaceManager.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
@@ -192,14 +193,14 @@ nsPlainTextSerializer::OutputManager::OutputManager(const int32_t aFlags,
}
void nsPlainTextSerializer::OutputManager::Append(
- const CurrentLine& aCurrentLine,
+ const CurrentLine& aLine,
const StripTrailingWhitespaces aStripTrailingWhitespaces) {
if (IsAtFirstColumn()) {
nsAutoString quotesAndIndent;
- aCurrentLine.CreateQuotesAndIndent(quotesAndIndent);
+ aLine.CreateQuotesAndIndent(quotesAndIndent);
if ((aStripTrailingWhitespaces == StripTrailingWhitespaces::kMaybe)) {
- const bool stripTrailingSpaces = aCurrentLine.mContent.IsEmpty();
+ const bool stripTrailingSpaces = aLine.mContent.IsEmpty();
if (stripTrailingSpaces) {
quotesAndIndent.Trim(" ", false, true, false);
}
@@ -208,7 +209,7 @@ void nsPlainTextSerializer::OutputManager::Append(
Append(quotesAndIndent);
}
- Append(aCurrentLine.mContent);
+ Append(aLine.mContent);
}
void nsPlainTextSerializer::OutputManager::Append(const nsAString& aString) {
@@ -315,6 +316,29 @@ void nsPlainTextSerializer::Settings::Init(const int32_t aFlags,
mWrapColumn = aWrapColumn;
}
+void nsPlainTextSerializer::HardWrapString(nsAString& aString,
+ uint32_t aWrapColumn,
+ int32_t aFlags) {
+ MOZ_ASSERT(aFlags & nsIDocumentEncoder::OutputWrap, "Why?");
+ MOZ_ASSERT(aWrapColumn, "Why?");
+
+ Settings settings;
+ settings.Init(aFlags, aWrapColumn);
+
+ // Line breaker will do the right thing, no need to split manually.
+ CurrentLine line;
+ line.mContent.Assign(aString);
+
+ nsAutoString output;
+ {
+ OutputManager manager(aFlags, output);
+ PerformWrapAndOutputCompleteLines(settings, line, manager,
+ /* aUseLineBreaker = */ true, nullptr);
+ manager.Flush(line);
+ }
+ aString.Assign(output);
+}
+
NS_IMETHODIMP
nsPlainTextSerializer::Init(const uint32_t aFlags, uint32_t aWrapColumn,
const Encoding* aEncoding, bool aIsCopying,
@@ -1200,25 +1224,25 @@ void nsPlainTextSerializer::EnsureVerticalSpace(const int32_t aNumberOfRows) {
// it and it's not included in the count for empty lines so we don't
// realize that we should start a new line.
if (aNumberOfRows >= 0 && !mCurrentLine.mIndentation.mHeader.IsEmpty()) {
- EndLine(false);
+ EndHardBreakLine();
mInWhitespace = true;
}
while (mEmptyLines < aNumberOfRows) {
- EndLine(false);
+ EndHardBreakLine();
mInWhitespace = true;
}
mLineBreakDue = false;
mFloatingLines = -1;
}
-void nsPlainTextSerializer::OutputManager::Flush(CurrentLine& aCurrentLine) {
- if (!aCurrentLine.mContent.IsEmpty()) {
- aCurrentLine.MaybeReplaceNbspsInContent(mFlags);
+void nsPlainTextSerializer::OutputManager::Flush(CurrentLine& aLine) {
+ if (!aLine.mContent.IsEmpty()) {
+ aLine.MaybeReplaceNbspsInContent(mFlags);
- Append(aCurrentLine, StripTrailingWhitespaces::kNo);
+ Append(aLine, StripTrailingWhitespaces::kNo);
- aCurrentLine.ResetContentAndIndentationHeader();
+ aLine.ResetContentAndIndentationHeader();
}
}
@@ -1227,8 +1251,10 @@ static bool IsSpaceStuffable(const char16_t* s) {
NS_strncmp(s, u"From ", 5) == 0);
}
-void nsPlainTextSerializer::MaybeWrapAndOutputCompleteLines() {
- if (!mSettings.MayWrap()) {
+void nsPlainTextSerializer::PerformWrapAndOutputCompleteLines(
+ const Settings& aSettings, CurrentLine& aLine, OutputManager& aOutput,
+ bool aUseLineBreaker, nsPlainTextSerializer* aSerializer) {
+ if (!aSettings.MayWrap()) {
return;
}
@@ -1236,21 +1262,21 @@ void nsPlainTextSerializer::MaybeWrapAndOutputCompleteLines() {
// The "+4" is to avoid wrap lines that only would be a couple
// of letters too long. We give this bonus only if the
// wrapcolumn is more than 20.
- const uint32_t wrapColumn = mSettings.GetWrapColumn();
+ const uint32_t wrapColumn = aSettings.GetWrapColumn();
uint32_t bonuswidth = (wrapColumn > 20) ? 4 : 0;
- while (!mCurrentLine.mContent.IsEmpty()) {
- const uint32_t prefixwidth = mCurrentLine.DeterminePrefixWidth();
+ while (!aLine.mContent.IsEmpty()) {
+ const uint32_t prefixwidth = aLine.DeterminePrefixWidth();
// The width of the line as it will appear on the screen (approx.).
const uint32_t currentLineContentWidth =
- GetUnicharStringWidth(mCurrentLine.mContent);
+ GetUnicharStringWidth(aLine.mContent);
if (currentLineContentWidth + prefixwidth <= wrapColumn + bonuswidth) {
break;
}
const int32_t goodSpace =
- mCurrentLine.FindWrapIndexForContent(wrapColumn, mUseLineBreaker);
+ aLine.FindWrapIndexForContent(wrapColumn, aUseLineBreaker);
- const int32_t contentLength = mCurrentLine.mContent.Length();
+ const int32_t contentLength = aLine.mContent.Length();
if (goodSpace <= 0 || goodSpace >= contentLength) {
// Nothing to do. Hopefully we get more data later to use for a place to
// break line.
@@ -1260,28 +1286,57 @@ void nsPlainTextSerializer::MaybeWrapAndOutputCompleteLines() {
// -1 (trim a char at the break position) only if the line break was a
// space.
nsAutoString restOfContent;
- if (nsCRT::IsAsciiSpace(mCurrentLine.mContent.CharAt(goodSpace))) {
- mCurrentLine.mContent.Right(restOfContent, contentLength - goodSpace - 1);
+ if (nsCRT::IsAsciiSpace(aLine.mContent.CharAt(goodSpace))) {
+ aLine.mContent.Right(restOfContent, contentLength - goodSpace - 1);
} else {
- mCurrentLine.mContent.Right(restOfContent, contentLength - goodSpace);
+ aLine.mContent.Right(restOfContent, contentLength - goodSpace);
}
// if breaker was U+0020, it has to consider for delsp=yes support
- const bool breakBySpace = mCurrentLine.mContent.CharAt(goodSpace) == ' ';
- mCurrentLine.mContent.Truncate(goodSpace);
- EndLine(true, breakBySpace);
- mCurrentLine.mContent.Truncate();
+ const bool breakBySpace = aLine.mContent.CharAt(goodSpace) == ' ';
+ aLine.mContent.Truncate(goodSpace);
+ // Append the line to the output.
+ if (!aLine.mContent.IsEmpty()) {
+ if (!aSettings.HasFlag(nsIDocumentEncoder::OutputPreformatted)) {
+ aLine.mContent.Trim(" ", false, true, false);
+ }
+ if (aSettings.HasFlag(nsIDocumentEncoder::OutputFormatFlowed) &&
+ !aLine.mIndentation.mLength) {
+ // Add the soft part of the soft linebreak (RFC 2646 4.1)
+ // We only do this when there is no indentation since format=flowed
+ // lines and indentation doesn't work well together.
+
+ // If breaker character is ASCII space with RFC 3676 support
+ // (delsp=yes), add twice space.
+ if (aSettings.HasFlag(nsIDocumentEncoder::OutputFormatDelSp) &&
+ breakBySpace) {
+ aLine.mContent.AppendLiteral(" ");
+ } else {
+ aLine.mContent.Append(char16_t(' '));
+ }
+ }
+ AppendLineToOutput(aSettings, aLine, aOutput);
+ if (aSerializer) {
+ aSerializer->ResetStateAfterLine();
+ aSerializer->mEmptyLines = -1;
+ }
+ }
+ aLine.mContent.Truncate();
// Space stuffing a la RFC 2646 (format=flowed)
- if (mSettings.HasFlag(nsIDocumentEncoder::OutputFormatFlowed)) {
- mCurrentLine.mSpaceStuffed = !restOfContent.IsEmpty() &&
- IsSpaceStuffable(restOfContent.get()) &&
- // We space-stuff quoted lines anyway
- mCurrentLine.mCiteQuoteLevel == 0;
+ if (aSettings.HasFlag(nsIDocumentEncoder::OutputFormatFlowed)) {
+ aLine.mSpaceStuffed = !restOfContent.IsEmpty() &&
+ IsSpaceStuffable(restOfContent.get()) &&
+ // We space-stuff quoted lines anyway
+ aLine.mCiteQuoteLevel == 0;
}
- mCurrentLine.mContent.Append(restOfContent);
- mEmptyLines = -1;
+ aLine.mContent.Append(restOfContent);
}
}
+void nsPlainTextSerializer::MaybeWrapAndOutputCompleteLines() {
+ PerformWrapAndOutputCompleteLines(mSettings, mCurrentLine, *mOutputManager,
+ mUseLineBreaker, this);
+}
+
/**
* This function adds a piece of text to the current stored line. If we are
* wrapping text and the stored line will become too long, a suitable
@@ -1323,17 +1378,24 @@ static bool IsSignatureSeparator(const nsAString& aString) {
aString.EqualsLiteral(kDashEscapedSignatureSeparator);
}
+void nsPlainTextSerializer::AppendLineToOutput(const Settings& aSettings,
+ CurrentLine& aLine,
+ OutputManager& aOutput) {
+ aLine.MaybeReplaceNbspsInContent(aSettings.GetFlags());
+ // If we don't have anything "real" to output we have to
+ // make sure the indent doesn't end in a space since that
+ // would trick a format=flowed-aware receiver.
+ aOutput.Append(aLine, OutputManager::StripTrailingWhitespaces::kMaybe);
+ aOutput.AppendLineBreak();
+ aLine.ResetContentAndIndentationHeader();
+}
+
/**
* Outputs the contents of mCurrentLine.mContent, and resets line
* specific variables. Also adds an indentation and prefix if there is one
* specified. Strips ending spaces from the line if it isn't preformatted.
*/
-void nsPlainTextSerializer::EndLine(bool aSoftLineBreak, bool aBreakBySpace) {
- if (aSoftLineBreak && mCurrentLine.mContent.IsEmpty()) {
- // No meaning
- return;
- }
-
+void nsPlainTextSerializer::EndHardBreakLine() {
/* In non-preformatted mode, remove spaces from the end of the line for
* format=flowed compatibility. Don't do this for these special cases:
* "-- ", the signature separator (RFC 2646) shouldn't be touched and
@@ -1341,52 +1403,20 @@ void nsPlainTextSerializer::EndLine(bool aSoftLineBreak, bool aBreakBySpace) {
* signed messages according to the OpenPGP standard (RFC 2440).
*/
if (!mSettings.HasFlag(nsIDocumentEncoder::OutputPreformatted) &&
- (aSoftLineBreak || !IsSignatureSeparator(mCurrentLine.mContent))) {
+ !IsSignatureSeparator(mCurrentLine.mContent)) {
mCurrentLine.mContent.Trim(" ", false, true, false);
}
- if (aSoftLineBreak &&
- mSettings.HasFlag(nsIDocumentEncoder::OutputFormatFlowed) &&
- !mCurrentLine.mIndentation.mLength) {
- // Add the soft part of the soft linebreak (RFC 2646 4.1)
- // We only do this when there is no indentation since format=flowed
- // lines and indentation doesn't work well together.
-
- // If breaker character is ASCII space with RFC 3676 support (delsp=yes),
- // add twice space.
- if (mSettings.HasFlag(nsIDocumentEncoder::OutputFormatDelSp) &&
- aBreakBySpace) {
- mCurrentLine.mContent.AppendLiteral(" ");
- } else {
- mCurrentLine.mContent.Append(char16_t(' '));
- }
- }
-
- if (aSoftLineBreak) {
+ // Hard break
+ if (mCurrentLine.HasContentOrIndentationHeader()) {
mEmptyLines = 0;
} else {
- // Hard break
- if (mCurrentLine.HasContentOrIndentationHeader()) {
- mEmptyLines = 0;
- } else {
- mEmptyLines++;
- }
+ mEmptyLines++;
}
MOZ_ASSERT(mOutputManager);
-
- mCurrentLine.MaybeReplaceNbspsInContent(mSettings.GetFlags());
-
- // If we don't have anything "real" to output we have to
- // make sure the indent doesn't end in a space since that
- // would trick a format=flowed-aware receiver.
- mOutputManager->Append(mCurrentLine,
- OutputManager::StripTrailingWhitespaces::kMaybe);
- mOutputManager->AppendLineBreak();
- mCurrentLine.ResetContentAndIndentationHeader();
- mInWhitespace = true;
- mLineBreakDue = false;
- mFloatingLines = -1;
+ AppendLineToOutput(mSettings, mCurrentLine, *mOutputManager);
+ ResetStateAfterLine();
}
/**
@@ -1536,8 +1566,9 @@ void nsPlainTextSerializer::ConvertToLinesAndOutput(const nsAString& aString) {
/**
* Write a string. This is the highlevel function to use to get text output.
- * By using AddToLine, Output, EndLine and other functions it handles quotation,
- * line wrapping, indentation, whitespace compression and other things.
+ * By using AddToLine, Output, EndHardBreakLine and other functions it handles
+ * quotation, line wrapping, indentation, whitespace compression and other
+ * things.
*/
void nsPlainTextSerializer::Write(const nsAString& aStr) {
// XXX Copy necessary to use nsString methods and gain
diff --git a/dom/serializers/nsPlainTextSerializer.h b/dom/serializers/nsPlainTextSerializer.h
@@ -79,6 +79,9 @@ class nsPlainTextSerializer final : public nsIContentSerializer {
NS_IMETHOD ForgetElementForPreformat(
mozilla::dom::Element* aElement) override;
+ static void HardWrapString(nsAString& aString, uint32_t aWrapCols,
+ int32_t flags);
+
private:
~nsPlainTextSerializer();
@@ -87,9 +90,12 @@ class nsPlainTextSerializer final : public nsIContentSerializer {
void MaybeWrapAndOutputCompleteLines();
- // @param aSoftLineBreak A soft line break is a space followed by a linebreak
- // (cf. https://www.ietf.org/rfc/rfc3676.txt, section 4.2).
- void EndLine(bool aSoftLineBreak, bool aBreakBySpace = false);
+ void EndHardBreakLine();
+ void ResetStateAfterLine() {
+ mInWhitespace = true;
+ mLineBreakDue = false;
+ mFloatingLines = -1;
+ }
void EnsureVerticalSpace(int32_t noOfRows);
@@ -306,6 +312,12 @@ class nsPlainTextSerializer final : public nsIContentSerializer {
nsString mLineBreak;
};
+ static void PerformWrapAndOutputCompleteLines(
+ const Settings& aSettings, CurrentLine& aLine, OutputManager& aOutput,
+ bool aUseLineBreaker, nsPlainTextSerializer* aSerializer);
+ static void AppendLineToOutput(const Settings& aSettings, CurrentLine& aLine,
+ OutputManager& aOutput);
+
mozilla::Maybe<OutputManager> mOutputManager;
// If we've just written out a cite blockquote, we need to remember it
diff --git a/testing/web-platform/meta/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html.ini b/testing/web-platform/meta/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html.ini
@@ -1,5 +0,0 @@
-[wrap-enumerated-ascii-case-insensitive.html]
- expected:
- if (os == "android") and fission: [OK, TIMEOUT]
- [keywords]
- expected: FAIL
diff --git a/testing/web-platform/meta/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js.ini b/testing/web-platform/meta/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js.ini
@@ -1,5 +0,0 @@
-[wrapping-transformation.window.html]
- expected:
- if (os == "android") and fission: [OK, TIMEOUT]
- [Textarea wrapping transformation: Wrapping happens with LF newlines.]
- expected: FAIL