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/. */
8 * Object that can be used to serialize selections, ranges, or nodes
9 * to strings in a gazillion different ways.
15 #include "nsISupports.h"
18 #include "nsIContentSerializer.h"
19 #include "nsIDocumentEncoder.h"
21 #include "nsIContentInlines.h"
22 #include "nsComponentManagerUtils.h"
23 #include "nsIOutputStream.h"
25 #include "nsGkAtoms.h"
26 #include "nsHTMLDocument.h"
27 #include "nsIContent.h"
28 #include "nsIScriptContext.h"
29 #include "nsIScriptGlobalObject.h"
30 #include "nsITransferable.h"
31 #include "mozilla/dom/Selection.h"
32 #include "nsContentUtils.h"
33 #include "nsElementTable.h"
34 #include "nsUnicharUtils.h"
35 #include "nsReadableUtils.h"
38 #include "nsLayoutUtils.h"
39 #include "nsStringBuffer.h"
40 #include "mozilla/dom/Comment.h"
41 #include "mozilla/dom/Document.h"
42 #include "mozilla/dom/DocumentType.h"
43 #include "mozilla/dom/Element.h"
44 #include "mozilla/dom/HTMLBRElement.h"
45 #include "mozilla/dom/ProcessingInstruction.h"
46 #include "mozilla/dom/ShadowRoot.h"
47 #include "mozilla/dom/Text.h"
48 #include "mozilla/Encoding.h"
49 #include "mozilla/IntegerRange.h"
50 #include "mozilla/Maybe.h"
51 #include "mozilla/ScopeExit.h"
52 #include "mozilla/UniquePtr.h"
54 using namespace mozilla
;
55 using namespace mozilla::dom
;
57 enum nsRangeIterationDirection
{ kDirectionOut
= -1, kDirectionIn
= 1 };
62 * @param aStream Will be kept alive by the TextStreamer.
63 * @param aUnicodeEncoder Needs to be non-nullptr.
65 TextStreamer(nsIOutputStream
& aStream
, UniquePtr
<Encoder
> aUnicodeEncoder
,
66 bool aIsPlainText
, nsAString
& aOutputBuffer
);
69 * String will be truncated if it is written to stream.
71 nsresult
FlushIfStringLongEnough();
74 * String will be truncated.
76 nsresult
ForceFlush();
79 const static uint32_t kMaxLengthBeforeFlush
= 1024;
81 const static uint32_t kEncoderBufferSizeInBytes
= 4096;
83 nsresult
EncodeAndWrite();
85 nsresult
EncodeAndWriteAndTruncate();
87 const nsCOMPtr
<nsIOutputStream
> mStream
;
88 const UniquePtr
<Encoder
> mUnicodeEncoder
;
89 const bool mIsPlainText
;
90 nsAString
& mOutputBuffer
;
93 TextStreamer::TextStreamer(nsIOutputStream
& aStream
,
94 UniquePtr
<Encoder
> aUnicodeEncoder
,
95 bool aIsPlainText
, nsAString
& aOutputBuffer
)
97 mUnicodeEncoder(std::move(aUnicodeEncoder
)),
98 mIsPlainText(aIsPlainText
),
99 mOutputBuffer(aOutputBuffer
) {
100 MOZ_ASSERT(mUnicodeEncoder
);
103 nsresult
TextStreamer::FlushIfStringLongEnough() {
106 if (mOutputBuffer
.Length() > kMaxLengthBeforeFlush
) {
107 rv
= EncodeAndWriteAndTruncate();
113 nsresult
TextStreamer::ForceFlush() { return EncodeAndWriteAndTruncate(); }
115 nsresult
TextStreamer::EncodeAndWrite() {
116 if (mOutputBuffer
.IsEmpty()) {
120 uint8_t buffer
[kEncoderBufferSizeInBytes
];
121 auto src
= Span(mOutputBuffer
);
122 auto bufferSpan
= Span(buffer
);
123 // Reserve space for terminator
124 auto dst
= bufferSpan
.To(bufferSpan
.Length() - 1);
130 std::tie(result
, read
, written
) =
131 mUnicodeEncoder
->EncodeFromUTF16WithoutReplacement(src
, dst
, false);
132 if (result
!= kInputEmpty
&& result
!= kOutputFull
) {
133 // There's always room for one byte in the case of
134 // an unmappable character, because otherwise
135 // we'd have gotten `kOutputFull`.
136 dst
[written
++] = '?';
139 std::tie(result
, read
, written
, std::ignore
) =
140 mUnicodeEncoder
->EncodeFromUTF16(src
, dst
, false);
142 src
= src
.From(read
);
143 // Sadly, we still have test cases that implement nsIOutputStream in JS, so
144 // the buffer needs to be zero-terminated for XPConnect to do its thing.
146 bufferSpan
[written
] = 0;
147 uint32_t streamWritten
;
148 nsresult rv
= mStream
->Write(reinterpret_cast<char*>(dst
.Elements()),
149 written
, &streamWritten
);
153 if (result
== kInputEmpty
) {
159 nsresult
TextStreamer::EncodeAndWriteAndTruncate() {
160 const nsresult rv
= EncodeAndWrite();
161 mOutputBuffer
.Truncate();
166 * The scope may be limited to either a selection, range, or node.
168 class EncodingScope
{
171 * @return true, iff the scope is limited to a selection, range or node.
173 bool IsLimited() const;
175 RefPtr
<Selection
> mSelection
;
176 RefPtr
<nsRange
> mRange
;
177 nsCOMPtr
<nsINode
> mNode
;
178 bool mNodeIsContainer
= false;
181 bool EncodingScope::IsLimited() const { return mSelection
|| mRange
|| mNode
; }
183 struct RangeBoundariesInclusiveAncestorsAndOffsets
{
185 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
187 using InclusiveAncestors
= AutoTArray
<nsIContent
*, 8>;
190 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
192 using InclusiveAncestorsOffsets
= AutoTArray
<Maybe
<uint32_t>, 8>;
194 // The first node is the range's boundary node, the following ones the
196 InclusiveAncestors mInclusiveAncestorsOfStart
;
197 // The first offset represents where at the boundary node the range starts.
198 // Each other offset is the index of the child relative to its parent.
199 InclusiveAncestorsOffsets mInclusiveAncestorsOffsetsOfStart
;
201 // The first node is the range's boundary node, the following one the
203 InclusiveAncestors mInclusiveAncestorsOfEnd
;
204 // The first offset represents where at the boundary node the range ends.
205 // Each other offset is the index of the child relative to its parent.
206 InclusiveAncestorsOffsets mInclusiveAncestorsOffsetsOfEnd
;
209 struct ContextInfoDepth
{
214 class nsDocumentEncoder
: public nsIDocumentEncoder
{
216 class RangeNodeContext
{
218 virtual ~RangeNodeContext() = default;
220 virtual bool IncludeInContext(nsINode
& aNode
) const { return false; }
222 virtual int32_t GetImmediateContextCount(
223 const nsTArray
<nsINode
*>& aAncestorArray
) const {
233 * @param aRangeNodeContext has to be non-null.
235 explicit nsDocumentEncoder(UniquePtr
<RangeNodeContext
> aRangeNodeContext
);
238 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
239 NS_DECL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder
)
240 NS_DECL_NSIDOCUMENTENCODER
243 virtual ~nsDocumentEncoder();
245 void Initialize(bool aClearCachedSerializer
= true);
248 * @param aMaxLength As described at
249 * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
251 nsresult
SerializeDependingOnScope(uint32_t aMaxLength
);
253 nsresult
SerializeSelection();
255 nsresult
SerializeNode();
258 * @param aMaxLength As described at
259 * `nsIDocumentEncodder.encodeToStringWithMaxLength`.
261 nsresult
SerializeWholeDocument(uint32_t aMaxLength
);
264 * @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.o
266 static bool IsInvisibleNodeAndShouldBeSkipped(const nsINode
& aNode
,
267 const uint32_t aFlags
) {
268 if (aFlags
& SkipInvisibleContent
) {
269 // Treat the visibility of the ShadowRoot as if it were
272 // FIXME(emilio): I suspect instead of this a bunch of the GetParent()
273 // calls here should be doing GetFlattenedTreeParent, then this condition
274 // should be unreachable...
275 const nsINode
* node
{&aNode
};
276 if (const ShadowRoot
* shadowRoot
= ShadowRoot::FromNode(node
)) {
277 node
= shadowRoot
->GetHost();
280 if (node
->IsContent()) {
281 nsIFrame
* frame
= node
->AsContent()->GetPrimaryFrame();
283 if (node
->IsElement() && node
->AsElement()->IsDisplayContents()) {
286 if (node
->IsText()) {
287 // We have already checked that our parent is visible.
289 // FIXME(emilio): Text not assigned to a <slot> in Shadow DOM should
290 // probably return false...
293 if (node
->IsHTMLElement(nsGkAtoms::rp
)) {
294 // Ruby parentheses are part of ruby structure, hence
295 // shouldn't be stripped out even if it is not displayed.
300 bool isVisible
= frame
->StyleVisibility()->IsVisible();
301 if (!isVisible
&& node
->IsText()) {
309 void ReleaseDocumentReferenceAndInitialize(bool aClearCachedSerializer
);
311 class MOZ_STACK_CLASS AutoReleaseDocumentIfNeeded final
{
313 explicit AutoReleaseDocumentIfNeeded(nsDocumentEncoder
* aEncoder
)
314 : mEncoder(aEncoder
) {}
316 ~AutoReleaseDocumentIfNeeded() {
317 if (mEncoder
->mFlags
& RequiresReinitAfterOutput
) {
318 const bool clearCachedSerializer
= false;
319 mEncoder
->ReleaseDocumentReferenceAndInitialize(clearCachedSerializer
);
324 nsDocumentEncoder
* mEncoder
;
327 nsCOMPtr
<Document
> mDocument
;
328 EncodingScope mEncodingScope
;
329 nsCOMPtr
<nsIContentSerializer
> mSerializer
;
331 Maybe
<TextStreamer
> mTextStreamer
;
332 nsCOMPtr
<nsIDocumentEncoderNodeFixup
> mNodeFixup
;
335 const Encoding
* mEncoding
;
336 // Multiple of the flags defined in nsIDocumentEncoder.idl.
338 uint32_t mWrapColumn
;
339 // Whether the serializer cares about being notified to scan elements to
340 // keep track of whether they are preformatted. This stores the out
341 // argument of nsIContentSerializer::Init().
342 bool mNeedsPreformatScanning
;
343 bool mIsCopying
; // Set to true only while copying
344 nsStringBuffer
* mCachedBuffer
;
346 class NodeSerializer
{
349 * @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.
351 NodeSerializer(const bool& aNeedsPreformatScanning
,
352 const nsCOMPtr
<nsIContentSerializer
>& aSerializer
,
353 const uint32_t& aFlags
,
354 const nsCOMPtr
<nsIDocumentEncoderNodeFixup
>& aNodeFixup
,
355 Maybe
<TextStreamer
>& aTextStreamer
)
356 : mNeedsPreformatScanning
{aNeedsPreformatScanning
},
357 mSerializer
{aSerializer
},
359 mNodeFixup
{aNodeFixup
},
360 mTextStreamer
{aTextStreamer
} {}
362 nsresult
SerializeNodeStart(nsINode
& aOriginalNode
, int32_t aStartOffset
,
364 nsINode
* aFixupNode
= nullptr) const;
366 enum class SerializeRoot
{ eYes
, eNo
};
368 nsresult
SerializeToStringRecursive(nsINode
* aNode
,
369 SerializeRoot aSerializeRoot
,
370 uint32_t aMaxLength
= 0) const;
372 nsresult
SerializeNodeEnd(nsINode
& aOriginalNode
,
373 nsINode
* aFixupNode
= nullptr) const;
375 [[nodiscard
]] nsresult
SerializeTextNode(nsINode
& aNode
,
376 int32_t aStartOffset
,
377 int32_t aEndOffset
) const;
379 nsresult
SerializeToStringIterative(nsINode
* aNode
) const;
382 const bool& mNeedsPreformatScanning
;
383 const nsCOMPtr
<nsIContentSerializer
>& mSerializer
;
384 // Multiple of the flags defined in nsIDocumentEncoder.idl.
385 const uint32_t& mFlags
;
386 const nsCOMPtr
<nsIDocumentEncoderNodeFixup
>& mNodeFixup
;
387 Maybe
<TextStreamer
>& mTextStreamer
;
390 NodeSerializer mNodeSerializer
;
392 const UniquePtr
<RangeNodeContext
> mRangeNodeContext
;
394 struct RangeContextSerializer final
{
395 RangeContextSerializer(const RangeNodeContext
& aRangeNodeContext
,
396 const NodeSerializer
& aNodeSerializer
)
397 : mDisableContextSerialize
{false},
398 mRangeNodeContext
{aRangeNodeContext
},
399 mNodeSerializer
{aNodeSerializer
} {}
401 nsresult
SerializeRangeContextStart(
402 const nsTArray
<nsINode
*>& aAncestorArray
);
403 nsresult
SerializeRangeContextEnd();
405 // Used when context has already been serialized for
406 // table cell selections (where parent is <tr>)
407 bool mDisableContextSerialize
;
408 AutoTArray
<AutoTArray
<nsINode
*, 8>, 8> mRangeContexts
;
410 const RangeNodeContext
& mRangeNodeContext
;
413 const NodeSerializer
& mNodeSerializer
;
416 RangeContextSerializer mRangeContextSerializer
;
418 struct RangeSerializer
{
419 // @param aFlags multiple of the flags defined in nsIDocumentEncoder.idl.
420 RangeSerializer(const uint32_t& aFlags
,
421 const NodeSerializer
& aNodeSerializer
,
422 RangeContextSerializer
& aRangeContextSerializer
)
423 : mStartRootIndex
{0},
425 mHaltRangeHint
{false},
427 mNodeSerializer
{aNodeSerializer
},
428 mRangeContextSerializer
{aRangeContextSerializer
} {}
433 * @param aDepth the distance (number of `GetParent` calls) from aNode to
434 * aRange's closest common inclusive ancestor.
436 nsresult
SerializeRangeNodes(const nsRange
* aRange
, nsINode
* aNode
,
440 * Serialize aContent's children from aStartOffset to aEndOffset.
442 * @param aDepth the distance (number of `GetParent` calls) from aContent to
443 * aRange's closest common inclusive ancestor.
445 [[nodiscard
]] nsresult
SerializeChildrenOfContent(nsIContent
& aContent
,
446 uint32_t aStartOffset
,
448 const nsRange
* aRange
,
451 nsresult
SerializeRangeToString(const nsRange
* aRange
);
454 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
456 nsCOMPtr
<nsINode
> mClosestCommonInclusiveAncestorOfRange
;
459 * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor.
461 AutoTArray
<nsINode
*, 8> mCommonInclusiveAncestors
;
463 ContextInfoDepth mContextInfoDepth
;
466 struct StartAndEndContent
{
467 nsCOMPtr
<nsIContent
> mStart
;
468 nsCOMPtr
<nsIContent
> mEnd
;
471 StartAndEndContent
GetStartAndEndContentForRecursionLevel(
472 int32_t aDepth
) const;
474 bool HasInvisibleParentAndShouldBeSkipped(nsINode
& aNode
) const;
476 nsresult
SerializeNodePartiallyContainedInRange(
477 nsINode
& aNode
, nsIContent
& aContent
,
478 const StartAndEndContent
& aStartAndEndContent
, const nsRange
& aRange
,
481 nsresult
SerializeTextNode(nsINode
& aNode
, const nsIContent
& aContent
,
482 const StartAndEndContent
& aStartAndEndContent
,
483 const nsRange
& aRange
) const;
485 RangeBoundariesInclusiveAncestorsAndOffsets
486 mRangeBoundariesInclusiveAncestorsAndOffsets
;
487 int32_t mStartRootIndex
;
488 int32_t mEndRootIndex
;
491 // Multiple of the flags defined in nsIDocumentEncoder.idl.
492 const uint32_t& mFlags
;
494 const NodeSerializer
& mNodeSerializer
;
495 RangeContextSerializer
& mRangeContextSerializer
;
498 RangeSerializer mRangeSerializer
;
501 void nsDocumentEncoder::RangeSerializer::Initialize() {
502 mContextInfoDepth
= {};
505 mHaltRangeHint
= false;
506 mClosestCommonInclusiveAncestorOfRange
= nullptr;
507 mRangeBoundariesInclusiveAncestorsAndOffsets
= {};
510 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder
)
511 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
512 nsDocumentEncoder
, ReleaseDocumentReferenceAndInitialize(true))
514 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder
)
515 NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder
)
516 NS_INTERFACE_MAP_ENTRY(nsISupports
)
519 NS_IMPL_CYCLE_COLLECTION(
520 nsDocumentEncoder
, mDocument
, mEncodingScope
.mSelection
,
521 mEncodingScope
.mRange
, mEncodingScope
.mNode
, mSerializer
,
522 mRangeSerializer
.mClosestCommonInclusiveAncestorOfRange
)
524 nsDocumentEncoder::nsDocumentEncoder(
525 UniquePtr
<RangeNodeContext
> aRangeNodeContext
)
526 : mEncoding(nullptr),
528 mCachedBuffer(nullptr),
529 mNodeSerializer(mNeedsPreformatScanning
, mSerializer
, mFlags
, mNodeFixup
,
531 mRangeNodeContext(std::move(aRangeNodeContext
)),
532 mRangeContextSerializer(*mRangeNodeContext
, mNodeSerializer
),
533 mRangeSerializer(mFlags
, mNodeSerializer
, mRangeContextSerializer
) {
534 MOZ_ASSERT(mRangeNodeContext
);
537 mMimeType
.AssignLiteral("text/plain");
540 nsDocumentEncoder::nsDocumentEncoder()
541 : nsDocumentEncoder(MakeUnique
<RangeNodeContext
>()) {}
543 void nsDocumentEncoder::Initialize(bool aClearCachedSerializer
) {
546 mRangeSerializer
.Initialize();
547 mNeedsPreformatScanning
= false;
548 mRangeContextSerializer
.mDisableContextSerialize
= false;
550 mNodeFixup
= nullptr;
551 if (aClearCachedSerializer
) {
552 mSerializer
= nullptr;
556 static bool ParentIsTR(nsIContent
* aContent
) {
557 mozilla::dom::Element
* parent
= aContent
->GetParentElement();
561 return parent
->IsHTMLElement(nsGkAtoms::tr
);
564 nsresult
nsDocumentEncoder::SerializeDependingOnScope(uint32_t aMaxLength
) {
566 if (mEncodingScope
.mSelection
) {
567 rv
= SerializeSelection();
568 } else if (nsRange
* range
= mEncodingScope
.mRange
) {
569 rv
= mRangeSerializer
.SerializeRangeToString(range
);
570 } else if (mEncodingScope
.mNode
) {
571 rv
= SerializeNode();
573 rv
= SerializeWholeDocument(aMaxLength
);
581 nsresult
nsDocumentEncoder::SerializeSelection() {
582 NS_ENSURE_TRUE(mEncodingScope
.mSelection
, NS_ERROR_FAILURE
);
585 const Selection
* selection
= mEncodingScope
.mSelection
;
586 nsCOMPtr
<nsINode
> node
;
587 nsCOMPtr
<nsINode
> prevNode
;
588 uint32_t firstRangeStartDepth
= 0;
589 const uint32_t rangeCount
= selection
->RangeCount();
590 for (const uint32_t i
: IntegerRange(rangeCount
)) {
591 MOZ_ASSERT(selection
->RangeCount() == rangeCount
);
592 RefPtr
<const nsRange
> range
= selection
->GetRangeAt(i
);
594 // Bug 236546: newlines not added when copying table cells into clipboard
595 // Each selected cell shows up as a range containing a row with a single
596 // cell get the row, compare it to previous row and emit </tr><tr> as
597 // needed Bug 137450: Problem copying/pasting a table from a web page to
598 // Excel. Each separate block of <tr></tr> produced above will be wrapped
599 // by the immediate context. This assumes that you can't select cells that
600 // are multiple selections from two tables simultaneously.
601 node
= range
->GetStartContainer();
602 NS_ENSURE_TRUE(node
, NS_ERROR_FAILURE
);
603 if (node
!= prevNode
) {
605 rv
= mNodeSerializer
.SerializeNodeEnd(*prevNode
);
606 NS_ENSURE_SUCCESS(rv
, rv
);
608 nsCOMPtr
<nsIContent
> content
= nsIContent::FromNodeOrNull(node
);
609 if (content
&& content
->IsHTMLElement(nsGkAtoms::tr
) &&
610 !ParentIsTR(content
)) {
612 // Went from a non-<tr> to a <tr>
613 mRangeSerializer
.mCommonInclusiveAncestors
.Clear();
614 nsContentUtils::GetInclusiveAncestors(
615 node
->GetParentNode(),
616 mRangeSerializer
.mCommonInclusiveAncestors
);
617 rv
= mRangeContextSerializer
.SerializeRangeContextStart(
618 mRangeSerializer
.mCommonInclusiveAncestors
);
619 NS_ENSURE_SUCCESS(rv
, rv
);
620 // Don't let SerializeRangeToString serialize the context again
621 mRangeContextSerializer
.mDisableContextSerialize
= true;
624 rv
= mNodeSerializer
.SerializeNodeStart(*node
, 0, -1);
625 NS_ENSURE_SUCCESS(rv
, rv
);
627 } else if (prevNode
) {
628 // Went from a <tr> to a non-<tr>
629 mRangeContextSerializer
.mDisableContextSerialize
= false;
631 // `mCommonInclusiveAncestors` is used in `EncodeToStringWithContext`
632 // too. Update it here to mimic the old behavior.
633 mRangeSerializer
.mCommonInclusiveAncestors
.Clear();
634 nsContentUtils::GetInclusiveAncestors(
635 prevNode
->GetParentNode(),
636 mRangeSerializer
.mCommonInclusiveAncestors
);
638 rv
= mRangeContextSerializer
.SerializeRangeContextEnd();
639 NS_ENSURE_SUCCESS(rv
, rv
);
644 rv
= mRangeSerializer
.SerializeRangeToString(range
);
645 NS_ENSURE_SUCCESS(rv
, rv
);
647 firstRangeStartDepth
= mRangeSerializer
.mContextInfoDepth
.mStart
;
650 mRangeSerializer
.mContextInfoDepth
.mStart
= firstRangeStartDepth
;
653 rv
= mNodeSerializer
.SerializeNodeEnd(*prevNode
);
654 NS_ENSURE_SUCCESS(rv
, rv
);
655 mRangeContextSerializer
.mDisableContextSerialize
= false;
657 // `mCommonInclusiveAncestors` is used in `EncodeToStringWithContext`
658 // too. Update it here to mimic the old behavior.
659 mRangeSerializer
.mCommonInclusiveAncestors
.Clear();
660 nsContentUtils::GetInclusiveAncestors(
661 prevNode
->GetParentNode(), mRangeSerializer
.mCommonInclusiveAncestors
);
663 rv
= mRangeContextSerializer
.SerializeRangeContextEnd();
664 NS_ENSURE_SUCCESS(rv
, rv
);
668 mRangeContextSerializer
.mDisableContextSerialize
= false;
673 nsresult
nsDocumentEncoder::SerializeNode() {
674 NS_ENSURE_TRUE(mEncodingScope
.mNode
, NS_ERROR_FAILURE
);
677 nsINode
* node
= mEncodingScope
.mNode
;
678 const bool nodeIsContainer
= mEncodingScope
.mNodeIsContainer
;
679 if (!mNodeFixup
&& !(mFlags
& SkipInvisibleContent
) && !mTextStreamer
&&
681 rv
= mNodeSerializer
.SerializeToStringIterative(node
);
683 rv
= mNodeSerializer
.SerializeToStringRecursive(
684 node
, nodeIsContainer
? NodeSerializer::SerializeRoot::eNo
685 : NodeSerializer::SerializeRoot::eYes
);
691 nsresult
nsDocumentEncoder::SerializeWholeDocument(uint32_t aMaxLength
) {
692 NS_ENSURE_FALSE(mEncodingScope
.mSelection
, NS_ERROR_FAILURE
);
693 NS_ENSURE_FALSE(mEncodingScope
.mRange
, NS_ERROR_FAILURE
);
694 NS_ENSURE_FALSE(mEncodingScope
.mNode
, NS_ERROR_FAILURE
);
696 nsresult rv
= mSerializer
->AppendDocumentStart(mDocument
);
697 NS_ENSURE_SUCCESS(rv
, rv
);
699 rv
= mNodeSerializer
.SerializeToStringRecursive(
700 mDocument
, NodeSerializer::SerializeRoot::eYes
, aMaxLength
);
704 nsDocumentEncoder::~nsDocumentEncoder() {
706 mCachedBuffer
->Release();
711 nsDocumentEncoder::Init(Document
* aDocument
, const nsAString
& aMimeType
,
713 return NativeInit(aDocument
, aMimeType
, aFlags
);
717 nsDocumentEncoder::NativeInit(Document
* aDocument
, const nsAString
& aMimeType
,
719 if (!aDocument
) return NS_ERROR_INVALID_ARG
;
721 Initialize(!mMimeType
.Equals(aMimeType
));
723 mDocument
= aDocument
;
725 mMimeType
= aMimeType
;
734 nsDocumentEncoder::SetWrapColumn(uint32_t aWC
) {
740 nsDocumentEncoder::SetSelection(Selection
* aSelection
) {
741 mEncodingScope
.mSelection
= aSelection
;
746 nsDocumentEncoder::SetRange(nsRange
* aRange
) {
747 mEncodingScope
.mRange
= aRange
;
752 nsDocumentEncoder::SetNode(nsINode
* aNode
) {
753 mEncodingScope
.mNodeIsContainer
= false;
754 mEncodingScope
.mNode
= aNode
;
759 nsDocumentEncoder::SetContainerNode(nsINode
* aContainer
) {
760 mEncodingScope
.mNodeIsContainer
= true;
761 mEncodingScope
.mNode
= aContainer
;
766 nsDocumentEncoder::SetCharset(const nsACString
& aCharset
) {
767 const Encoding
* encoding
= Encoding::ForLabel(aCharset
);
769 return NS_ERROR_UCONV_NOCONV
;
771 mEncoding
= encoding
->OutputEncoding();
776 nsDocumentEncoder::GetMimeType(nsAString
& aMimeType
) {
777 aMimeType
= mMimeType
;
781 class FixupNodeDeterminer
{
783 FixupNodeDeterminer(nsIDocumentEncoderNodeFixup
* aNodeFixup
,
784 nsINode
* aFixupNode
, nsINode
& aOriginalNode
)
785 : mIsSerializationOfFixupChildrenNeeded
{false},
786 mNodeFixup(aNodeFixup
),
787 mOriginalNode(aOriginalNode
) {
790 mFixupNode
= aFixupNode
;
792 mNodeFixup
->FixupNode(&mOriginalNode
,
793 &mIsSerializationOfFixupChildrenNeeded
,
794 getter_AddRefs(mFixupNode
));
799 bool IsSerializationOfFixupChildrenNeeded() const {
800 return mIsSerializationOfFixupChildrenNeeded
;
804 * @return The fixup node, if available, otherwise the original node. The
805 * former is kept alive by this object.
807 nsINode
& GetFixupNodeFallBackToOriginalNode() const {
808 return mFixupNode
? *mFixupNode
: mOriginalNode
;
812 bool mIsSerializationOfFixupChildrenNeeded
;
813 nsIDocumentEncoderNodeFixup
* mNodeFixup
;
814 nsCOMPtr
<nsINode
> mFixupNode
;
815 nsINode
& mOriginalNode
;
818 nsresult
nsDocumentEncoder::NodeSerializer::SerializeNodeStart(
819 nsINode
& aOriginalNode
, int32_t aStartOffset
, int32_t aEndOffset
,
820 nsINode
* aFixupNode
) const {
821 if (mNeedsPreformatScanning
) {
822 if (aOriginalNode
.IsElement()) {
823 mSerializer
->ScanElementForPreformat(aOriginalNode
.AsElement());
824 } else if (aOriginalNode
.IsText()) {
825 const nsCOMPtr
<nsINode
> parent
= aOriginalNode
.GetParent();
826 if (parent
&& parent
->IsElement()) {
827 mSerializer
->ScanElementForPreformat(parent
->AsElement());
832 if (IsInvisibleNodeAndShouldBeSkipped(aOriginalNode
, mFlags
)) {
836 FixupNodeDeterminer fixupNodeDeterminer
{mNodeFixup
, aFixupNode
,
838 nsINode
* node
= &fixupNodeDeterminer
.GetFixupNodeFallBackToOriginalNode();
842 if (node
->IsElement()) {
843 if ((mFlags
& (nsIDocumentEncoder::OutputPreformatted
|
844 nsIDocumentEncoder::OutputDropInvisibleBreak
)) &&
845 nsLayoutUtils::IsInvisibleBreak(node
)) {
848 rv
= mSerializer
->AppendElementStart(node
->AsElement(),
849 aOriginalNode
.AsElement());
853 switch (node
->NodeType()) {
854 case nsINode::TEXT_NODE
: {
855 rv
= mSerializer
->AppendText(static_cast<nsIContent
*>(node
), aStartOffset
,
859 case nsINode::CDATA_SECTION_NODE
: {
860 rv
= mSerializer
->AppendCDATASection(static_cast<nsIContent
*>(node
),
861 aStartOffset
, aEndOffset
);
864 case nsINode::PROCESSING_INSTRUCTION_NODE
: {
865 rv
= mSerializer
->AppendProcessingInstruction(
866 static_cast<ProcessingInstruction
*>(node
), aStartOffset
, aEndOffset
);
869 case nsINode::COMMENT_NODE
: {
870 rv
= mSerializer
->AppendComment(static_cast<Comment
*>(node
), aStartOffset
,
874 case nsINode::DOCUMENT_TYPE_NODE
: {
875 rv
= mSerializer
->AppendDoctype(static_cast<DocumentType
*>(node
));
883 nsresult
nsDocumentEncoder::NodeSerializer::SerializeNodeEnd(
884 nsINode
& aOriginalNode
, nsINode
* aFixupNode
) const {
885 if (mNeedsPreformatScanning
) {
886 if (aOriginalNode
.IsElement()) {
887 mSerializer
->ForgetElementForPreformat(aOriginalNode
.AsElement());
888 } else if (aOriginalNode
.IsText()) {
889 const nsCOMPtr
<nsINode
> parent
= aOriginalNode
.GetParent();
890 if (parent
&& parent
->IsElement()) {
891 mSerializer
->ForgetElementForPreformat(parent
->AsElement());
896 if (IsInvisibleNodeAndShouldBeSkipped(aOriginalNode
, mFlags
)) {
902 FixupNodeDeterminer fixupNodeDeterminer
{mNodeFixup
, aFixupNode
,
904 nsINode
* node
= &fixupNodeDeterminer
.GetFixupNodeFallBackToOriginalNode();
906 if (node
->IsElement()) {
907 rv
= mSerializer
->AppendElementEnd(node
->AsElement(),
908 aOriginalNode
.AsElement());
914 nsresult
nsDocumentEncoder::NodeSerializer::SerializeToStringRecursive(
915 nsINode
* aNode
, SerializeRoot aSerializeRoot
, uint32_t aMaxLength
) const {
916 uint32_t outputLength
{0};
917 nsresult rv
= mSerializer
->GetOutputLength(outputLength
);
918 NS_ENSURE_SUCCESS(rv
, rv
);
920 if (aMaxLength
> 0 && outputLength
>= aMaxLength
) {
924 NS_ENSURE_TRUE(aNode
, NS_ERROR_NULL_POINTER
);
926 if (IsInvisibleNodeAndShouldBeSkipped(*aNode
, mFlags
)) {
930 FixupNodeDeterminer fixupNodeDeterminer
{mNodeFixup
, nullptr, *aNode
};
931 nsINode
* maybeFixedNode
=
932 &fixupNodeDeterminer
.GetFixupNodeFallBackToOriginalNode();
934 if (mFlags
& SkipInvisibleContent
) {
935 if (aNode
->IsContent()) {
936 if (nsIFrame
* frame
= aNode
->AsContent()->GetPrimaryFrame()) {
937 if (!frame
->IsSelectable(nullptr)) {
938 aSerializeRoot
= SerializeRoot::eNo
;
944 if (aSerializeRoot
== SerializeRoot::eYes
) {
945 int32_t endOffset
= -1;
946 if (aMaxLength
> 0) {
947 MOZ_ASSERT(aMaxLength
>= outputLength
);
948 endOffset
= aMaxLength
- outputLength
;
950 rv
= SerializeNodeStart(*aNode
, 0, endOffset
, maybeFixedNode
);
951 NS_ENSURE_SUCCESS(rv
, rv
);
954 nsINode
* node
= fixupNodeDeterminer
.IsSerializationOfFixupChildrenNeeded()
958 for (nsINode
* child
= node
->GetFirstChildOfTemplateOrNode(); child
;
959 child
= child
->GetNextSibling()) {
960 rv
= SerializeToStringRecursive(child
, SerializeRoot::eYes
, aMaxLength
);
961 NS_ENSURE_SUCCESS(rv
, rv
);
964 if (aSerializeRoot
== SerializeRoot::eYes
) {
965 rv
= SerializeNodeEnd(*aNode
, maybeFixedNode
);
966 NS_ENSURE_SUCCESS(rv
, rv
);
970 rv
= mTextStreamer
->FlushIfStringLongEnough();
976 nsresult
nsDocumentEncoder::NodeSerializer::SerializeToStringIterative(
977 nsINode
* aNode
) const {
980 nsINode
* node
= aNode
->GetFirstChildOfTemplateOrNode();
982 nsINode
* current
= node
;
983 rv
= SerializeNodeStart(*current
, 0, -1, current
);
984 NS_ENSURE_SUCCESS(rv
, rv
);
985 node
= current
->GetFirstChildOfTemplateOrNode();
986 while (!node
&& current
&& current
!= aNode
) {
987 rv
= SerializeNodeEnd(*current
);
988 NS_ENSURE_SUCCESS(rv
, rv
);
989 // Check if we have siblings.
990 node
= current
->GetNextSibling();
992 // Perhaps parent node has siblings.
993 current
= current
->GetParentNode();
995 // Handle template element. If the parent is a template's content,
996 // then adjust the parent to be the template element.
997 if (current
&& current
!= aNode
&& current
->IsDocumentFragment()) {
998 nsIContent
* host
= current
->AsDocumentFragment()->GetHost();
999 if (host
&& host
->IsHTMLElement(nsGkAtoms::_template
)) {
1010 static bool IsTextNode(nsINode
* aNode
) { return aNode
&& aNode
->IsText(); }
1012 nsresult
nsDocumentEncoder::NodeSerializer::SerializeTextNode(
1013 nsINode
& aNode
, int32_t aStartOffset
, int32_t aEndOffset
) const {
1014 MOZ_ASSERT(IsTextNode(&aNode
));
1016 nsresult rv
= SerializeNodeStart(aNode
, aStartOffset
, aEndOffset
);
1017 NS_ENSURE_SUCCESS(rv
, rv
);
1018 rv
= SerializeNodeEnd(aNode
);
1019 NS_ENSURE_SUCCESS(rv
, rv
);
1023 nsDocumentEncoder::RangeSerializer::StartAndEndContent
1024 nsDocumentEncoder::RangeSerializer::GetStartAndEndContentForRecursionLevel(
1025 const int32_t aDepth
) const {
1026 StartAndEndContent result
;
1028 const auto& inclusiveAncestorsOfStart
=
1029 mRangeBoundariesInclusiveAncestorsAndOffsets
.mInclusiveAncestorsOfStart
;
1030 const auto& inclusiveAncestorsOfEnd
=
1031 mRangeBoundariesInclusiveAncestorsAndOffsets
.mInclusiveAncestorsOfEnd
;
1032 int32_t start
= mStartRootIndex
- aDepth
;
1033 if (start
>= 0 && (uint32_t)start
<= inclusiveAncestorsOfStart
.Length()) {
1034 result
.mStart
= inclusiveAncestorsOfStart
[start
];
1037 int32_t end
= mEndRootIndex
- aDepth
;
1038 if (end
>= 0 && (uint32_t)end
<= inclusiveAncestorsOfEnd
.Length()) {
1039 result
.mEnd
= inclusiveAncestorsOfEnd
[end
];
1045 nsresult
nsDocumentEncoder::RangeSerializer::SerializeTextNode(
1046 nsINode
& aNode
, const nsIContent
& aContent
,
1047 const StartAndEndContent
& aStartAndEndContent
,
1048 const nsRange
& aRange
) const {
1049 const int32_t startOffset
=
1050 (aStartAndEndContent
.mStart
== &aContent
) ? aRange
.StartOffset() : 0;
1051 const int32_t endOffset
=
1052 (aStartAndEndContent
.mEnd
== &aContent
) ? aRange
.EndOffset() : -1;
1053 return mNodeSerializer
.SerializeTextNode(aNode
, startOffset
, endOffset
);
1056 nsresult
nsDocumentEncoder::RangeSerializer::SerializeRangeNodes(
1057 const nsRange
* const aRange
, nsINode
* const aNode
, const int32_t aDepth
) {
1058 MOZ_ASSERT(aDepth
>= 0);
1061 nsCOMPtr
<nsIContent
> content
= nsIContent::FromNodeOrNull(aNode
);
1062 NS_ENSURE_TRUE(content
, NS_ERROR_FAILURE
);
1064 if (nsDocumentEncoder::IsInvisibleNodeAndShouldBeSkipped(*aNode
, mFlags
)) {
1068 nsresult rv
= NS_OK
;
1070 StartAndEndContent startAndEndContent
=
1071 GetStartAndEndContentForRecursionLevel(aDepth
);
1073 if (startAndEndContent
.mStart
!= content
&&
1074 startAndEndContent
.mEnd
!= content
) {
1075 // node is completely contained in range. Serialize the whole subtree
1076 // rooted by this node.
1077 rv
= mNodeSerializer
.SerializeToStringRecursive(
1078 aNode
, NodeSerializer::SerializeRoot::eYes
);
1079 NS_ENSURE_SUCCESS(rv
, rv
);
1081 rv
= SerializeNodePartiallyContainedInRange(
1082 *aNode
, *content
, startAndEndContent
, *aRange
, aDepth
);
1083 if (NS_WARN_IF(NS_FAILED(rv
))) {
1091 nsDocumentEncoder::RangeSerializer::SerializeNodePartiallyContainedInRange(
1092 nsINode
& aNode
, nsIContent
& aContent
,
1093 const StartAndEndContent
& aStartAndEndContent
, const nsRange
& aRange
,
1094 const int32_t aDepth
) {
1095 // due to implementation it is impossible for text node to be both start and
1096 // end of range. We would have handled that case without getting here.
1097 // XXXsmaug What does this all mean?
1098 if (IsTextNode(&aNode
)) {
1100 SerializeTextNode(aNode
, aContent
, aStartAndEndContent
, aRange
);
1101 NS_ENSURE_SUCCESS(rv
, rv
);
1103 if (&aNode
!= mClosestCommonInclusiveAncestorOfRange
) {
1104 if (mRangeContextSerializer
.mRangeNodeContext
.IncludeInContext(aNode
)) {
1105 // halt the incrementing of mContextInfoDepth. This
1106 // is so paste client will include this node in paste.
1107 mHaltRangeHint
= true;
1109 if ((aStartAndEndContent
.mStart
== &aContent
) && !mHaltRangeHint
) {
1110 ++mContextInfoDepth
.mStart
;
1112 if ((aStartAndEndContent
.mEnd
== &aContent
) && !mHaltRangeHint
) {
1113 ++mContextInfoDepth
.mEnd
;
1116 // serialize the start of this node
1117 nsresult rv
= mNodeSerializer
.SerializeNodeStart(aNode
, 0, -1);
1118 NS_ENSURE_SUCCESS(rv
, rv
);
1121 const auto& inclusiveAncestorsOffsetsOfStart
=
1122 mRangeBoundariesInclusiveAncestorsAndOffsets
1123 .mInclusiveAncestorsOffsetsOfStart
;
1124 const auto& inclusiveAncestorsOffsetsOfEnd
=
1125 mRangeBoundariesInclusiveAncestorsAndOffsets
1126 .mInclusiveAncestorsOffsetsOfEnd
;
1127 // do some calculations that will tell us which children of this
1128 // node are in the range.
1129 Maybe
<uint32_t> startOffset
= Some(0);
1130 Maybe
<uint32_t> endOffset
;
1131 if (aStartAndEndContent
.mStart
== &aContent
&& mStartRootIndex
>= aDepth
) {
1132 startOffset
= inclusiveAncestorsOffsetsOfStart
[mStartRootIndex
- aDepth
];
1134 if (aStartAndEndContent
.mEnd
== &aContent
&& mEndRootIndex
>= aDepth
) {
1135 endOffset
= inclusiveAncestorsOffsetsOfEnd
[mEndRootIndex
- aDepth
];
1137 // generated aContent will cause offset values of Nothing to be returned.
1138 if (startOffset
.isNothing()) {
1139 startOffset
= Some(0);
1141 if (endOffset
.isNothing()) {
1142 endOffset
= Some(aContent
.GetChildCount());
1144 // if we are at the "tip" of the selection, endOffset is fine.
1145 // otherwise, we need to add one. This is because of the semantics
1146 // of the offset list created by GetInclusiveAncestorsAndOffsets(). The
1147 // intermediate points on the list use the endOffset of the
1148 // location of the ancestor, rather than just past it. So we need
1149 // to add one here in order to include it in the children we serialize.
1150 if (&aNode
!= aRange
.GetEndContainer()) {
1151 MOZ_ASSERT(*endOffset
!= UINT32_MAX
);
1157 nsresult rv
= SerializeChildrenOfContent(aContent
, *startOffset
,
1158 *endOffset
, &aRange
, aDepth
);
1159 NS_ENSURE_SUCCESS(rv
, rv
);
1161 // serialize the end of this node
1162 if (&aNode
!= mClosestCommonInclusiveAncestorOfRange
) {
1163 nsresult rv
= mNodeSerializer
.SerializeNodeEnd(aNode
);
1164 NS_ENSURE_SUCCESS(rv
, rv
);
1171 nsresult
nsDocumentEncoder::RangeSerializer::SerializeChildrenOfContent(
1172 nsIContent
& aContent
, uint32_t aStartOffset
, uint32_t aEndOffset
,
1173 const nsRange
* aRange
, int32_t aDepth
) {
1174 // serialize the children of this node that are in the range
1175 nsIContent
* childAsNode
= aContent
.GetFirstChild();
1178 for (; j
< aStartOffset
&& childAsNode
; ++j
) {
1179 childAsNode
= childAsNode
->GetNextSibling();
1182 MOZ_ASSERT(j
== aStartOffset
);
1184 for (; childAsNode
&& j
< aEndOffset
; ++j
) {
1186 if ((j
== aStartOffset
) || (j
== aEndOffset
- 1)) {
1187 rv
= SerializeRangeNodes(aRange
, childAsNode
, aDepth
+ 1);
1189 rv
= mNodeSerializer
.SerializeToStringRecursive(
1190 childAsNode
, NodeSerializer::SerializeRoot::eYes
);
1193 if (NS_FAILED(rv
)) {
1197 childAsNode
= childAsNode
->GetNextSibling();
1203 nsresult
nsDocumentEncoder::RangeContextSerializer::SerializeRangeContextStart(
1204 const nsTArray
<nsINode
*>& aAncestorArray
) {
1205 if (mDisableContextSerialize
) {
1209 AutoTArray
<nsINode
*, 8>* serializedContext
= mRangeContexts
.AppendElement();
1211 int32_t i
= aAncestorArray
.Length(), j
;
1212 nsresult rv
= NS_OK
;
1214 // currently only for table-related elements; see Bug 137450
1215 j
= mRangeNodeContext
.GetImmediateContextCount(aAncestorArray
);
1218 nsINode
* node
= aAncestorArray
.ElementAt(--i
);
1221 // Either a general inclusion or as immediate context
1222 if (mRangeNodeContext
.IncludeInContext(*node
) || i
< j
) {
1223 rv
= mNodeSerializer
.SerializeNodeStart(*node
, 0, -1);
1224 serializedContext
->AppendElement(node
);
1225 if (NS_FAILED(rv
)) break;
1232 nsresult
nsDocumentEncoder::RangeContextSerializer::SerializeRangeContextEnd() {
1233 if (mDisableContextSerialize
) {
1237 MOZ_RELEASE_ASSERT(!mRangeContexts
.IsEmpty(),
1238 "Tried to end context without starting one.");
1239 AutoTArray
<nsINode
*, 8>& serializedContext
= mRangeContexts
.LastElement();
1241 nsresult rv
= NS_OK
;
1242 for (nsINode
* node
: Reversed(serializedContext
)) {
1243 rv
= mNodeSerializer
.SerializeNodeEnd(*node
);
1245 if (NS_FAILED(rv
)) break;
1248 mRangeContexts
.RemoveLastElement();
1252 bool nsDocumentEncoder::RangeSerializer::HasInvisibleParentAndShouldBeSkipped(
1253 nsINode
& aNode
) const {
1254 if (!(mFlags
& SkipInvisibleContent
)) {
1258 // Check that the parent is visible if we don't a frame.
1259 // IsInvisibleNodeAndShouldBeSkipped() will do it when there's a frame.
1260 nsCOMPtr
<nsIContent
> content
= nsIContent::FromNode(aNode
);
1261 if (content
&& !content
->GetPrimaryFrame()) {
1262 nsIContent
* parent
= content
->GetParent();
1263 return !parent
|| IsInvisibleNodeAndShouldBeSkipped(*parent
, mFlags
);
1269 nsresult
nsDocumentEncoder::RangeSerializer::SerializeRangeToString(
1270 const nsRange
* aRange
) {
1271 if (!aRange
|| aRange
->Collapsed()) return NS_OK
;
1273 mClosestCommonInclusiveAncestorOfRange
=
1274 aRange
->GetClosestCommonInclusiveAncestor();
1276 if (!mClosestCommonInclusiveAncestorOfRange
) {
1280 nsINode
* startContainer
= aRange
->GetStartContainer();
1281 NS_ENSURE_TRUE(startContainer
, NS_ERROR_FAILURE
);
1282 int32_t startOffset
= aRange
->StartOffset();
1284 nsINode
* endContainer
= aRange
->GetEndContainer();
1285 NS_ENSURE_TRUE(endContainer
, NS_ERROR_FAILURE
);
1286 int32_t endOffset
= aRange
->EndOffset();
1288 mContextInfoDepth
= {};
1289 mCommonInclusiveAncestors
.Clear();
1291 mRangeBoundariesInclusiveAncestorsAndOffsets
= {};
1292 auto& inclusiveAncestorsOfStart
=
1293 mRangeBoundariesInclusiveAncestorsAndOffsets
.mInclusiveAncestorsOfStart
;
1294 auto& inclusiveAncestorsOffsetsOfStart
=
1295 mRangeBoundariesInclusiveAncestorsAndOffsets
1296 .mInclusiveAncestorsOffsetsOfStart
;
1297 auto& inclusiveAncestorsOfEnd
=
1298 mRangeBoundariesInclusiveAncestorsAndOffsets
.mInclusiveAncestorsOfEnd
;
1299 auto& inclusiveAncestorsOffsetsOfEnd
=
1300 mRangeBoundariesInclusiveAncestorsAndOffsets
1301 .mInclusiveAncestorsOffsetsOfEnd
;
1303 nsContentUtils::GetInclusiveAncestors(mClosestCommonInclusiveAncestorOfRange
,
1304 mCommonInclusiveAncestors
);
1305 nsContentUtils::GetInclusiveAncestorsAndOffsets(
1306 startContainer
, startOffset
, &inclusiveAncestorsOfStart
,
1307 &inclusiveAncestorsOffsetsOfStart
);
1308 nsContentUtils::GetInclusiveAncestorsAndOffsets(
1309 endContainer
, endOffset
, &inclusiveAncestorsOfEnd
,
1310 &inclusiveAncestorsOffsetsOfEnd
);
1312 nsCOMPtr
<nsIContent
> commonContent
=
1313 nsIContent::FromNodeOrNull(mClosestCommonInclusiveAncestorOfRange
);
1314 mStartRootIndex
= inclusiveAncestorsOfStart
.IndexOf(commonContent
);
1315 mEndRootIndex
= inclusiveAncestorsOfEnd
.IndexOf(commonContent
);
1317 nsresult rv
= NS_OK
;
1319 rv
= mRangeContextSerializer
.SerializeRangeContextStart(
1320 mCommonInclusiveAncestors
);
1321 NS_ENSURE_SUCCESS(rv
, rv
);
1323 if (startContainer
== endContainer
&& IsTextNode(startContainer
)) {
1324 if (HasInvisibleParentAndShouldBeSkipped(*startContainer
)) {
1327 rv
= mNodeSerializer
.SerializeTextNode(*startContainer
, startOffset
,
1329 NS_ENSURE_SUCCESS(rv
, rv
);
1331 rv
= SerializeRangeNodes(aRange
, mClosestCommonInclusiveAncestorOfRange
, 0);
1332 NS_ENSURE_SUCCESS(rv
, rv
);
1334 rv
= mRangeContextSerializer
.SerializeRangeContextEnd();
1335 NS_ENSURE_SUCCESS(rv
, rv
);
1340 void nsDocumentEncoder::ReleaseDocumentReferenceAndInitialize(
1341 bool aClearCachedSerializer
) {
1342 mDocument
= nullptr;
1344 Initialize(aClearCachedSerializer
);
1348 nsDocumentEncoder::EncodeToString(nsAString
& aOutputString
) {
1349 return EncodeToStringWithMaxLength(0, aOutputString
);
1353 nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength
,
1354 nsAString
& aOutputString
) {
1355 MOZ_ASSERT(mRangeContextSerializer
.mRangeContexts
.IsEmpty(),
1356 "Re-entrant call to nsDocumentEncoder.");
1357 auto rangeContextGuard
=
1358 MakeScopeExit([&] { mRangeContextSerializer
.mRangeContexts
.Clear(); });
1360 if (!mDocument
) return NS_ERROR_NOT_INITIALIZED
;
1362 AutoReleaseDocumentIfNeeded
autoReleaseDocument(this);
1364 aOutputString
.Truncate();
1367 static const size_t kStringBufferSizeInBytes
= 2048;
1368 if (!mCachedBuffer
) {
1369 mCachedBuffer
= nsStringBuffer::Alloc(kStringBufferSizeInBytes
).take();
1370 if (NS_WARN_IF(!mCachedBuffer
)) {
1371 return NS_ERROR_OUT_OF_MEMORY
;
1375 !mCachedBuffer
->IsReadonly(),
1376 "nsIDocumentEncoder shouldn't keep reference to non-readonly buffer!");
1377 static_cast<char16_t
*>(mCachedBuffer
->Data())[0] = char16_t(0);
1378 mCachedBuffer
->ToString(0, output
, true);
1379 // output owns the buffer now!
1380 mCachedBuffer
= nullptr;
1383 nsAutoCString
progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX
);
1384 AppendUTF16toUTF8(mMimeType
, progId
);
1386 mSerializer
= do_CreateInstance(progId
.get());
1387 NS_ENSURE_TRUE(mSerializer
, NS_ERROR_NOT_IMPLEMENTED
);
1390 nsresult rv
= NS_OK
;
1392 bool rewriteEncodingDeclaration
=
1393 !mEncodingScope
.IsLimited() &&
1394 !(mFlags
& OutputDontRewriteEncodingDeclaration
);
1395 mSerializer
->Init(mFlags
, mWrapColumn
, mEncoding
, mIsCopying
,
1396 rewriteEncodingDeclaration
, &mNeedsPreformatScanning
,
1399 rv
= SerializeDependingOnScope(aMaxLength
);
1400 NS_ENSURE_SUCCESS(rv
, rv
);
1402 rv
= mSerializer
->FlushAndFinish();
1404 mCachedBuffer
= nsStringBuffer::FromString(output
);
1405 // We have to be careful how we set aOutputString, because we don't
1406 // want it to end up sharing mCachedBuffer if we plan to reuse it.
1407 bool setOutput
= false;
1408 // Try to cache the buffer.
1409 if (mCachedBuffer
) {
1410 if ((mCachedBuffer
->StorageSize() == kStringBufferSizeInBytes
) &&
1411 !mCachedBuffer
->IsReadonly()) {
1412 mCachedBuffer
->AddRef();
1414 if (NS_SUCCEEDED(rv
)) {
1415 mCachedBuffer
->ToString(output
.Length(), aOutputString
);
1418 mCachedBuffer
= nullptr;
1422 if (!setOutput
&& NS_SUCCEEDED(rv
)) {
1423 aOutputString
.Append(output
.get(), output
.Length());
1430 nsDocumentEncoder::EncodeToStream(nsIOutputStream
* aStream
) {
1431 MOZ_ASSERT(mRangeContextSerializer
.mRangeContexts
.IsEmpty(),
1432 "Re-entrant call to nsDocumentEncoder.");
1433 auto rangeContextGuard
=
1434 MakeScopeExit([&] { mRangeContextSerializer
.mRangeContexts
.Clear(); });
1435 NS_ENSURE_ARG_POINTER(aStream
);
1437 nsresult rv
= NS_OK
;
1439 if (!mDocument
) return NS_ERROR_NOT_INITIALIZED
;
1442 return NS_ERROR_UCONV_NOCONV
;
1446 const bool isPlainText
= mMimeType
.LowerCaseEqualsLiteral(kTextMime
);
1447 mTextStreamer
.emplace(*aStream
, mEncoding
->NewEncoder(), isPlainText
, buf
);
1449 rv
= EncodeToString(buf
);
1451 // Force a flush of the last chunk of data.
1452 rv
= mTextStreamer
->ForceFlush();
1453 NS_ENSURE_SUCCESS(rv
, rv
);
1455 mTextStreamer
.reset();
1461 nsDocumentEncoder::EncodeToStringWithContext(nsAString
& aContextString
,
1462 nsAString
& aInfoString
,
1463 nsAString
& aEncodedString
) {
1464 return NS_ERROR_NOT_IMPLEMENTED
;
1468 nsDocumentEncoder::SetNodeFixup(nsIDocumentEncoderNodeFixup
* aFixup
) {
1469 mNodeFixup
= aFixup
;
1473 bool do_getDocumentTypeSupportedForEncoding(const char* aContentType
) {
1474 if (!nsCRT::strcmp(aContentType
, "text/xml") ||
1475 !nsCRT::strcmp(aContentType
, "application/xml") ||
1476 !nsCRT::strcmp(aContentType
, "application/xhtml+xml") ||
1477 !nsCRT::strcmp(aContentType
, "image/svg+xml") ||
1478 !nsCRT::strcmp(aContentType
, "text/html") ||
1479 !nsCRT::strcmp(aContentType
, "text/plain")) {
1485 already_AddRefed
<nsIDocumentEncoder
> do_createDocumentEncoder(
1486 const char* aContentType
) {
1487 if (do_getDocumentTypeSupportedForEncoding(aContentType
)) {
1488 return do_AddRef(new nsDocumentEncoder
);
1493 class nsHTMLCopyEncoder
: public nsDocumentEncoder
{
1495 class RangeNodeContext final
: public nsDocumentEncoder::RangeNodeContext
{
1496 bool IncludeInContext(nsINode
& aNode
) const final
;
1498 int32_t GetImmediateContextCount(
1499 const nsTArray
<nsINode
*>& aAncestorArray
) const final
;
1503 nsHTMLCopyEncoder();
1504 ~nsHTMLCopyEncoder();
1506 NS_IMETHOD
Init(Document
* aDocument
, const nsAString
& aMimeType
,
1507 uint32_t aFlags
) override
;
1509 // overridden methods from nsDocumentEncoder
1510 MOZ_CAN_RUN_SCRIPT_BOUNDARY
1511 NS_IMETHOD
SetSelection(Selection
* aSelection
) override
;
1512 NS_IMETHOD
EncodeToStringWithContext(nsAString
& aContextString
,
1513 nsAString
& aInfoString
,
1514 nsAString
& aEncodedString
) override
;
1515 NS_IMETHOD
EncodeToString(nsAString
& aOutputString
) override
;
1518 enum Endpoint
{ kStart
, kEnd
};
1520 nsresult
PromoteRange(nsRange
* inRange
);
1521 nsresult
PromoteAncestorChain(nsCOMPtr
<nsINode
>* ioNode
,
1522 int32_t* ioStartOffset
, int32_t* ioEndOffset
);
1523 nsresult
GetPromotedPoint(Endpoint aWhere
, nsINode
* aNode
, int32_t aOffset
,
1524 nsCOMPtr
<nsINode
>* outNode
, int32_t* outOffset
,
1526 static nsCOMPtr
<nsINode
> GetChildAt(nsINode
* aParent
, int32_t aOffset
);
1527 static bool IsMozBR(Element
* aNode
);
1528 static nsresult
GetNodeLocation(nsINode
* inChild
,
1529 nsCOMPtr
<nsINode
>* outParent
,
1530 int32_t* outOffset
);
1531 bool IsRoot(nsINode
* aNode
);
1532 static bool IsFirstNode(nsINode
* aNode
);
1533 static bool IsLastNode(nsINode
* aNode
);
1538 nsHTMLCopyEncoder::nsHTMLCopyEncoder()
1539 : nsDocumentEncoder
{MakeUnique
<nsHTMLCopyEncoder::RangeNodeContext
>()} {
1540 mIsTextWidget
= false;
1543 nsHTMLCopyEncoder::~nsHTMLCopyEncoder() = default;
1546 nsHTMLCopyEncoder::Init(Document
* aDocument
, const nsAString
& aMimeType
,
1548 if (!aDocument
) return NS_ERROR_INVALID_ARG
;
1550 mIsTextWidget
= false;
1554 mDocument
= aDocument
;
1556 // Hack, hack! Traditionally, the caller passes text/plain, which is
1557 // treated as "guess text/html or text/plain" in this context. (It has a
1558 // different meaning in other contexts. Sigh.) From now on, "text/plain"
1559 // means forcing text/plain instead of guessing.
1560 if (aMimeType
.EqualsLiteral("text/plain")) {
1561 mMimeType
.AssignLiteral("text/plain");
1563 mMimeType
.AssignLiteral("text/html");
1566 // Make all links absolute when copying
1567 // (see related bugs #57296, #41924, #58646, #32768)
1568 mFlags
= aFlags
| OutputAbsoluteLinks
;
1570 if (!mDocument
->IsScriptEnabled()) mFlags
|= OutputNoScriptContent
;
1576 nsHTMLCopyEncoder::SetSelection(Selection
* aSelection
) {
1577 // check for text widgets: we need to recognize these so that
1578 // we don't tweak the selection to be outside of the magic
1579 // div that ender-lite text widgets are embedded in.
1581 if (!aSelection
) return NS_ERROR_NULL_POINTER
;
1583 const uint32_t rangeCount
= aSelection
->RangeCount();
1585 // if selection is uninitialized return
1587 return NS_ERROR_FAILURE
;
1590 // we'll just use the common parent of the first range. Implicit assumption
1591 // here that multi-range selections are table cell selections, in which case
1592 // the common parent is somewhere in the table and we don't really care where.
1594 // FIXME(emilio, bug 1455894): This assumption is already wrong, and will
1595 // probably be more wrong in a Shadow DOM world...
1597 // We should be able to write this as "Find the common ancestor of the
1598 // selection, then go through the flattened tree and serialize the selected
1599 // nodes", effectively serializing the composed tree.
1600 RefPtr
<nsRange
> range
= aSelection
->GetRangeAt(0);
1601 nsINode
* commonParent
= range
->GetClosestCommonInclusiveAncestor();
1603 for (nsCOMPtr
<nsIContent
> selContent(
1604 nsIContent::FromNodeOrNull(commonParent
));
1605 selContent
; selContent
= selContent
->GetParent()) {
1606 // checking for selection inside a plaintext form widget
1607 if (selContent
->IsAnyOfHTMLElements(nsGkAtoms::input
,
1608 nsGkAtoms::textarea
)) {
1609 mIsTextWidget
= true;
1614 // normalize selection if we are not in a widget
1615 if (mIsTextWidget
) {
1616 mEncodingScope
.mSelection
= aSelection
;
1617 mMimeType
.AssignLiteral("text/plain");
1621 // XXX We should try to get rid of the Selection object here.
1624 // also consider ourselves in a text widget if we can't find an html document
1625 if (!(mDocument
&& mDocument
->IsHTMLDocument())) {
1626 mIsTextWidget
= true;
1627 mEncodingScope
.mSelection
= aSelection
;
1628 // mMimeType is set to text/plain when encoding starts.
1632 // there's no Clone() for selection! fix...
1633 // nsresult rv = aSelection->Clone(getter_AddRefs(mSelection);
1634 // NS_ENSURE_SUCCESS(rv, rv);
1635 mEncodingScope
.mSelection
= new Selection(SelectionType::eNormal
, nullptr);
1637 // loop thru the ranges in the selection
1638 for (const uint32_t rangeIdx
: IntegerRange(rangeCount
)) {
1639 MOZ_ASSERT(aSelection
->RangeCount() == rangeCount
);
1640 range
= aSelection
->GetRangeAt(rangeIdx
);
1641 NS_ENSURE_TRUE(range
, NS_ERROR_FAILURE
);
1642 RefPtr
<nsRange
> myRange
= range
->CloneRange();
1643 MOZ_ASSERT(myRange
);
1645 // adjust range to include any ancestors who's children are entirely
1647 nsresult rv
= PromoteRange(myRange
);
1648 NS_ENSURE_SUCCESS(rv
, rv
);
1651 RefPtr
<Selection
> selection(mEncodingScope
.mSelection
);
1652 RefPtr
<Document
> document(mDocument
);
1653 selection
->AddRangeAndSelectFramesAndNotifyListenersInternal(
1654 *myRange
, document
, result
);
1655 rv
= result
.StealNSResult();
1656 NS_ENSURE_SUCCESS(rv
, rv
);
1663 nsHTMLCopyEncoder::EncodeToString(nsAString
& aOutputString
) {
1664 if (mIsTextWidget
) {
1665 mMimeType
.AssignLiteral("text/plain");
1667 return nsDocumentEncoder::EncodeToString(aOutputString
);
1671 nsHTMLCopyEncoder::EncodeToStringWithContext(nsAString
& aContextString
,
1672 nsAString
& aInfoString
,
1673 nsAString
& aEncodedString
) {
1674 nsresult rv
= EncodeToString(aEncodedString
);
1675 NS_ENSURE_SUCCESS(rv
, rv
);
1677 // do not encode any context info or range hints if we are in a text widget.
1678 if (mIsTextWidget
) return NS_OK
;
1680 // now encode common ancestors into aContextString. Note that the common
1681 // ancestors will be for the last range in the selection in the case of
1682 // multirange selections. encoding ancestors every range in a multirange
1683 // selection in a way that could be understood by the paste code would be a
1684 // lot more work to do. As a practical matter, selections are single range,
1685 // and the ones that aren't are table cell selections where all the cells are
1686 // in the same table.
1688 mSerializer
->Init(mFlags
, mWrapColumn
, mEncoding
, mIsCopying
, false,
1689 &mNeedsPreformatScanning
, aContextString
);
1691 // leaf of ancestors might be text node. If so discard it.
1692 int32_t count
= mRangeSerializer
.mCommonInclusiveAncestors
.Length();
1694 nsCOMPtr
<nsINode
> node
;
1696 node
= mRangeSerializer
.mCommonInclusiveAncestors
.ElementAt(0);
1699 if (node
&& IsTextNode(node
)) {
1700 mRangeSerializer
.mCommonInclusiveAncestors
.RemoveElementAt(0);
1701 if (mRangeSerializer
.mContextInfoDepth
.mStart
) {
1702 --mRangeSerializer
.mContextInfoDepth
.mStart
;
1704 if (mRangeSerializer
.mContextInfoDepth
.mEnd
) {
1705 --mRangeSerializer
.mContextInfoDepth
.mEnd
;
1712 node
= mRangeSerializer
.mCommonInclusiveAncestors
.ElementAt(--i
);
1713 rv
= mNodeSerializer
.SerializeNodeStart(*node
, 0, -1);
1714 NS_ENSURE_SUCCESS(rv
, rv
);
1716 // i = 0; guaranteed by above
1718 node
= mRangeSerializer
.mCommonInclusiveAncestors
.ElementAt(i
++);
1719 rv
= mNodeSerializer
.SerializeNodeEnd(*node
);
1720 NS_ENSURE_SUCCESS(rv
, rv
);
1723 mSerializer
->Finish();
1725 // encode range info : the start and end depth of the selection, where the
1726 // depth is distance down in the parent hierarchy. Later we will need to add
1727 // leading/trailing whitespace info to this.
1728 nsAutoString infoString
;
1729 infoString
.AppendInt(mRangeSerializer
.mContextInfoDepth
.mStart
);
1730 infoString
.Append(char16_t(','));
1731 infoString
.AppendInt(mRangeSerializer
.mContextInfoDepth
.mEnd
);
1732 aInfoString
= infoString
;
1737 bool nsHTMLCopyEncoder::RangeNodeContext::IncludeInContext(
1738 nsINode
& aNode
) const {
1739 nsCOMPtr
<nsIContent
> content(nsIContent::FromNodeOrNull(&aNode
));
1741 if (!content
) return false;
1743 return content
->IsAnyOfHTMLElements(
1744 nsGkAtoms::b
, nsGkAtoms::i
, nsGkAtoms::u
, nsGkAtoms::a
, nsGkAtoms::tt
,
1745 nsGkAtoms::s
, nsGkAtoms::big
, nsGkAtoms::small
, nsGkAtoms::strike
,
1746 nsGkAtoms::em
, nsGkAtoms::strong
, nsGkAtoms::dfn
, nsGkAtoms::code
,
1747 nsGkAtoms::cite
, nsGkAtoms::var
, nsGkAtoms::abbr
, nsGkAtoms::font
,
1748 nsGkAtoms::script
, nsGkAtoms::span
, nsGkAtoms::pre
, nsGkAtoms::h1
,
1749 nsGkAtoms::h2
, nsGkAtoms::h3
, nsGkAtoms::h4
, nsGkAtoms::h5
,
1753 nsresult
nsHTMLCopyEncoder::PromoteRange(nsRange
* inRange
) {
1754 if (!inRange
->IsPositioned()) {
1755 return NS_ERROR_UNEXPECTED
;
1757 nsCOMPtr
<nsINode
> startNode
= inRange
->GetStartContainer();
1758 uint32_t startOffset
= inRange
->StartOffset();
1759 nsCOMPtr
<nsINode
> endNode
= inRange
->GetEndContainer();
1760 uint32_t endOffset
= inRange
->EndOffset();
1761 nsCOMPtr
<nsINode
> common
= inRange
->GetClosestCommonInclusiveAncestor();
1763 nsCOMPtr
<nsINode
> opStartNode
;
1764 nsCOMPtr
<nsINode
> opEndNode
;
1765 int32_t opStartOffset
, opEndOffset
;
1767 // examine range endpoints.
1769 GetPromotedPoint(kStart
, startNode
, static_cast<int32_t>(startOffset
),
1770 address_of(opStartNode
), &opStartOffset
, common
);
1771 NS_ENSURE_SUCCESS(rv
, rv
);
1772 rv
= GetPromotedPoint(kEnd
, endNode
, static_cast<int32_t>(endOffset
),
1773 address_of(opEndNode
), &opEndOffset
, common
);
1774 NS_ENSURE_SUCCESS(rv
, rv
);
1776 // if both range endpoints are at the common ancestor, check for possible
1777 // inclusion of ancestors
1778 if (opStartNode
== common
&& opEndNode
== common
) {
1779 rv
= PromoteAncestorChain(address_of(opStartNode
), &opStartOffset
,
1781 NS_ENSURE_SUCCESS(rv
, rv
);
1782 opEndNode
= opStartNode
;
1785 // set the range to the new values
1787 inRange
->SetStart(*opStartNode
, static_cast<uint32_t>(opStartOffset
), err
);
1788 if (NS_WARN_IF(err
.Failed())) {
1789 return err
.StealNSResult();
1791 inRange
->SetEnd(*opEndNode
, static_cast<uint32_t>(opEndOffset
), err
);
1792 if (NS_WARN_IF(err
.Failed())) {
1793 return err
.StealNSResult();
1798 // PromoteAncestorChain will promote a range represented by
1799 // [{*ioNode,*ioStartOffset} , {*ioNode,*ioEndOffset}] The promotion is
1800 // different from that found in getPromotedPoint: it will only promote one
1801 // endpoint if it can promote the other. Thus, instead of having a
1802 // startnode/endNode, there is just the one ioNode.
1803 nsresult
nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr
<nsINode
>* ioNode
,
1804 int32_t* ioStartOffset
,
1805 int32_t* ioEndOffset
) {
1806 if (!ioNode
|| !ioStartOffset
|| !ioEndOffset
) return NS_ERROR_NULL_POINTER
;
1808 nsresult rv
= NS_OK
;
1811 nsCOMPtr
<nsINode
> frontNode
, endNode
, parent
;
1812 int32_t frontOffset
, endOffset
;
1814 // save the editable state of the ioNode, so we don't promote an ancestor if
1815 // it has different editable state
1816 nsCOMPtr
<nsINode
> node
= *ioNode
;
1817 bool isEditable
= node
->IsEditable();
1819 // loop for as long as we can promote both endpoints
1822 parent
= node
->GetParentNode();
1826 // passing parent as last param to GetPromotedPoint() allows it to promote
1827 // only one level up the hierarchy.
1828 rv
= GetPromotedPoint(kStart
, *ioNode
, *ioStartOffset
,
1829 address_of(frontNode
), &frontOffset
, parent
);
1830 NS_ENSURE_SUCCESS(rv
, rv
);
1831 // then we make the same attempt with the endpoint
1832 rv
= GetPromotedPoint(kEnd
, *ioNode
, *ioEndOffset
, address_of(endNode
),
1833 &endOffset
, parent
);
1834 NS_ENSURE_SUCCESS(rv
, rv
);
1836 // if both endpoints were promoted one level and isEditable is the same as
1837 // the original node, keep looping - otherwise we are done.
1838 if ((frontNode
!= parent
) || (endNode
!= parent
) ||
1839 (frontNode
->IsEditable() != isEditable
))
1842 *ioNode
= frontNode
;
1843 *ioStartOffset
= frontOffset
;
1844 *ioEndOffset
= endOffset
;
1851 nsresult
nsHTMLCopyEncoder::GetPromotedPoint(Endpoint aWhere
, nsINode
* aNode
,
1853 nsCOMPtr
<nsINode
>* outNode
,
1856 nsresult rv
= NS_OK
;
1857 nsCOMPtr
<nsINode
> node
= aNode
;
1858 nsCOMPtr
<nsINode
> parent
= aNode
;
1859 int32_t offset
= aOffset
;
1860 bool bResetPromotion
= false;
1864 *outOffset
= offset
;
1866 if (common
== node
) return NS_OK
;
1868 if (aWhere
== kStart
) {
1869 // some special casing for text nodes
1870 if (auto nodeAsText
= aNode
->GetAsText()) {
1871 // if not at beginning of text node, we are done
1873 // unless everything before us in just whitespace. NOTE: we need a more
1874 // general solution that truly detects all cases of non-significant
1875 // whitesace with no false alarms.
1877 nodeAsText
->SubstringData(0, offset
, text
, IgnoreErrors());
1878 text
.CompressWhitespace();
1879 if (!text
.IsEmpty()) return NS_OK
;
1880 bResetPromotion
= true;
1883 rv
= GetNodeLocation(aNode
, address_of(parent
), &offset
);
1884 NS_ENSURE_SUCCESS(rv
, rv
);
1886 node
= GetChildAt(parent
, offset
);
1888 if (!node
) node
= parent
;
1890 // finding the real start for this point. look up the tree for as long as
1891 // we are the first node in the container, and as long as we haven't hit the
1893 if (!IsRoot(node
) && (parent
!= common
)) {
1894 rv
= GetNodeLocation(node
, address_of(parent
), &offset
);
1895 NS_ENSURE_SUCCESS(rv
, rv
);
1896 if (offset
== -1) return NS_OK
; // we hit generated content; STOP
1897 while ((IsFirstNode(node
)) && (!IsRoot(parent
)) && (parent
!= common
)) {
1898 if (bResetPromotion
) {
1899 nsCOMPtr
<nsIContent
> content
= nsIContent::FromNodeOrNull(parent
);
1900 if (content
&& content
->IsHTMLElement()) {
1901 if (nsHTMLElement::IsBlock(
1902 nsHTMLTags::AtomTagToId(content
->NodeInfo()->NameAtom()))) {
1903 bResetPromotion
= false;
1909 rv
= GetNodeLocation(node
, address_of(parent
), &offset
);
1910 NS_ENSURE_SUCCESS(rv
, rv
);
1911 if (offset
== -1) // we hit generated content; STOP
1919 if (bResetPromotion
) {
1921 *outOffset
= aOffset
;
1924 *outOffset
= offset
;
1930 if (aWhere
== kEnd
) {
1931 // some special casing for text nodes
1932 if (auto nodeAsText
= aNode
->GetAsText()) {
1933 // if not at end of text node, we are done
1934 uint32_t len
= aNode
->Length();
1935 if (offset
< (int32_t)len
) {
1936 // unless everything after us in just whitespace. NOTE: we need a more
1937 // general solution that truly detects all cases of non-significant
1938 // whitespace with no false alarms.
1940 nodeAsText
->SubstringData(offset
, len
- offset
, text
, IgnoreErrors());
1941 text
.CompressWhitespace();
1942 if (!text
.IsEmpty()) return NS_OK
;
1943 bResetPromotion
= true;
1945 rv
= GetNodeLocation(aNode
, address_of(parent
), &offset
);
1946 NS_ENSURE_SUCCESS(rv
, rv
);
1948 if (offset
) offset
--; // we want node _before_ offset
1949 node
= GetChildAt(parent
, offset
);
1951 if (!node
) node
= parent
;
1953 // finding the real end for this point. look up the tree for as long as we
1954 // are the last node in the container, and as long as we haven't hit the
1956 if (!IsRoot(node
) && (parent
!= common
)) {
1957 rv
= GetNodeLocation(node
, address_of(parent
), &offset
);
1958 NS_ENSURE_SUCCESS(rv
, rv
);
1959 if (offset
== -1) return NS_OK
; // we hit generated content; STOP
1960 while ((IsLastNode(node
)) && (!IsRoot(parent
)) && (parent
!= common
)) {
1961 if (bResetPromotion
) {
1962 nsCOMPtr
<nsIContent
> content
= nsIContent::FromNodeOrNull(parent
);
1963 if (content
&& content
->IsHTMLElement()) {
1964 if (nsHTMLElement::IsBlock(
1965 nsHTMLTags::AtomTagToId(content
->NodeInfo()->NameAtom()))) {
1966 bResetPromotion
= false;
1972 rv
= GetNodeLocation(node
, address_of(parent
), &offset
);
1973 NS_ENSURE_SUCCESS(rv
, rv
);
1974 if (offset
== -1) // we hit generated content; STOP
1982 if (bResetPromotion
) {
1984 *outOffset
= aOffset
;
1987 offset
++; // add one since this in an endpoint - want to be AFTER node.
1988 *outOffset
= offset
;
1997 nsCOMPtr
<nsINode
> nsHTMLCopyEncoder::GetChildAt(nsINode
* aParent
,
1999 nsCOMPtr
<nsINode
> resultNode
;
2001 if (!aParent
) return resultNode
;
2003 nsCOMPtr
<nsIContent
> content
= nsIContent::FromNodeOrNull(aParent
);
2004 MOZ_ASSERT(content
, "null content in nsHTMLCopyEncoder::GetChildAt");
2006 resultNode
= content
->GetChildAt_Deprecated(aOffset
);
2011 bool nsHTMLCopyEncoder::IsMozBR(Element
* aElement
) {
2012 HTMLBRElement
* brElement
= HTMLBRElement::FromNodeOrNull(aElement
);
2013 return brElement
&& brElement
->IsPaddingForEmptyLastLine();
2016 nsresult
nsHTMLCopyEncoder::GetNodeLocation(nsINode
* inChild
,
2017 nsCOMPtr
<nsINode
>* outParent
,
2018 int32_t* outOffset
) {
2019 NS_ASSERTION((inChild
&& outParent
&& outOffset
), "bad args");
2020 if (inChild
&& outParent
&& outOffset
) {
2021 nsCOMPtr
<nsIContent
> child
= nsIContent::FromNodeOrNull(inChild
);
2023 return NS_ERROR_NULL_POINTER
;
2026 nsIContent
* parent
= child
->GetParent();
2028 return NS_ERROR_NULL_POINTER
;
2031 *outParent
= parent
;
2032 *outOffset
= parent
->ComputeIndexOf_Deprecated(child
);
2035 return NS_ERROR_NULL_POINTER
;
2038 bool nsHTMLCopyEncoder::IsRoot(nsINode
* aNode
) {
2039 nsCOMPtr
<nsIContent
> content
= nsIContent::FromNodeOrNull(aNode
);
2044 if (mIsTextWidget
) {
2045 return content
->IsHTMLElement(nsGkAtoms::div
);
2048 return content
->IsAnyOfHTMLElements(nsGkAtoms::body
, nsGkAtoms::td
,
2052 bool nsHTMLCopyEncoder::IsFirstNode(nsINode
* aNode
) {
2053 // need to check if any nodes before us are really visible.
2054 // Mike wrote something for me along these lines in nsSelectionController,
2055 // but I don't think it's ready for use yet - revisit.
2056 // HACK: for now, simply consider all whitespace text nodes to be
2057 // invisible formatting nodes.
2058 for (nsIContent
* sibling
= aNode
->GetPreviousSibling(); sibling
;
2059 sibling
= sibling
->GetPreviousSibling()) {
2060 if (!sibling
->TextIsOnlyWhitespace()) {
2068 bool nsHTMLCopyEncoder::IsLastNode(nsINode
* aNode
) {
2069 // need to check if any nodes after us are really visible.
2070 // Mike wrote something for me along these lines in nsSelectionController,
2071 // but I don't think it's ready for use yet - revisit.
2072 // HACK: for now, simply consider all whitespace text nodes to be
2073 // invisible formatting nodes.
2074 for (nsIContent
* sibling
= aNode
->GetNextSibling(); sibling
;
2075 sibling
= sibling
->GetNextSibling()) {
2076 if (sibling
->IsElement() && IsMozBR(sibling
->AsElement())) {
2077 // we ignore trailing moz BRs.
2080 if (!sibling
->TextIsOnlyWhitespace()) {
2088 already_AddRefed
<nsIDocumentEncoder
> do_createHTMLCopyEncoder() {
2089 return do_AddRef(new nsHTMLCopyEncoder
);
2092 int32_t nsHTMLCopyEncoder::RangeNodeContext::GetImmediateContextCount(
2093 const nsTArray
<nsINode
*>& aAncestorArray
) const {
2094 int32_t i
= aAncestorArray
.Length(), j
= 0;
2096 nsINode
* node
= aAncestorArray
.ElementAt(j
);
2100 nsCOMPtr
<nsIContent
> content(nsIContent::FromNodeOrNull(node
));
2101 if (!content
|| !content
->IsAnyOfHTMLElements(
2102 nsGkAtoms::tr
, nsGkAtoms::thead
, nsGkAtoms::tbody
,
2103 nsGkAtoms::tfoot
, nsGkAtoms::table
)) {