Bug 1575585: prevent mutating individual members of `nsPlainTextSerializer::Settings`. r=hsivonen
authorMirko Brodesser <mbrodesser@mozilla.com>
Mon, 26 Aug 2019 12:00:58 +0000
changeset 553628 9e735fd6f88341e1eb30f5e1b49dedec7f674366
parent 553627 5cafdf3cf8f9386e1aa0cabe403723e41676ee56
child 553629 cb92c46d1d5b479df80ca0565defa012ba6ac8ac
push id2165
push userffxbld-merge
push dateMon, 14 Oct 2019 16:30:58 +0000
treeherdermozilla-release@0eae18af659f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershsivonen
bugs1575585
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1575585: prevent mutating individual members of `nsPlainTextSerializer::Settings`. r=hsivonen Reasoning about this class and its callers becomes easier. Differential Revision: https://phabricator.services.mozilla.com/D42896
dom/base/nsPlainTextSerializer.cpp
dom/base/nsPlainTextSerializer.h
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -170,16 +170,37 @@ nsPlainTextSerializer::nsPlainTextSerial
 }
 
 nsPlainTextSerializer::~nsPlainTextSerializer() {
   delete[] mTagStack;
   delete[] mOLStack;
   NS_WARNING_ASSERTION(mHeadLevel == 0, "Wrong head level!");
 }
 
+void nsPlainTextSerializer::Settings::Init(const int32_t aFlags) {
+  mFlags = aFlags;
+
+  if (mFlags & nsIDocumentEncoder::OutputFormatted) {
+    // Get some prefs that controls how we do formatted output
+    mStructs = Preferences::GetBool(PREF_STRUCTS, mStructs);
+
+    mHeaderStrategy =
+        Preferences::GetInt(PREF_HEADER_STRATEGY, mHeaderStrategy);
+  }
+
+  // The pref is default inited to false in libpref, but we use true
+  // as fallback value because we don't want to affect behavior in
+  // other places which use this serializer currently.
+  mWithRubyAnnotation =
+      gAlwaysIncludeRuby || (mFlags & nsIDocumentEncoder::OutputRubyAnnotation);
+
+  // XXX We should let the caller decide whether to do this or not
+  mFlags &= ~nsIDocumentEncoder::OutputNoFramesContent;
+}
+
 NS_IMETHODIMP
 nsPlainTextSerializer::Init(const uint32_t aFlags, uint32_t aWrapColumn,
                             const Encoding* aEncoding, bool aIsCopying,
                             bool aIsWholeDocument,
                             bool* aNeedsPreformatScanning) {
 #ifdef DEBUG
   // Check if the major control flags are set correctly.
   if (aFlags & nsIDocumentEncoder::OutputFormatFlowed) {
@@ -191,48 +212,30 @@ nsPlainTextSerializer::Init(const uint32
   if (aFlags & nsIDocumentEncoder::OutputFormatted) {
     NS_ASSERTION(
         !(aFlags & nsIDocumentEncoder::OutputPreformatted),
         "Can't do formatted and preformatted output at the same time!");
   }
 #endif
 
   *aNeedsPreformatScanning = true;
-  mSettings.mFlags = aFlags;
+  mSettings.Init(aFlags);
   mWrapColumn = aWrapColumn;
 
   // Only create a linebreaker if we will handle wrapping.
   if (MayWrap() && MayBreakLines()) {
     mLineBreaker = nsContentUtils::LineBreaker();
   }
 
   mLineBreakDue = false;
   mFloatingLines = -1;
 
   mPreformattedBlockBoundary = false;
 
-  if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatted) {
-    // Get some prefs that controls how we do formatted output
-    mSettings.mStructs = Preferences::GetBool(PREF_STRUCTS, mSettings.mStructs);
-
-    mSettings.mHeaderStrategy =
-        Preferences::GetInt(PREF_HEADER_STRATEGY, mSettings.mHeaderStrategy);
-  }
-
-  // The pref is default inited to false in libpref, but we use true
-  // as fallback value because we don't want to affect behavior in
-  // other places which use this serializer currently.
-  mSettings.mWithRubyAnnotation =
-      gAlwaysIncludeRuby ||
-      (mSettings.mFlags & nsIDocumentEncoder::OutputRubyAnnotation);
-
-  // XXX We should let the caller decide whether to do this or not
-  mSettings.mFlags &= ~nsIDocumentEncoder::OutputNoFramesContent;
-
-  mCurrentLineContent = CurrentLineContent{mSettings.mFlags};
+  mCurrentLineContent = CurrentLineContent{mSettings.GetFlags()};
 
   return NS_OK;
 }
 
 bool nsPlainTextSerializer::GetLastBool(const nsTArray<bool>& aStack) {
   uint32_t size = aStack.Length();
   if (size == 0) {
     return false;
@@ -259,17 +262,17 @@ bool nsPlainTextSerializer::PopBool(nsTA
   if (size > 0) {
     returnValue = aStack.ElementAt(size - 1);
     aStack.RemoveElementAt(size - 1);
   }
   return returnValue;
 }
 
 bool nsPlainTextSerializer::IsIgnorableRubyAnnotation(nsAtom* aTag) {
-  if (mSettings.mWithRubyAnnotation) {
+  if (mSettings.GetWithRubyAnnotation()) {
     return false;
   }
 
   return aTag == nsGkAtoms::rp || aTag == nsGkAtoms::rt ||
          aTag == nsGkAtoms::rtc;
 }
 
 // Return true if aElement has 'display:none' or if we just don't know.
@@ -465,26 +468,27 @@ nsresult nsPlainTextSerializer::DoOpenCo
     mIgnoredChildNodeLevel++;
     return NS_OK;
   }
   if (IsIgnorableScriptOrStyle(mElement)) {
     mIgnoredChildNodeLevel++;
     return NS_OK;
   }
 
-  if (mSettings.mFlags & nsIDocumentEncoder::OutputForPlainTextClipboardCopy) {
+  if (mSettings.GetFlags() &
+      nsIDocumentEncoder::OutputForPlainTextClipboardCopy) {
     if (mPreformattedBlockBoundary && DoOutput()) {
       // Should always end a line, but get no more whitespace
       if (mFloatingLines < 0) mFloatingLines = 0;
       mLineBreakDue = true;
     }
     mPreformattedBlockBoundary = false;
   }
 
-  if (mSettings.mFlags & nsIDocumentEncoder::OutputRaw) {
+  if (mSettings.GetFlags() & nsIDocumentEncoder::OutputRaw) {
     // Raw means raw.  Don't even think about doing anything fancy
     // here like indenting, adding line breaks or any other
     // characters such as list item bullets, quote characters
     // around <q>, etc.  I mean it!  Don't make me smack you!
 
     return NS_OK;
   }
 
@@ -510,19 +514,19 @@ nsresult nsPlainTextSerializer::DoOpenCo
     nsresult rv = GetAttributeValue(nsGkAtoms::type, value);
     isInCiteBlockquote = NS_SUCCEEDED(rv) && value.EqualsIgnoreCase("cite");
   }
 
   if (mLineBreakDue && !isInCiteBlockquote) EnsureVerticalSpace(mFloatingLines);
 
   // Check if this tag's content that should not be output
   if ((aTag == nsGkAtoms::noscript &&
-       !(mSettings.mFlags & nsIDocumentEncoder::OutputNoScriptContent)) ||
+       !(mSettings.GetFlags() & nsIDocumentEncoder::OutputNoScriptContent)) ||
       ((aTag == nsGkAtoms::iframe || aTag == nsGkAtoms::noframes) &&
-       !(mSettings.mFlags & nsIDocumentEncoder::OutputNoFramesContent))) {
+       !(mSettings.GetFlags() & nsIDocumentEncoder::OutputNoFramesContent))) {
     // Ignore everything that follows the current tag in
     // question until a matching end tag is encountered.
     mIgnoreAboveIndex = mTagStackIndex - 1;
     return NS_OK;
   }
 
   if (aTag == nsGkAtoms::body) {
     // Try to figure out here whether we have a
@@ -617,34 +621,34 @@ nsresult nsPlainTextSerializer::DoOpenCo
   } else if (aTag == nsGkAtoms::ul) {
     // Indent here to support nested lists, which aren't included in li :-(
     EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
     // Must end the current line before we change indention
     mIndent += kIndentSizeList;
     mULCount++;
   } else if (aTag == nsGkAtoms::ol) {
     EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
-    if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatted) {
+    if (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted) {
       // Must end the current line before we change indention
       if (mOLStackIndex < OLStackSize) {
         nsAutoString startAttr;
         int32_t startVal = 1;
         if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::start, startAttr))) {
           nsresult rv = NS_OK;
           startVal = startAttr.ToInteger(&rv);
           if (NS_FAILED(rv)) startVal = 1;
         }
         mOLStack[mOLStackIndex++] = startVal;
       }
     } else {
       mOLStackIndex++;
     }
     mIndent += kIndentSizeList;  // see ul
   } else if (aTag == nsGkAtoms::li &&
-             (mSettings.mFlags & nsIDocumentEncoder::OutputFormatted)) {
+             (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted)) {
     if (mTagStackIndex > 1 && IsInOL()) {
       if (mOLStackIndex > 0) {
         nsAutoString valueAttr;
         if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::value, valueAttr))) {
           nsresult rv = NS_OK;
           int32_t valueAttrVal = valueAttr.ToInteger(&rv);
           if (NS_SUCCEEDED(rv)) mOLStack[mOLStackIndex - 1] = valueAttrVal;
         }
@@ -689,31 +693,31 @@ nsresult nsPlainTextSerializer::DoOpenCo
 
   // Else make sure we'll separate block level tags,
   // even if we're about to leave, before doing any other formatting.
   else if (IsCssBlockLevelElement(mElement)) {
     EnsureVerticalSpace(0);
   }
 
   //////////////////////////////////////////////////////////////
-  if (!(mSettings.mFlags & nsIDocumentEncoder::OutputFormatted)) {
+  if (!(mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted)) {
     return NS_OK;
   }
   //////////////////////////////////////////////////////////////
   // The rest of this routine is formatted output stuff,
   // which we should skip if we're not formatted:
   //////////////////////////////////////////////////////////////
 
   // Push on stack
   bool currentNodeIsConverted = IsCurrentNodeConverted();
 
   if (aTag == nsGkAtoms::h1 || aTag == nsGkAtoms::h2 || aTag == nsGkAtoms::h3 ||
       aTag == nsGkAtoms::h4 || aTag == nsGkAtoms::h5 || aTag == nsGkAtoms::h6) {
     EnsureVerticalSpace(2);
-    if (mSettings.mHeaderStrategy == 2) {  // numbered
+    if (mSettings.GetHeaderStrategy() == 2) {  // numbered
       mIndent += kIndentSizeHeaders;
       // Caching
       int32_t level = HeaderLevel(aTag);
       // Increase counter for current level
       mHeaderCounter[level]++;
       // Reset all lower levels
       int32_t i;
 
@@ -724,45 +728,45 @@ nsresult nsPlainTextSerializer::DoOpenCo
       // Construct numbers
       nsAutoString leadup;
       for (i = 1; i <= level; i++) {
         leadup.AppendInt(mHeaderCounter[i]);
         leadup.Append(char16_t('.'));
       }
       leadup.Append(char16_t(' '));
       Write(leadup);
-    } else if (mSettings.mHeaderStrategy == 1) {  // indent increasingly
+    } else if (mSettings.GetHeaderStrategy() == 1) {  // indent increasingly
       mIndent += kIndentSizeHeaders;
       for (int32_t i = HeaderLevel(aTag); i > 1; i--) {
         // for h(x), run x-1 times
         mIndent += kIndentIncrementHeaders;
       }
     }
   } else if (aTag == nsGkAtoms::a && !currentNodeIsConverted) {
     nsAutoString url;
     if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::href, url)) &&
         !url.IsEmpty()) {
       mURL = url;
     }
-  } else if (aTag == nsGkAtoms::sup && mSettings.mStructs &&
+  } else if (aTag == nsGkAtoms::sup && mSettings.GetStructs() &&
              !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("^"));
-  } else if (aTag == nsGkAtoms::sub && mSettings.mStructs &&
+  } else if (aTag == nsGkAtoms::sub && mSettings.GetStructs() &&
              !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("_"));
-  } else if (aTag == nsGkAtoms::code && mSettings.mStructs &&
+  } else if (aTag == nsGkAtoms::code && mSettings.GetStructs() &&
              !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("|"));
   } else if ((aTag == nsGkAtoms::strong || aTag == nsGkAtoms::b) &&
-             mSettings.mStructs && !currentNodeIsConverted) {
+             mSettings.GetStructs() && !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("*"));
   } else if ((aTag == nsGkAtoms::em || aTag == nsGkAtoms::i) &&
-             mSettings.mStructs && !currentNodeIsConverted) {
+             mSettings.GetStructs() && !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("/"));
-  } else if (aTag == nsGkAtoms::u && mSettings.mStructs &&
+  } else if (aTag == nsGkAtoms::u && mSettings.GetStructs() &&
              !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("_"));
   }
 
   /* Container elements are always block elements, so we shouldn't
      output any whitespace immediately after the container tag even if
      there's extra whitespace there because the HTML is pretty-printed
      or something. To ensure that happens, tell the serializer we're
@@ -777,26 +781,27 @@ nsresult nsPlainTextSerializer::DoCloseC
     mIgnoredChildNodeLevel--;
     return NS_OK;
   }
   if (IsIgnorableScriptOrStyle(mElement)) {
     mIgnoredChildNodeLevel--;
     return NS_OK;
   }
 
-  if (mSettings.mFlags & nsIDocumentEncoder::OutputForPlainTextClipboardCopy) {
+  if (mSettings.GetFlags() &
+      nsIDocumentEncoder::OutputForPlainTextClipboardCopy) {
     if (DoOutput() && IsElementPreformatted() &&
         IsCssBlockLevelElement(mElement)) {
       // If we're closing a preformatted block element, output a line break
       // when we find a new container.
       mPreformattedBlockBoundary = true;
     }
   }
 
-  if (mSettings.mFlags & nsIDocumentEncoder::OutputRaw) {
+  if (mSettings.GetFlags() & nsIDocumentEncoder::OutputRaw) {
     // Raw means raw.  Don't even think about doing anything fancy
     // here like indenting, adding line breaks or any other
     // characters such as list item bullets, quote characters
     // around <q>, etc.  I mean it!  Don't make me smack you!
 
     return NS_OK;
   }
 
@@ -815,17 +820,17 @@ nsresult nsPlainTextSerializer::DoCloseC
   }
 
   // End current line if we're ending a block level tag
   if ((aTag == nsGkAtoms::body) || (aTag == nsGkAtoms::html)) {
     // We want the output to end with a new line,
     // but in preformatted areas like text fields,
     // we can't emit newlines that weren't there.
     // So add the newline only in the case of formatted output.
-    if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatted) {
+    if (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted) {
       EnsureVerticalSpace(0);
     } else {
       FlushLine();
     }
     // We won't want to do anything with these in formatted mode either,
     // so just return now:
     return NS_OK;
   }
@@ -836,17 +841,17 @@ nsresult nsPlainTextSerializer::DoCloseC
   }
 
   if (aTag == nsGkAtoms::tr) {
     PopBool(mHasWrittenCellsForRow);
     // Should always end a line, but get no more whitespace
     if (mFloatingLines < 0) mFloatingLines = 0;
     mLineBreakDue = true;
   } else if (((aTag == nsGkAtoms::li) || (aTag == nsGkAtoms::dt)) &&
-             (mSettings.mFlags & nsIDocumentEncoder::OutputFormatted)) {
+             (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted)) {
     // Items that should always end a line, but get no more whitespace
     if (mFloatingLines < 0) mFloatingLines = 0;
     mLineBreakDue = true;
   } else if (aTag == nsGkAtoms::pre) {
     mFloatingLines = GetLastBool(mIsInCiteBlockquote) ? 0 : 1;
     mLineBreakDue = true;
   } else if (aTag == nsGkAtoms::ul) {
     FlushLine();
@@ -894,69 +899,69 @@ nsresult nsPlainTextSerializer::DoCloseC
     mLineBreakDue = true;
   } else if (aTag == nsGkAtoms::q) {
     Write(NS_LITERAL_STRING("\""));
   } else if (IsCssBlockLevelElement(mElement)) {
     // All other blocks get 1 vertical space after them
     // in formatted mode, otherwise 0.
     // This is hard. Sometimes 0 is a better number, but
     // how to know?
-    if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatted)
+    if (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted) {
       EnsureVerticalSpace(1);
-    else {
+    } else {
       if (mFloatingLines < 0) mFloatingLines = 0;
       mLineBreakDue = true;
     }
   }
 
   //////////////////////////////////////////////////////////////
-  if (!(mSettings.mFlags & nsIDocumentEncoder::OutputFormatted)) {
+  if (!(mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted)) {
     return NS_OK;
   }
   //////////////////////////////////////////////////////////////
   // The rest of this routine is formatted output stuff,
   // which we should skip if we're not formatted:
   //////////////////////////////////////////////////////////////
 
   // Pop the currentConverted stack
   bool currentNodeIsConverted = IsCurrentNodeConverted();
 
   if (aTag == nsGkAtoms::h1 || aTag == nsGkAtoms::h2 || aTag == nsGkAtoms::h3 ||
       aTag == nsGkAtoms::h4 || aTag == nsGkAtoms::h5 || aTag == nsGkAtoms::h6) {
-    if (mSettings.mHeaderStrategy) { /*numbered or indent increasingly*/
+    if (mSettings.GetHeaderStrategy()) { /*numbered or indent increasingly*/
       mIndent -= kIndentSizeHeaders;
     }
-    if (mSettings.mHeaderStrategy == 1 /*indent increasingly*/) {
+    if (mSettings.GetHeaderStrategy() == 1 /*indent increasingly*/) {
       for (int32_t i = HeaderLevel(aTag); i > 1; i--) {
         // for h(x), run x-1 times
         mIndent -= kIndentIncrementHeaders;
       }
     }
     EnsureVerticalSpace(1);
   } else if (aTag == nsGkAtoms::a && !currentNodeIsConverted &&
              !mURL.IsEmpty()) {
     nsAutoString temp;
     temp.AssignLiteral(" <");
     temp += mURL;
     temp.Append(char16_t('>'));
     Write(temp);
     mURL.Truncate();
   } else if ((aTag == nsGkAtoms::sup || aTag == nsGkAtoms::sub) &&
-             mSettings.mStructs && !currentNodeIsConverted) {
+             mSettings.GetStructs() && !currentNodeIsConverted) {
     Write(kSpace);
-  } else if (aTag == nsGkAtoms::code && mSettings.mStructs &&
+  } else if (aTag == nsGkAtoms::code && mSettings.GetStructs() &&
              !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("|"));
   } else if ((aTag == nsGkAtoms::strong || aTag == nsGkAtoms::b) &&
-             mSettings.mStructs && !currentNodeIsConverted) {
+             mSettings.GetStructs() && !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("*"));
   } else if ((aTag == nsGkAtoms::em || aTag == nsGkAtoms::i) &&
-             mSettings.mStructs && !currentNodeIsConverted) {
+             mSettings.GetStructs() && !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("/"));
-  } else if (aTag == nsGkAtoms::u && mSettings.mStructs &&
+  } else if (aTag == nsGkAtoms::u && mSettings.GetStructs() &&
              !currentNodeIsConverted) {
     Write(NS_LITERAL_STRING("_"));
   }
 
   return NS_OK;
 }
 
 bool nsPlainTextSerializer::MustSuppressLeaf() {
@@ -999,17 +1004,17 @@ void nsPlainTextSerializer::DoAddText(bo
 
   if (aIsLineBreak) {
     // The only times we want to pass along whitespace from the original
     // html source are if we're forced into preformatted mode via flags,
     // or if we're prettyprinting and we're inside a <pre>.
     // Otherwise, either we're collapsing to minimal text, or we're
     // prettyprinting to mimic the html format, and in neither case
     // does the formatting of the html source help us.
-    if ((mSettings.mFlags & nsIDocumentEncoder::OutputPreformatted) ||
+    if ((mSettings.GetFlags() & nsIDocumentEncoder::OutputPreformatted) ||
         (mPreFormattedMail && !mWrapColumn) || IsElementPreformatted()) {
       EnsureVerticalSpace(mEmptyLines + 1);
     } else if (!mInWhitespace) {
       Write(kSpace);
       mInWhitespace = true;
     }
     return;
   }
@@ -1044,17 +1049,17 @@ nsresult nsPlainTextSerializer::DoAddLea
     //      of non-HTML element.
     // XXX Do we need to call `EnsureVerticalSpace()` when the <br> element
     //     is not an HTML element?
     HTMLBRElement* brElement = HTMLBRElement::FromNodeOrNull(mElement);
     if (!brElement || !brElement->IsPaddingForEmptyLastLine()) {
       EnsureVerticalSpace(mEmptyLines + 1);
     }
   } else if (aTag == nsGkAtoms::hr &&
-             (mSettings.mFlags & nsIDocumentEncoder::OutputFormatted)) {
+             (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted)) {
     EnsureVerticalSpace(0);
 
     // Make a line of dashes as wide as the wrap width
     // XXX honoring percentage would be nice
     nsAutoString line;
     uint32_t width = (mWrapColumn > 0 ? mWrapColumn : 25);
     while (line.Length() < width) {
       line.Append(char16_t('-'));
@@ -1155,17 +1160,17 @@ void nsPlainTextSerializer::AddToLine(co
 
   int32_t linelength = mCurrentLineContent.mValue.Length();
   if (0 == linelength) {
     if (0 == aLineFragmentLength) {
       // Nothing at all. Are you kidding me?
       return;
     }
 
-    if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+    if (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatFlowed) {
       if (IsSpaceStuffable(aLineFragment) &&
           mCiteQuoteLevel == 0  // We space-stuff quoted lines anyway
       ) {
         // Space stuffing a la RFC 2646 (format=flowed).
         mCurrentLineContent.mValue.Append(char16_t(' '));
 
         if (MayWrap()) {
           mCurrentLineContent.mWidth += GetUnicharWidth(' ');
@@ -1284,17 +1289,17 @@ void nsPlainTextSerializer::AddToLine(co
         }
         // if breaker was U+0020, it has to consider for delsp=yes support
         const bool breakBySpace =
             mCurrentLineContent.mValue.CharAt(goodSpace) == ' ';
         mCurrentLineContent.mValue.Truncate(goodSpace);
         EndLine(true, breakBySpace);
         mCurrentLineContent.mValue.Truncate();
         // Space stuff new line?
-        if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+        if (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatFlowed) {
           if (!restOfLine.IsEmpty() && IsSpaceStuffable(restOfLine.get()) &&
               mCiteQuoteLevel == 0  // We space-stuff quoted lines anyway
           ) {
             // Space stuffing a la RFC 2646 (format=flowed).
             mCurrentLineContent.mValue.Append(char16_t(' '));
             // XXX doesn't seem to work correctly for ' '
           }
         }
@@ -1328,38 +1333,38 @@ void nsPlainTextSerializer::EndLine(bool
   }
 
   /* 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
    * "- -- ", the OpenPGP dash-escaped signature separator in inline
    * signed messages according to the OpenPGP standard (RFC 2440).
    */
-  if (!(mSettings.mFlags & nsIDocumentEncoder::OutputPreformatted) &&
+  if (!(mSettings.GetFlags() & nsIDocumentEncoder::OutputPreformatted) &&
       (aSoftlinebreak ||
        !(mCurrentLineContent.mValue.EqualsLiteral("-- ") ||
          mCurrentLineContent.mValue.EqualsLiteral("- -- ")))) {
     // Remove spaces from the end of the line.
     while (currentlinelength > 0 &&
            mCurrentLineContent.mValue[currentlinelength - 1] == ' ') {
       --currentlinelength;
     }
     mCurrentLineContent.mValue.SetLength(currentlinelength);
   }
 
   if (aSoftlinebreak &&
-      (mSettings.mFlags & nsIDocumentEncoder::OutputFormatFlowed) &&
+      (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatFlowed) &&
       (mIndent == 0)) {
     // 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.mFlags & nsIDocumentEncoder::OutputFormatDelSp) &&
+    if ((mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatDelSp) &&
         aBreakBySpace)
       mCurrentLineContent.mValue.AppendLiteral("  ");
     else
       mCurrentLineContent.mValue.Append(char16_t(' '));
   }
 
   if (aSoftlinebreak) {
     mEmptyLines = 0;
@@ -1467,17 +1472,17 @@ void nsPlainTextSerializer::Write(const 
 
   int32_t totLen = str.Length();
 
   // If the string is empty, do nothing:
   if (totLen <= 0) return;
 
   // For Flowed text change nbsp-ses to spaces at end of lines to allow them
   // to be cut off along with usual spaces if required. (bug #125928)
-  if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+  if (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatFlowed) {
     for (int32_t i = totLen - 1; i >= 0; i--) {
       char16_t c = str[i];
       if ('\n' == c || '\r' == c || ' ' == c || '\t' == c) continue;
       if (kNBSP == c)
         str.Replace(i, 1, ' ');
       else
         break;
     }
@@ -1556,17 +1561,17 @@ void nsPlainTextSerializer::Write(const 
           // There was a CRLF in the input. This used to be illegal and
           // stripped by the parser. Apparently not anymore. Let's skip
           // over the LF.
           bol++;
         }
       }
 
       mCurrentLineContent.mValue.Truncate();
-      if (mSettings.mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+      if (mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatFlowed) {
         if ((outputLineBreak || !spacesOnly) &&  // bugs 261467,125928
             !IsQuotedLine(stringpart) && !stringpart.EqualsLiteral("-- ") &&
             !stringpart.EqualsLiteral("- -- "))
           stringpart.Trim(" ", false, true, true);
         if (IsSpaceStuffable(stringpart.get()) && !IsQuotedLine(stringpart))
           mCurrentLineContent.mValue.Append(char16_t(' '));
       }
       mCurrentLineContent.mValue.Append(stringpart);
@@ -1628,17 +1633,17 @@ void nsPlainTextSerializer::Write(const 
           offsetIntoBuffer = str.get() + bol;
           AddToLine(offsetIntoBuffer, nextpos - bol);
           bol = nextpos + 1;
           continue;
         }
       }
       // If we're already in whitespace and not preformatted, just skip it:
       if (mInWhitespace && (nextpos == bol) && !mPreFormattedMail &&
-          !(mSettings.mFlags & nsIDocumentEncoder::OutputPreformatted)) {
+          !(mSettings.GetFlags() & nsIDocumentEncoder::OutputPreformatted)) {
         // Skip whitespace
         bol++;
         continue;
       }
 
       if (nextpos == bol) {
         // Note that we are in whitespace.
         mInWhitespace = true;
@@ -1647,17 +1652,17 @@ void nsPlainTextSerializer::Write(const 
         bol++;
         continue;
       }
 
       mInWhitespace = true;
 
       offsetIntoBuffer = str.get() + bol;
       if (mPreFormattedMail ||
-          (mSettings.mFlags & nsIDocumentEncoder::OutputPreformatted)) {
+          (mSettings.GetFlags() & nsIDocumentEncoder::OutputPreformatted)) {
         // Preserve the real whitespace character
         nextpos++;
         AddToLine(offsetIntoBuffer, nextpos - bol);
         bol = nextpos;
       } else {
         // Replace the whitespace with a space
         AddToLine(offsetIntoBuffer, nextpos - bol);
         AddToLine(kSpace.get(), 1);
--- a/dom/base/nsPlainTextSerializer.h
+++ b/dom/base/nsPlainTextSerializer.h
@@ -111,21 +111,22 @@ class nsPlainTextSerializer final : publ
 
   void DoAddText();
   // @param aText Ignored if aIsLineBreak is true.
   void DoAddText(bool aIsLineBreak, const nsAString& aText);
 
   // Inlined functions
   inline bool MayWrap() const {
     return mWrapColumn &&
-           ((mSettings.mFlags & nsIDocumentEncoder::OutputFormatted) ||
-            (mSettings.mFlags & nsIDocumentEncoder::OutputWrap));
+           ((mSettings.GetFlags() & nsIDocumentEncoder::OutputFormatted) ||
+            (mSettings.GetFlags() & nsIDocumentEncoder::OutputWrap));
   }
   inline bool MayBreakLines() const {
-    return !(mSettings.mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
+    return !(mSettings.GetFlags() &
+             nsIDocumentEncoder::OutputDisallowLineBreaking);
   }
 
   inline bool DoOutput() const { return mHeadLevel == 0; }
 
   inline bool IsQuotedLine(const nsAString& aLine) {
     return !aLine.IsEmpty() && aLine.First() == char16_t('>');
   }
 
@@ -144,17 +145,36 @@ class nsPlainTextSerializer final : publ
 
   // https://drafts.csswg.org/css-display/#block-level
   static bool IsCssBlockLevelElement(mozilla::dom::Element* aElement);
 
  private:
   uint32_t mHeadLevel;
   bool mAtFirstColumn;
 
-  struct Settings {
+  class Settings {
+   public:
+    // May adapt the flags.
+    //
+    // @param aFlags As defined in nsIDocumentEncoder.idl.
+    void Init(int32_t aFlags);
+
+    // Pref: converter.html2txt.structs.
+    bool GetStructs() const { return mStructs; }
+
+    // Pref: converter.html2txt.header_strategy.
+    int32_t GetHeaderStrategy() const { return mHeaderStrategy; }
+
+    // @return As defined in nsIDocumentEncoder.idl.
+    int32_t GetFlags() const { return mFlags; }
+
+    // Whether the output should include ruby annotations.
+    bool GetWithRubyAnnotation() const { return mWithRubyAnnotation; }
+
+   private:
     // Pref: converter.html2txt.structs.
     bool mStructs = true;
 
     // Pref: converter.html2txt.header_strategy.
     int32_t mHeaderStrategy = 1; /* Header strategy (pref)
                                   0 = no indention
                                   1 = indention, increased with
                                       header level (default)