tor-browser

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

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:
Mdom/html/HTMLInputElement.cpp | 4++--
Mdom/html/HTMLTextAreaElement.cpp | 66++++++++++++++++++++++++++++++++++++++++++++----------------------
Mdom/html/HTMLTextAreaElement.h | 5+----
Mdom/html/TextControlElement.h | 10----------
Mdom/html/TextControlState.cpp | 74+++++++++++++-------------------------------------------------------------
Mdom/html/TextControlState.h | 2+-
Mdom/serializers/nsPlainTextSerializer.cpp | 189++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mdom/serializers/nsPlainTextSerializer.h | 18+++++++++++++++---
Dtesting/web-platform/meta/html/semantics/forms/the-textarea-element/wrap-enumerated-ascii-case-insensitive.html.ini | 5-----
Dtesting/web-platform/meta/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js.ini | 5-----
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