tor-browser

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

nsDocumentEncoder.cpp (78044B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /*
      8 * Object that can be used to serialize selections, ranges, or nodes
      9 * to strings in a gazillion different ways.
     10 */
     11 
     12 #include <utility>
     13 
     14 #include "mozilla/Encoding.h"
     15 #include "mozilla/IntegerRange.h"
     16 #include "mozilla/Maybe.h"
     17 #include "mozilla/ScopeExit.h"
     18 #include "mozilla/StringBuffer.h"
     19 #include "mozilla/UniquePtr.h"
     20 #include "mozilla/dom/AbstractRange.h"
     21 #include "mozilla/dom/Comment.h"
     22 #include "mozilla/dom/Document.h"
     23 #include "mozilla/dom/DocumentType.h"
     24 #include "mozilla/dom/Element.h"
     25 #include "mozilla/dom/HTMLBRElement.h"
     26 #include "mozilla/dom/ProcessingInstruction.h"
     27 #include "mozilla/dom/Selection.h"
     28 #include "mozilla/dom/ShadowRoot.h"
     29 #include "mozilla/dom/Text.h"
     30 #include "nsCOMPtr.h"
     31 #include "nsCRT.h"
     32 #include "nsComponentManagerUtils.h"
     33 #include "nsContentUtils.h"
     34 #include "nsElementTable.h"
     35 #include "nsGkAtoms.h"
     36 #include "nsHTMLDocument.h"
     37 #include "nsIContent.h"
     38 #include "nsIContentInlines.h"
     39 #include "nsIContentSerializer.h"
     40 #include "nsIDocumentEncoder.h"
     41 #include "nsIFrame.h"
     42 #include "nsINode.h"
     43 #include "nsIOutputStream.h"
     44 #include "nsIScriptContext.h"
     45 #include "nsIScriptGlobalObject.h"
     46 #include "nsISupports.h"
     47 #include "nsITransferable.h"
     48 #include "nsLayoutUtils.h"
     49 #include "nsMimeTypes.h"
     50 #include "nsRange.h"
     51 #include "nsReadableUtils.h"
     52 #include "nsTArray.h"
     53 #include "nsUnicharUtils.h"
     54 #include "nscore.h"
     55 
     56 using namespace mozilla;
     57 using namespace mozilla::dom;
     58 
     59 enum nsRangeIterationDirection { kDirectionOut = -1, kDirectionIn = 1 };
     60 
     61 class TextStreamer {
     62 public:
     63  /**
     64   * @param aStream Will be kept alive by the TextStreamer.
     65   * @param aUnicodeEncoder Needs to be non-nullptr.
     66   */
     67  TextStreamer(nsIOutputStream& aStream, UniquePtr<Encoder> aUnicodeEncoder,
     68               bool aIsPlainText, nsAString& aOutputBuffer);
     69 
     70  /**
     71   * String will be truncated if it is written to stream.
     72   */
     73  nsresult FlushIfStringLongEnough();
     74 
     75  /**
     76   * String will be truncated.
     77   */
     78  nsresult ForceFlush();
     79 
     80 private:
     81  const static uint32_t kMaxLengthBeforeFlush = 1024;
     82 
     83  const static uint32_t kEncoderBufferSizeInBytes = 4096;
     84 
     85  nsresult EncodeAndWrite();
     86 
     87  nsresult EncodeAndWriteAndTruncate();
     88 
     89  const nsCOMPtr<nsIOutputStream> mStream;
     90  const UniquePtr<Encoder> mUnicodeEncoder;
     91  const bool mIsPlainText;
     92  nsAString& mOutputBuffer;
     93 };
     94 
     95 TextStreamer::TextStreamer(nsIOutputStream& aStream,
     96                           UniquePtr<Encoder> aUnicodeEncoder,
     97                           bool aIsPlainText, nsAString& aOutputBuffer)
     98    : mStream{&aStream},
     99      mUnicodeEncoder(std::move(aUnicodeEncoder)),
    100      mIsPlainText(aIsPlainText),
    101      mOutputBuffer(aOutputBuffer) {
    102  MOZ_ASSERT(mUnicodeEncoder);
    103 }
    104 
    105 nsresult TextStreamer::FlushIfStringLongEnough() {
    106  nsresult rv = NS_OK;
    107 
    108  if (mOutputBuffer.Length() > kMaxLengthBeforeFlush) {
    109    rv = EncodeAndWriteAndTruncate();
    110  }
    111 
    112  return rv;
    113 }
    114 
    115 nsresult TextStreamer::ForceFlush() { return EncodeAndWriteAndTruncate(); }
    116 
    117 nsresult TextStreamer::EncodeAndWrite() {
    118  if (mOutputBuffer.IsEmpty()) {
    119    return NS_OK;
    120  }
    121 
    122  uint8_t buffer[kEncoderBufferSizeInBytes];
    123  auto src = Span(mOutputBuffer);
    124  auto bufferSpan = Span(buffer);
    125  // Reserve space for terminator
    126  auto dst = bufferSpan.To(bufferSpan.Length() - 1);
    127  for (;;) {
    128    uint32_t result;
    129    size_t read;
    130    size_t written;
    131    if (mIsPlainText) {
    132      std::tie(result, read, written) =
    133          mUnicodeEncoder->EncodeFromUTF16WithoutReplacement(src, dst, false);
    134      if (result != kInputEmpty && result != kOutputFull) {
    135        // There's always room for one byte in the case of
    136        // an unmappable character, because otherwise
    137        // we'd have gotten `kOutputFull`.
    138        dst[written++] = '?';
    139      }
    140    } else {
    141      std::tie(result, read, written, std::ignore) =
    142          mUnicodeEncoder->EncodeFromUTF16(src, dst, false);
    143    }
    144    src = src.From(read);
    145    // Sadly, we still have test cases that implement nsIOutputStream in JS, so
    146    // the buffer needs to be zero-terminated for XPConnect to do its thing.
    147    // See bug 170416.
    148    bufferSpan[written] = 0;
    149    uint32_t streamWritten;
    150    nsresult rv = mStream->Write(reinterpret_cast<char*>(dst.Elements()),
    151                                 written, &streamWritten);
    152    if (NS_FAILED(rv)) {
    153      return rv;
    154    }
    155    if (result == kInputEmpty) {
    156      return NS_OK;
    157    }
    158  }
    159 }
    160 
    161 nsresult TextStreamer::EncodeAndWriteAndTruncate() {
    162  const nsresult rv = EncodeAndWrite();
    163  mOutputBuffer.Truncate();
    164  return rv;
    165 }
    166 
    167 /**
    168 * The scope may be limited to either a selection, range, or node.
    169 */
    170 class EncodingScope {
    171 public:
    172  /**
    173   * @return true, iff the scope is limited to a selection, range or node.
    174   */
    175  bool IsLimited() const;
    176 
    177  RefPtr<Selection> mSelection;
    178  RefPtr<nsRange> mRange;
    179  nsCOMPtr<nsINode> mNode;
    180  bool mNodeIsContainer = false;
    181 };
    182 
    183 bool EncodingScope::IsLimited() const { return mSelection || mRange || mNode; }
    184 
    185 struct RangeBoundariesInclusiveAncestorsAndOffsets {
    186  /**
    187   * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
    188   */
    189  using InclusiveAncestors = AutoTArray<nsIContent*, 8>;
    190 
    191  /**
    192   * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
    193   */
    194  using InclusiveAncestorsOffsets = AutoTArray<Maybe<uint32_t>, 8>;
    195 
    196  // The first node is the range's boundary node, the following ones the
    197  // ancestors.
    198  InclusiveAncestors mInclusiveAncestorsOfStart;
    199  // The first offset represents where at the boundary node the range starts.
    200  // Each other offset is the index of the child relative to its parent.
    201  InclusiveAncestorsOffsets mInclusiveAncestorsOffsetsOfStart;
    202 
    203  // The first node is the range's boundary node, the following one the
    204  // ancestors.
    205  InclusiveAncestors mInclusiveAncestorsOfEnd;
    206  // The first offset represents where at the boundary node the range ends.
    207  // Each other offset is the index of the child relative to its parent.
    208  InclusiveAncestorsOffsets mInclusiveAncestorsOffsetsOfEnd;
    209 };
    210 
    211 struct ContextInfoDepth {
    212  uint32_t mStart = 0;
    213  uint32_t mEnd = 0;
    214 };
    215 
    216 class nsDocumentEncoder : public nsIDocumentEncoder {
    217 protected:
    218  class RangeNodeContext {
    219   public:
    220    virtual ~RangeNodeContext() = default;
    221 
    222    virtual bool IncludeInContext(nsINode& aNode) const { return false; }
    223 
    224    virtual int32_t GetImmediateContextCount(
    225        const nsTArray<nsINode*>& aAncestorArray) const {
    226      return -1;
    227    }
    228  };
    229 
    230 public:
    231  nsDocumentEncoder();
    232 
    233 protected:
    234  /**
    235   * @param aRangeNodeContext has to be non-null.
    236   */
    237  explicit nsDocumentEncoder(UniquePtr<RangeNodeContext> aRangeNodeContext);
    238 
    239 public:
    240  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    241  NS_DECL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder)
    242  NS_DECL_NSIDOCUMENTENCODER
    243 
    244 protected:
    245  virtual ~nsDocumentEncoder();
    246 
    247  void Initialize(bool aClearCachedSerializer = true,
    248                  AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary =
    249                      AllowRangeCrossShadowBoundary::No);
    250 
    251  /**
    252   * @param aMaxLength As described at
    253   * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
    254   */
    255  nsresult SerializeDependingOnScope(uint32_t aMaxLength);
    256 
    257  nsresult SerializeSelection();
    258 
    259  nsresult SerializeNode();
    260 
    261  /**
    262   * @param aMaxLength As described at
    263   * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
    264   */
    265  nsresult SerializeWholeDocument(uint32_t aMaxLength);
    266 
    267  /**
    268   * @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.o
    269   */
    270  static bool IsInvisibleNodeAndShouldBeSkipped(const nsINode& aNode,
    271                                                const uint32_t aFlags) {
    272    if (aFlags & SkipInvisibleContent) {
    273      // Treat the visibility of the ShadowRoot as if it were
    274      // the host content.
    275      //
    276      // FIXME(emilio): I suspect instead of this a bunch of the GetParent()
    277      // calls here should be doing GetFlattenedTreeParent, then this condition
    278      // should be unreachable...
    279      const nsINode* node{&aNode};
    280      if (const ShadowRoot* shadowRoot = ShadowRoot::FromNode(node)) {
    281        node = shadowRoot->GetHost();
    282      }
    283 
    284      if (node->IsContent()) {
    285        nsIFrame* frame = node->AsContent()->GetPrimaryFrame();
    286        if (!frame) {
    287          if (node->IsElement() && node->AsElement()->IsDisplayContents()) {
    288            return false;
    289          }
    290          if (node->IsText()) {
    291            // We have already checked that our parent is visible.
    292            //
    293            // FIXME(emilio): Text not assigned to a <slot> in Shadow DOM should
    294            // probably return false...
    295            return false;
    296          }
    297          if (node->IsHTMLElement(nsGkAtoms::rp)) {
    298            // Ruby parentheses are part of ruby structure, hence
    299            // shouldn't be stripped out even if it is not displayed.
    300            return false;
    301          }
    302          return true;
    303        }
    304        if (node->IsText() &&
    305            (!frame->StyleVisibility()->IsVisible() ||
    306             frame->IsHiddenByContentVisibilityOnAnyAncestor())) {
    307          return true;
    308        }
    309      }
    310    }
    311    return false;
    312  }
    313 
    314  void ReleaseDocumentReferenceAndInitialize(bool aClearCachedSerializer);
    315 
    316  class MOZ_STACK_CLASS AutoReleaseDocumentIfNeeded final {
    317   public:
    318    explicit AutoReleaseDocumentIfNeeded(nsDocumentEncoder* aEncoder)
    319        : mEncoder(aEncoder) {}
    320 
    321    ~AutoReleaseDocumentIfNeeded() {
    322      if (mEncoder->mFlags & RequiresReinitAfterOutput) {
    323        const bool clearCachedSerializer = false;
    324        mEncoder->ReleaseDocumentReferenceAndInitialize(clearCachedSerializer);
    325      }
    326    }
    327 
    328   private:
    329    nsDocumentEncoder* mEncoder;
    330  };
    331 
    332  nsCOMPtr<Document> mDocument;
    333  EncodingScope mEncodingScope;
    334  nsCOMPtr<nsIContentSerializer> mSerializer;
    335 
    336  Maybe<TextStreamer> mTextStreamer;
    337  nsCOMPtr<nsIDocumentEncoderNodeFixup> mNodeFixup;
    338 
    339  nsString mMimeType;
    340  const Encoding* mEncoding;
    341  // Multiple of the flags defined in nsIDocumentEncoder.idl.
    342  uint32_t mFlags;
    343  uint32_t mWrapColumn;
    344  // Whether the serializer cares about being notified to scan elements to
    345  // keep track of whether they are preformatted.  This stores the out
    346  // argument of nsIContentSerializer::Init().
    347  bool mNeedsPreformatScanning;
    348  bool mIsCopying;  // Set to true only while copying
    349  RefPtr<StringBuffer> mCachedBuffer;
    350 
    351  class NodeSerializer {
    352   public:
    353    /**
    354     * @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.
    355     */
    356    NodeSerializer(const bool& aNeedsPreformatScanning,
    357                   const nsCOMPtr<nsIContentSerializer>& aSerializer,
    358                   const uint32_t& aFlags,
    359                   const nsCOMPtr<nsIDocumentEncoderNodeFixup>& aNodeFixup,
    360                   Maybe<TextStreamer>& aTextStreamer)
    361        : mNeedsPreformatScanning{aNeedsPreformatScanning},
    362          mSerializer{aSerializer},
    363          mFlags{aFlags},
    364          mNodeFixup{aNodeFixup},
    365          mTextStreamer{aTextStreamer} {}
    366 
    367    nsresult SerializeNodeStart(nsINode& aOriginalNode, int32_t aStartOffset,
    368                                int32_t aEndOffset,
    369                                nsINode* aFixupNode = nullptr) const;
    370 
    371    enum class SerializeRoot { eYes, eNo };
    372 
    373    nsresult SerializeToStringRecursive(nsINode* aNode,
    374                                        SerializeRoot aSerializeRoot,
    375                                        uint32_t aMaxLength = 0) const;
    376 
    377    nsresult SerializeNodeEnd(nsINode& aOriginalNode,
    378                              nsINode* aFixupNode = nullptr) const;
    379 
    380    [[nodiscard]] nsresult SerializeTextNode(nsINode& aNode,
    381                                             int32_t aStartOffset,
    382                                             int32_t aEndOffset) const;
    383 
    384    nsresult SerializeToStringIterative(nsINode* aNode) const;
    385 
    386   private:
    387    const bool& mNeedsPreformatScanning;
    388    const nsCOMPtr<nsIContentSerializer>& mSerializer;
    389    // Multiple of the flags defined in nsIDocumentEncoder.idl.
    390    const uint32_t& mFlags;
    391    const nsCOMPtr<nsIDocumentEncoderNodeFixup>& mNodeFixup;
    392    Maybe<TextStreamer>& mTextStreamer;
    393  };
    394 
    395  NodeSerializer mNodeSerializer;
    396 
    397  const UniquePtr<RangeNodeContext> mRangeNodeContext;
    398 
    399  struct RangeContextSerializer final {
    400    RangeContextSerializer(const RangeNodeContext& aRangeNodeContext,
    401                           const NodeSerializer& aNodeSerializer)
    402        : mDisableContextSerialize{false},
    403          mRangeNodeContext{aRangeNodeContext},
    404          mNodeSerializer{aNodeSerializer} {}
    405 
    406    nsresult SerializeRangeContextStart(
    407        const nsTArray<nsINode*>& aAncestorArray);
    408    nsresult SerializeRangeContextEnd();
    409 
    410    // Used when context has already been serialized for
    411    // table cell selections (where parent is <tr>)
    412    bool mDisableContextSerialize;
    413    AutoTArray<AutoTArray<nsINode*, 8>, 8> mRangeContexts;
    414 
    415    const RangeNodeContext& mRangeNodeContext;
    416 
    417   private:
    418    const NodeSerializer& mNodeSerializer;
    419  };
    420 
    421  RangeContextSerializer mRangeContextSerializer;
    422 
    423  struct RangeSerializer {
    424    // @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.
    425    RangeSerializer(const uint32_t& aFlags,
    426                    const NodeSerializer& aNodeSerializer,
    427                    RangeContextSerializer& aRangeContextSerializer)
    428        : mStartRootIndex{0},
    429          mEndRootIndex{0},
    430          mHaltRangeHint{false},
    431          mFlags{aFlags},
    432          mNodeSerializer{aNodeSerializer},
    433          mRangeContextSerializer{aRangeContextSerializer} {}
    434 
    435    void Initialize(AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary);
    436 
    437    /**
    438     * @param aDepth the distance (number of `GetParent` calls) from aNode to
    439     *               aRange's closest common inclusive ancestor.
    440     */
    441    nsresult SerializeRangeNodes(const nsRange* aRange, nsINode* aNode,
    442                                 int32_t aDepth);
    443 
    444    /**
    445     * Serialize aContent's children from aStartOffset to aEndOffset.
    446     *
    447     * @param aDepth the distance (number of `GetParent` calls) from aContent to
    448     *               aRange's closest common inclusive ancestor.
    449     */
    450    [[nodiscard]] nsresult SerializeChildrenOfContent(nsIContent& aContent,
    451                                                      uint32_t aStartOffset,
    452                                                      uint32_t aEndOffset,
    453                                                      const nsRange* aRange,
    454                                                      int32_t aDepth);
    455 
    456    nsresult SerializeRangeToString(const nsRange* aRange);
    457 
    458    /**
    459     * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
    460     */
    461    nsCOMPtr<nsINode> mClosestCommonInclusiveAncestorOfRange;
    462 
    463    /**
    464     * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
    465     */
    466    AutoTArray<nsINode*, 8> mCommonInclusiveAncestors;
    467 
    468    ContextInfoDepth mContextInfoDepth;
    469 
    470   private:
    471    struct StartAndEndContent {
    472      nsCOMPtr<nsIContent> mStart;
    473      nsCOMPtr<nsIContent> mEnd;
    474    };
    475 
    476    StartAndEndContent GetStartAndEndContentForRecursionLevel(
    477        int32_t aDepth) const;
    478 
    479    bool HasInvisibleParentAndShouldBeSkipped(nsINode& aNode) const;
    480 
    481    nsresult SerializeNodePartiallyContainedInRange(
    482        nsIContent& aContent, const StartAndEndContent& aStartAndEndContent,
    483        const nsRange& aRange, int32_t aDepth);
    484 
    485    nsresult SerializeTextNode(nsIContent& aContent,
    486                               const StartAndEndContent& aStartAndEndContent,
    487                               const nsRange& aRange) const;
    488 
    489    RangeBoundariesInclusiveAncestorsAndOffsets
    490        mRangeBoundariesInclusiveAncestorsAndOffsets;
    491    int32_t mStartRootIndex;
    492    int32_t mEndRootIndex;
    493    bool mHaltRangeHint;
    494 
    495    // Multiple of the flags defined in nsIDocumentEncoder.idl.
    496    const uint32_t& mFlags;
    497 
    498    const NodeSerializer& mNodeSerializer;
    499    RangeContextSerializer& mRangeContextSerializer;
    500 
    501    AllowRangeCrossShadowBoundary mAllowCrossShadowBoundary =
    502        AllowRangeCrossShadowBoundary::No;
    503  };
    504 
    505  RangeSerializer mRangeSerializer;
    506 };
    507 
    508 void nsDocumentEncoder::RangeSerializer::Initialize(
    509    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    510  mContextInfoDepth = {};
    511  mStartRootIndex = 0;
    512  mEndRootIndex = 0;
    513  mHaltRangeHint = false;
    514  mClosestCommonInclusiveAncestorOfRange = nullptr;
    515  mRangeBoundariesInclusiveAncestorsAndOffsets = {};
    516  mAllowCrossShadowBoundary = aAllowCrossShadowBoundary;
    517 }
    518 
    519 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder)
    520 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
    521    nsDocumentEncoder, ReleaseDocumentReferenceAndInitialize(true))
    522 
    523 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder)
    524  NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder)
    525  NS_INTERFACE_MAP_ENTRY(nsISupports)
    526 NS_INTERFACE_MAP_END
    527 
    528 NS_IMPL_CYCLE_COLLECTION(
    529    nsDocumentEncoder, mDocument, mEncodingScope.mSelection,
    530    mEncodingScope.mRange, mEncodingScope.mNode, mSerializer,
    531    mRangeSerializer.mClosestCommonInclusiveAncestorOfRange)
    532 
    533 nsDocumentEncoder::nsDocumentEncoder(
    534    UniquePtr<RangeNodeContext> aRangeNodeContext)
    535    : mEncoding(nullptr),
    536      mIsCopying(false),
    537      mCachedBuffer(nullptr),
    538      mNodeSerializer(mNeedsPreformatScanning, mSerializer, mFlags, mNodeFixup,
    539                      mTextStreamer),
    540      mRangeNodeContext(std::move(aRangeNodeContext)),
    541      mRangeContextSerializer(*mRangeNodeContext, mNodeSerializer),
    542      mRangeSerializer(mFlags, mNodeSerializer, mRangeContextSerializer) {
    543  MOZ_ASSERT(mRangeNodeContext);
    544 
    545  Initialize();
    546  mMimeType.AssignLiteral("text/plain");
    547 }
    548 
    549 nsDocumentEncoder::nsDocumentEncoder()
    550    : nsDocumentEncoder(MakeUnique<RangeNodeContext>()) {}
    551 
    552 void nsDocumentEncoder::Initialize(
    553    bool aClearCachedSerializer,
    554    AllowRangeCrossShadowBoundary aAllowCrossShadowBoundary) {
    555  mFlags = 0;
    556  mWrapColumn = 72;
    557  mRangeSerializer.Initialize(aAllowCrossShadowBoundary);
    558  mNeedsPreformatScanning = false;
    559  mRangeContextSerializer.mDisableContextSerialize = false;
    560  mEncodingScope = {};
    561  mNodeFixup = nullptr;
    562  if (aClearCachedSerializer) {
    563    mSerializer = nullptr;
    564  }
    565 }
    566 
    567 static bool ParentIsTR(nsIContent* aContent) {
    568  mozilla::dom::Element* parent = aContent->GetParentElement();
    569  if (!parent) {
    570    return false;
    571  }
    572  return parent->IsHTMLElement(nsGkAtoms::tr);
    573 }
    574 
    575 static AllowRangeCrossShadowBoundary GetAllowRangeCrossShadowBoundary(
    576    const uint32_t aFlags) {
    577  return (aFlags & nsIDocumentEncoder::AllowCrossShadowBoundary)
    578             ? AllowRangeCrossShadowBoundary::Yes
    579             : AllowRangeCrossShadowBoundary::No;
    580 }
    581 
    582 nsresult nsDocumentEncoder::SerializeDependingOnScope(uint32_t aMaxLength) {
    583  nsresult rv = NS_OK;
    584  if (mEncodingScope.mSelection) {
    585    rv = SerializeSelection();
    586  } else if (nsRange* range = mEncodingScope.mRange) {
    587    rv = mRangeSerializer.SerializeRangeToString(range);
    588  } else if (mEncodingScope.mNode) {
    589    rv = SerializeNode();
    590  } else {
    591    rv = SerializeWholeDocument(aMaxLength);
    592  }
    593 
    594  mEncodingScope = {};
    595 
    596  return rv;
    597 }
    598 
    599 nsresult nsDocumentEncoder::SerializeSelection() {
    600  NS_ENSURE_TRUE(mEncodingScope.mSelection, NS_ERROR_FAILURE);
    601 
    602  nsresult rv = NS_OK;
    603  const Selection* selection = mEncodingScope.mSelection;
    604  nsCOMPtr<nsINode> node;
    605  nsCOMPtr<nsINode> prevNode;
    606  uint32_t firstRangeStartDepth = 0;
    607  const uint32_t rangeCount = selection->RangeCount();
    608  for (const uint32_t i : IntegerRange(rangeCount)) {
    609    MOZ_ASSERT(selection->RangeCount() == rangeCount);
    610    RefPtr<const nsRange> range = selection->GetRangeAt(i);
    611 
    612    // Bug 236546: newlines not added when copying table cells into clipboard
    613    // Each selected cell shows up as a range containing a row with a single
    614    // cell get the row, compare it to previous row and emit </tr><tr> as
    615    // needed Bug 137450: Problem copying/pasting a table from a web page to
    616    // Excel. Each separate block of <tr></tr> produced above will be wrapped
    617    // by the immediate context. This assumes that you can't select cells that
    618    // are multiple selections from two tables simultaneously.
    619    node = ShadowDOMSelectionHelpers::GetStartContainer(
    620        range, GetAllowRangeCrossShadowBoundary(mFlags));
    621    NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
    622    if (node != prevNode) {
    623      if (prevNode) {
    624        rv = mNodeSerializer.SerializeNodeEnd(*prevNode);
    625        NS_ENSURE_SUCCESS(rv, rv);
    626      }
    627      nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(node);
    628      if (content && content->IsHTMLElement(nsGkAtoms::tr) &&
    629          !ParentIsTR(content)) {
    630        if (!prevNode) {
    631          // Went from a non-<tr> to a <tr>
    632          mRangeSerializer.mCommonInclusiveAncestors.Clear();
    633          nsContentUtils::GetInclusiveAncestors(
    634              node->GetParentNode(),
    635              mRangeSerializer.mCommonInclusiveAncestors);
    636          rv = mRangeContextSerializer.SerializeRangeContextStart(
    637              mRangeSerializer.mCommonInclusiveAncestors);
    638          NS_ENSURE_SUCCESS(rv, rv);
    639          // Don't let SerializeRangeToString serialize the context again
    640          mRangeContextSerializer.mDisableContextSerialize = true;
    641        }
    642 
    643        rv = mNodeSerializer.SerializeNodeStart(*node, 0, -1);
    644        NS_ENSURE_SUCCESS(rv, rv);
    645        prevNode = node;
    646      } else if (prevNode) {
    647        // Went from a <tr> to a non-<tr>
    648        mRangeContextSerializer.mDisableContextSerialize = false;
    649 
    650        // `mCommonInclusiveAncestors` is used in `EncodeToStringWithContext`
    651        // too. Update it here to mimic the old behavior.
    652        mRangeSerializer.mCommonInclusiveAncestors.Clear();
    653        nsContentUtils::GetInclusiveAncestors(
    654            prevNode->GetParentNode(),
    655            mRangeSerializer.mCommonInclusiveAncestors);
    656 
    657        rv = mRangeContextSerializer.SerializeRangeContextEnd();
    658        NS_ENSURE_SUCCESS(rv, rv);
    659        prevNode = nullptr;
    660      }
    661    }
    662 
    663    rv = mRangeSerializer.SerializeRangeToString(range);
    664    NS_ENSURE_SUCCESS(rv, rv);
    665    if (i == 0) {
    666      firstRangeStartDepth = mRangeSerializer.mContextInfoDepth.mStart;
    667    }
    668  }
    669  mRangeSerializer.mContextInfoDepth.mStart = firstRangeStartDepth;
    670 
    671  if (prevNode) {
    672    rv = mNodeSerializer.SerializeNodeEnd(*prevNode);
    673    NS_ENSURE_SUCCESS(rv, rv);
    674    mRangeContextSerializer.mDisableContextSerialize = false;
    675 
    676    // `mCommonInclusiveAncestors` is used in `EncodeToStringWithContext`
    677    // too. Update it here to mimic the old behavior.
    678    mRangeSerializer.mCommonInclusiveAncestors.Clear();
    679    nsContentUtils::GetInclusiveAncestors(
    680        prevNode->GetParentNode(), mRangeSerializer.mCommonInclusiveAncestors);
    681 
    682    rv = mRangeContextSerializer.SerializeRangeContextEnd();
    683    NS_ENSURE_SUCCESS(rv, rv);
    684  }
    685 
    686  // Just to be safe
    687  mRangeContextSerializer.mDisableContextSerialize = false;
    688 
    689  return rv;
    690 }
    691 
    692 nsresult nsDocumentEncoder::SerializeNode() {
    693  NS_ENSURE_TRUE(mEncodingScope.mNode, NS_ERROR_FAILURE);
    694 
    695  nsresult rv = NS_OK;
    696  nsINode* node = mEncodingScope.mNode;
    697  const bool nodeIsContainer = mEncodingScope.mNodeIsContainer;
    698  if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mTextStreamer &&
    699      nodeIsContainer) {
    700    rv = mNodeSerializer.SerializeToStringIterative(node);
    701  } else {
    702    rv = mNodeSerializer.SerializeToStringRecursive(
    703        node, nodeIsContainer ? NodeSerializer::SerializeRoot::eNo
    704                              : NodeSerializer::SerializeRoot::eYes);
    705  }
    706 
    707  return rv;
    708 }
    709 
    710 nsresult nsDocumentEncoder::SerializeWholeDocument(uint32_t aMaxLength) {
    711  NS_ENSURE_FALSE(mEncodingScope.mSelection, NS_ERROR_FAILURE);
    712  NS_ENSURE_FALSE(mEncodingScope.mRange, NS_ERROR_FAILURE);
    713  NS_ENSURE_FALSE(mEncodingScope.mNode, NS_ERROR_FAILURE);
    714 
    715  nsresult rv = mSerializer->AppendDocumentStart(mDocument);
    716  NS_ENSURE_SUCCESS(rv, rv);
    717 
    718  rv = mNodeSerializer.SerializeToStringRecursive(
    719      mDocument, NodeSerializer::SerializeRoot::eYes, aMaxLength);
    720  return rv;
    721 }
    722 
    723 nsDocumentEncoder::~nsDocumentEncoder() = default;
    724 
    725 NS_IMETHODIMP
    726 nsDocumentEncoder::Init(Document* aDocument, const nsAString& aMimeType,
    727                        uint32_t aFlags) {
    728  if (!aDocument) {
    729    return NS_ERROR_INVALID_ARG;
    730  }
    731 
    732  Initialize(!mMimeType.Equals(aMimeType),
    733             GetAllowRangeCrossShadowBoundary(aFlags));
    734 
    735  mDocument = aDocument;
    736 
    737  mMimeType = aMimeType;
    738 
    739  mFlags = aFlags;
    740  mIsCopying = false;
    741 
    742  return NS_OK;
    743 }
    744 
    745 NS_IMETHODIMP
    746 nsDocumentEncoder::SetWrapColumn(uint32_t aWC) {
    747  mWrapColumn = aWC;
    748  return NS_OK;
    749 }
    750 
    751 NS_IMETHODIMP
    752 nsDocumentEncoder::SetSelection(Selection* aSelection) {
    753  mEncodingScope.mSelection = aSelection;
    754  return NS_OK;
    755 }
    756 
    757 NS_IMETHODIMP
    758 nsDocumentEncoder::SetRange(nsRange* aRange) {
    759  mEncodingScope.mRange = aRange;
    760  return NS_OK;
    761 }
    762 
    763 NS_IMETHODIMP
    764 nsDocumentEncoder::SetNode(nsINode* aNode) {
    765  mEncodingScope.mNodeIsContainer = false;
    766  mEncodingScope.mNode = aNode;
    767  return NS_OK;
    768 }
    769 
    770 NS_IMETHODIMP
    771 nsDocumentEncoder::SetContainerNode(nsINode* aContainer) {
    772  mEncodingScope.mNodeIsContainer = true;
    773  mEncodingScope.mNode = aContainer;
    774  return NS_OK;
    775 }
    776 
    777 NS_IMETHODIMP
    778 nsDocumentEncoder::SetCharset(const nsACString& aCharset) {
    779  const Encoding* encoding = Encoding::ForLabel(aCharset);
    780  if (!encoding) {
    781    return NS_ERROR_UCONV_NOCONV;
    782  }
    783  mEncoding = encoding->OutputEncoding();
    784  return NS_OK;
    785 }
    786 
    787 NS_IMETHODIMP
    788 nsDocumentEncoder::GetMimeType(nsAString& aMimeType) {
    789  aMimeType = mMimeType;
    790  return NS_OK;
    791 }
    792 
    793 class FixupNodeDeterminer {
    794 public:
    795  FixupNodeDeterminer(nsIDocumentEncoderNodeFixup* aNodeFixup,
    796                      nsINode* aFixupNode, nsINode& aOriginalNode)
    797      : mIsSerializationOfFixupChildrenNeeded{false},
    798        mNodeFixup(aNodeFixup),
    799        mOriginalNode(aOriginalNode) {
    800    if (mNodeFixup) {
    801      if (aFixupNode) {
    802        mFixupNode = aFixupNode;
    803      } else {
    804        mNodeFixup->FixupNode(&mOriginalNode,
    805                              &mIsSerializationOfFixupChildrenNeeded,
    806                              getter_AddRefs(mFixupNode));
    807      }
    808    }
    809  }
    810 
    811  bool IsSerializationOfFixupChildrenNeeded() const {
    812    return mIsSerializationOfFixupChildrenNeeded;
    813  }
    814 
    815  /**
    816   * @return The fixup node, if available, otherwise the original node. The
    817   * former is kept alive by this object.
    818   */
    819  nsINode& GetFixupNodeFallBackToOriginalNode() const {
    820    return mFixupNode ? *mFixupNode : mOriginalNode;
    821  }
    822 
    823 private:
    824  bool mIsSerializationOfFixupChildrenNeeded;
    825  nsIDocumentEncoderNodeFixup* mNodeFixup;
    826  nsCOMPtr<nsINode> mFixupNode;
    827  nsINode& mOriginalNode;
    828 };
    829 
    830 nsresult nsDocumentEncoder::NodeSerializer::SerializeNodeStart(
    831    nsINode& aOriginalNode, int32_t aStartOffset, int32_t aEndOffset,
    832    nsINode* aFixupNode) const {
    833  if (mNeedsPreformatScanning) {
    834    if (aOriginalNode.IsElement()) {
    835      mSerializer->ScanElementForPreformat(aOriginalNode.AsElement());
    836    } else if (aOriginalNode.IsText()) {
    837      const nsCOMPtr<nsINode> parent = aOriginalNode.GetParent();
    838      if (parent && parent->IsElement()) {
    839        mSerializer->ScanElementForPreformat(parent->AsElement());
    840      }
    841    }
    842  }
    843 
    844  if (IsInvisibleNodeAndShouldBeSkipped(aOriginalNode, mFlags)) {
    845    return NS_OK;
    846  }
    847 
    848  FixupNodeDeterminer fixupNodeDeterminer{mNodeFixup, aFixupNode,
    849                                          aOriginalNode};
    850  nsINode* node = &fixupNodeDeterminer.GetFixupNodeFallBackToOriginalNode();
    851 
    852  nsresult rv = NS_OK;
    853 
    854  if (node->IsElement()) {
    855    if ((mFlags & (nsIDocumentEncoder::OutputPreformatted |
    856                   nsIDocumentEncoder::OutputDropInvisibleBreak)) &&
    857        nsLayoutUtils::IsInvisibleBreak(node)) {
    858      return rv;
    859    }
    860    rv = mSerializer->AppendElementStart(node->AsElement(),
    861                                         aOriginalNode.AsElement());
    862    return rv;
    863  }
    864 
    865  switch (node->NodeType()) {
    866    case nsINode::TEXT_NODE: {
    867      rv = mSerializer->AppendText(node->AsText(), aStartOffset, aEndOffset);
    868      break;
    869    }
    870    case nsINode::CDATA_SECTION_NODE: {
    871      rv = mSerializer->AppendCDATASection(node->AsText(), aStartOffset,
    872                                           aEndOffset);
    873      break;
    874    }
    875    case nsINode::PROCESSING_INSTRUCTION_NODE: {
    876      rv = mSerializer->AppendProcessingInstruction(
    877          static_cast<ProcessingInstruction*>(node), aStartOffset, aEndOffset);
    878      break;
    879    }
    880    case nsINode::COMMENT_NODE: {
    881      rv = mSerializer->AppendComment(static_cast<Comment*>(node), aStartOffset,
    882                                      aEndOffset);
    883      break;
    884    }
    885    case nsINode::DOCUMENT_TYPE_NODE: {
    886      rv = mSerializer->AppendDoctype(static_cast<DocumentType*>(node));
    887      break;
    888    }
    889  }
    890 
    891  return rv;
    892 }
    893 
    894 nsresult nsDocumentEncoder::NodeSerializer::SerializeNodeEnd(
    895    nsINode& aOriginalNode, nsINode* aFixupNode) const {
    896  if (mNeedsPreformatScanning) {
    897    if (aOriginalNode.IsElement()) {
    898      mSerializer->ForgetElementForPreformat(aOriginalNode.AsElement());
    899    } else if (aOriginalNode.IsText()) {
    900      const nsCOMPtr<nsINode> parent = aOriginalNode.GetParent();
    901      if (parent && parent->IsElement()) {
    902        mSerializer->ForgetElementForPreformat(parent->AsElement());
    903      }
    904    }
    905  }
    906 
    907  if (IsInvisibleNodeAndShouldBeSkipped(aOriginalNode, mFlags)) {
    908    return NS_OK;
    909  }
    910 
    911  nsresult rv = NS_OK;
    912 
    913  FixupNodeDeterminer fixupNodeDeterminer{mNodeFixup, aFixupNode,
    914                                          aOriginalNode};
    915  nsINode* node = &fixupNodeDeterminer.GetFixupNodeFallBackToOriginalNode();
    916 
    917  if (node->IsElement()) {
    918    rv = mSerializer->AppendElementEnd(node->AsElement(),
    919                                       aOriginalNode.AsElement());
    920  }
    921 
    922  return rv;
    923 }
    924 
    925 nsresult nsDocumentEncoder::NodeSerializer::SerializeToStringRecursive(
    926    nsINode* aNode, SerializeRoot aSerializeRoot, uint32_t aMaxLength) const {
    927  uint32_t outputLength{0};
    928  nsresult rv = mSerializer->GetOutputLength(outputLength);
    929  NS_ENSURE_SUCCESS(rv, rv);
    930 
    931  if (aMaxLength > 0 && outputLength >= aMaxLength) {
    932    return NS_OK;
    933  }
    934 
    935  NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
    936 
    937  if (IsInvisibleNodeAndShouldBeSkipped(*aNode, mFlags)) {
    938    return NS_OK;
    939  }
    940 
    941  FixupNodeDeterminer fixupNodeDeterminer{mNodeFixup, nullptr, *aNode};
    942  nsINode* maybeFixedNode =
    943      &fixupNodeDeterminer.GetFixupNodeFallBackToOriginalNode();
    944 
    945  if (mFlags & SkipInvisibleContent) {
    946    if (aNode->IsContent()) {
    947      if (nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame()) {
    948        if (!frame->IsSelectable()) {
    949          aSerializeRoot = SerializeRoot::eNo;
    950        }
    951      }
    952    }
    953  }
    954 
    955  if (aSerializeRoot == SerializeRoot::eYes) {
    956    int32_t endOffset = -1;
    957    if (aMaxLength > 0) {
    958      MOZ_ASSERT(aMaxLength >= outputLength);
    959      endOffset = aMaxLength - outputLength;
    960    }
    961    rv = SerializeNodeStart(*aNode, 0, endOffset, maybeFixedNode);
    962    NS_ENSURE_SUCCESS(rv, rv);
    963  }
    964 
    965  ShadowRoot* shadowRoot = ShadowDOMSelectionHelpers::GetShadowRoot(
    966      aNode, GetAllowRangeCrossShadowBoundary(mFlags));
    967 
    968  if (shadowRoot) {
    969    MOZ_ASSERT(StaticPrefs::dom_shadowdom_selection_across_boundary_enabled());
    970    // Serialize the ShadowRoot first when the entire node needs to be
    971    // serialized.
    972    SerializeToStringRecursive(shadowRoot, aSerializeRoot, aMaxLength);
    973  }
    974 
    975  nsINode* node = fixupNodeDeterminer.IsSerializationOfFixupChildrenNeeded()
    976                      ? maybeFixedNode
    977                      : aNode;
    978 
    979  int32_t counter = -1;
    980 
    981  const bool allowCrossShadowBoundary =
    982      GetAllowRangeCrossShadowBoundary(mFlags) ==
    983      AllowRangeCrossShadowBoundary::Yes;
    984  auto GetNextNode = [&counter, node, allowCrossShadowBoundary](
    985                         nsINode* aCurrentNode) -> nsINode* {
    986    ++counter;
    987    if (allowCrossShadowBoundary) {
    988      if (const auto* slot = HTMLSlotElement::FromNode(node)) {
    989        auto assigned = slot->AssignedNodes();
    990        if (size_t(counter) < assigned.Length()) {
    991          return assigned[counter];
    992        }
    993        return nullptr;
    994      }
    995    }
    996 
    997    if (counter == 0) {
    998      return node->GetFirstChildOfTemplateOrNode();
    999    }
   1000    // counter isn't really used for non-slot cases.
   1001    return aCurrentNode->GetNextSibling();
   1002  };
   1003 
   1004  if (!shadowRoot) {
   1005    // We only iterate light DOM children of aNode if it isn't a shadow host
   1006    // since it doesn't make sense to iterate them this way. Slotted contents
   1007    // has been handled by serializing the <slot> element.
   1008    for (nsINode* child = GetNextNode(nullptr); child;
   1009         child = GetNextNode(child)) {
   1010      rv = SerializeToStringRecursive(child, SerializeRoot::eYes, aMaxLength);
   1011      NS_ENSURE_SUCCESS(rv, rv);
   1012    }
   1013  }
   1014 
   1015  if (aSerializeRoot == SerializeRoot::eYes) {
   1016    rv = SerializeNodeEnd(*aNode, maybeFixedNode);
   1017    NS_ENSURE_SUCCESS(rv, rv);
   1018  }
   1019 
   1020  if (mTextStreamer) {
   1021    rv = mTextStreamer->FlushIfStringLongEnough();
   1022  }
   1023 
   1024  return rv;
   1025 }
   1026 
   1027 nsresult nsDocumentEncoder::NodeSerializer::SerializeToStringIterative(
   1028    nsINode* aNode) const {
   1029  nsresult rv;
   1030 
   1031  nsINode* node = aNode->GetFirstChildOfTemplateOrNode();
   1032  while (node) {
   1033    nsINode* current = node;
   1034    rv = SerializeNodeStart(*current, 0, -1, current);
   1035    NS_ENSURE_SUCCESS(rv, rv);
   1036    node = current->GetFirstChildOfTemplateOrNode();
   1037    while (!node && current && current != aNode) {
   1038      rv = SerializeNodeEnd(*current);
   1039      NS_ENSURE_SUCCESS(rv, rv);
   1040      // Check if we have siblings.
   1041      node = current->GetNextSibling();
   1042      if (!node) {
   1043        // Perhaps parent node has siblings.
   1044        current = current->GetParentNode();
   1045 
   1046        // Handle template element. If the parent is a template's content,
   1047        // then adjust the parent to be the template element.
   1048        if (current && current != aNode && current->IsDocumentFragment()) {
   1049          nsIContent* host = current->AsDocumentFragment()->GetHost();
   1050          if (host && host->IsHTMLElement(nsGkAtoms::_template)) {
   1051            current = host;
   1052          }
   1053        }
   1054      }
   1055    }
   1056  }
   1057 
   1058  return NS_OK;
   1059 }
   1060 
   1061 static bool IsTextNode(nsINode* aNode) { return aNode && aNode->IsText(); }
   1062 
   1063 nsresult nsDocumentEncoder::NodeSerializer::SerializeTextNode(
   1064    nsINode& aNode, int32_t aStartOffset, int32_t aEndOffset) const {
   1065  MOZ_ASSERT(IsTextNode(&aNode));
   1066 
   1067  nsresult rv = SerializeNodeStart(aNode, aStartOffset, aEndOffset);
   1068  NS_ENSURE_SUCCESS(rv, rv);
   1069  rv = SerializeNodeEnd(aNode);
   1070  NS_ENSURE_SUCCESS(rv, rv);
   1071  return rv;
   1072 }
   1073 
   1074 nsDocumentEncoder::RangeSerializer::StartAndEndContent
   1075 nsDocumentEncoder::RangeSerializer::GetStartAndEndContentForRecursionLevel(
   1076    const int32_t aDepth) const {
   1077  StartAndEndContent result;
   1078 
   1079  const auto& inclusiveAncestorsOfStart =
   1080      mRangeBoundariesInclusiveAncestorsAndOffsets.mInclusiveAncestorsOfStart;
   1081  const auto& inclusiveAncestorsOfEnd =
   1082      mRangeBoundariesInclusiveAncestorsAndOffsets.mInclusiveAncestorsOfEnd;
   1083  int32_t start = mStartRootIndex - aDepth;
   1084  if (start >= 0 && (uint32_t)start <= inclusiveAncestorsOfStart.Length()) {
   1085    result.mStart = inclusiveAncestorsOfStart[start];
   1086  }
   1087 
   1088  int32_t end = mEndRootIndex - aDepth;
   1089  if (end >= 0 && (uint32_t)end <= inclusiveAncestorsOfEnd.Length()) {
   1090    result.mEnd = inclusiveAncestorsOfEnd[end];
   1091  }
   1092 
   1093  return result;
   1094 }
   1095 
   1096 nsresult nsDocumentEncoder::RangeSerializer::SerializeTextNode(
   1097    nsIContent& aContent, const StartAndEndContent& aStartAndEndContent,
   1098    const nsRange& aRange) const {
   1099  const int32_t startOffset = (aStartAndEndContent.mStart == &aContent)
   1100                                  ? ShadowDOMSelectionHelpers::StartOffset(
   1101                                        &aRange, mAllowCrossShadowBoundary)
   1102                                  : 0;
   1103  const int32_t endOffset = (aStartAndEndContent.mEnd == &aContent)
   1104                                ? ShadowDOMSelectionHelpers::EndOffset(
   1105                                      &aRange, mAllowCrossShadowBoundary)
   1106                                : -1;
   1107  return mNodeSerializer.SerializeTextNode(aContent, startOffset, endOffset);
   1108 }
   1109 
   1110 nsresult nsDocumentEncoder::RangeSerializer::SerializeRangeNodes(
   1111    const nsRange* const aRange, nsINode* const aNode, const int32_t aDepth) {
   1112  MOZ_ASSERT(aDepth >= 0);
   1113  MOZ_ASSERT(aRange);
   1114 
   1115  nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(aNode);
   1116  NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
   1117 
   1118  if (nsDocumentEncoder::IsInvisibleNodeAndShouldBeSkipped(*aNode, mFlags)) {
   1119    return NS_OK;
   1120  }
   1121 
   1122  nsresult rv = NS_OK;
   1123 
   1124  StartAndEndContent startAndEndContent =
   1125      GetStartAndEndContentForRecursionLevel(aDepth);
   1126 
   1127  if (startAndEndContent.mStart != content &&
   1128      startAndEndContent.mEnd != content) {
   1129    // node is completely contained in range.  Serialize the whole subtree
   1130    // rooted by this node.
   1131    rv = mNodeSerializer.SerializeToStringRecursive(
   1132        aNode, NodeSerializer::SerializeRoot::eYes);
   1133    NS_ENSURE_SUCCESS(rv, rv);
   1134  } else {
   1135    rv = SerializeNodePartiallyContainedInRange(*content, startAndEndContent,
   1136                                                *aRange, aDepth);
   1137    if (NS_WARN_IF(NS_FAILED(rv))) {
   1138      return rv;
   1139    }
   1140  }
   1141  return NS_OK;
   1142 }
   1143 
   1144 nsresult
   1145 nsDocumentEncoder::RangeSerializer::SerializeNodePartiallyContainedInRange(
   1146    nsIContent& aContent, const StartAndEndContent& aStartAndEndContent,
   1147    const nsRange& aRange, const int32_t aDepth) {
   1148  // due to implementation it is impossible for text node to be both start and
   1149  // end of range.  We would have handled that case without getting here.
   1150  // XXXsmaug What does this all mean?
   1151  if (IsTextNode(&aContent)) {
   1152    nsresult rv = SerializeTextNode(aContent, aStartAndEndContent, aRange);
   1153    NS_ENSURE_SUCCESS(rv, rv);
   1154  } else {
   1155    if (&aContent != mClosestCommonInclusiveAncestorOfRange) {
   1156      if (mRangeContextSerializer.mRangeNodeContext.IncludeInContext(
   1157              aContent)) {
   1158        // halt the incrementing of mContextInfoDepth.  This
   1159        // is so paste client will include this node in paste.
   1160        mHaltRangeHint = true;
   1161      }
   1162      if ((aStartAndEndContent.mStart == &aContent) && !mHaltRangeHint) {
   1163        ++mContextInfoDepth.mStart;
   1164      }
   1165      if ((aStartAndEndContent.mEnd == &aContent) && !mHaltRangeHint) {
   1166        ++mContextInfoDepth.mEnd;
   1167      }
   1168 
   1169      // serialize the start of this node
   1170      nsresult rv = mNodeSerializer.SerializeNodeStart(aContent, 0, -1);
   1171      NS_ENSURE_SUCCESS(rv, rv);
   1172    }
   1173 
   1174    const auto& inclusiveAncestorsOffsetsOfStart =
   1175        mRangeBoundariesInclusiveAncestorsAndOffsets
   1176            .mInclusiveAncestorsOffsetsOfStart;
   1177    const auto& inclusiveAncestorsOffsetsOfEnd =
   1178        mRangeBoundariesInclusiveAncestorsAndOffsets
   1179            .mInclusiveAncestorsOffsetsOfEnd;
   1180    // do some calculations that will tell us which children of this
   1181    // node are in the range.
   1182    Maybe<uint32_t> startOffset = Some(0);
   1183    Maybe<uint32_t> endOffset;
   1184    if (aStartAndEndContent.mStart == &aContent && mStartRootIndex >= aDepth) {
   1185      startOffset = inclusiveAncestorsOffsetsOfStart[mStartRootIndex - aDepth];
   1186    }
   1187    if (aStartAndEndContent.mEnd == &aContent && mEndRootIndex >= aDepth) {
   1188      endOffset = inclusiveAncestorsOffsetsOfEnd[mEndRootIndex - aDepth];
   1189    }
   1190    // generated aContent will cause offset values of Nothing to be returned.
   1191    if (startOffset.isNothing()) {
   1192      startOffset = Some(0);
   1193    }
   1194    if (endOffset.isNothing()) {
   1195      endOffset = Some(aContent.GetChildCount());
   1196 
   1197      if (mAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
   1198        if (const auto* slot = HTMLSlotElement::FromNode(aContent)) {
   1199          const auto& assignedNodes = slot->AssignedNodes();
   1200          if (!assignedNodes.IsEmpty()) {
   1201            endOffset = Some(assignedNodes.Length());
   1202          }
   1203        }
   1204      }
   1205    } else {
   1206      // if we are at the "tip" of the selection, endOffset is fine.
   1207      // otherwise, we need to add one.  This is because of the semantics
   1208      // of the offset list created by GetInclusiveAncestorsAndOffsets().  The
   1209      // intermediate points on the list use the endOffset of the
   1210      // location of the ancestor, rather than just past it.  So we need
   1211      // to add one here in order to include it in the children we serialize.
   1212      const nsINode* endContainer = ShadowDOMSelectionHelpers::GetEndContainer(
   1213          &aRange, mAllowCrossShadowBoundary);
   1214      if (&aContent != endContainer) {
   1215        MOZ_ASSERT(*endOffset != UINT32_MAX);
   1216        endOffset.ref()++;
   1217      }
   1218    }
   1219 
   1220    MOZ_ASSERT(endOffset.isSome());
   1221    nsresult rv = SerializeChildrenOfContent(aContent, *startOffset, *endOffset,
   1222                                             &aRange, aDepth);
   1223    NS_ENSURE_SUCCESS(rv, rv);
   1224 
   1225    // serialize the end of this node
   1226    if (&aContent != mClosestCommonInclusiveAncestorOfRange) {
   1227      nsresult rv = mNodeSerializer.SerializeNodeEnd(aContent);
   1228      NS_ENSURE_SUCCESS(rv, rv);
   1229    }
   1230  }
   1231 
   1232  return NS_OK;
   1233 }
   1234 
   1235 nsresult nsDocumentEncoder::RangeSerializer::SerializeChildrenOfContent(
   1236    nsIContent& aContent, uint32_t aStartOffset, uint32_t aEndOffset,
   1237    const nsRange* aRange, int32_t aDepth) {
   1238  ShadowRoot* shadowRoot = ShadowDOMSelectionHelpers::GetShadowRoot(
   1239      &aContent, mAllowCrossShadowBoundary);
   1240  if (shadowRoot) {
   1241    // Serialize the ShadowRoot when the entire node needs to be serialized.
   1242    // Return early to skip light DOM children.
   1243    SerializeRangeNodes(aRange, shadowRoot, aDepth + 1);
   1244    return NS_OK;
   1245  }
   1246 
   1247  if (!aEndOffset) {
   1248    return NS_OK;
   1249  }
   1250 
   1251  nsINode* childAsNode =
   1252      mAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes
   1253          ? aContent.GetChildAtInFlatTree(aStartOffset)
   1254          : aContent.GetChildAt_Deprecated(aStartOffset);
   1255 
   1256  MOZ_ASSERT_IF(childAsNode, childAsNode->IsContent());
   1257 
   1258  auto GetNextSibling = [this, &aContent](
   1259                            nsINode* aCurrentNode,
   1260                            uint32_t aCurrentIndex) -> nsIContent* {
   1261    if (mAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
   1262      if (const auto* slot = HTMLSlotElement::FromNode(&aContent)) {
   1263        auto assigned = slot->AssignedNodes();
   1264        if (++aCurrentIndex < assigned.Length()) {
   1265          return nsIContent::FromNode(assigned[aCurrentIndex]);
   1266        }
   1267        return nullptr;
   1268      }
   1269    }
   1270 
   1271    return aCurrentNode->GetNextSibling();
   1272  };
   1273 
   1274  for (size_t j = aStartOffset; childAsNode && j < aEndOffset; ++j) {
   1275    nsresult rv{NS_OK};
   1276    const bool isFirstOrLastNodeToSerialize =
   1277        j == aStartOffset || j == aEndOffset - 1;
   1278    if (isFirstOrLastNodeToSerialize) {
   1279      rv = SerializeRangeNodes(aRange, childAsNode, aDepth + 1);
   1280    } else {
   1281      rv = mNodeSerializer.SerializeToStringRecursive(
   1282          childAsNode, NodeSerializer::SerializeRoot::eYes);
   1283    }
   1284 
   1285    if (NS_FAILED(rv)) {
   1286      return rv;
   1287    }
   1288 
   1289    childAsNode = GetNextSibling(childAsNode, j);
   1290  }
   1291 
   1292  return NS_OK;
   1293 }
   1294 
   1295 nsresult nsDocumentEncoder::RangeContextSerializer::SerializeRangeContextStart(
   1296    const nsTArray<nsINode*>& aAncestorArray) {
   1297  if (mDisableContextSerialize) {
   1298    return NS_OK;
   1299  }
   1300 
   1301  AutoTArray<nsINode*, 8>* serializedContext = mRangeContexts.AppendElement();
   1302 
   1303  int32_t i = aAncestorArray.Length(), j;
   1304  nsresult rv = NS_OK;
   1305 
   1306  // currently only for table-related elements; see Bug 137450
   1307  j = mRangeNodeContext.GetImmediateContextCount(aAncestorArray);
   1308 
   1309  while (i > 0) {
   1310    nsINode* node = aAncestorArray.ElementAt(--i);
   1311    if (!node) break;
   1312 
   1313    // Either a general inclusion or as immediate context
   1314    if (mRangeNodeContext.IncludeInContext(*node) || i < j) {
   1315      rv = mNodeSerializer.SerializeNodeStart(*node, 0, -1);
   1316      serializedContext->AppendElement(node);
   1317      if (NS_FAILED(rv)) break;
   1318    }
   1319  }
   1320 
   1321  return rv;
   1322 }
   1323 
   1324 nsresult nsDocumentEncoder::RangeContextSerializer::SerializeRangeContextEnd() {
   1325  if (mDisableContextSerialize) {
   1326    return NS_OK;
   1327  }
   1328 
   1329  MOZ_RELEASE_ASSERT(!mRangeContexts.IsEmpty(),
   1330                     "Tried to end context without starting one.");
   1331  AutoTArray<nsINode*, 8>& serializedContext = mRangeContexts.LastElement();
   1332 
   1333  nsresult rv = NS_OK;
   1334  for (nsINode* node : Reversed(serializedContext)) {
   1335    rv = mNodeSerializer.SerializeNodeEnd(*node);
   1336 
   1337    if (NS_FAILED(rv)) break;
   1338  }
   1339 
   1340  mRangeContexts.RemoveLastElement();
   1341  return rv;
   1342 }
   1343 
   1344 bool nsDocumentEncoder::RangeSerializer::HasInvisibleParentAndShouldBeSkipped(
   1345    nsINode& aNode) const {
   1346  if (!(mFlags & SkipInvisibleContent)) {
   1347    return false;
   1348  }
   1349 
   1350  // Check that the parent is visible if we don't a frame.
   1351  // IsInvisibleNodeAndShouldBeSkipped() will do it when there's a frame.
   1352  nsCOMPtr<nsIContent> content = nsIContent::FromNode(aNode);
   1353  if (content && !content->GetPrimaryFrame()) {
   1354    nsIContent* parent = content->GetParent();
   1355    return !parent || IsInvisibleNodeAndShouldBeSkipped(*parent, mFlags);
   1356  }
   1357 
   1358  return false;
   1359 }
   1360 
   1361 nsresult nsDocumentEncoder::RangeSerializer::SerializeRangeToString(
   1362    const nsRange* aRange) {
   1363  if (!aRange ||
   1364      (aRange->Collapsed() &&
   1365       (mAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::No ||
   1366        !aRange->MayCrossShadowBoundary()))) {
   1367    return NS_OK;
   1368  }
   1369 
   1370  // Consider a case where the boundary of the selection is ShadowRoot (ie, the
   1371  // first child of ShadowRoot is selected, so ShadowRoot is the container hence
   1372  // the boundary), allowing GetClosestCommonInclusiveAncestor to cross the
   1373  // boundary can return the host element as the container.
   1374  // SerializeRangeContextStart doesn't support this case.
   1375  mClosestCommonInclusiveAncestorOfRange =
   1376      aRange->GetClosestCommonInclusiveAncestor(mAllowCrossShadowBoundary);
   1377 
   1378  if (!mClosestCommonInclusiveAncestorOfRange) {
   1379    return NS_OK;
   1380  }
   1381 
   1382  nsINode* startContainer = ShadowDOMSelectionHelpers::GetStartContainer(
   1383      aRange, mAllowCrossShadowBoundary);
   1384  NS_ENSURE_TRUE(startContainer, NS_ERROR_FAILURE);
   1385  const int32_t startOffset =
   1386      ShadowDOMSelectionHelpers::StartOffset(aRange, mAllowCrossShadowBoundary);
   1387 
   1388  nsINode* endContainer = ShadowDOMSelectionHelpers::GetEndContainer(
   1389      aRange, mAllowCrossShadowBoundary);
   1390  NS_ENSURE_TRUE(endContainer, NS_ERROR_FAILURE);
   1391  const int32_t endOffset =
   1392      ShadowDOMSelectionHelpers::EndOffset(aRange, mAllowCrossShadowBoundary);
   1393 
   1394  mContextInfoDepth = {};
   1395  mCommonInclusiveAncestors.Clear();
   1396 
   1397  mRangeBoundariesInclusiveAncestorsAndOffsets = {};
   1398  auto& inclusiveAncestorsOfStart =
   1399      mRangeBoundariesInclusiveAncestorsAndOffsets.mInclusiveAncestorsOfStart;
   1400  auto& inclusiveAncestorsOffsetsOfStart =
   1401      mRangeBoundariesInclusiveAncestorsAndOffsets
   1402          .mInclusiveAncestorsOffsetsOfStart;
   1403  auto& inclusiveAncestorsOfEnd =
   1404      mRangeBoundariesInclusiveAncestorsAndOffsets.mInclusiveAncestorsOfEnd;
   1405  auto& inclusiveAncestorsOffsetsOfEnd =
   1406      mRangeBoundariesInclusiveAncestorsAndOffsets
   1407          .mInclusiveAncestorsOffsetsOfEnd;
   1408 
   1409  nsContentUtils::GetInclusiveAncestors(mClosestCommonInclusiveAncestorOfRange,
   1410                                        mCommonInclusiveAncestors);
   1411  if (mAllowCrossShadowBoundary == AllowRangeCrossShadowBoundary::Yes) {
   1412    nsContentUtils::GetFlattenedTreeAncestorsAndOffsets(
   1413        startContainer, startOffset, inclusiveAncestorsOfStart,
   1414        inclusiveAncestorsOffsetsOfStart);
   1415    nsContentUtils::GetFlattenedTreeAncestorsAndOffsets(
   1416        endContainer, endOffset, inclusiveAncestorsOfEnd,
   1417        inclusiveAncestorsOffsetsOfEnd);
   1418  } else {
   1419    nsContentUtils::GetInclusiveAncestorsAndOffsets(
   1420        startContainer, startOffset, inclusiveAncestorsOfStart,
   1421        inclusiveAncestorsOffsetsOfStart);
   1422    nsContentUtils::GetInclusiveAncestorsAndOffsets(
   1423        endContainer, endOffset, inclusiveAncestorsOfEnd,
   1424        inclusiveAncestorsOffsetsOfEnd);
   1425  }
   1426 
   1427  nsCOMPtr<nsIContent> commonContent =
   1428      nsIContent::FromNodeOrNull(mClosestCommonInclusiveAncestorOfRange);
   1429  mStartRootIndex = inclusiveAncestorsOfStart.IndexOf(commonContent);
   1430  mEndRootIndex = inclusiveAncestorsOfEnd.IndexOf(commonContent);
   1431 
   1432  nsresult rv = NS_OK;
   1433 
   1434  rv = mRangeContextSerializer.SerializeRangeContextStart(
   1435      mCommonInclusiveAncestors);
   1436  NS_ENSURE_SUCCESS(rv, rv);
   1437 
   1438  if (startContainer == endContainer && IsTextNode(startContainer)) {
   1439    if (HasInvisibleParentAndShouldBeSkipped(*startContainer)) {
   1440      return NS_OK;
   1441    }
   1442    rv = mNodeSerializer.SerializeTextNode(*startContainer, startOffset,
   1443                                           endOffset);
   1444    NS_ENSURE_SUCCESS(rv, rv);
   1445  } else {
   1446    rv = SerializeRangeNodes(aRange, mClosestCommonInclusiveAncestorOfRange, 0);
   1447    NS_ENSURE_SUCCESS(rv, rv);
   1448  }
   1449  rv = mRangeContextSerializer.SerializeRangeContextEnd();
   1450  NS_ENSURE_SUCCESS(rv, rv);
   1451 
   1452  return rv;
   1453 }
   1454 
   1455 void nsDocumentEncoder::ReleaseDocumentReferenceAndInitialize(
   1456    bool aClearCachedSerializer) {
   1457  mDocument = nullptr;
   1458 
   1459  Initialize(aClearCachedSerializer);
   1460 }
   1461 
   1462 NS_IMETHODIMP
   1463 nsDocumentEncoder::EncodeToString(nsAString& aOutputString) {
   1464  return EncodeToStringWithMaxLength(0, aOutputString);
   1465 }
   1466 
   1467 NS_IMETHODIMP
   1468 nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
   1469                                               nsAString& aOutputString) {
   1470  MOZ_ASSERT(mRangeContextSerializer.mRangeContexts.IsEmpty(),
   1471             "Re-entrant call to nsDocumentEncoder.");
   1472  auto rangeContextGuard =
   1473      MakeScopeExit([&] { mRangeContextSerializer.mRangeContexts.Clear(); });
   1474 
   1475  if (!mDocument) return NS_ERROR_NOT_INITIALIZED;
   1476 
   1477  AutoReleaseDocumentIfNeeded autoReleaseDocument(this);
   1478 
   1479  aOutputString.Truncate();
   1480 
   1481  nsString output;
   1482  static const size_t kStringBufferSizeInBytes = 2048;
   1483  if (!mCachedBuffer) {
   1484    mCachedBuffer = StringBuffer::Alloc(kStringBufferSizeInBytes);
   1485    if (NS_WARN_IF(!mCachedBuffer)) {
   1486      return NS_ERROR_OUT_OF_MEMORY;
   1487    }
   1488  }
   1489  NS_ASSERTION(
   1490      !mCachedBuffer->IsReadonly(),
   1491      "nsIDocumentEncoder shouldn't keep reference to non-readonly buffer!");
   1492  static_cast<char16_t*>(mCachedBuffer->Data())[0] = char16_t(0);
   1493  output.Assign(mCachedBuffer.forget(), 0);
   1494 
   1495  if (!mSerializer) {
   1496    nsAutoCString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX);
   1497    AppendUTF16toUTF8(mMimeType, progId);
   1498 
   1499    mSerializer = do_CreateInstance(progId.get());
   1500    NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED);
   1501  }
   1502 
   1503  nsresult rv = NS_OK;
   1504 
   1505  bool rewriteEncodingDeclaration =
   1506      !mEncodingScope.IsLimited() &&
   1507      !(mFlags & OutputDontRewriteEncodingDeclaration);
   1508  mSerializer->Init(mFlags, mWrapColumn, mEncoding, mIsCopying,
   1509                    rewriteEncodingDeclaration, &mNeedsPreformatScanning,
   1510                    output);
   1511 
   1512  rv = SerializeDependingOnScope(aMaxLength);
   1513  NS_ENSURE_SUCCESS(rv, rv);
   1514 
   1515  rv = mSerializer->FlushAndFinish();
   1516 
   1517  // We have to be careful how we set aOutputString, because we don't
   1518  // want it to end up sharing mCachedBuffer if we plan to reuse it.
   1519  bool setOutput = false;
   1520  MOZ_ASSERT(!mCachedBuffer);
   1521  // Try to cache the buffer.
   1522  if (StringBuffer* outputBuffer = output.GetStringBuffer()) {
   1523    if (outputBuffer->StorageSize() == kStringBufferSizeInBytes &&
   1524        !outputBuffer->IsReadonly()) {
   1525      mCachedBuffer = outputBuffer;
   1526    } else if (NS_SUCCEEDED(rv)) {
   1527      aOutputString.Assign(outputBuffer, output.Length());
   1528      setOutput = true;
   1529    }
   1530  }
   1531 
   1532  if (!setOutput && NS_SUCCEEDED(rv)) {
   1533    aOutputString.Append(output.get(), output.Length());
   1534  }
   1535 
   1536  return rv;
   1537 }
   1538 
   1539 NS_IMETHODIMP
   1540 nsDocumentEncoder::EncodeToStream(nsIOutputStream* aStream) {
   1541  MOZ_ASSERT(mRangeContextSerializer.mRangeContexts.IsEmpty(),
   1542             "Re-entrant call to nsDocumentEncoder.");
   1543  auto rangeContextGuard =
   1544      MakeScopeExit([&] { mRangeContextSerializer.mRangeContexts.Clear(); });
   1545  NS_ENSURE_ARG_POINTER(aStream);
   1546 
   1547  nsresult rv = NS_OK;
   1548 
   1549  if (!mDocument) return NS_ERROR_NOT_INITIALIZED;
   1550 
   1551  if (!mEncoding) {
   1552    return NS_ERROR_UCONV_NOCONV;
   1553  }
   1554 
   1555  nsAutoString buf;
   1556  const bool isPlainText = mMimeType.LowerCaseEqualsLiteral(kTextMime);
   1557  mTextStreamer.emplace(*aStream, mEncoding->NewEncoder(), isPlainText, buf);
   1558 
   1559  rv = EncodeToString(buf);
   1560 
   1561  // Force a flush of the last chunk of data.
   1562  rv = mTextStreamer->ForceFlush();
   1563  NS_ENSURE_SUCCESS(rv, rv);
   1564 
   1565  mTextStreamer.reset();
   1566 
   1567  return rv;
   1568 }
   1569 
   1570 NS_IMETHODIMP
   1571 nsDocumentEncoder::EncodeToStringWithContext(nsAString& aContextString,
   1572                                             nsAString& aInfoString,
   1573                                             nsAString& aEncodedString) {
   1574  return NS_ERROR_NOT_IMPLEMENTED;
   1575 }
   1576 
   1577 NS_IMETHODIMP
   1578 nsDocumentEncoder::SetNodeFixup(nsIDocumentEncoderNodeFixup* aFixup) {
   1579  mNodeFixup = aFixup;
   1580  return NS_OK;
   1581 }
   1582 
   1583 bool do_getDocumentTypeSupportedForEncoding(const char* aContentType) {
   1584  if (!nsCRT::strcmp(aContentType, TEXT_XML) ||
   1585      !nsCRT::strcmp(aContentType, APPLICATION_XML) ||
   1586      !nsCRT::strcmp(aContentType, APPLICATION_XHTML_XML) ||
   1587      !nsCRT::strcmp(aContentType, IMAGE_SVG_XML) ||
   1588      !nsCRT::strcmp(aContentType, TEXT_HTML) ||
   1589      !nsCRT::strcmp(aContentType, TEXT_PLAIN)) {
   1590    return true;
   1591  }
   1592  return false;
   1593 }
   1594 
   1595 already_AddRefed<nsIDocumentEncoder> do_createDocumentEncoder(
   1596    const char* aContentType) {
   1597  if (do_getDocumentTypeSupportedForEncoding(aContentType)) {
   1598    return do_AddRef(new nsDocumentEncoder);
   1599  }
   1600  return nullptr;
   1601 }
   1602 
   1603 class nsHTMLCopyEncoder final : public nsDocumentEncoder {
   1604 private:
   1605  class RangeNodeContext final : public nsDocumentEncoder::RangeNodeContext {
   1606    bool IncludeInContext(nsINode& aNode) const final;
   1607 
   1608    int32_t GetImmediateContextCount(
   1609        const nsTArray<nsINode*>& aAncestorArray) const final;
   1610  };
   1611 
   1612 public:
   1613  nsHTMLCopyEncoder();
   1614  ~nsHTMLCopyEncoder();
   1615 
   1616  NS_IMETHOD Init(Document* aDocument, const nsAString& aMimeType,
   1617                  uint32_t aFlags) override;
   1618 
   1619  // overridden methods from nsDocumentEncoder
   1620  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   1621  NS_IMETHOD SetSelection(Selection* aSelection) override;
   1622  NS_IMETHOD EncodeToStringWithContext(nsAString& aContextString,
   1623                                       nsAString& aInfoString,
   1624                                       nsAString& aEncodedString) override;
   1625  NS_IMETHOD EncodeToString(nsAString& aOutputString) override;
   1626 
   1627 protected:
   1628  enum Endpoint { kStart, kEnd };
   1629 
   1630  nsresult PromoteRange(nsRange* inRange);
   1631  nsresult PromoteAncestorChain(nsCOMPtr<nsINode>* ioNode,
   1632                                uint32_t* aIOStartOffset,
   1633                                uint32_t* aIOEndOffset);
   1634  nsresult GetPromotedPoint(const Endpoint aWhere, nsINode* const aNode,
   1635                            const uint32_t aOffset, nsCOMPtr<nsINode>* aOutNode,
   1636                            uint32_t* aOutOffset, nsINode* const aCommon);
   1637  static nsCOMPtr<nsINode> GetChildAt(nsINode* aParent, const uint32_t aOffset);
   1638  static bool IsMozBR(Element* aNode);
   1639  nsresult GetNodeLocation(nsINode* const aInChild,
   1640                           nsCOMPtr<nsINode>* aOutParent,
   1641                           Maybe<uint32_t>* aOutOffsetInParent);
   1642  bool IsRoot(nsINode* aNode);
   1643  static bool IsFirstNode(nsINode* aNode);
   1644  static bool IsLastNode(nsINode* aNode);
   1645 
   1646  bool mIsTextWidget{false};
   1647 };
   1648 
   1649 nsHTMLCopyEncoder::nsHTMLCopyEncoder()
   1650    : nsDocumentEncoder{MakeUnique<nsHTMLCopyEncoder::RangeNodeContext>()} {}
   1651 
   1652 nsHTMLCopyEncoder::~nsHTMLCopyEncoder() = default;
   1653 
   1654 NS_IMETHODIMP
   1655 nsHTMLCopyEncoder::Init(Document* aDocument, const nsAString& aMimeType,
   1656                        uint32_t aFlags) {
   1657  if (!aDocument) return NS_ERROR_INVALID_ARG;
   1658 
   1659  mIsTextWidget = false;
   1660  Initialize(true, GetAllowRangeCrossShadowBoundary(aFlags));
   1661 
   1662  mIsCopying = true;
   1663  mDocument = aDocument;
   1664 
   1665  // nsHTMLCopyEncoder only accepts "text/plain" or "text/html" MIME types, and
   1666  // the initial MIME type may change after setting the selection.
   1667  MOZ_ASSERT(aMimeType.EqualsLiteral(kTextMime) ||
   1668             aMimeType.EqualsLiteral(kHTMLMime));
   1669  if (aMimeType.EqualsLiteral(kTextMime)) {
   1670    mMimeType.AssignLiteral(kTextMime);
   1671  } else {
   1672    mMimeType.AssignLiteral(kHTMLMime);
   1673  }
   1674 
   1675  // Make all links absolute when copying
   1676  // (see related bugs #57296, #41924, #58646, #32768)
   1677  mFlags = aFlags | OutputAbsoluteLinks;
   1678 
   1679  if (!mDocument->IsScriptEnabled()) mFlags |= OutputNoScriptContent;
   1680 
   1681  return NS_OK;
   1682 }
   1683 
   1684 NS_IMETHODIMP
   1685 nsHTMLCopyEncoder::SetSelection(Selection* aSelection) {
   1686  // check for text widgets: we need to recognize these so that
   1687  // we don't tweak the selection to be outside of the magic
   1688  // div that ender-lite text widgets are embedded in.
   1689 
   1690  if (!aSelection) return NS_ERROR_NULL_POINTER;
   1691 
   1692  const uint32_t rangeCount = aSelection->RangeCount();
   1693 
   1694  // if selection is uninitialized return
   1695  if (!rangeCount) {
   1696    return NS_ERROR_FAILURE;
   1697  }
   1698 
   1699  // we'll just use the common parent of the first range.  Implicit assumption
   1700  // here that multi-range selections are table cell selections, in which case
   1701  // the common parent is somewhere in the table and we don't really care where.
   1702  //
   1703  // FIXME(emilio, bug 1455894): This assumption is already wrong, and will
   1704  // probably be more wrong in a Shadow DOM world...
   1705  //
   1706  // We should be able to write this as "Find the common ancestor of the
   1707  // selection, then go through the flattened tree and serialize the selected
   1708  // nodes", effectively serializing the composed tree.
   1709  RefPtr<nsRange> range = aSelection->GetRangeAt(0);
   1710  nsINode* commonParent = range->GetClosestCommonInclusiveAncestor();
   1711 
   1712  for (nsCOMPtr<nsIContent> selContent(
   1713           nsIContent::FromNodeOrNull(commonParent));
   1714       selContent; selContent = selContent->GetParent()) {
   1715    // checking for selection inside a plaintext form widget
   1716    if (selContent->IsAnyOfHTMLElements(nsGkAtoms::input,
   1717                                        nsGkAtoms::textarea)) {
   1718      mIsTextWidget = true;
   1719      break;
   1720    }
   1721  }
   1722 
   1723  // normalize selection if we are not in a widget
   1724  if (mIsTextWidget) {
   1725    mEncodingScope.mSelection = aSelection;
   1726    mMimeType.AssignLiteral("text/plain");
   1727    return NS_OK;
   1728  }
   1729 
   1730  // XXX We should try to get rid of the Selection object here.
   1731  // XXX bug 1245883
   1732 
   1733  // also consider ourselves in a text widget if we can't find an html document
   1734  // XXX: nsCopySupport relies on the MIME type not being updated immediately
   1735  // here, so it can apply different encoding for XHTML documents.
   1736  if (!(mDocument && mDocument->IsHTMLDocument())) {
   1737    mIsTextWidget = true;
   1738    mEncodingScope.mSelection = aSelection;
   1739    // mMimeType is set to text/plain when encoding starts.
   1740    return NS_OK;
   1741  }
   1742 
   1743  // there's no Clone() for selection! fix...
   1744  // nsresult rv = aSelection->Clone(getter_AddRefs(mSelection);
   1745  // NS_ENSURE_SUCCESS(rv, rv);
   1746  mEncodingScope.mSelection = new Selection(SelectionType::eNormal, nullptr);
   1747 
   1748  // loop thru the ranges in the selection
   1749  for (const uint32_t rangeIdx : IntegerRange(rangeCount)) {
   1750    MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
   1751    range = aSelection->GetRangeAt(rangeIdx);
   1752    NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
   1753    RefPtr<nsRange> myRange = range->CloneRange();
   1754    MOZ_ASSERT(myRange);
   1755 
   1756    // adjust range to include any ancestors who's children are entirely
   1757    // selected
   1758    nsresult rv = PromoteRange(myRange);
   1759    NS_ENSURE_SUCCESS(rv, rv);
   1760 
   1761    ErrorResult result;
   1762    RefPtr<Selection> selection(mEncodingScope.mSelection);
   1763    RefPtr<Document> document(mDocument);
   1764    selection->AddRangeAndSelectFramesAndNotifyListenersInternal(
   1765        *myRange, document, result);
   1766    rv = result.StealNSResult();
   1767    NS_ENSURE_SUCCESS(rv, rv);
   1768  }
   1769 
   1770  return NS_OK;
   1771 }
   1772 
   1773 NS_IMETHODIMP
   1774 nsHTMLCopyEncoder::EncodeToString(nsAString& aOutputString) {
   1775  if (mIsTextWidget) {
   1776    mMimeType.AssignLiteral("text/plain");
   1777  }
   1778  return nsDocumentEncoder::EncodeToString(aOutputString);
   1779 }
   1780 
   1781 NS_IMETHODIMP
   1782 nsHTMLCopyEncoder::EncodeToStringWithContext(nsAString& aContextString,
   1783                                             nsAString& aInfoString,
   1784                                             nsAString& aEncodedString) {
   1785  nsresult rv = EncodeToString(aEncodedString);
   1786  NS_ENSURE_SUCCESS(rv, rv);
   1787 
   1788  // do not encode any context info or range hints if we are in a text widget.
   1789  if (mIsTextWidget) return NS_OK;
   1790 
   1791  // now encode common ancestors into aContextString.  Note that the common
   1792  // ancestors will be for the last range in the selection in the case of
   1793  // multirange selections. encoding ancestors every range in a multirange
   1794  // selection in a way that could be understood by the paste code would be a
   1795  // lot more work to do.  As a practical matter, selections are single range,
   1796  // and the ones that aren't are table cell selections where all the cells are
   1797  // in the same table.
   1798 
   1799  mSerializer->Init(mFlags, mWrapColumn, mEncoding, mIsCopying, false,
   1800                    &mNeedsPreformatScanning, aContextString);
   1801 
   1802  // leaf of ancestors might be text node.  If so discard it.
   1803  int32_t count = mRangeSerializer.mCommonInclusiveAncestors.Length();
   1804  int32_t i;
   1805  nsCOMPtr<nsINode> node;
   1806  if (count > 0) {
   1807    node = mRangeSerializer.mCommonInclusiveAncestors.ElementAt(0);
   1808  }
   1809 
   1810  if (node && IsTextNode(node)) {
   1811    mRangeSerializer.mCommonInclusiveAncestors.RemoveElementAt(0);
   1812    if (mRangeSerializer.mContextInfoDepth.mStart) {
   1813      --mRangeSerializer.mContextInfoDepth.mStart;
   1814    }
   1815    if (mRangeSerializer.mContextInfoDepth.mEnd) {
   1816      --mRangeSerializer.mContextInfoDepth.mEnd;
   1817    }
   1818    count--;
   1819  }
   1820 
   1821  i = count;
   1822  while (i > 0) {
   1823    node = mRangeSerializer.mCommonInclusiveAncestors.ElementAt(--i);
   1824    rv = mNodeSerializer.SerializeNodeStart(*node, 0, -1);
   1825    NS_ENSURE_SUCCESS(rv, rv);
   1826  }
   1827  // i = 0; guaranteed by above
   1828  while (i < count) {
   1829    node = mRangeSerializer.mCommonInclusiveAncestors.ElementAt(i++);
   1830    rv = mNodeSerializer.SerializeNodeEnd(*node);
   1831    NS_ENSURE_SUCCESS(rv, rv);
   1832  }
   1833 
   1834  mSerializer->Finish();
   1835 
   1836  // encode range info : the start and end depth of the selection, where the
   1837  // depth is distance down in the parent hierarchy.  Later we will need to add
   1838  // leading/trailing whitespace info to this.
   1839  nsAutoString infoString;
   1840  infoString.AppendInt(mRangeSerializer.mContextInfoDepth.mStart);
   1841  infoString.Append(char16_t(','));
   1842  infoString.AppendInt(mRangeSerializer.mContextInfoDepth.mEnd);
   1843  aInfoString = infoString;
   1844 
   1845  return rv;
   1846 }
   1847 
   1848 bool nsHTMLCopyEncoder::RangeNodeContext::IncludeInContext(
   1849    nsINode& aNode) const {
   1850  const nsIContent* const content = nsIContent::FromNodeOrNull(&aNode);
   1851  if (!content) {
   1852    return false;
   1853  }
   1854 
   1855  // If it's an inline editing host, we should not treat it gives a context to
   1856  // avoid to duplicate its style.
   1857  if (content->IsEditingHost()) {
   1858    return false;
   1859  }
   1860 
   1861  return content->IsAnyOfHTMLElements(
   1862      nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::a, nsGkAtoms::tt,
   1863      nsGkAtoms::s, nsGkAtoms::big, nsGkAtoms::small, nsGkAtoms::strike,
   1864      nsGkAtoms::em, nsGkAtoms::strong, nsGkAtoms::dfn, nsGkAtoms::code,
   1865      nsGkAtoms::cite, nsGkAtoms::var, nsGkAtoms::abbr, nsGkAtoms::font,
   1866      nsGkAtoms::script, nsGkAtoms::span, nsGkAtoms::pre, nsGkAtoms::h1,
   1867      nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5,
   1868      nsGkAtoms::h6);
   1869 }
   1870 
   1871 nsresult nsHTMLCopyEncoder::PromoteRange(nsRange* inRange) {
   1872  if (!inRange->IsPositioned()) {
   1873    return NS_ERROR_UNEXPECTED;
   1874  }
   1875  nsCOMPtr<nsINode> startNode =
   1876      inRange->GetMayCrossShadowBoundaryStartContainer();
   1877  const uint32_t startOffset = inRange->MayCrossShadowBoundaryStartOffset();
   1878  nsCOMPtr<nsINode> endNode = inRange->GetMayCrossShadowBoundaryEndContainer();
   1879  const uint32_t endOffset = inRange->MayCrossShadowBoundaryEndOffset();
   1880  nsCOMPtr<nsINode> common = inRange->GetClosestCommonInclusiveAncestor(
   1881      AllowRangeCrossShadowBoundary::Yes);
   1882 
   1883  nsCOMPtr<nsINode> opStartNode;
   1884  nsCOMPtr<nsINode> opEndNode;
   1885  uint32_t opStartOffset, opEndOffset;
   1886 
   1887  // examine range endpoints.
   1888  nsresult rv =
   1889      GetPromotedPoint(kStart, startNode, startOffset, address_of(opStartNode),
   1890                       &opStartOffset, common);
   1891  NS_ENSURE_SUCCESS(rv, rv);
   1892  rv = GetPromotedPoint(kEnd, endNode, endOffset, address_of(opEndNode),
   1893                        &opEndOffset, common);
   1894  NS_ENSURE_SUCCESS(rv, rv);
   1895 
   1896  // if both range endpoints are at the common ancestor, check for possible
   1897  // inclusion of ancestors
   1898  if (StaticPrefs::dom_serializer_includeCommonAncestor_enabled() &&
   1899      opStartNode == common && opEndNode == common) {
   1900    rv = PromoteAncestorChain(address_of(opStartNode), &opStartOffset,
   1901                              &opEndOffset);
   1902    NS_ENSURE_SUCCESS(rv, rv);
   1903    opEndNode = opStartNode;
   1904  }
   1905 
   1906  // set the range to the new values
   1907  ErrorResult err;
   1908  inRange->SetStart(*opStartNode, opStartOffset, err,
   1909                    GetAllowRangeCrossShadowBoundary(mFlags));
   1910  if (NS_WARN_IF(err.Failed())) {
   1911    return err.StealNSResult();
   1912  }
   1913  inRange->SetEnd(*opEndNode, opEndOffset, err,
   1914                  GetAllowRangeCrossShadowBoundary(mFlags));
   1915  if (NS_WARN_IF(err.Failed())) {
   1916    return err.StealNSResult();
   1917  }
   1918  return NS_OK;
   1919 }
   1920 
   1921 // PromoteAncestorChain will promote a range represented by
   1922 // [{*ioNode,*aIOStartOffset} , {*ioNode,*aIOEndOffset}] The promotion is
   1923 // different from that found in getPromotedPoint: it will only promote one
   1924 // endpoint if it can promote the other.  Thus, instead of having a
   1925 // startnode/endNode, there is just the one ioNode.
   1926 nsresult nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr<nsINode>* ioNode,
   1927                                                 uint32_t* aIOStartOffset,
   1928                                                 uint32_t* aIOEndOffset) {
   1929  if (!ioNode || !aIOStartOffset || !aIOEndOffset) {
   1930    return NS_ERROR_NULL_POINTER;
   1931  }
   1932 
   1933  nsresult rv = NS_OK;
   1934  bool done = false;
   1935 
   1936  nsCOMPtr<nsINode> frontNode, endNode, parent;
   1937  uint32_t frontOffset, endOffset;
   1938 
   1939  // save the editable state of the ioNode, so we don't promote an ancestor if
   1940  // it has different editable state
   1941  nsCOMPtr<nsINode> node = *ioNode;
   1942  bool isEditable = node->IsEditable();
   1943 
   1944  // loop for as long as we can promote both endpoints
   1945  while (!done) {
   1946    node = *ioNode;
   1947    parent = node->GetParentNode();
   1948    if (!parent) {
   1949      done = true;
   1950    } else {
   1951      // passing parent as last param to GetPromotedPoint() allows it to promote
   1952      // only one level up the hierarchy.
   1953      rv = GetPromotedPoint(kStart, *ioNode, *aIOStartOffset,
   1954                            address_of(frontNode), &frontOffset, parent);
   1955      NS_ENSURE_SUCCESS(rv, rv);
   1956      // then we make the same attempt with the endpoint
   1957      rv = GetPromotedPoint(kEnd, *ioNode, *aIOEndOffset, address_of(endNode),
   1958                            &endOffset, parent);
   1959      NS_ENSURE_SUCCESS(rv, rv);
   1960 
   1961      // if both endpoints were promoted one level and isEditable is the same as
   1962      // the original node, keep looping - otherwise we are done.
   1963      if ((frontNode != parent) || (endNode != parent) ||
   1964          (frontNode->IsEditable() != isEditable))
   1965        done = true;
   1966      else {
   1967        *ioNode = frontNode;
   1968        *aIOStartOffset = frontOffset;
   1969        *aIOEndOffset = endOffset;
   1970      }
   1971    }
   1972  }
   1973  return rv;
   1974 }
   1975 
   1976 nsresult nsHTMLCopyEncoder::GetPromotedPoint(
   1977    const Endpoint aWhere, nsINode* const aNode, const uint32_t aOffset,
   1978    nsCOMPtr<nsINode>* aOutNode, uint32_t* aOutOffset, nsINode* const aCommon) {
   1979  MOZ_ASSERT(aOutNode);
   1980  MOZ_ASSERT(aOutOffset);
   1981 
   1982  // default values
   1983  *aOutNode = aNode;
   1984  *aOutOffset = aOffset;
   1985 
   1986  if (aCommon == aNode) {
   1987    return NS_OK;
   1988  }
   1989 
   1990  nsresult rv = NS_OK;
   1991  // XXX: These don’t seem to need to be strong pointers.
   1992  nsCOMPtr<nsINode> node;
   1993  nsCOMPtr<nsINode> parent;
   1994  Maybe<uint32_t> offsetInParent;
   1995  bool bResetPromotion = false;
   1996 
   1997  if (aWhere == kStart) {
   1998    // some special casing for text nodes
   1999    if (auto nodeAsText = aNode->GetAsText()) {
   2000      // if not at beginning of text node, we are done
   2001      if (aOffset > 0) {
   2002        // unless everything before us in just whitespace.  NOTE: we need a more
   2003        // general solution that truly detects all cases of non-significant
   2004        // whitesace with no false alarms.
   2005        if (!nodeAsText->TextStartsWithOnlyWhitespace(aOffset)) {
   2006          return NS_OK;
   2007        }
   2008        bResetPromotion = true;
   2009      }
   2010      // else
   2011      rv = GetNodeLocation(aNode, address_of(parent), &offsetInParent);
   2012      NS_ENSURE_SUCCESS(rv, rv);
   2013      node = aNode;
   2014    } else {
   2015      node = GetChildAt(aNode, aOffset);
   2016      if (node) {
   2017        parent = aNode;
   2018        offsetInParent = Some(aOffset);
   2019      } else {
   2020        // XXX: Should we only start from aNode when aOffset is 0 and aNode has
   2021        // no children? Currently we start from aNode even when aOffset is an
   2022        // invalid offset, which seems wrong.
   2023        node = aNode;
   2024      }
   2025    }
   2026    MOZ_ASSERT(node);
   2027 
   2028    // finding the real start for this point.  look up the tree for as long as
   2029    // we are the first node in the container, and as long as we haven't hit the
   2030    // body node.
   2031    if (!IsRoot(node) && parent != aCommon) {
   2032      // XXX: We need to do this again because parent and offsetInParent might
   2033      // not be set up properly above, but this also means it will be performed
   2034      // twice on text nodes. Perhaps we could move this above and only do it
   2035      // when needed?
   2036      rv = GetNodeLocation(node, address_of(parent), &offsetInParent);
   2037      NS_ENSURE_SUCCESS(rv, rv);
   2038      // we hit generated content; STOP
   2039      if (!offsetInParent) {
   2040        return NS_OK;
   2041      }
   2042 
   2043      while (IsFirstNode(node) && !IsRoot(parent) && parent != aCommon) {
   2044        if (bResetPromotion) {
   2045          nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(parent);
   2046          if (content && content->IsHTMLElement()) {
   2047            if (nsHTMLElement::IsBlock(
   2048                    nsHTMLTags::AtomTagToId(content->NodeInfo()->NameAtom()))) {
   2049              bResetPromotion = false;
   2050            }
   2051          }
   2052        }
   2053 
   2054        node = parent;
   2055        rv = GetNodeLocation(node, address_of(parent), &offsetInParent);
   2056        NS_ENSURE_SUCCESS(rv, rv);
   2057        // we hit generated content; STOP
   2058        if (!offsetInParent) {
   2059          // back up a bit
   2060          parent = node;
   2061          offsetInParent = Some(0);
   2062          break;
   2063        }
   2064      }
   2065 
   2066      if (bResetPromotion) {
   2067        *aOutNode = aNode;
   2068        *aOutOffset = aOffset;
   2069      } else {
   2070        *aOutNode = parent;
   2071        *aOutOffset = *offsetInParent;
   2072      }
   2073      return rv;
   2074    }
   2075  }
   2076 
   2077  if (aWhere == kEnd) {
   2078    // some special casing for text nodes
   2079    if (auto nodeAsText = aNode->GetAsText()) {
   2080      // if not at end of text node, we are done
   2081      uint32_t len = aNode->Length();
   2082      if (aOffset < len) {
   2083        // unless everything after us in just whitespace.  NOTE: we need a more
   2084        // general solution that truly detects all cases of non-significant
   2085        // whitespace with no false alarms.
   2086        if (!nodeAsText->TextEndsWithOnlyWhitespace(aOffset)) {
   2087          return NS_OK;
   2088        }
   2089        bResetPromotion = true;
   2090      }
   2091      rv = GetNodeLocation(aNode, address_of(parent), &offsetInParent);
   2092      NS_ENSURE_SUCCESS(rv, rv);
   2093      node = aNode;
   2094    } else {
   2095      // we want node _before_ offset
   2096      node = GetChildAt(aNode, aOffset ? aOffset - 1 : aOffset);
   2097      if (node) {
   2098        parent = aNode;
   2099        offsetInParent = Some(aOffset);
   2100      } else {
   2101        // XXX: Should we only start from aNode when aOffset is 0 and aNode has
   2102        // no children? Currently we start from aNode even when aOffset is an
   2103        // invalid offset, which seems wrong.
   2104        node = aNode;
   2105      }
   2106    }
   2107    MOZ_ASSERT(node);
   2108 
   2109    // finding the real end for this point.  look up the tree for as long as we
   2110    // are the last node in the container, and as long as we haven't hit the
   2111    // body node.
   2112    if (!IsRoot(node) && parent != aCommon) {
   2113      // XXX: We need to do this again because parent and offsetInParent might
   2114      // not be set up properly above, but this also means it will be performed
   2115      // twice on text nodes. Perhaps we could move this above and only do it
   2116      // when needed?
   2117      rv = GetNodeLocation(node, address_of(parent), &offsetInParent);
   2118      NS_ENSURE_SUCCESS(rv, rv);
   2119      // we hit generated content; STOP
   2120      if (!offsetInParent) {
   2121        return NS_OK;
   2122      }
   2123 
   2124      while (IsLastNode(node) && !IsRoot(parent) && parent != aCommon) {
   2125        if (bResetPromotion) {
   2126          nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(parent);
   2127          if (content && content->IsHTMLElement()) {
   2128            if (nsHTMLElement::IsBlock(
   2129                    nsHTMLTags::AtomTagToId(content->NodeInfo()->NameAtom()))) {
   2130              bResetPromotion = false;
   2131            }
   2132          }
   2133        }
   2134 
   2135        node = parent;
   2136        rv = GetNodeLocation(node, address_of(parent), &offsetInParent);
   2137        NS_ENSURE_SUCCESS(rv, rv);
   2138 
   2139        // When node is the shadow root and parent is the shadow host,
   2140        // the offsetInParent would also be -1, and we'd like to keep going.
   2141        const bool isGeneratedContent =
   2142            !offsetInParent &&
   2143            ShadowDOMSelectionHelpers::GetShadowRoot(
   2144                parent, GetAllowRangeCrossShadowBoundary(mFlags)) != node;
   2145        // we hit generated content; STOP
   2146        if (isGeneratedContent) {
   2147          // back up a bit
   2148          parent = node;
   2149          offsetInParent = Some(0);
   2150          break;
   2151        }
   2152      }
   2153 
   2154      if (bResetPromotion) {
   2155        *aOutNode = aNode;
   2156        *aOutOffset = aOffset;
   2157      } else {
   2158        *aOutNode = parent;
   2159        // add one since this in an endpoint - want to be AFTER node.
   2160        *aOutOffset = *offsetInParent + 1;
   2161      }
   2162      return rv;
   2163    }
   2164  }
   2165 
   2166  return rv;
   2167 }
   2168 
   2169 nsCOMPtr<nsINode> nsHTMLCopyEncoder::GetChildAt(nsINode* aParent,
   2170                                                const uint32_t aOffset) {
   2171  nsCOMPtr<nsINode> resultNode;
   2172 
   2173  if (!aParent) {
   2174    return resultNode;
   2175  }
   2176 
   2177  nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(aParent);
   2178  MOZ_ASSERT(content, "null content in nsHTMLCopyEncoder::GetChildAt");
   2179 
   2180  resultNode = content->GetChildAt_Deprecated(aOffset);
   2181 
   2182  return resultNode;
   2183 }
   2184 
   2185 bool nsHTMLCopyEncoder::IsMozBR(Element* aElement) {
   2186  HTMLBRElement* brElement = HTMLBRElement::FromNodeOrNull(aElement);
   2187  return brElement && brElement->IsPaddingForEmptyLastLine();
   2188 }
   2189 
   2190 nsresult nsHTMLCopyEncoder::GetNodeLocation(
   2191    nsINode* const aInChild, nsCOMPtr<nsINode>* aOutParent,
   2192    Maybe<uint32_t>* aOutOffsetInParent) {
   2193  NS_ASSERTION(aInChild && aOutParent && aOutOffsetInParent, "bad args");
   2194 
   2195  if (aInChild && aOutParent && aOutOffsetInParent) {
   2196    nsCOMPtr<nsIContent> child = nsIContent::FromNodeOrNull(aInChild);
   2197    if (!child) {
   2198      return NS_ERROR_NULL_POINTER;
   2199    }
   2200 
   2201    nsINode* parent = mFlags & nsIDocumentEncoder::AllowCrossShadowBoundary
   2202                          ? child->GetFlattenedTreeParentNodeForSelection()
   2203                          : child->GetParent();
   2204    if (!parent) {
   2205      return NS_ERROR_NULL_POINTER;
   2206    }
   2207 
   2208    *aOutParent = parent;
   2209    *aOutOffsetInParent = mFlags & nsIDocumentEncoder::AllowCrossShadowBoundary
   2210                              ? parent->ComputeFlatTreeIndexOf(child)
   2211                              : parent->ComputeIndexOf(child);
   2212 
   2213    return NS_OK;
   2214  }
   2215 
   2216  return NS_ERROR_NULL_POINTER;
   2217 }
   2218 
   2219 bool nsHTMLCopyEncoder::IsRoot(nsINode* aNode) {
   2220  nsCOMPtr<nsIContent> content = nsIContent::FromNodeOrNull(aNode);
   2221  if (!content) {
   2222    return false;
   2223  }
   2224 
   2225  if (mIsTextWidget) {
   2226    return content->IsHTMLElement(nsGkAtoms::div);
   2227  }
   2228 
   2229  // XXX(sefeng): This is some old code from 2006, so I can't
   2230  // promise my comment is correct. However, I think these elements
   2231  // are considered to be `Root` because if we keep going up
   2232  // in nsHTMLCopyEncoder::GetPromotedPoint, we would lose the
   2233  // correct representation of the point, so we have to stop at
   2234  // these nodes.
   2235 
   2236  // nsGkAtoms::slot is here because we'd lose the index
   2237  // of the slotted element if we keep going up as
   2238  // `nsHTMLCopyEncoder::GetNodeLocation` would promote the
   2239  // offset to be index of the <slot> that is relative to
   2240  // the <slot>'s parent.
   2241  return content->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::td,
   2242                                      nsGkAtoms::th, nsGkAtoms::slot);
   2243 }
   2244 
   2245 bool nsHTMLCopyEncoder::IsFirstNode(nsINode* aNode) {
   2246  // need to check if any nodes before us are really visible.
   2247  // Mike wrote something for me along these lines in nsSelectionController,
   2248  // but I don't think it's ready for use yet - revisit.
   2249  // HACK: for now, simply consider all whitespace text nodes to be
   2250  // invisible formatting nodes.
   2251  for (nsIContent* sibling = aNode->GetPreviousSibling(); sibling;
   2252       sibling = sibling->GetPreviousSibling()) {
   2253    if (!sibling->TextIsOnlyWhitespace()) {
   2254      return false;
   2255    }
   2256  }
   2257 
   2258  return true;
   2259 }
   2260 
   2261 bool nsHTMLCopyEncoder::IsLastNode(nsINode* aNode) {
   2262  // need to check if any nodes after us are really visible.
   2263  // Mike wrote something for me along these lines in nsSelectionController,
   2264  // but I don't think it's ready for use yet - revisit.
   2265  // HACK: for now, simply consider all whitespace text nodes to be
   2266  // invisible formatting nodes.
   2267  for (nsIContent* sibling = aNode->GetNextSibling(); sibling;
   2268       sibling = sibling->GetNextSibling()) {
   2269    if (sibling->IsElement() && IsMozBR(sibling->AsElement())) {
   2270      // we ignore trailing moz BRs.
   2271      continue;
   2272    }
   2273    if (!sibling->TextIsOnlyWhitespace()) {
   2274      return false;
   2275    }
   2276  }
   2277 
   2278  return true;
   2279 }
   2280 
   2281 already_AddRefed<nsIDocumentEncoder> do_createHTMLCopyEncoder() {
   2282  return do_AddRef(new nsHTMLCopyEncoder);
   2283 }
   2284 
   2285 int32_t nsHTMLCopyEncoder::RangeNodeContext::GetImmediateContextCount(
   2286    const nsTArray<nsINode*>& aAncestorArray) const {
   2287  int32_t i = aAncestorArray.Length(), j = 0;
   2288  while (j < i) {
   2289    nsINode* node = aAncestorArray.ElementAt(j);
   2290    if (!node) {
   2291      break;
   2292    }
   2293    nsCOMPtr<nsIContent> content(nsIContent::FromNodeOrNull(node));
   2294    if (!content || !content->IsAnyOfHTMLElements(
   2295                        nsGkAtoms::tr, nsGkAtoms::thead, nsGkAtoms::tbody,
   2296                        nsGkAtoms::tfoot, nsGkAtoms::table)) {
   2297      break;
   2298    }
   2299    ++j;
   2300  }
   2301  return j;
   2302 }