Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/nsDocumentEncoder.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsIDocumentEncoder.h"
13
14
#include "nscore.h"
15
#include "nsIFactory.h"
16
#include "nsISupports.h"
17
#include "nsIDocument.h"
18
#include "nsIHTMLDocument.h"
19
#include "nsCOMPtr.h"
20
#include "nsIContentSerializer.h"
21
#include "mozilla/Encoding.h"
22
#include "nsIOutputStream.h"
23
#include "nsRange.h"
24
#include "nsGkAtoms.h"
25
#include "nsIContent.h"
26
#include "nsIScriptContext.h"
27
#include "nsIScriptGlobalObject.h"
28
#include "nsIScriptSecurityManager.h"
29
#include "mozilla/dom/Selection.h"
30
#include "nsITransferable.h" // for kUnicodeMime
31
#include "nsContentUtils.h"
32
#include "nsElementTable.h"
33
#include "nsNodeUtils.h"
34
#include "nsUnicharUtils.h"
35
#include "nsReadableUtils.h"
36
#include "nsTArray.h"
37
#include "nsIFrame.h"
38
#include "nsStringBuffer.h"
39
#include "mozilla/dom/Comment.h"
40
#include "mozilla/dom/DocumentType.h"
41
#include "mozilla/dom/Element.h"
42
#include "mozilla/dom/ProcessingInstruction.h"
43
#include "mozilla/dom/ShadowRoot.h"
44
#include "mozilla/dom/Text.h"
45
#include "nsLayoutUtils.h"
46
#include "mozilla/ScopeExit.h"
47
48
using namespace mozilla;
49
using namespace mozilla::dom;
50
51
enum nsRangeIterationDirection {
52
  kDirectionOut = -1,
53
  kDirectionIn = 1
54
};
55
56
class nsDocumentEncoder : public nsIDocumentEncoder
57
{
58
public:
59
  nsDocumentEncoder();
60
61
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
62
  NS_DECL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder)
63
  NS_DECL_NSIDOCUMENTENCODER
64
65
protected:
66
  virtual ~nsDocumentEncoder();
67
68
  void Initialize(bool aClearCachedSerializer = true);
69
  nsresult SerializeNodeStart(nsINode* aNode, int32_t aStartOffset,
70
                              int32_t aEndOffset, nsAString& aStr,
71
                              nsINode* aOriginalNode = nullptr);
72
  nsresult SerializeToStringRecursive(nsINode* aNode,
73
                                      nsAString& aStr,
74
                                      bool aDontSerializeRoot,
75
                                      uint32_t aMaxLength = 0);
76
  nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr);
77
  // This serializes the content of aNode.
78
  nsresult SerializeToStringIterative(nsINode* aNode,
79
                                      nsAString& aStr);
80
  nsresult SerializeRangeToString(nsRange *aRange,
81
                                  nsAString& aOutputString);
82
  nsresult SerializeRangeNodes(nsRange* aRange,
83
                               nsINode* aNode,
84
                               nsAString& aString,
85
                               int32_t aDepth);
86
  nsresult SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
87
                                      nsAString& aString);
88
  nsresult SerializeRangeContextEnd(nsAString& aString);
89
90
  virtual int32_t
91
  GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray)
92
0
  {
93
0
    return -1;
94
0
  }
95
96
  nsresult FlushText(nsAString& aString, bool aForce);
97
98
  bool IsVisibleNode(nsINode* aNode)
99
0
  {
100
0
    MOZ_ASSERT(aNode, "null node");
101
0
102
0
    if (mFlags & SkipInvisibleContent) {
103
0
      // Treat the visibility of the ShadowRoot as if it were
104
0
      // the host content.
105
0
      //
106
0
      // FIXME(emilio): I suspect instead of this a bunch of the GetParent()
107
0
      // calls here should be doing GetFlattenedTreeParent, then this condition
108
0
      // should be unreachable...
109
0
      if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(aNode)) {
110
0
        aNode = shadowRoot->GetHost();
111
0
      }
112
0
113
0
      if (aNode->IsContent()) {
114
0
        nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
115
0
        if (!frame) {
116
0
          if (aNode->IsElement() && aNode->AsElement()->IsDisplayContents()) {
117
0
            return true;
118
0
          }
119
0
          if (aNode->IsText()) {
120
0
            // We have already checked that our parent is visible.
121
0
            //
122
0
            // FIXME(emilio): Text not assigned to a <slot> in Shadow DOM should
123
0
            // probably return false...
124
0
            return true;
125
0
          }
126
0
          if (aNode->IsHTMLElement(nsGkAtoms::rp)) {
127
0
            // Ruby parentheses are part of ruby structure, hence
128
0
            // shouldn't be stripped out even if it is not displayed.
129
0
            return true;
130
0
          }
131
0
          return false;
132
0
        }
133
0
        bool isVisible = frame->StyleVisibility()->IsVisible();
134
0
        if (!isVisible && aNode->IsText())
135
0
          return false;
136
0
      }
137
0
    }
138
0
    return true;
139
0
  }
140
141
  virtual bool IncludeInContext(nsINode *aNode);
142
143
  void Clear();
144
145
  class MOZ_STACK_CLASS AutoReleaseDocumentIfNeeded final
146
  {
147
  public:
148
    explicit AutoReleaseDocumentIfNeeded(nsDocumentEncoder* aEncoder)
149
      : mEncoder(aEncoder)
150
0
    {
151
0
    }
152
153
    ~AutoReleaseDocumentIfNeeded()
154
0
    {
155
0
      if (mEncoder->mFlags & RequiresReinitAfterOutput) {
156
0
        mEncoder->Clear();
157
0
      }
158
0
    }
159
160
  private:
161
    nsDocumentEncoder* mEncoder;
162
  };
163
164
  nsCOMPtr<nsIDocument>          mDocument;
165
  RefPtr<Selection>              mSelection;
166
  RefPtr<nsRange>              mRange;
167
  nsCOMPtr<nsINode>              mNode;
168
  nsCOMPtr<nsIOutputStream>      mStream;
169
  nsCOMPtr<nsIContentSerializer> mSerializer;
170
  UniquePtr<Encoder> mUnicodeEncoder;
171
  nsCOMPtr<nsINode>              mCommonParent;
172
  nsCOMPtr<nsIDocumentEncoderNodeFixup> mNodeFixup;
173
174
  nsString          mMimeType;
175
  const Encoding* mEncoding;
176
  uint32_t          mFlags;
177
  uint32_t          mWrapColumn;
178
  uint32_t          mStartDepth;
179
  uint32_t          mEndDepth;
180
  int32_t           mStartRootIndex;
181
  int32_t           mEndRootIndex;
182
  AutoTArray<nsINode*, 8>    mCommonAncestors;
183
  AutoTArray<nsIContent*, 8> mStartNodes;
184
  AutoTArray<int32_t, 8>     mStartOffsets;
185
  AutoTArray<nsIContent*, 8> mEndNodes;
186
  AutoTArray<int32_t, 8>     mEndOffsets;
187
  AutoTArray<AutoTArray<nsINode*, 8>, 8> mRangeContexts;
188
  // Whether the serializer cares about being notified to scan elements to
189
  // keep track of whether they are preformatted.  This stores the out
190
  // argument of nsIContentSerializer::Init().
191
  bool              mNeedsPreformatScanning;
192
  bool              mHaltRangeHint;
193
  // Used when context has already been serialized for
194
  // table cell selections (where parent is <tr>)
195
  bool              mDisableContextSerialize;
196
  bool              mIsCopying;  // Set to true only while copying
197
  bool              mNodeIsContainer;
198
  bool mIsPlainText;
199
  nsStringBuffer*   mCachedBuffer;
200
};
201
202
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder)
203
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocumentEncoder)
204
205
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder)
206
0
   NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder)
207
0
   NS_INTERFACE_MAP_ENTRY(nsISupports)
208
0
NS_INTERFACE_MAP_END
209
210
NS_IMPL_CYCLE_COLLECTION(nsDocumentEncoder,
211
                         mDocument, mSelection, mRange, mNode, mSerializer,
212
                         mCommonParent)
213
214
nsDocumentEncoder::nsDocumentEncoder()
215
  : mEncoding(nullptr)
216
  , mIsCopying(false)
217
  , mCachedBuffer(nullptr)
218
0
{
219
0
  Initialize();
220
0
  mMimeType.AssignLiteral("text/plain");
221
0
}
222
223
void nsDocumentEncoder::Initialize(bool aClearCachedSerializer)
224
0
{
225
0
  mFlags = 0;
226
0
  mWrapColumn = 72;
227
0
  mStartDepth = 0;
228
0
  mEndDepth = 0;
229
0
  mStartRootIndex = 0;
230
0
  mEndRootIndex = 0;
231
0
  mNeedsPreformatScanning = false;
232
0
  mHaltRangeHint = false;
233
0
  mDisableContextSerialize = false;
234
0
  mNodeIsContainer = false;
235
0
  mIsPlainText = false;
236
0
  if (aClearCachedSerializer) {
237
0
    mSerializer = nullptr;
238
0
  }
239
0
}
240
241
nsDocumentEncoder::~nsDocumentEncoder()
242
0
{
243
0
  if (mCachedBuffer) {
244
0
    mCachedBuffer->Release();
245
0
  }
246
0
}
247
248
NS_IMETHODIMP
249
nsDocumentEncoder::Init(nsIDocument* aDocument,
250
                        const nsAString& aMimeType,
251
                        uint32_t aFlags)
252
0
{
253
0
  return NativeInit(aDocument, aMimeType, aFlags);
254
0
}
255
256
NS_IMETHODIMP
257
nsDocumentEncoder::NativeInit(nsIDocument* aDocument,
258
                              const nsAString& aMimeType,
259
                              uint32_t aFlags)
260
0
{
261
0
  if (!aDocument)
262
0
    return NS_ERROR_INVALID_ARG;
263
0
264
0
  Initialize(!mMimeType.Equals(aMimeType));
265
0
266
0
  mDocument = aDocument;
267
0
268
0
  mMimeType = aMimeType;
269
0
270
0
  mFlags = aFlags;
271
0
  mIsCopying = false;
272
0
273
0
  return NS_OK;
274
0
}
275
276
NS_IMETHODIMP
277
nsDocumentEncoder::SetWrapColumn(uint32_t aWC)
278
0
{
279
0
  mWrapColumn = aWC;
280
0
  return NS_OK;
281
0
}
282
283
NS_IMETHODIMP
284
nsDocumentEncoder::SetSelection(Selection* aSelection)
285
0
{
286
0
  mSelection = aSelection;
287
0
  return NS_OK;
288
0
}
289
290
NS_IMETHODIMP
291
nsDocumentEncoder::SetRange(nsRange* aRange)
292
0
{
293
0
  mRange = aRange;
294
0
  return NS_OK;
295
0
}
296
297
NS_IMETHODIMP
298
nsDocumentEncoder::SetNode(nsINode* aNode)
299
0
{
300
0
  mNodeIsContainer = false;
301
0
  mNode = aNode;
302
0
  return NS_OK;
303
0
}
304
305
NS_IMETHODIMP
306
nsDocumentEncoder::SetContainerNode(nsINode* aContainer)
307
0
{
308
0
  mNodeIsContainer = true;
309
0
  mNode = aContainer;
310
0
  return NS_OK;
311
0
}
312
313
NS_IMETHODIMP
314
nsDocumentEncoder::SetCharset(const nsACString& aCharset)
315
0
{
316
0
  const Encoding* encoding = Encoding::ForLabel(aCharset);
317
0
  if (!encoding) {
318
0
    return NS_ERROR_UCONV_NOCONV;
319
0
  }
320
0
  mEncoding = encoding->OutputEncoding();
321
0
  return NS_OK;
322
0
}
323
324
NS_IMETHODIMP
325
nsDocumentEncoder::GetMimeType(nsAString& aMimeType)
326
0
{
327
0
  aMimeType = mMimeType;
328
0
  return NS_OK;
329
0
}
330
331
332
bool
333
nsDocumentEncoder::IncludeInContext(nsINode *aNode)
334
0
{
335
0
  return false;
336
0
}
337
338
nsresult
339
nsDocumentEncoder::SerializeNodeStart(nsINode* aNode,
340
                                      int32_t aStartOffset,
341
                                      int32_t aEndOffset,
342
                                      nsAString& aStr,
343
                                      nsINode* aOriginalNode)
344
0
{
345
0
  if (mNeedsPreformatScanning && aNode->IsElement()) {
346
0
    mSerializer->ScanElementForPreformat(aNode->AsElement());
347
0
  }
348
0
349
0
  if (!IsVisibleNode(aNode))
350
0
    return NS_OK;
351
0
352
0
  nsINode* node = nullptr;
353
0
  nsCOMPtr<nsINode> fixedNodeKungfuDeathGrip;
354
0
355
0
  // Caller didn't do fixup, so we'll do it ourselves
356
0
  if (!aOriginalNode) {
357
0
    aOriginalNode = aNode;
358
0
    if (mNodeFixup) {
359
0
      bool dummy;
360
0
      mNodeFixup->FixupNode(aNode, &dummy,
361
0
                            getter_AddRefs(fixedNodeKungfuDeathGrip));
362
0
      node = fixedNodeKungfuDeathGrip;
363
0
    }
364
0
  }
365
0
366
0
  // Either there was no fixed-up node,
367
0
  // or the caller did fixup themselves and aNode is already fixed
368
0
  if (!node)
369
0
    node = aNode;
370
0
371
0
  if (node->IsElement()) {
372
0
    if ((mFlags & (nsIDocumentEncoder::OutputPreformatted |
373
0
                   nsIDocumentEncoder::OutputDropInvisibleBreak)) &&
374
0
        nsLayoutUtils::IsInvisibleBreak(node)) {
375
0
      return NS_OK;
376
0
    }
377
0
    Element* originalElement = Element::FromNodeOrNull(aOriginalNode);
378
0
    mSerializer->AppendElementStart(node->AsElement(), originalElement, aStr);
379
0
    return NS_OK;
380
0
  }
381
0
382
0
  switch (node->NodeType()) {
383
0
    case nsINode::TEXT_NODE:
384
0
    {
385
0
      mSerializer->AppendText(static_cast<nsIContent*>(node),
386
0
                              aStartOffset, aEndOffset, aStr);
387
0
      break;
388
0
    }
389
0
    case nsINode::CDATA_SECTION_NODE:
390
0
    {
391
0
      mSerializer->AppendCDATASection(static_cast<nsIContent*>(node),
392
0
                                      aStartOffset, aEndOffset, aStr);
393
0
      break;
394
0
    }
395
0
    case nsINode::PROCESSING_INSTRUCTION_NODE:
396
0
    {
397
0
      mSerializer->AppendProcessingInstruction(static_cast<ProcessingInstruction*>(node),
398
0
                                               aStartOffset, aEndOffset, aStr);
399
0
      break;
400
0
    }
401
0
    case nsINode::COMMENT_NODE:
402
0
    {
403
0
      mSerializer->AppendComment(static_cast<Comment*>(node),
404
0
                                 aStartOffset, aEndOffset, aStr);
405
0
      break;
406
0
    }
407
0
    case nsINode::DOCUMENT_TYPE_NODE:
408
0
    {
409
0
      mSerializer->AppendDoctype(static_cast<DocumentType*>(node), aStr);
410
0
      break;
411
0
    }
412
0
  }
413
0
414
0
  return NS_OK;
415
0
}
416
417
nsresult
418
nsDocumentEncoder::SerializeNodeEnd(nsINode* aNode,
419
                                    nsAString& aStr)
420
0
{
421
0
  if (mNeedsPreformatScanning && aNode->IsElement()) {
422
0
    mSerializer->ForgetElementForPreformat(aNode->AsElement());
423
0
  }
424
0
425
0
  if (!IsVisibleNode(aNode))
426
0
    return NS_OK;
427
0
428
0
  if (aNode->IsElement()) {
429
0
    mSerializer->AppendElementEnd(aNode->AsElement(), aStr);
430
0
  }
431
0
  return NS_OK;
432
0
}
433
434
nsresult
435
nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode,
436
                                              nsAString& aStr,
437
                                              bool aDontSerializeRoot,
438
                                              uint32_t aMaxLength)
439
0
{
440
0
  if (aMaxLength > 0 && aStr.Length() >= aMaxLength) {
441
0
    return NS_OK;
442
0
  }
443
0
444
0
  if (!IsVisibleNode(aNode))
445
0
    return NS_OK;
446
0
447
0
  nsresult rv = NS_OK;
448
0
  bool serializeClonedChildren = false;
449
0
  nsINode* maybeFixedNode = nullptr;
450
0
451
0
  // Keep the node from FixupNode alive.
452
0
  nsCOMPtr<nsINode> fixedNodeKungfuDeathGrip;
453
0
  if (mNodeFixup) {
454
0
    mNodeFixup->FixupNode(aNode, &serializeClonedChildren,
455
0
                          getter_AddRefs(fixedNodeKungfuDeathGrip));
456
0
    maybeFixedNode = fixedNodeKungfuDeathGrip;
457
0
  }
458
0
459
0
  if (!maybeFixedNode)
460
0
    maybeFixedNode = aNode;
461
0
462
0
  if ((mFlags & SkipInvisibleContent) &&
463
0
      !(mFlags & OutputNonTextContentAsPlaceholder)) {
464
0
    if (aNode->IsContent()) {
465
0
      if (nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame()) {
466
0
        if (!frame->IsSelectable(nullptr)) {
467
0
          aDontSerializeRoot = true;
468
0
        }
469
0
      }
470
0
    }
471
0
  }
472
0
473
0
  if (!aDontSerializeRoot) {
474
0
    int32_t endOffset = -1;
475
0
    if (aMaxLength > 0) {
476
0
      MOZ_ASSERT(aMaxLength >= aStr.Length());
477
0
      endOffset = aMaxLength - aStr.Length();
478
0
    }
479
0
    rv = SerializeNodeStart(maybeFixedNode, 0, endOffset, aStr, aNode);
480
0
    NS_ENSURE_SUCCESS(rv, rv);
481
0
  }
482
0
483
0
  nsINode* node = serializeClonedChildren ? maybeFixedNode : aNode;
484
0
485
0
  for (nsINode* child = nsNodeUtils::GetFirstChildOfTemplateOrNode(node);
486
0
       child;
487
0
       child = child->GetNextSibling()) {
488
0
    rv = SerializeToStringRecursive(child, aStr, false, aMaxLength);
489
0
    NS_ENSURE_SUCCESS(rv, rv);
490
0
  }
491
0
492
0
  if (!aDontSerializeRoot) {
493
0
    rv = SerializeNodeEnd(maybeFixedNode, aStr);
494
0
    NS_ENSURE_SUCCESS(rv, rv);
495
0
  }
496
0
497
0
  return FlushText(aStr, false);
498
0
}
499
500
nsresult
501
nsDocumentEncoder::SerializeToStringIterative(nsINode* aNode,
502
                                              nsAString& aStr)
503
0
{
504
0
  nsresult rv;
505
0
506
0
  nsINode* node = nsNodeUtils::GetFirstChildOfTemplateOrNode(aNode);
507
0
  while (node) {
508
0
    nsINode* current = node;
509
0
    rv = SerializeNodeStart(current, 0, -1, aStr, current);
510
0
    NS_ENSURE_SUCCESS(rv, rv);
511
0
    node = nsNodeUtils::GetFirstChildOfTemplateOrNode(current);
512
0
    while (!node && current && current != aNode) {
513
0
      rv = SerializeNodeEnd(current, aStr);
514
0
      NS_ENSURE_SUCCESS(rv, rv);
515
0
      // Check if we have siblings.
516
0
      node = current->GetNextSibling();
517
0
      if (!node) {
518
0
        // Perhaps parent node has siblings.
519
0
        current = current->GetParentNode();
520
0
521
0
        // Handle template element. If the parent is a template's content,
522
0
        // then adjust the parent to be the template element.
523
0
        if (current && current != aNode && current->IsDocumentFragment()) {
524
0
          nsIContent* host = current->AsDocumentFragment()->GetHost();
525
0
          if (host && host->IsHTMLElement(nsGkAtoms::_template)) {
526
0
            current = host;
527
0
          }
528
0
        }
529
0
      }
530
0
    }
531
0
  }
532
0
533
0
  return NS_OK;
534
0
}
535
536
static nsresult
537
ConvertAndWrite(const nsAString& aString,
538
                nsIOutputStream* aStream,
539
                Encoder* aEncoder,
540
                bool aIsPlainText)
541
0
{
542
0
  NS_ENSURE_ARG_POINTER(aStream);
543
0
  NS_ENSURE_ARG_POINTER(aEncoder);
544
0
545
0
  if (!aString.Length()) {
546
0
    return NS_OK;
547
0
  }
548
0
549
0
  uint8_t buffer[4096];
550
0
  auto src = MakeSpan(aString);
551
0
  auto bufferSpan = MakeSpan(buffer);
552
0
  // Reserve space for terminator
553
0
  auto dst = bufferSpan.To(bufferSpan.Length() - 1);
554
0
  for (;;) {
555
0
    uint32_t result;
556
0
    size_t read;
557
0
    size_t written;
558
0
    bool hadErrors;
559
0
    if (aIsPlainText) {
560
0
      Tie(result, read, written) =
561
0
        aEncoder->EncodeFromUTF16WithoutReplacement(src, dst, false);
562
0
      if (result != kInputEmpty && result != kOutputFull) {
563
0
        // There's always room for one byte in the case of
564
0
        // an unmappable character, because otherwise
565
0
        // we'd have gotten `kOutputFull`.
566
0
        dst[written++] = '?';
567
0
      }
568
0
    } else {
569
0
      Tie(result, read, written, hadErrors) =
570
0
        aEncoder->EncodeFromUTF16(src, dst, false);
571
0
    }
572
0
    Unused << hadErrors;
573
0
    src = src.From(read);
574
0
    // Sadly, we still have test cases that implement nsIOutputStream in JS, so
575
0
    // the buffer needs to be zero-terminated for XPConnect to do its thing.
576
0
    // See bug 170416.
577
0
    bufferSpan[written] = 0;
578
0
    uint32_t streamWritten;
579
0
    nsresult rv = aStream->Write(
580
0
      reinterpret_cast<char*>(dst.Elements()), written, &streamWritten);
581
0
    if (NS_FAILED(rv)) {
582
0
      return rv;
583
0
    }
584
0
    if (result == kInputEmpty) {
585
0
      return NS_OK;
586
0
    }
587
0
  }
588
0
}
589
590
nsresult
591
nsDocumentEncoder::FlushText(nsAString& aString, bool aForce)
592
0
{
593
0
  if (!mStream)
594
0
    return NS_OK;
595
0
596
0
  nsresult rv = NS_OK;
597
0
598
0
  if (aString.Length() > 1024 || aForce) {
599
0
    rv = ConvertAndWrite(aString, mStream, mUnicodeEncoder.get(), mIsPlainText);
600
0
601
0
    aString.Truncate();
602
0
  }
603
0
604
0
  return rv;
605
0
}
606
607
static bool IsTextNode(nsINode *aNode)
608
0
{
609
0
  return aNode && aNode->IsText();
610
0
}
611
612
nsresult
613
nsDocumentEncoder::SerializeRangeNodes(nsRange* aRange,
614
                                       nsINode* aNode,
615
                                       nsAString& aString,
616
                                       int32_t aDepth)
617
0
{
618
0
  nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
619
0
  NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
620
0
621
0
  if (!IsVisibleNode(aNode))
622
0
    return NS_OK;
623
0
624
0
  nsresult rv = NS_OK;
625
0
626
0
  // get start and end nodes for this recursion level
627
0
  nsCOMPtr<nsIContent> startNode, endNode;
628
0
  {
629
0
    int32_t start = mStartRootIndex - aDepth;
630
0
    if (start >= 0 && (uint32_t)start <= mStartNodes.Length())
631
0
      startNode = mStartNodes[start];
632
0
633
0
    int32_t end = mEndRootIndex - aDepth;
634
0
    if (end >= 0 && (uint32_t)end <= mEndNodes.Length())
635
0
      endNode = mEndNodes[end];
636
0
  }
637
0
638
0
  if (startNode != content && endNode != content)
639
0
  {
640
0
    // node is completely contained in range.  Serialize the whole subtree
641
0
    // rooted by this node.
642
0
    rv = SerializeToStringRecursive(aNode, aString, false);
643
0
    NS_ENSURE_SUCCESS(rv, rv);
644
0
  }
645
0
  else
646
0
  {
647
0
    // due to implementation it is impossible for text node to be both start and end of
648
0
    // range.  We would have handled that case without getting here.
649
0
    //XXXsmaug What does this all mean?
650
0
    if (IsTextNode(aNode))
651
0
    {
652
0
      if (startNode == content)
653
0
      {
654
0
        int32_t startOffset = aRange->StartOffset();
655
0
        rv = SerializeNodeStart(aNode, startOffset, -1, aString);
656
0
        NS_ENSURE_SUCCESS(rv, rv);
657
0
      }
658
0
      else
659
0
      {
660
0
        int32_t endOffset = aRange->EndOffset();
661
0
        rv = SerializeNodeStart(aNode, 0, endOffset, aString);
662
0
        NS_ENSURE_SUCCESS(rv, rv);
663
0
      }
664
0
    }
665
0
    else
666
0
    {
667
0
      if (aNode != mCommonParent)
668
0
      {
669
0
        if (IncludeInContext(aNode))
670
0
        {
671
0
          // halt the incrementing of mStartDepth/mEndDepth.  This is
672
0
          // so paste client will include this node in paste.
673
0
          mHaltRangeHint = true;
674
0
        }
675
0
        if ((startNode == content) && !mHaltRangeHint) mStartDepth++;
676
0
        if ((endNode == content) && !mHaltRangeHint) mEndDepth++;
677
0
678
0
        // serialize the start of this node
679
0
        rv = SerializeNodeStart(aNode, 0, -1, aString);
680
0
        NS_ENSURE_SUCCESS(rv, rv);
681
0
      }
682
0
683
0
      // do some calculations that will tell us which children of this
684
0
      // node are in the range.
685
0
      int32_t startOffset = 0, endOffset = -1;
686
0
      if (startNode == content && mStartRootIndex >= aDepth)
687
0
        startOffset = mStartOffsets[mStartRootIndex - aDepth];
688
0
      if (endNode == content && mEndRootIndex >= aDepth)
689
0
        endOffset = mEndOffsets[mEndRootIndex - aDepth];
690
0
      // generated content will cause offset values of -1 to be returned.
691
0
      uint32_t childCount = content->GetChildCount();
692
0
693
0
      if (startOffset == -1) startOffset = 0;
694
0
      if (endOffset == -1) endOffset = childCount;
695
0
      else
696
0
      {
697
0
        // if we are at the "tip" of the selection, endOffset is fine.
698
0
        // otherwise, we need to add one.  This is because of the semantics
699
0
        // of the offset list created by GetAncestorsAndOffsets().  The
700
0
        // intermediate points on the list use the endOffset of the
701
0
        // location of the ancestor, rather than just past it.  So we need
702
0
        // to add one here in order to include it in the children we serialize.
703
0
        if (aNode != aRange->GetEndContainer())
704
0
        {
705
0
          endOffset++;
706
0
        }
707
0
      }
708
0
709
0
      if (endOffset) {
710
0
        // serialize the children of this node that are in the range
711
0
        nsIContent* childAsNode = content->GetFirstChild();
712
0
        int32_t j = 0;
713
0
714
0
        for (; j < startOffset && childAsNode; ++j) {
715
0
          childAsNode = childAsNode->GetNextSibling();
716
0
        }
717
0
718
0
        NS_ENSURE_TRUE(!!childAsNode, NS_ERROR_FAILURE);
719
0
        MOZ_ASSERT(j == startOffset);
720
0
721
0
        for (; childAsNode && j < endOffset; ++j)
722
0
        {
723
0
          if ((j==startOffset) || (j==endOffset-1)) {
724
0
            rv = SerializeRangeNodes(aRange, childAsNode, aString, aDepth+1);
725
0
          } else {
726
0
            rv = SerializeToStringRecursive(childAsNode, aString, false);
727
0
          }
728
0
729
0
          NS_ENSURE_SUCCESS(rv, rv);
730
0
731
0
          childAsNode = childAsNode->GetNextSibling();
732
0
        }
733
0
      }
734
0
735
0
      // serialize the end of this node
736
0
      if (aNode != mCommonParent)
737
0
      {
738
0
        rv = SerializeNodeEnd(aNode, aString);
739
0
        NS_ENSURE_SUCCESS(rv, rv);
740
0
      }
741
0
    }
742
0
  }
743
0
  return NS_OK;
744
0
}
745
746
nsresult
747
nsDocumentEncoder::SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
748
                                              nsAString& aString)
749
0
{
750
0
  if (mDisableContextSerialize) {
751
0
    return NS_OK;
752
0
  }
753
0
754
0
  AutoTArray<nsINode*, 8>* serializedContext = mRangeContexts.AppendElement();
755
0
756
0
  int32_t i = aAncestorArray.Length(), j;
757
0
  nsresult rv = NS_OK;
758
0
759
0
  // currently only for table-related elements; see Bug 137450
760
0
  j = GetImmediateContextCount(aAncestorArray);
761
0
762
0
  while (i > 0) {
763
0
    nsINode *node = aAncestorArray.ElementAt(--i);
764
0
765
0
    if (!node)
766
0
      break;
767
0
768
0
    // Either a general inclusion or as immediate context
769
0
    if (IncludeInContext(node) || i < j) {
770
0
      rv = SerializeNodeStart(node, 0, -1, aString);
771
0
      serializedContext->AppendElement(node);
772
0
      if (NS_FAILED(rv))
773
0
        break;
774
0
    }
775
0
  }
776
0
777
0
  return rv;
778
0
}
779
780
nsresult
781
nsDocumentEncoder::SerializeRangeContextEnd(nsAString& aString)
782
0
{
783
0
  if (mDisableContextSerialize) {
784
0
    return NS_OK;
785
0
  }
786
0
787
0
  MOZ_RELEASE_ASSERT(!mRangeContexts.IsEmpty(), "Tried to end context without starting one.");
788
0
  AutoTArray<nsINode*, 8>& serializedContext = mRangeContexts.LastElement();
789
0
790
0
  nsresult rv = NS_OK;
791
0
  for (nsINode* node : Reversed(serializedContext)) {
792
0
    rv = SerializeNodeEnd(node, aString);
793
0
794
0
    if (NS_FAILED(rv))
795
0
      break;
796
0
  }
797
0
798
0
  mRangeContexts.RemoveLastElement();
799
0
  return rv;
800
0
}
801
802
nsresult
803
nsDocumentEncoder::SerializeRangeToString(nsRange *aRange,
804
                                          nsAString& aOutputString)
805
0
{
806
0
  if (!aRange || aRange->Collapsed())
807
0
    return NS_OK;
808
0
809
0
  mCommonParent = aRange->GetCommonAncestor();
810
0
811
0
  if (!mCommonParent)
812
0
    return NS_OK;
813
0
814
0
  nsINode* startContainer = aRange->GetStartContainer();
815
0
  NS_ENSURE_TRUE(startContainer, NS_ERROR_FAILURE);
816
0
  int32_t startOffset = aRange->StartOffset();
817
0
818
0
  nsINode* endContainer = aRange->GetEndContainer();
819
0
  NS_ENSURE_TRUE(endContainer, NS_ERROR_FAILURE);
820
0
  int32_t endOffset = aRange->EndOffset();
821
0
822
0
  mStartDepth = mEndDepth = 0;
823
0
  mCommonAncestors.Clear();
824
0
  mStartNodes.Clear();
825
0
  mStartOffsets.Clear();
826
0
  mEndNodes.Clear();
827
0
  mEndOffsets.Clear();
828
0
829
0
  nsContentUtils::GetAncestors(mCommonParent, mCommonAncestors);
830
0
  nsContentUtils::GetAncestorsAndOffsets(startContainer, startOffset,
831
0
                                         &mStartNodes, &mStartOffsets);
832
0
  nsContentUtils::GetAncestorsAndOffsets(endContainer, endOffset,
833
0
                                         &mEndNodes, &mEndOffsets);
834
0
835
0
  nsCOMPtr<nsIContent> commonContent = do_QueryInterface(mCommonParent);
836
0
  mStartRootIndex = mStartNodes.IndexOf(commonContent);
837
0
  mEndRootIndex = mEndNodes.IndexOf(commonContent);
838
0
839
0
  nsresult rv = NS_OK;
840
0
841
0
  rv = SerializeRangeContextStart(mCommonAncestors, aOutputString);
842
0
  NS_ENSURE_SUCCESS(rv, rv);
843
0
844
0
  if (startContainer == endContainer && IsTextNode(startContainer)) {
845
0
    if (mFlags & SkipInvisibleContent) {
846
0
      // Check that the parent is visible if we don't a frame.
847
0
      // IsVisibleNode() will do it when there's a frame.
848
0
      nsCOMPtr<nsIContent> content = do_QueryInterface(startContainer);
849
0
      if (content && !content->GetPrimaryFrame()) {
850
0
        nsIContent* parent = content->GetParent();
851
0
        if (!parent || !IsVisibleNode(parent))
852
0
          return NS_OK;
853
0
      }
854
0
    }
855
0
    rv = SerializeNodeStart(startContainer, startOffset, endOffset,
856
0
                            aOutputString);
857
0
    NS_ENSURE_SUCCESS(rv, rv);
858
0
  } else {
859
0
    rv = SerializeRangeNodes(aRange, mCommonParent, aOutputString, 0);
860
0
    NS_ENSURE_SUCCESS(rv, rv);
861
0
  }
862
0
  rv = SerializeRangeContextEnd(aOutputString);
863
0
  NS_ENSURE_SUCCESS(rv, rv);
864
0
865
0
  return rv;
866
0
}
867
868
void
869
nsDocumentEncoder::Clear()
870
0
{
871
0
  mDocument = nullptr;
872
0
  mSelection = nullptr;
873
0
  mRange = nullptr;
874
0
  mNode = nullptr;
875
0
  mCommonParent = nullptr;
876
0
  mNodeFixup = nullptr;
877
0
878
0
  Initialize(false);
879
0
}
880
881
NS_IMETHODIMP
882
nsDocumentEncoder::EncodeToString(nsAString& aOutputString)
883
0
{
884
0
  return EncodeToStringWithMaxLength(0, aOutputString);
885
0
}
886
887
0
static bool ParentIsTR(nsIContent* aContent) {
888
0
  mozilla::dom::Element* parent = aContent->GetParentElement();
889
0
  if (!parent) {
890
0
    return false;
891
0
  }
892
0
  return parent->IsHTMLElement(nsGkAtoms::tr);
893
0
}
894
895
NS_IMETHODIMP
896
nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
897
                                               nsAString& aOutputString)
898
0
{
899
0
  MOZ_ASSERT(mRangeContexts.IsEmpty(), "Re-entrant call to nsDocumentEncoder.");
900
0
  auto rangeContextGuard = MakeScopeExit([&] {
901
0
    mRangeContexts.Clear();
902
0
  });
903
0
904
0
  if (!mDocument)
905
0
    return NS_ERROR_NOT_INITIALIZED;
906
0
907
0
  AutoReleaseDocumentIfNeeded autoReleaseDocument(this);
908
0
909
0
  aOutputString.Truncate();
910
0
911
0
  nsString output;
912
0
  static const size_t bufferSize = 2048;
913
0
  if (!mCachedBuffer) {
914
0
    mCachedBuffer = nsStringBuffer::Alloc(bufferSize).take();
915
0
    if (NS_WARN_IF(!mCachedBuffer)) {
916
0
      return NS_ERROR_OUT_OF_MEMORY;
917
0
    }
918
0
  }
919
0
  NS_ASSERTION(!mCachedBuffer->IsReadonly(),
920
0
               "DocumentEncoder shouldn't keep reference to non-readonly buffer!");
921
0
  static_cast<char16_t*>(mCachedBuffer->Data())[0] = char16_t(0);
922
0
  mCachedBuffer->ToString(0, output, true);
923
0
  // output owns the buffer now!
924
0
  mCachedBuffer = nullptr;
925
0
926
0
927
0
  if (!mSerializer) {
928
0
    nsAutoCString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX);
929
0
    AppendUTF16toUTF8(mMimeType, progId);
930
0
931
0
    mSerializer = do_CreateInstance(progId.get());
932
0
    NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED);
933
0
  }
934
0
935
0
  nsresult rv = NS_OK;
936
0
937
0
  bool rewriteEncodingDeclaration = !(mSelection || mRange || mNode) && !(mFlags & OutputDontRewriteEncodingDeclaration);
938
0
  mSerializer->Init(
939
0
    mFlags, mWrapColumn, mEncoding, mIsCopying, rewriteEncodingDeclaration, &mNeedsPreformatScanning);
940
0
941
0
  if (mSelection) {
942
0
    uint32_t count = mSelection->RangeCount();
943
0
944
0
    nsCOMPtr<nsINode> node, prevNode;
945
0
    uint32_t firstRangeStartDepth = 0;
946
0
    for (uint32_t i = 0; i < count; ++i) {
947
0
      RefPtr<nsRange> range = mSelection->GetRangeAt(i);
948
0
949
0
      // Bug 236546: newlines not added when copying table cells into clipboard
950
0
      // Each selected cell shows up as a range containing a row with a single cell
951
0
      // get the row, compare it to previous row and emit </tr><tr> as needed
952
0
      // Bug 137450: Problem copying/pasting a table from a web page to Excel.
953
0
      // Each separate block of <tr></tr> produced above will be wrapped by the
954
0
      // immediate context. This assumes that you can't select cells that are
955
0
      // multiple selections from two tables simultaneously.
956
0
      node = range->GetStartContainer();
957
0
      NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
958
0
      if (node != prevNode) {
959
0
        if (prevNode) {
960
0
          rv = SerializeNodeEnd(prevNode, output);
961
0
          NS_ENSURE_SUCCESS(rv, rv);
962
0
        }
963
0
        nsCOMPtr<nsIContent> content = do_QueryInterface(node);
964
0
        if (content && content->IsHTMLElement(nsGkAtoms::tr) && !ParentIsTR(content)) {
965
0
          if (!prevNode) {
966
0
            // Went from a non-<tr> to a <tr>
967
0
            mCommonAncestors.Clear();
968
0
            nsContentUtils::GetAncestors(node->GetParentNode(),
969
0
                                         mCommonAncestors);
970
0
            rv = SerializeRangeContextStart(mCommonAncestors, output);
971
0
            NS_ENSURE_SUCCESS(rv, rv);
972
0
            // Don't let SerializeRangeToString serialize the context again
973
0
            mDisableContextSerialize = true;
974
0
          }
975
0
976
0
          rv = SerializeNodeStart(node, 0, -1, output);
977
0
          NS_ENSURE_SUCCESS(rv, rv);
978
0
          prevNode = node;
979
0
        } else if (prevNode) {
980
0
          // Went from a <tr> to a non-<tr>
981
0
          mDisableContextSerialize = false;
982
0
          rv = SerializeRangeContextEnd(output);
983
0
          NS_ENSURE_SUCCESS(rv, rv);
984
0
          prevNode = nullptr;
985
0
        }
986
0
      }
987
0
988
0
      rv = SerializeRangeToString(range, output);
989
0
      NS_ENSURE_SUCCESS(rv, rv);
990
0
      if (i == 0) {
991
0
        firstRangeStartDepth = mStartDepth;
992
0
      }
993
0
    }
994
0
    mStartDepth = firstRangeStartDepth;
995
0
996
0
    if (prevNode) {
997
0
      rv = SerializeNodeEnd(prevNode, output);
998
0
      NS_ENSURE_SUCCESS(rv, rv);
999
0
      mDisableContextSerialize = false;
1000
0
      rv = SerializeRangeContextEnd(output);
1001
0
      NS_ENSURE_SUCCESS(rv, rv);
1002
0
    }
1003
0
1004
0
    // Just to be safe
1005
0
    mDisableContextSerialize = false;
1006
0
1007
0
    mSelection = nullptr;
1008
0
  } else if (mRange) {
1009
0
      rv = SerializeRangeToString(mRange, output);
1010
0
1011
0
      mRange = nullptr;
1012
0
  } else if (mNode) {
1013
0
    if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mStream &&
1014
0
        mNodeIsContainer) {
1015
0
      rv = SerializeToStringIterative(mNode, output);
1016
0
    } else {
1017
0
      rv = SerializeToStringRecursive(mNode, output, mNodeIsContainer);
1018
0
    }
1019
0
    mNode = nullptr;
1020
0
  } else {
1021
0
    rv = mSerializer->AppendDocumentStart(mDocument, output);
1022
0
1023
0
    if (NS_SUCCEEDED(rv)) {
1024
0
      rv = SerializeToStringRecursive(mDocument, output, false, aMaxLength);
1025
0
    }
1026
0
  }
1027
0
1028
0
  NS_ENSURE_SUCCESS(rv, rv);
1029
0
  rv = mSerializer->Flush(output);
1030
0
1031
0
  mCachedBuffer = nsStringBuffer::FromString(output);
1032
0
  // We have to be careful how we set aOutputString, because we don't
1033
0
  // want it to end up sharing mCachedBuffer if we plan to reuse it.
1034
0
  bool setOutput = false;
1035
0
  // Try to cache the buffer.
1036
0
  if (mCachedBuffer) {
1037
0
    if (mCachedBuffer->StorageSize() == bufferSize &&
1038
0
        !mCachedBuffer->IsReadonly()) {
1039
0
      mCachedBuffer->AddRef();
1040
0
    } else {
1041
0
      if (NS_SUCCEEDED(rv)) {
1042
0
        mCachedBuffer->ToString(output.Length(), aOutputString);
1043
0
        setOutput = true;
1044
0
      }
1045
0
      mCachedBuffer = nullptr;
1046
0
    }
1047
0
  }
1048
0
1049
0
  if (!setOutput && NS_SUCCEEDED(rv)) {
1050
0
    aOutputString.Append(output.get(), output.Length());
1051
0
  }
1052
0
1053
0
  return rv;
1054
0
}
1055
1056
NS_IMETHODIMP
1057
nsDocumentEncoder::EncodeToStream(nsIOutputStream* aStream)
1058
0
{
1059
0
  MOZ_ASSERT(mRangeContexts.IsEmpty(), "Re-entrant call to nsDocumentEncoder.");
1060
0
  auto rangeContextGuard = MakeScopeExit([&] {
1061
0
    mRangeContexts.Clear();
1062
0
  });
1063
0
  nsresult rv = NS_OK;
1064
0
1065
0
  if (!mDocument)
1066
0
    return NS_ERROR_NOT_INITIALIZED;
1067
0
1068
0
  if (!mEncoding) {
1069
0
    return NS_ERROR_UCONV_NOCONV;
1070
0
  }
1071
0
1072
0
  mUnicodeEncoder = mEncoding->NewEncoder();
1073
0
1074
0
  mIsPlainText = (mMimeType.LowerCaseEqualsLiteral("text/plain"));
1075
0
1076
0
  mStream = aStream;
1077
0
1078
0
  nsAutoString buf;
1079
0
1080
0
  rv = EncodeToString(buf);
1081
0
1082
0
  // Force a flush of the last chunk of data.
1083
0
  FlushText(buf, true);
1084
0
1085
0
  mStream = nullptr;
1086
0
  mUnicodeEncoder = nullptr;
1087
0
1088
0
  return rv;
1089
0
}
1090
1091
NS_IMETHODIMP
1092
nsDocumentEncoder::EncodeToStringWithContext(nsAString& aContextString,
1093
                                             nsAString& aInfoString,
1094
                                             nsAString& aEncodedString)
1095
0
{
1096
0
  return NS_ERROR_NOT_IMPLEMENTED;
1097
0
}
1098
1099
NS_IMETHODIMP
1100
nsDocumentEncoder::SetNodeFixup(nsIDocumentEncoderNodeFixup *aFixup)
1101
0
{
1102
0
  mNodeFixup = aFixup;
1103
0
  return NS_OK;
1104
0
}
1105
1106
1107
nsresult NS_NewTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy
1108
1109
nsresult
1110
NS_NewTextEncoder(nsIDocumentEncoder** aResult)
1111
0
{
1112
0
  *aResult = new nsDocumentEncoder;
1113
0
 NS_ADDREF(*aResult);
1114
0
 return NS_OK;
1115
0
}
1116
1117
class nsHTMLCopyEncoder : public nsDocumentEncoder
1118
{
1119
public:
1120
1121
  nsHTMLCopyEncoder();
1122
  virtual ~nsHTMLCopyEncoder();
1123
1124
  NS_IMETHOD Init(nsIDocument* aDocument, const nsAString& aMimeType, uint32_t aFlags) override;
1125
1126
  // overridden methods from nsDocumentEncoder
1127
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
1128
  NS_IMETHOD SetSelection(Selection* aSelection) override;
1129
  NS_IMETHOD EncodeToStringWithContext(nsAString& aContextString,
1130
                                       nsAString& aInfoString,
1131
                                       nsAString& aEncodedString) override;
1132
  NS_IMETHOD EncodeToString(nsAString& aOutputString) override;
1133
1134
protected:
1135
1136
  enum Endpoint
1137
  {
1138
    kStart,
1139
    kEnd
1140
  };
1141
1142
  nsresult PromoteRange(nsRange* inRange);
1143
  nsresult PromoteAncestorChain(nsCOMPtr<nsINode>* ioNode,
1144
                                int32_t* ioStartOffset,
1145
                                int32_t* ioEndOffset);
1146
  nsresult GetPromotedPoint(Endpoint aWhere, nsINode* aNode, int32_t aOffset,
1147
                            nsCOMPtr<nsINode>* outNode, int32_t* outOffset, nsINode* aCommon);
1148
  nsCOMPtr<nsINode> GetChildAt(nsINode *aParent, int32_t aOffset);
1149
  bool IsMozBR(Element* aNode);
1150
  nsresult GetNodeLocation(nsINode *inChild, nsCOMPtr<nsINode> *outParent, int32_t *outOffset);
1151
  bool IsRoot(nsINode* aNode);
1152
  bool IsFirstNode(nsINode *aNode);
1153
  bool IsLastNode(nsINode *aNode);
1154
  virtual bool IncludeInContext(nsINode *aNode) override;
1155
  virtual int32_t
1156
  GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray) override;
1157
1158
  bool mIsTextWidget;
1159
};
1160
1161
nsHTMLCopyEncoder::nsHTMLCopyEncoder()
1162
0
{
1163
0
  mIsTextWidget = false;
1164
0
}
1165
1166
nsHTMLCopyEncoder::~nsHTMLCopyEncoder()
1167
{
1168
}
1169
1170
NS_IMETHODIMP
1171
nsHTMLCopyEncoder::Init(nsIDocument* aDocument,
1172
                        const nsAString& aMimeType,
1173
                        uint32_t aFlags)
1174
0
{
1175
0
  if (!aDocument)
1176
0
    return NS_ERROR_INVALID_ARG;
1177
0
1178
0
  mIsTextWidget = false;
1179
0
  Initialize();
1180
0
1181
0
  mIsCopying = true;
1182
0
  mDocument = aDocument;
1183
0
1184
0
  // Hack, hack! Traditionally, the caller passes text/unicode, which is
1185
0
  // treated as "guess text/html or text/plain" in this context. (It has a
1186
0
  // different meaning in other contexts. Sigh.) From now on, "text/plain"
1187
0
  // means forcing text/plain instead of guessing.
1188
0
  if (aMimeType.EqualsLiteral("text/plain")) {
1189
0
    mMimeType.AssignLiteral("text/plain");
1190
0
  } else {
1191
0
    mMimeType.AssignLiteral("text/html");
1192
0
  }
1193
0
1194
0
  // Make all links absolute when copying
1195
0
  // (see related bugs #57296, #41924, #58646, #32768)
1196
0
  mFlags = aFlags | OutputAbsoluteLinks;
1197
0
1198
0
  if (!mDocument->IsScriptEnabled())
1199
0
    mFlags |= OutputNoScriptContent;
1200
0
1201
0
  return NS_OK;
1202
0
}
1203
1204
NS_IMETHODIMP
1205
nsHTMLCopyEncoder::SetSelection(Selection* aSelection)
1206
0
{
1207
0
  // check for text widgets: we need to recognize these so that
1208
0
  // we don't tweak the selection to be outside of the magic
1209
0
  // div that ender-lite text widgets are embedded in.
1210
0
1211
0
  if (!aSelection)
1212
0
    return NS_ERROR_NULL_POINTER;
1213
0
1214
0
  uint32_t rangeCount = aSelection->RangeCount();
1215
0
1216
0
  // if selection is uninitialized return
1217
0
  if (!rangeCount) {
1218
0
    return NS_ERROR_FAILURE;
1219
0
  }
1220
0
1221
0
  // we'll just use the common parent of the first range.  Implicit assumption
1222
0
  // here that multi-range selections are table cell selections, in which case
1223
0
  // the common parent is somewhere in the table and we don't really care where.
1224
0
  //
1225
0
  // FIXME(emilio, bug 1455894): This assumption is already wrong, and will
1226
0
  // probably be more wrong in a Shadow DOM world...
1227
0
  //
1228
0
  // We should be able to write this as "Find the common ancestor of the
1229
0
  // selection, then go through the flattened tree and serialize the selected
1230
0
  // nodes", effectively serializing the composed tree.
1231
0
  RefPtr<nsRange> range = aSelection->GetRangeAt(0);
1232
0
  nsINode* commonParent = range->GetCommonAncestor();
1233
0
1234
0
  for (nsCOMPtr<nsIContent> selContent(do_QueryInterface(commonParent));
1235
0
       selContent;
1236
0
       selContent = selContent->GetParent())
1237
0
  {
1238
0
    // checking for selection inside a plaintext form widget
1239
0
    if (selContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea))
1240
0
    {
1241
0
      mIsTextWidget = true;
1242
0
      break;
1243
0
    }
1244
#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
1245
    else if (selContent->IsHTMLElement(nsGkAtoms::body)) {
1246
      // Currently, setting mIsTextWidget to 'true' will result in the selection
1247
      // being encoded/copied as pre-formatted plain text.
1248
      // This is fine for copying pre-formatted plain text with Firefox, it is
1249
      // already not correct for copying pre-formatted "rich" text (bold, colour)
1250
      // with Firefox. As long as the serialisers aren't fixed, copying
1251
      // pre-formatted text in Firefox is broken. If we set mIsTextWidget,
1252
      // pre-formatted plain text is copied, but pre-formatted "rich" text loses
1253
      // the "rich" formatting. If we don't set mIsTextWidget, "rich" text
1254
      // attributes aren't lost, but white-space is lost.
1255
      // So far the story for Firefox.
1256
      //
1257
      // Thunderbird has two *conflicting* requirements.
1258
      // Case 1:
1259
      // When selecting and copying text, even pre-formatted text, as a quote
1260
      // to be placed into a reply, we *always* expect HTML to be copied.
1261
      // Case 2:
1262
      // When copying text in a so-called "plain text" message, that is
1263
      // one where the body carries style "white-space:pre-wrap", the text should
1264
      // be copied as pre-formatted plain text.
1265
      //
1266
      // Therefore the following code checks for "pre-wrap" on the body.
1267
      // This is a terrible hack.
1268
      //
1269
      // The proper fix would be this:
1270
      // For case 1:
1271
      // Communicate the fact that HTML is required to EncodeToString(),
1272
      // bug 1141786.
1273
      // For case 2:
1274
      // Wait for Firefox to get fixed to detect pre-formatting correctly,
1275
      // bug 1174452.
1276
      nsAutoString styleVal;
1277
      if (selContent->IsElement() &&
1278
          selContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleVal) &&
1279
          styleVal.Find(NS_LITERAL_STRING("pre-wrap")) != kNotFound) {
1280
        mIsTextWidget = true;
1281
        break;
1282
      }
1283
    }
1284
#endif
1285
  }
1286
0
1287
0
  // normalize selection if we are not in a widget
1288
0
  if (mIsTextWidget)
1289
0
  {
1290
0
    mSelection = aSelection;
1291
0
    mMimeType.AssignLiteral("text/plain");
1292
0
    return NS_OK;
1293
0
  }
1294
0
1295
0
  // XXX We should try to get rid of the Selection object here.
1296
0
  // XXX bug 1245883
1297
0
1298
0
  // also consider ourselves in a text widget if we can't find an html document
1299
0
  nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
1300
0
  if (!(htmlDoc && mDocument->IsHTMLDocument())) {
1301
0
    mIsTextWidget = true;
1302
0
    mSelection = aSelection;
1303
0
    // mMimeType is set to text/plain when encoding starts.
1304
0
    return NS_OK;
1305
0
  }
1306
0
1307
0
  // there's no Clone() for selection! fix...
1308
0
  //nsresult rv = aSelection->Clone(getter_AddRefs(mSelection);
1309
0
  //NS_ENSURE_SUCCESS(rv, rv);
1310
0
  mSelection = new Selection();
1311
0
1312
0
  // loop thru the ranges in the selection
1313
0
  for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
1314
0
    range = aSelection->GetRangeAt(rangeIdx);
1315
0
    NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
1316
0
    RefPtr<nsRange> myRange = range->CloneRange();
1317
0
    MOZ_ASSERT(myRange);
1318
0
1319
0
    // adjust range to include any ancestors who's children are entirely selected
1320
0
    nsresult rv = PromoteRange(myRange);
1321
0
    NS_ENSURE_SUCCESS(rv, rv);
1322
0
1323
0
    ErrorResult result;
1324
0
    mSelection->AddRangeInternal(*myRange, mDocument, result);
1325
0
    rv = result.StealNSResult();
1326
0
    NS_ENSURE_SUCCESS(rv, rv);
1327
0
  }
1328
0
1329
0
  return NS_OK;
1330
0
}
1331
1332
NS_IMETHODIMP
1333
nsHTMLCopyEncoder::EncodeToString(nsAString& aOutputString)
1334
0
{
1335
0
  if (mIsTextWidget) {
1336
0
    mMimeType.AssignLiteral("text/plain");
1337
0
  }
1338
0
  return nsDocumentEncoder::EncodeToString(aOutputString);
1339
0
}
1340
1341
NS_IMETHODIMP
1342
nsHTMLCopyEncoder::EncodeToStringWithContext(nsAString& aContextString,
1343
                                             nsAString& aInfoString,
1344
                                             nsAString& aEncodedString)
1345
0
{
1346
0
  nsresult rv = EncodeToString(aEncodedString);
1347
0
  NS_ENSURE_SUCCESS(rv, rv);
1348
0
1349
0
  // do not encode any context info or range hints if we are in a text widget.
1350
0
  if (mIsTextWidget) return NS_OK;
1351
0
1352
0
  // now encode common ancestors into aContextString.  Note that the common ancestors
1353
0
  // will be for the last range in the selection in the case of multirange selections.
1354
0
  // encoding ancestors every range in a multirange selection in a way that could be
1355
0
  // understood by the paste code would be a lot more work to do.  As a practical matter,
1356
0
  // selections are single range, and the ones that aren't are table cell selections
1357
0
  // where all the cells are in the same table.
1358
0
1359
0
  // leaf of ancestors might be text node.  If so discard it.
1360
0
  int32_t count = mCommonAncestors.Length();
1361
0
  int32_t i;
1362
0
  nsCOMPtr<nsINode> node;
1363
0
  if (count > 0)
1364
0
    node = mCommonAncestors.ElementAt(0);
1365
0
1366
0
  if (node && IsTextNode(node))
1367
0
  {
1368
0
    mCommonAncestors.RemoveElementAt(0);
1369
0
    // don't forget to adjust range depth info
1370
0
    if (mStartDepth) mStartDepth--;
1371
0
    if (mEndDepth) mEndDepth--;
1372
0
    // and the count
1373
0
    count--;
1374
0
  }
1375
0
1376
0
  i = count;
1377
0
  while (i > 0)
1378
0
  {
1379
0
    node = mCommonAncestors.ElementAt(--i);
1380
0
    SerializeNodeStart(node, 0, -1, aContextString);
1381
0
  }
1382
0
  //i = 0; guaranteed by above
1383
0
  while (i < count)
1384
0
  {
1385
0
    node = mCommonAncestors.ElementAt(i++);
1386
0
    SerializeNodeEnd(node, aContextString);
1387
0
  }
1388
0
1389
0
  // encode range info : the start and end depth of the selection, where the depth is
1390
0
  // distance down in the parent hierarchy.  Later we will need to add leading/trailing
1391
0
  // whitespace info to this.
1392
0
  nsAutoString infoString;
1393
0
  infoString.AppendInt(mStartDepth);
1394
0
  infoString.Append(char16_t(','));
1395
0
  infoString.AppendInt(mEndDepth);
1396
0
  aInfoString = infoString;
1397
0
1398
0
  return NS_OK;
1399
0
}
1400
1401
1402
bool
1403
nsHTMLCopyEncoder::IncludeInContext(nsINode *aNode)
1404
0
{
1405
0
  nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
1406
0
1407
0
  if (!content)
1408
0
    return false;
1409
0
1410
0
  return content->IsAnyOfHTMLElements(nsGkAtoms::b,
1411
0
                                      nsGkAtoms::i,
1412
0
                                      nsGkAtoms::u,
1413
0
                                      nsGkAtoms::a,
1414
0
                                      nsGkAtoms::tt,
1415
0
                                      nsGkAtoms::s,
1416
0
                                      nsGkAtoms::big,
1417
0
                                      nsGkAtoms::small,
1418
0
                                      nsGkAtoms::strike,
1419
0
                                      nsGkAtoms::em,
1420
0
                                      nsGkAtoms::strong,
1421
0
                                      nsGkAtoms::dfn,
1422
0
                                      nsGkAtoms::code,
1423
0
                                      nsGkAtoms::cite,
1424
0
                                      nsGkAtoms::var,
1425
0
                                      nsGkAtoms::abbr,
1426
0
                                      nsGkAtoms::font,
1427
0
                                      nsGkAtoms::script,
1428
0
                                      nsGkAtoms::span,
1429
0
                                      nsGkAtoms::pre,
1430
0
                                      nsGkAtoms::h1,
1431
0
                                      nsGkAtoms::h2,
1432
0
                                      nsGkAtoms::h3,
1433
0
                                      nsGkAtoms::h4,
1434
0
                                      nsGkAtoms::h5,
1435
0
                                      nsGkAtoms::h6);
1436
0
}
1437
1438
1439
nsresult
1440
nsHTMLCopyEncoder::PromoteRange(nsRange* inRange)
1441
0
{
1442
0
  if (!inRange->IsPositioned()) {
1443
0
    return NS_ERROR_UNEXPECTED;
1444
0
  }
1445
0
  nsCOMPtr<nsINode> startNode = inRange->GetStartContainer();
1446
0
  uint32_t startOffset = inRange->StartOffset();
1447
0
  nsCOMPtr<nsINode> endNode = inRange->GetEndContainer();
1448
0
  uint32_t endOffset = inRange->EndOffset();
1449
0
  nsCOMPtr<nsINode> common = inRange->GetCommonAncestor();
1450
0
1451
0
  nsCOMPtr<nsINode> opStartNode;
1452
0
  nsCOMPtr<nsINode> opEndNode;
1453
0
  int32_t opStartOffset, opEndOffset;
1454
0
1455
0
  // examine range endpoints.
1456
0
  nsresult rv =
1457
0
    GetPromotedPoint(kStart, startNode,
1458
0
                     static_cast<int32_t>(startOffset),
1459
0
                     address_of(opStartNode), &opStartOffset,
1460
0
                     common);
1461
0
  NS_ENSURE_SUCCESS(rv, rv);
1462
0
  rv = GetPromotedPoint(kEnd, endNode,
1463
0
                        static_cast<int32_t>(endOffset),
1464
0
                        address_of(opEndNode), &opEndOffset,
1465
0
                        common);
1466
0
  NS_ENSURE_SUCCESS(rv, rv);
1467
0
1468
0
  // if both range endpoints are at the common ancestor, check for possible inclusion of ancestors
1469
0
  if (opStartNode == common && opEndNode == common) {
1470
0
    rv = PromoteAncestorChain(address_of(opStartNode), &opStartOffset, &opEndOffset);
1471
0
    NS_ENSURE_SUCCESS(rv, rv);
1472
0
    opEndNode = opStartNode;
1473
0
  }
1474
0
1475
0
  // set the range to the new values
1476
0
  ErrorResult err;
1477
0
  inRange->SetStart(*opStartNode, static_cast<uint32_t>(opStartOffset), err);
1478
0
  if (NS_WARN_IF(err.Failed())) {
1479
0
    return err.StealNSResult();
1480
0
  }
1481
0
  inRange->SetEnd(*opEndNode, static_cast<uint32_t>(opEndOffset), err);
1482
0
  if (NS_WARN_IF(err.Failed())) {
1483
0
    return err.StealNSResult();
1484
0
  }
1485
0
  return NS_OK;
1486
0
}
1487
1488
1489
// PromoteAncestorChain will promote a range represented by [{*ioNode,*ioStartOffset} , {*ioNode,*ioEndOffset}]
1490
// The promotion is different from that found in getPromotedPoint: it will only promote one endpoint if it can
1491
// promote the other.  Thus, instead of having a startnode/endNode, there is just the one ioNode.
1492
nsresult
1493
nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr<nsINode>* ioNode,
1494
                                        int32_t* ioStartOffset,
1495
                                        int32_t* ioEndOffset)
1496
0
{
1497
0
  if (!ioNode || !ioStartOffset || !ioEndOffset) return NS_ERROR_NULL_POINTER;
1498
0
1499
0
  nsresult rv = NS_OK;
1500
0
  bool done = false;
1501
0
1502
0
  nsCOMPtr<nsINode> frontNode, endNode, parent;
1503
0
  int32_t frontOffset, endOffset;
1504
0
1505
0
  //save the editable state of the ioNode, so we don't promote an ancestor if it has different editable state
1506
0
  nsCOMPtr<nsINode> node = *ioNode;
1507
0
  bool isEditable = node->IsEditable();
1508
0
1509
0
  // loop for as long as we can promote both endpoints
1510
0
  while (!done)
1511
0
  {
1512
0
    node = *ioNode;
1513
0
    parent = node->GetParentNode();
1514
0
    if (!parent) {
1515
0
      done = true;
1516
0
    } else {
1517
0
      // passing parent as last param to GetPromotedPoint() allows it to promote only one level
1518
0
      // up the hierarchy.
1519
0
      rv = GetPromotedPoint( kStart, *ioNode, *ioStartOffset, address_of(frontNode), &frontOffset, parent);
1520
0
      NS_ENSURE_SUCCESS(rv, rv);
1521
0
      // then we make the same attempt with the endpoint
1522
0
      rv = GetPromotedPoint( kEnd, *ioNode, *ioEndOffset, address_of(endNode), &endOffset, parent);
1523
0
      NS_ENSURE_SUCCESS(rv, rv);
1524
0
1525
0
      // if both endpoints were promoted one level and isEditable is the same as the original node,
1526
0
      // keep looping - otherwise we are done.
1527
0
      if ( (frontNode != parent) || (endNode != parent) || (frontNode->IsEditable() != isEditable) )
1528
0
        done = true;
1529
0
      else
1530
0
      {
1531
0
        *ioNode = frontNode;
1532
0
        *ioStartOffset = frontOffset;
1533
0
        *ioEndOffset = endOffset;
1534
0
      }
1535
0
    }
1536
0
  }
1537
0
  return rv;
1538
0
}
1539
1540
nsresult
1541
nsHTMLCopyEncoder::GetPromotedPoint(Endpoint aWhere, nsINode* aNode,
1542
                                    int32_t aOffset, nsCOMPtr<nsINode>* outNode,
1543
                                    int32_t* outOffset, nsINode* common)
1544
0
{
1545
0
  nsresult rv = NS_OK;
1546
0
  nsCOMPtr<nsINode> node = aNode;
1547
0
  nsCOMPtr<nsINode> parent = aNode;
1548
0
  int32_t offset = aOffset;
1549
0
  bool    bResetPromotion = false;
1550
0
1551
0
  // default values
1552
0
  *outNode = node;
1553
0
  *outOffset = offset;
1554
0
1555
0
  if (common == node)
1556
0
    return NS_OK;
1557
0
1558
0
  if (aWhere == kStart)
1559
0
  {
1560
0
    // some special casing for text nodes
1561
0
    if (auto nodeAsText = aNode->GetAsText())
1562
0
    {
1563
0
      // if not at beginning of text node, we are done
1564
0
      if (offset >  0)
1565
0
      {
1566
0
        // unless everything before us in just whitespace.  NOTE: we need a more
1567
0
        // general solution that truly detects all cases of non-significant
1568
0
        // whitesace with no false alarms.
1569
0
        nsAutoString text;
1570
0
        nodeAsText->SubstringData(0, offset, text, IgnoreErrors());
1571
0
        text.CompressWhitespace();
1572
0
        if (!text.IsEmpty())
1573
0
          return NS_OK;
1574
0
        bResetPromotion = true;
1575
0
      }
1576
0
      // else
1577
0
      rv = GetNodeLocation(aNode, address_of(parent), &offset);
1578
0
      NS_ENSURE_SUCCESS(rv, rv);
1579
0
    }
1580
0
    else
1581
0
    {
1582
0
      node = GetChildAt(parent,offset);
1583
0
    }
1584
0
    if (!node) node = parent;
1585
0
1586
0
    // finding the real start for this point.  look up the tree for as long as we are the
1587
0
    // first node in the container, and as long as we haven't hit the body node.
1588
0
    if (!IsRoot(node) && (parent != common))
1589
0
    {
1590
0
      rv = GetNodeLocation(node, address_of(parent), &offset);
1591
0
      NS_ENSURE_SUCCESS(rv, rv);
1592
0
      if (offset == -1) return NS_OK; // we hit generated content; STOP
1593
0
      while ((IsFirstNode(node)) && (!IsRoot(parent)) && (parent != common))
1594
0
      {
1595
0
        if (bResetPromotion)
1596
0
        {
1597
0
          nsCOMPtr<nsIContent> content = do_QueryInterface(parent);
1598
0
          if (content && content->IsHTMLElement())
1599
0
          {
1600
0
            if (nsHTMLElement::IsBlock(nsHTMLTags::AtomTagToId(
1601
0
                                       content->NodeInfo()->NameAtom()))) {
1602
0
              bResetPromotion = false;
1603
0
            }
1604
0
          }
1605
0
        }
1606
0
1607
0
        node = parent;
1608
0
        rv = GetNodeLocation(node, address_of(parent), &offset);
1609
0
        NS_ENSURE_SUCCESS(rv, rv);
1610
0
        if (offset == -1)  // we hit generated content; STOP
1611
0
        {
1612
0
          // back up a bit
1613
0
          parent = node;
1614
0
          offset = 0;
1615
0
          break;
1616
0
        }
1617
0
      }
1618
0
      if (bResetPromotion)
1619
0
      {
1620
0
        *outNode = aNode;
1621
0
        *outOffset = aOffset;
1622
0
      }
1623
0
      else
1624
0
      {
1625
0
        *outNode = parent;
1626
0
        *outOffset = offset;
1627
0
      }
1628
0
      return rv;
1629
0
    }
1630
0
  }
1631
0
1632
0
  if (aWhere == kEnd)
1633
0
  {
1634
0
    // some special casing for text nodes
1635
0
    if (auto nodeAsText = aNode->GetAsText())
1636
0
    {
1637
0
      // if not at end of text node, we are done
1638
0
      uint32_t len = aNode->Length();
1639
0
      if (offset < (int32_t)len)
1640
0
      {
1641
0
        // unless everything after us in just whitespace.  NOTE: we need a more
1642
0
        // general solution that truly detects all cases of non-significant
1643
0
        // whitespace with no false alarms.
1644
0
        nsAutoString text;
1645
0
        nodeAsText->SubstringData(offset, len-offset, text, IgnoreErrors());
1646
0
        text.CompressWhitespace();
1647
0
        if (!text.IsEmpty())
1648
0
          return NS_OK;
1649
0
        bResetPromotion = true;
1650
0
      }
1651
0
      rv = GetNodeLocation(aNode, address_of(parent), &offset);
1652
0
      NS_ENSURE_SUCCESS(rv, rv);
1653
0
    }
1654
0
    else
1655
0
    {
1656
0
      if (offset) offset--; // we want node _before_ offset
1657
0
      node = GetChildAt(parent,offset);
1658
0
    }
1659
0
    if (!node) node = parent;
1660
0
1661
0
    // finding the real end for this point.  look up the tree for as long as we are the
1662
0
    // last node in the container, and as long as we haven't hit the body node.
1663
0
    if (!IsRoot(node) && (parent != common))
1664
0
    {
1665
0
      rv = GetNodeLocation(node, address_of(parent), &offset);
1666
0
      NS_ENSURE_SUCCESS(rv, rv);
1667
0
      if (offset == -1) return NS_OK; // we hit generated content; STOP
1668
0
      while ((IsLastNode(node)) && (!IsRoot(parent)) && (parent != common))
1669
0
      {
1670
0
        if (bResetPromotion)
1671
0
        {
1672
0
          nsCOMPtr<nsIContent> content = do_QueryInterface(parent);
1673
0
          if (content && content->IsHTMLElement())
1674
0
          {
1675
0
            if (nsHTMLElement::IsBlock(nsHTMLTags::AtomTagToId(
1676
0
                                       content->NodeInfo()->NameAtom()))) {
1677
0
              bResetPromotion = false;
1678
0
            }
1679
0
          }
1680
0
        }
1681
0
1682
0
        node = parent;
1683
0
        rv = GetNodeLocation(node, address_of(parent), &offset);
1684
0
        NS_ENSURE_SUCCESS(rv, rv);
1685
0
        if (offset == -1)  // we hit generated content; STOP
1686
0
        {
1687
0
          // back up a bit
1688
0
          parent = node;
1689
0
          offset = 0;
1690
0
          break;
1691
0
        }
1692
0
      }
1693
0
      if (bResetPromotion)
1694
0
      {
1695
0
        *outNode = aNode;
1696
0
        *outOffset = aOffset;
1697
0
      }
1698
0
      else
1699
0
      {
1700
0
        *outNode = parent;
1701
0
        offset++;  // add one since this in an endpoint - want to be AFTER node.
1702
0
        *outOffset = offset;
1703
0
      }
1704
0
      return rv;
1705
0
    }
1706
0
  }
1707
0
1708
0
  return rv;
1709
0
}
1710
1711
nsCOMPtr<nsINode>
1712
nsHTMLCopyEncoder::GetChildAt(nsINode *aParent, int32_t aOffset)
1713
0
{
1714
0
  nsCOMPtr<nsINode> resultNode;
1715
0
1716
0
  if (!aParent)
1717
0
    return resultNode;
1718
0
1719
0
  nsCOMPtr<nsIContent> content = do_QueryInterface(aParent);
1720
0
  MOZ_ASSERT(content, "null content in nsHTMLCopyEncoder::GetChildAt");
1721
0
1722
0
  resultNode = content->GetChildAt_Deprecated(aOffset);
1723
0
1724
0
  return resultNode;
1725
0
}
1726
1727
bool
1728
nsHTMLCopyEncoder::IsMozBR(Element* aElement)
1729
0
{
1730
0
  return aElement->IsHTMLElement(nsGkAtoms::br) &&
1731
0
         aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1732
0
                               NS_LITERAL_STRING("_moz"), eIgnoreCase);
1733
0
}
1734
1735
nsresult
1736
nsHTMLCopyEncoder::GetNodeLocation(nsINode *inChild,
1737
                                   nsCOMPtr<nsINode> *outParent,
1738
                                   int32_t *outOffset)
1739
0
{
1740
0
  NS_ASSERTION((inChild && outParent && outOffset), "bad args");
1741
0
  if (inChild && outParent && outOffset)
1742
0
  {
1743
0
    nsCOMPtr<nsIContent> child = do_QueryInterface(inChild);
1744
0
    if (!child) {
1745
0
      return NS_ERROR_NULL_POINTER;
1746
0
    }
1747
0
1748
0
    nsIContent* parent = child->GetParent();
1749
0
    if (!parent) {
1750
0
      return NS_ERROR_NULL_POINTER;
1751
0
    }
1752
0
1753
0
    *outParent = parent;
1754
0
    *outOffset = parent->ComputeIndexOf(child);
1755
0
    return NS_OK;
1756
0
  }
1757
0
  return NS_ERROR_NULL_POINTER;
1758
0
}
1759
1760
bool
1761
nsHTMLCopyEncoder::IsRoot(nsINode* aNode)
1762
0
{
1763
0
  nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
1764
0
  if (!content) {
1765
0
    return false;
1766
0
  }
1767
0
1768
0
  if (mIsTextWidget) {
1769
0
    return content->IsHTMLElement(nsGkAtoms::div);
1770
0
  }
1771
0
1772
0
  return content->IsAnyOfHTMLElements(nsGkAtoms::body,
1773
0
                                      nsGkAtoms::td,
1774
0
                                      nsGkAtoms::th);
1775
0
}
1776
1777
bool
1778
nsHTMLCopyEncoder::IsFirstNode(nsINode *aNode)
1779
0
{
1780
0
  // need to check if any nodes before us are really visible.
1781
0
  // Mike wrote something for me along these lines in nsSelectionController,
1782
0
  // but I don't think it's ready for use yet - revisit.
1783
0
  // HACK: for now, simply consider all whitespace text nodes to be
1784
0
  // invisible formatting nodes.
1785
0
  for (nsIContent* sibling = aNode->GetPreviousSibling();
1786
0
       sibling;
1787
0
       sibling = sibling->GetPreviousSibling()) {
1788
0
    if (!sibling->TextIsOnlyWhitespace()) {
1789
0
      return false;
1790
0
    }
1791
0
  }
1792
0
1793
0
  return true;
1794
0
}
1795
1796
bool
1797
nsHTMLCopyEncoder::IsLastNode(nsINode *aNode)
1798
0
{
1799
0
  // need to check if any nodes after us are really visible.
1800
0
  // Mike wrote something for me along these lines in nsSelectionController,
1801
0
  // but I don't think it's ready for use yet - revisit.
1802
0
  // HACK: for now, simply consider all whitespace text nodes to be
1803
0
  // invisible formatting nodes.
1804
0
  for (nsIContent* sibling = aNode->GetNextSibling();
1805
0
       sibling;
1806
0
       sibling = sibling->GetNextSibling()) {
1807
0
    if (sibling->IsElement() && IsMozBR(sibling->AsElement())) {
1808
0
      // we ignore trailing moz BRs.
1809
0
      continue;
1810
0
    }
1811
0
    if (!sibling->TextIsOnlyWhitespace()) {
1812
0
      return false;
1813
0
    }
1814
0
  }
1815
0
1816
0
  return true;
1817
0
}
1818
1819
nsresult NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy
1820
1821
nsresult
1822
NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult)
1823
0
{
1824
0
  *aResult = new nsHTMLCopyEncoder;
1825
0
 NS_ADDREF(*aResult);
1826
0
 return NS_OK;
1827
0
}
1828
1829
int32_t
1830
nsHTMLCopyEncoder::GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray)
1831
0
{
1832
0
  int32_t i = aAncestorArray.Length(), j = 0;
1833
0
  while (j < i) {
1834
0
    nsINode *node = aAncestorArray.ElementAt(j);
1835
0
    if (!node) {
1836
0
      break;
1837
0
    }
1838
0
    nsCOMPtr<nsIContent> content(do_QueryInterface(node));
1839
0
    if (!content ||
1840
0
        !content->IsAnyOfHTMLElements(nsGkAtoms::tr,
1841
0
                                      nsGkAtoms::thead,
1842
0
                                      nsGkAtoms::tbody,
1843
0
                                      nsGkAtoms::tfoot,
1844
0
                                      nsGkAtoms::table)) {
1845
0
      break;
1846
0
    }
1847
0
    ++j;
1848
0
  }
1849
0
  return j;
1850
0
}