Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/editor/libeditor/HTMLEditorDataTransfer.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=2 sw=2 et tw=78: */
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
#include "mozilla/HTMLEditor.h"
8
9
#include <string.h>
10
11
#include "HTMLEditUtils.h"
12
#include "InternetCiter.h"
13
#include "TextEditUtils.h"
14
#include "WSRunObject.h"
15
#include "mozilla/dom/Comment.h"
16
#include "mozilla/dom/DataTransfer.h"
17
#include "mozilla/dom/DocumentFragment.h"
18
#include "mozilla/dom/DOMException.h"
19
#include "mozilla/dom/DOMStringList.h"
20
#include "mozilla/dom/FileReader.h"
21
#include "mozilla/dom/Selection.h"
22
#include "mozilla/dom/WorkerRef.h"
23
#include "mozilla/ArrayUtils.h"
24
#include "mozilla/Base64.h"
25
#include "mozilla/BasicEvents.h"
26
#include "mozilla/EditAction.h"
27
#include "mozilla/EditorDOMPoint.h"
28
#include "mozilla/EditorUtils.h"
29
#include "mozilla/OwningNonNull.h"
30
#include "mozilla/Preferences.h"
31
#include "mozilla/SelectionState.h"
32
#include "mozilla/TextEditRules.h"
33
#include "nsAString.h"
34
#include "nsCOMPtr.h"
35
#include "nsCRTGlue.h" // for CRLF
36
#include "nsComponentManagerUtils.h"
37
#include "nsIScriptError.h"
38
#include "nsContentUtils.h"
39
#include "nsDebug.h"
40
#include "nsDependentSubstring.h"
41
#include "nsError.h"
42
#include "nsGkAtoms.h"
43
#include "nsIClipboard.h"
44
#include "nsIContent.h"
45
#include "nsIDocument.h"
46
#include "nsIFile.h"
47
#include "nsIInputStream.h"
48
#include "nsIMIMEService.h"
49
#include "nsNameSpaceManager.h"
50
#include "nsINode.h"
51
#include "nsIParserUtils.h"
52
#include "nsISupportsImpl.h"
53
#include "nsISupportsPrimitives.h"
54
#include "nsISupportsUtils.h"
55
#include "nsITransferable.h"
56
#include "nsIURI.h"
57
#include "nsIVariant.h"
58
#include "nsLinebreakConverter.h"
59
#include "nsLiteralString.h"
60
#include "nsNetUtil.h"
61
#include "nsRange.h"
62
#include "nsReadableUtils.h"
63
#include "nsServiceManagerUtils.h"
64
#include "nsStreamUtils.h"
65
#include "nsString.h"
66
#include "nsStringFwd.h"
67
#include "nsStringIterator.h"
68
#include "nsTreeSanitizer.h"
69
#include "nsXPCOM.h"
70
#include "nscore.h"
71
#include "nsContentUtils.h"
72
#include "nsQueryObject.h"
73
74
class nsAtom;
75
class nsILoadContext;
76
class nsISupports;
77
78
namespace mozilla {
79
80
using namespace dom;
81
82
0
#define kInsertCookie  "_moz_Insert Here_moz_"
83
84
// some little helpers
85
static bool FindIntegerAfterString(const char* aLeadingString,
86
                                   nsCString& aCStr, int32_t& foundNumber);
87
static nsresult RemoveFragComments(nsCString& theStr);
88
static void RemoveBodyAndHead(nsINode& aNode);
89
static nsresult FindTargetNode(nsINode* aStart,
90
                               nsCOMPtr<nsINode>& aResult);
91
92
nsresult
93
HTMLEditor::LoadHTML(const nsAString& aInputString)
94
0
{
95
0
  NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
96
0
97
0
  // force IME commit; set up rules sniffing and batching
98
0
  CommitComposition();
99
0
  AutoPlaceholderBatch beginBatching(this);
100
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
101
0
                                      *this,
102
0
                                      EditSubAction::eInsertHTMLSource,
103
0
                                      nsIEditor::eNext);
104
0
105
0
  // Get selection
106
0
  RefPtr<Selection> selection = GetSelection();
107
0
  NS_ENSURE_STATE(selection);
108
0
109
0
  EditSubActionInfo subActionInfo(EditSubAction::eInsertHTMLSource);
110
0
  bool cancel, handled;
111
0
  // Protect the edit rules object from dying
112
0
  RefPtr<TextEditRules> rules(mRules);
113
0
  nsresult rv =
114
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
115
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
116
0
    return rv;
117
0
  }
118
0
  if (cancel) {
119
0
    return NS_OK; // rules canceled the operation
120
0
  }
121
0
122
0
  if (!handled) {
123
0
    // Delete Selection, but only if it isn't collapsed, see bug #106269
124
0
    if (!selection->IsCollapsed()) {
125
0
      rv = DeleteSelectionAsSubAction(eNone, eStrip);
126
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
127
0
        return rv;
128
0
      }
129
0
    }
130
0
131
0
    // Get the first range in the selection, for context:
132
0
    RefPtr<nsRange> range = selection->GetRangeAt(0);
133
0
    NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
134
0
135
0
    // Create fragment for pasted HTML.
136
0
    ErrorResult error;
137
0
    RefPtr<DocumentFragment> documentFragment =
138
0
      range->CreateContextualFragment(aInputString, error);
139
0
    if (NS_WARN_IF(error.Failed())) {
140
0
      return error.StealNSResult();
141
0
    }
142
0
143
0
    // Put the fragment into the document at start of selection.
144
0
    EditorDOMPoint pointToInsert(range->StartRef());
145
0
    // XXX We need to make pointToInsert store offset for keeping traditional
146
0
    //     behavior since using only child node to pointing insertion point
147
0
    //     changes the behavior when inserted child is moved by mutation
148
0
    //     observer.  We need to investigate what we should do here.
149
0
    Unused << pointToInsert.Offset();
150
0
    for (nsCOMPtr<nsIContent> contentToInsert =
151
0
           documentFragment->GetFirstChild();
152
0
         contentToInsert;
153
0
         contentToInsert = documentFragment->GetFirstChild()) {
154
0
      rv = InsertNodeWithTransaction(*contentToInsert, pointToInsert);
155
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
156
0
        return rv;
157
0
      }
158
0
      // XXX If the inserted node has been moved by mutation observer,
159
0
      //     incrementing offset will cause odd result.  Next new node
160
0
      //     will be inserted after existing node and the offset will be
161
0
      //     overflown from the container node.
162
0
      pointToInsert.Set(pointToInsert.GetContainer(),
163
0
                        pointToInsert.Offset() + 1);
164
0
      if (NS_WARN_IF(!pointToInsert.Offset())) {
165
0
        // Append the remaining children to the container if offset is
166
0
        // overflown.
167
0
        pointToInsert.SetToEndOf(pointToInsert.GetContainer());
168
0
      }
169
0
    }
170
0
  }
171
0
172
0
  return rules->DidDoAction(selection, subActionInfo, rv);
173
0
}
174
175
NS_IMETHODIMP
176
HTMLEditor::InsertHTML(const nsAString& aInString)
177
0
{
178
0
  const nsString& empty = EmptyString();
179
0
180
0
  return DoInsertHTMLWithContext(aInString, empty, empty, empty,
181
0
                                 nullptr,  nullptr, 0, true, true, false);
182
0
}
183
184
nsresult
185
HTMLEditor::DoInsertHTMLWithContext(const nsAString& aInputString,
186
                                    const nsAString& aContextStr,
187
                                    const nsAString& aInfoStr,
188
                                    const nsAString& aFlavor,
189
                                    nsIDocument* aSourceDoc,
190
                                    nsINode* aDestNode,
191
                                    int32_t aDestOffset,
192
                                    bool aDeleteSelection,
193
                                    bool aTrustedInput,
194
                                    bool aClearStyle)
195
0
{
196
0
  NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
197
0
198
0
  // Prevent the edit rules object from dying
199
0
  RefPtr<TextEditRules> rules(mRules);
200
0
201
0
  // force IME commit; set up rules sniffing and batching
202
0
  CommitComposition();
203
0
  AutoPlaceholderBatch beginBatching(this);
204
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
205
0
                                      *this, EditSubAction::ePasteHTMLContent,
206
0
                                      nsIEditor::eNext);
207
0
208
0
  // Get selection
209
0
  RefPtr<Selection> selection = GetSelection();
210
0
  if (NS_WARN_IF(!selection)) {
211
0
    return NS_ERROR_FAILURE;
212
0
  }
213
0
214
0
  // create a dom document fragment that represents the structure to paste
215
0
  nsCOMPtr<nsINode> fragmentAsNode, streamStartParent, streamEndParent;
216
0
  int32_t streamStartOffset = 0, streamEndOffset = 0;
217
0
218
0
  nsresult rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr,
219
0
                                           address_of(fragmentAsNode),
220
0
                                           address_of(streamStartParent),
221
0
                                           address_of(streamEndParent),
222
0
                                           &streamStartOffset,
223
0
                                           &streamEndOffset,
224
0
                                           aTrustedInput);
225
0
  NS_ENSURE_SUCCESS(rv, rv);
226
0
227
0
  EditorDOMPoint targetPoint;
228
0
  if (!aDestNode) {
229
0
    // if caller didn't provide the destination/target node,
230
0
    // fetch the paste insertion point from our selection
231
0
    targetPoint = EditorBase::GetStartPoint(selection);
232
0
    if (NS_WARN_IF(!targetPoint.IsSet()) ||
233
0
        !IsEditable(targetPoint.GetContainer())) {
234
0
      return NS_ERROR_FAILURE;
235
0
    }
236
0
  } else {
237
0
    targetPoint.Set(aDestNode, aDestOffset);
238
0
  }
239
0
240
0
  // if we have a destination / target node, we want to insert there
241
0
  // rather than in place of the selection
242
0
  // ignore aDeleteSelection here if no aDestNode since deletion will
243
0
  // also occur later; this block is intended to cover the various
244
0
  // scenarios where we are dropping in an editor (and may want to delete
245
0
  // the selection before collapsing the selection in the new destination)
246
0
  if (aDestNode) {
247
0
    if (aDeleteSelection) {
248
0
      // Use an auto tracker so that our drop point is correctly
249
0
      // positioned after the delete.
250
0
      AutoTrackDOMPoint tracker(mRangeUpdater, &targetPoint);
251
0
      rv = DeleteSelectionAsSubAction(eNone, eStrip);
252
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
253
0
        return rv;
254
0
      }
255
0
    }
256
0
257
0
    ErrorResult error;
258
0
    selection->Collapse(targetPoint, error);
259
0
    if (NS_WARN_IF(error.Failed())) {
260
0
      return error.StealNSResult();
261
0
    }
262
0
  }
263
0
264
0
  // we need to recalculate various things based on potentially new offsets
265
0
  // this is work to be completed at a later date (probably by jfrancis)
266
0
267
0
  // make a list of what nodes in docFrag we need to move
268
0
  nsTArray<OwningNonNull<nsINode>> nodeList;
269
0
  CreateListOfNodesToPaste(*fragmentAsNode->AsDocumentFragment(),
270
0
                           nodeList,
271
0
                           streamStartParent,
272
0
                           streamStartOffset,
273
0
                           streamEndParent,
274
0
                           streamEndOffset);
275
0
276
0
  if (nodeList.IsEmpty()) {
277
0
    // We aren't inserting anything, but if aDeleteSelection is set, we do want
278
0
    // to delete everything.
279
0
    if (aDeleteSelection) {
280
0
      nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
281
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
282
0
        return rv;
283
0
      }
284
0
    }
285
0
    return NS_OK;
286
0
  }
287
0
288
0
  // Are there any table elements in the list?
289
0
  // check for table cell selection mode
290
0
  bool cellSelectionMode = false;
291
0
  IgnoredErrorResult ignoredError;
292
0
  RefPtr<Element> cellElement =
293
0
    GetFirstSelectedTableCellElement(*selection, ignoredError);
294
0
  if (cellElement) {
295
0
    cellSelectionMode = true;
296
0
  }
297
0
298
0
  if (cellSelectionMode) {
299
0
    // do we have table content to paste?  If so, we want to delete
300
0
    // the selected table cells and replace with new table elements;
301
0
    // but if not we want to delete _contents_ of cells and replace
302
0
    // with non-table elements.  Use cellSelectionMode bool to
303
0
    // indicate results.
304
0
    if (!HTMLEditUtils::IsTableElement(nodeList[0])) {
305
0
      cellSelectionMode = false;
306
0
    }
307
0
  }
308
0
309
0
  if (!cellSelectionMode) {
310
0
    rv = DeleteSelectionAndPrepareToCreateNode();
311
0
    NS_ENSURE_SUCCESS(rv, rv);
312
0
313
0
    if (aClearStyle) {
314
0
      // pasting does not inherit local inline styles
315
0
      nsCOMPtr<nsINode> tmpNode = selection->GetAnchorNode();
316
0
      int32_t tmpOffset = static_cast<int32_t>(selection->AnchorOffset());
317
0
      rv = ClearStyle(address_of(tmpNode), &tmpOffset, nullptr, nullptr);
318
0
      NS_ENSURE_SUCCESS(rv, rv);
319
0
    }
320
0
  } else {
321
0
    // Delete whole cells: we will replace with new table content.
322
0
323
0
    // Braces for artificial block to scope AutoSelectionRestorer.
324
0
    // Save current selection since DeleteTableCellWithTransaction() perturbs
325
0
    // it.
326
0
    {
327
0
      AutoSelectionRestorer selectionRestorer(selection, this);
328
0
      rv = DeleteTableCellWithTransaction(1);
329
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
330
0
        return rv;
331
0
      }
332
0
    }
333
0
    // collapse selection to beginning of deleted table content
334
0
    selection->CollapseToStart(IgnoreErrors());
335
0
  }
336
0
337
0
  // give rules a chance to handle or cancel
338
0
  EditSubActionInfo subActionInfo(EditSubAction::eInsertElement);
339
0
  bool cancel, handled;
340
0
  rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
341
0
  NS_ENSURE_SUCCESS(rv, rv);
342
0
  if (cancel) {
343
0
    return NS_OK; // rules canceled the operation
344
0
  }
345
0
346
0
  if (!handled) {
347
0
    // Adjust position based on the first node we are going to insert.
348
0
    // FYI: WillDoAction() above might have changed the selection.
349
0
    EditorDOMPoint pointToInsert =
350
0
      GetBetterInsertionPointFor(nodeList[0], GetStartPoint(selection));
351
0
    if (NS_WARN_IF(!pointToInsert.IsSet())) {
352
0
      return NS_ERROR_FAILURE;
353
0
    }
354
0
355
0
    // if there are any invisible br's after our insertion point, remove them.
356
0
    // this is because if there is a br at end of what we paste, it will make
357
0
    // the invisible br visible.
358
0
    WSRunObject wsObj(this, pointToInsert);
359
0
    if (wsObj.mEndReasonNode &&
360
0
        TextEditUtils::IsBreak(wsObj.mEndReasonNode) &&
361
0
        !IsVisibleBRElement(wsObj.mEndReasonNode)) {
362
0
      AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
363
0
      rv = DeleteNodeWithTransaction(*wsObj.mEndReasonNode);
364
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
365
0
        return rv;
366
0
      }
367
0
    }
368
0
369
0
    // Remember if we are in a link.
370
0
    bool bStartedInLink = !!GetLinkElement(pointToInsert.GetContainer());
371
0
372
0
    // Are we in a text node? If so, split it.
373
0
    if (pointToInsert.IsInTextNode()) {
374
0
      SplitNodeResult splitNodeResult =
375
0
        SplitNodeDeepWithTransaction(
376
0
          *pointToInsert.GetContainerAsContent(), pointToInsert,
377
0
          SplitAtEdges::eAllowToCreateEmptyContainer);
378
0
      if (NS_WARN_IF(splitNodeResult.Failed())) {
379
0
        return splitNodeResult.Rv();
380
0
      }
381
0
      pointToInsert = splitNodeResult.SplitPoint();
382
0
      if (NS_WARN_IF(!pointToInsert.IsSet())) {
383
0
        return NS_ERROR_FAILURE;
384
0
      }
385
0
    }
386
0
387
0
    // build up list of parents of first node in list that are either
388
0
    // lists or tables.  First examine front of paste node list.
389
0
    nsTArray<OwningNonNull<Element>> startListAndTableArray;
390
0
    GetListAndTableParents(StartOrEnd::start, nodeList,
391
0
                           startListAndTableArray);
392
0
393
0
    // remember number of lists and tables above us
394
0
    int32_t highWaterMark = -1;
395
0
    if (!startListAndTableArray.IsEmpty()) {
396
0
      highWaterMark = DiscoverPartialListsAndTables(nodeList,
397
0
                                                    startListAndTableArray);
398
0
    }
399
0
400
0
    // if we have pieces of tables or lists to be inserted, let's force the paste
401
0
    // to deal with table elements right away, so that it doesn't orphan some
402
0
    // table or list contents outside the table or list.
403
0
    if (highWaterMark >= 0) {
404
0
      ReplaceOrphanedStructure(StartOrEnd::start, nodeList,
405
0
                               startListAndTableArray, highWaterMark);
406
0
    }
407
0
408
0
    // Now go through the same process again for the end of the paste node list.
409
0
    nsTArray<OwningNonNull<Element>> endListAndTableArray;
410
0
    GetListAndTableParents(StartOrEnd::end, nodeList, endListAndTableArray);
411
0
    highWaterMark = -1;
412
0
413
0
    // remember number of lists and tables above us
414
0
    if (!endListAndTableArray.IsEmpty()) {
415
0
      highWaterMark = DiscoverPartialListsAndTables(nodeList,
416
0
                                                    endListAndTableArray);
417
0
    }
418
0
419
0
    // don't orphan partial list or table structure
420
0
    if (highWaterMark >= 0) {
421
0
      ReplaceOrphanedStructure(StartOrEnd::end, nodeList,
422
0
                               endListAndTableArray, highWaterMark);
423
0
    }
424
0
425
0
    MOZ_ASSERT(pointToInsert.GetContainer()->
426
0
                               GetChildAt_Deprecated(pointToInsert.Offset()) ==
427
0
                 pointToInsert.GetChild());
428
0
429
0
    // Loop over the node list and paste the nodes:
430
0
    nsCOMPtr<nsINode> parentBlock =
431
0
      IsBlockNode(pointToInsert.GetContainer()) ?
432
0
        pointToInsert.GetContainer() :
433
0
        GetBlockNodeParent(pointToInsert.GetContainer());
434
0
    nsCOMPtr<nsIContent> lastInsertNode;
435
0
    nsCOMPtr<nsINode> insertedContextParent;
436
0
    for (OwningNonNull<nsINode>& curNode : nodeList) {
437
0
      if (NS_WARN_IF(curNode == fragmentAsNode) ||
438
0
          NS_WARN_IF(TextEditUtils::IsBody(curNode))) {
439
0
        return NS_ERROR_FAILURE;
440
0
      }
441
0
442
0
      if (insertedContextParent) {
443
0
        // If we had to insert something higher up in the paste hierarchy,
444
0
        // we want to skip any further paste nodes that descend from that.
445
0
        // Else we will paste twice.
446
0
        if (EditorUtils::IsDescendantOf(*curNode,
447
0
                                        *insertedContextParent)) {
448
0
          continue;
449
0
        }
450
0
      }
451
0
452
0
      // give the user a hand on table element insertion.  if they have
453
0
      // a table or table row on the clipboard, and are trying to insert
454
0
      // into a table or table row, insert the appropriate children instead.
455
0
      bool bDidInsert = false;
456
0
      if (HTMLEditUtils::IsTableRow(curNode) &&
457
0
          HTMLEditUtils::IsTableRow(pointToInsert.GetContainer()) &&
458
0
          (HTMLEditUtils::IsTable(curNode) ||
459
0
           HTMLEditUtils::IsTable(pointToInsert.GetContainer()))) {
460
0
        for (nsCOMPtr<nsIContent> firstChild = curNode->GetFirstChild();
461
0
             firstChild;
462
0
             firstChild = curNode->GetFirstChild()) {
463
0
          EditorDOMPoint insertedPoint =
464
0
            InsertNodeIntoProperAncestorWithTransaction(
465
0
              *firstChild, pointToInsert,
466
0
              SplitAtEdges::eDoNotCreateEmptyContainer);
467
0
          if (NS_WARN_IF(!insertedPoint.IsSet())) {
468
0
            break;
469
0
          }
470
0
          bDidInsert = true;
471
0
          lastInsertNode = firstChild;
472
0
          pointToInsert = insertedPoint;
473
0
          DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
474
0
          NS_WARNING_ASSERTION(advanced,
475
0
            "Failed to advance offset from inserted point");
476
0
        }
477
0
      }
478
0
      // give the user a hand on list insertion.  if they have
479
0
      // a list on the clipboard, and are trying to insert
480
0
      // into a list or list item, insert the appropriate children instead,
481
0
      // ie, merge the lists instead of pasting in a sublist.
482
0
      else if (HTMLEditUtils::IsList(curNode) &&
483
0
               (HTMLEditUtils::IsList(pointToInsert.GetContainer()) ||
484
0
                HTMLEditUtils::IsListItem(pointToInsert.GetContainer()))) {
485
0
        for (nsCOMPtr<nsIContent> firstChild = curNode->GetFirstChild();
486
0
             firstChild;
487
0
             firstChild = curNode->GetFirstChild()) {
488
0
          if (HTMLEditUtils::IsListItem(firstChild) ||
489
0
              HTMLEditUtils::IsList(firstChild)) {
490
0
            // Check if we are pasting into empty list item. If so
491
0
            // delete it and paste into parent list instead.
492
0
            if (HTMLEditUtils::IsListItem(pointToInsert.GetContainer())) {
493
0
              bool isEmpty;
494
0
              rv = IsEmptyNode(pointToInsert.GetContainer(), &isEmpty, true);
495
0
              if (NS_SUCCEEDED(rv) && isEmpty) {
496
0
                if (NS_WARN_IF(!pointToInsert.GetContainer()->
497
0
                                                GetParentNode())) {
498
0
                  // Is it an orphan node?
499
0
                } else {
500
0
                  DeleteNodeWithTransaction(*pointToInsert.GetContainer());
501
0
                  pointToInsert.Set(pointToInsert.GetContainer());
502
0
                }
503
0
              }
504
0
            }
505
0
            EditorDOMPoint insertedPoint =
506
0
              InsertNodeIntoProperAncestorWithTransaction(
507
0
                *firstChild, pointToInsert,
508
0
                SplitAtEdges::eDoNotCreateEmptyContainer);
509
0
            if (NS_WARN_IF(!insertedPoint.IsSet())) {
510
0
              break;
511
0
            }
512
0
513
0
            bDidInsert = true;
514
0
            lastInsertNode = firstChild;
515
0
            pointToInsert = insertedPoint;
516
0
            DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
517
0
            NS_WARNING_ASSERTION(advanced,
518
0
              "Failed to advance offset from inserted point");
519
0
          } else {
520
0
            AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
521
0
            ErrorResult error;
522
0
            curNode->RemoveChild(*firstChild, error);
523
0
            if (NS_WARN_IF(error.Failed())) {
524
0
              error.SuppressException();
525
0
            }
526
0
          }
527
0
        }
528
0
      } else if (parentBlock && HTMLEditUtils::IsPre(parentBlock) &&
529
0
                 HTMLEditUtils::IsPre(curNode)) {
530
0
        // Check for pre's going into pre's.
531
0
        for (nsCOMPtr<nsIContent> firstChild = curNode->GetFirstChild();
532
0
             firstChild;
533
0
             firstChild = curNode->GetFirstChild()) {
534
0
          EditorDOMPoint insertedPoint =
535
0
            InsertNodeIntoProperAncestorWithTransaction(
536
0
              *firstChild, pointToInsert,
537
0
              SplitAtEdges::eDoNotCreateEmptyContainer);
538
0
          if (NS_WARN_IF(!insertedPoint.IsSet())) {
539
0
            break;
540
0
          }
541
0
542
0
          bDidInsert = true;
543
0
          lastInsertNode = firstChild;
544
0
          pointToInsert = insertedPoint;
545
0
          DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
546
0
          NS_WARNING_ASSERTION(advanced,
547
0
            "Failed to advance offset from inserted point");
548
0
        }
549
0
      }
550
0
551
0
      if (!bDidInsert || NS_FAILED(rv)) {
552
0
        // Try to insert.
553
0
        EditorDOMPoint insertedPoint =
554
0
          InsertNodeIntoProperAncestorWithTransaction(
555
0
            *curNode->AsContent(), pointToInsert,
556
0
            SplitAtEdges::eDoNotCreateEmptyContainer);
557
0
        if (insertedPoint.IsSet()) {
558
0
          lastInsertNode = curNode->AsContent();
559
0
          pointToInsert = insertedPoint;
560
0
        }
561
0
562
0
        // Assume failure means no legal parent in the document hierarchy,
563
0
        // try again with the parent of curNode in the paste hierarchy.
564
0
        for (nsCOMPtr<nsIContent> content =
565
0
               curNode->IsContent() ? curNode->AsContent() : nullptr;
566
0
             content && !insertedPoint.IsSet();
567
0
             content = content->GetParent()) {
568
0
          if (NS_WARN_IF(!content->GetParent()) ||
569
0
              NS_WARN_IF(TextEditUtils::IsBody(content->GetParent()))) {
570
0
            continue;
571
0
          }
572
0
          nsCOMPtr<nsINode> oldParent = content->GetParentNode();
573
0
          insertedPoint =
574
0
            InsertNodeIntoProperAncestorWithTransaction(
575
0
              *content->GetParent(), pointToInsert,
576
0
              SplitAtEdges::eDoNotCreateEmptyContainer);
577
0
          if (insertedPoint.IsSet()) {
578
0
            insertedContextParent = oldParent;
579
0
            pointToInsert = insertedPoint;
580
0
          }
581
0
        }
582
0
      }
583
0
      if (lastInsertNode) {
584
0
        pointToInsert.Set(lastInsertNode);
585
0
        DebugOnly<bool> advanced = pointToInsert.AdvanceOffset();
586
0
        NS_WARNING_ASSERTION(advanced,
587
0
          "Failed to advance offset from inserted point");
588
0
      }
589
0
    }
590
0
591
0
    // Now collapse the selection to the end of what we just inserted:
592
0
    if (lastInsertNode) {
593
0
      // set selection to the end of what we just pasted.
594
0
      nsCOMPtr<nsINode> selNode;
595
0
      int32_t selOffset;
596
0
597
0
      // but don't cross tables
598
0
      if (!HTMLEditUtils::IsTable(lastInsertNode)) {
599
0
        selNode = GetLastEditableLeaf(*lastInsertNode);
600
0
        nsINode* highTable = nullptr;
601
0
        for (nsINode* parent = selNode;
602
0
             parent && parent != lastInsertNode;
603
0
             parent = parent->GetParentNode()) {
604
0
          if (HTMLEditUtils::IsTable(parent)) {
605
0
            highTable = parent;
606
0
          }
607
0
        }
608
0
        if (highTable) {
609
0
          selNode = highTable;
610
0
        }
611
0
      }
612
0
      if (!selNode) {
613
0
        selNode = lastInsertNode;
614
0
      }
615
0
      if (EditorBase::IsTextNode(selNode) ||
616
0
          (IsContainer(selNode) && !HTMLEditUtils::IsTable(selNode))) {
617
0
        selOffset = selNode->Length();
618
0
      } else {
619
0
        // We need to find a container for selection.  Look up.
620
0
        EditorRawDOMPoint pointAtContainer(selNode);
621
0
        if (NS_WARN_IF(!pointAtContainer.IsSet())) {
622
0
          return NS_ERROR_FAILURE;
623
0
        }
624
0
        // The container might be null in case a mutation listener removed
625
0
        // the stuff we just inserted from the DOM.
626
0
        selNode = pointAtContainer.GetContainer();
627
0
        // Want to be *after* last leaf node in paste.
628
0
        selOffset = pointAtContainer.Offset() + 1;
629
0
      }
630
0
631
0
      // make sure we don't end up with selection collapsed after an invisible break node
632
0
      WSRunObject wsRunObj(this, selNode, selOffset);
633
0
      WSType visType;
634
0
      wsRunObj.PriorVisibleNode(EditorRawDOMPoint(selNode, selOffset),
635
0
                                &visType);
636
0
      if (visType == WSType::br) {
637
0
        // we are after a break.  Is it visible?  Despite the name,
638
0
        // PriorVisibleNode does not make that determination for breaks.
639
0
        // It also may not return the break in visNode.  We have to pull it
640
0
        // out of the WSRunObject's state.
641
0
        if (!IsVisibleBRElement(wsRunObj.mStartReasonNode)) {
642
0
          // don't leave selection past an invisible break;
643
0
          // reset {selNode,selOffset} to point before break
644
0
          EditorRawDOMPoint atStartReasonNode(wsRunObj.mStartReasonNode);
645
0
          selNode = atStartReasonNode.GetContainer();
646
0
          selOffset = atStartReasonNode.Offset();
647
0
          // we want to be inside any inline style prior to break
648
0
          WSRunObject wsRunObj(this, selNode, selOffset);
649
0
          nsCOMPtr<nsINode> visNode;
650
0
          int32_t outVisOffset;
651
0
          wsRunObj.PriorVisibleNode(EditorRawDOMPoint(selNode, selOffset),
652
0
                                    address_of(visNode),
653
0
                                    &outVisOffset, &visType);
654
0
          if (visType == WSType::text || visType == WSType::normalWS) {
655
0
            selNode = visNode;
656
0
            selOffset = outVisOffset;  // PriorVisibleNode already set offset to _after_ the text or ws
657
0
          } else if (visType == WSType::special) {
658
0
            // prior visible thing is an image or some other non-text thingy.
659
0
            // We want to be right after it.
660
0
            atStartReasonNode.Set(wsRunObj.mStartReasonNode);
661
0
            selNode = atStartReasonNode.GetContainer();
662
0
            selOffset = atStartReasonNode.Offset() + 1;
663
0
          }
664
0
        }
665
0
      }
666
0
      selection->Collapse(selNode, selOffset);
667
0
668
0
      // if we just pasted a link, discontinue link style
669
0
      nsCOMPtr<nsIContent> linkContent;
670
0
      if (!bStartedInLink &&
671
0
          (linkContent = GetLinkElement(selNode))) {
672
0
        // so, if we just pasted a link, I split it.  Why do that instead of just
673
0
        // nudging selection point beyond it?  Because it might have ended in a BR
674
0
        // that is not visible.  If so, the code above just placed selection
675
0
        // inside that.  So I split it instead.
676
0
        SplitNodeResult splitLinkResult =
677
0
          SplitNodeDeepWithTransaction(
678
0
            *linkContent, EditorRawDOMPoint(selNode, selOffset),
679
0
            SplitAtEdges::eDoNotCreateEmptyContainer);
680
0
        NS_WARNING_ASSERTION(splitLinkResult.Succeeded(),
681
0
          "Failed to split the link");
682
0
        if (splitLinkResult.GetPreviousNode()) {
683
0
          EditorRawDOMPoint afterLeftLink(splitLinkResult.GetPreviousNode());
684
0
          if (afterLeftLink.AdvanceOffset()) {
685
0
            selection->Collapse(afterLeftLink);
686
0
          }
687
0
        }
688
0
      }
689
0
    }
690
0
  }
691
0
692
0
  return rules->DidDoAction(selection, subActionInfo, rv);
693
0
}
694
695
// static
696
Element*
697
HTMLEditor::GetLinkElement(nsINode* aNode)
698
0
{
699
0
  if (NS_WARN_IF(!aNode)) {
700
0
    return nullptr;
701
0
  }
702
0
  nsINode* node = aNode;
703
0
  while (node) {
704
0
    if (HTMLEditUtils::IsLink(node)) {
705
0
      return node->AsElement();
706
0
    }
707
0
    node = node->GetParentNode();
708
0
  }
709
0
  return nullptr;
710
0
}
711
712
nsresult
713
HTMLEditor::StripFormattingNodes(nsIContent& aNode,
714
                                 bool aListOnly)
715
0
{
716
0
  if (aNode.TextIsOnlyWhitespace()) {
717
0
    nsCOMPtr<nsINode> parent = aNode.GetParentNode();
718
0
    if (parent) {
719
0
      if (!aListOnly || HTMLEditUtils::IsList(parent)) {
720
0
        ErrorResult rv;
721
0
        parent->RemoveChild(aNode, rv);
722
0
        return rv.StealNSResult();
723
0
      }
724
0
      return NS_OK;
725
0
    }
726
0
  }
727
0
728
0
  if (!aNode.IsHTMLElement(nsGkAtoms::pre)) {
729
0
    nsCOMPtr<nsIContent> child = aNode.GetLastChild();
730
0
    while (child) {
731
0
      nsCOMPtr<nsIContent> previous = child->GetPreviousSibling();
732
0
      nsresult rv = StripFormattingNodes(*child, aListOnly);
733
0
      NS_ENSURE_SUCCESS(rv, rv);
734
0
      child = previous.forget();
735
0
    }
736
0
  }
737
0
  return NS_OK;
738
0
}
739
740
nsresult
741
HTMLEditor::PrepareTransferable(nsITransferable** aTransferable)
742
0
{
743
0
  return NS_OK;
744
0
}
745
746
nsresult
747
HTMLEditor::PrepareHTMLTransferable(nsITransferable** aTransferable)
748
0
{
749
0
  // Create generic Transferable for getting the data
750
0
  nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", aTransferable);
751
0
  NS_ENSURE_SUCCESS(rv, rv);
752
0
753
0
  // Get the nsITransferable interface for getting the data from the clipboard
754
0
  if (aTransferable) {
755
0
    nsCOMPtr<nsIDocument> destdoc = GetDocument();
756
0
    nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
757
0
    (*aTransferable)->Init(loadContext);
758
0
759
0
    // Create the desired DataFlavor for the type of data
760
0
    // we want to get out of the transferable
761
0
    // This should only happen in html editors, not plaintext
762
0
    if (!IsPlaintextEditor()) {
763
0
      (*aTransferable)->AddDataFlavor(kNativeHTMLMime);
764
0
      (*aTransferable)->AddDataFlavor(kHTMLMime);
765
0
      (*aTransferable)->AddDataFlavor(kFileMime);
766
0
767
0
      switch (Preferences::GetInt("clipboard.paste_image_type", 1)) {
768
0
        case 0:  // prefer JPEG over PNG over GIF encoding
769
0
          (*aTransferable)->AddDataFlavor(kJPEGImageMime);
770
0
          (*aTransferable)->AddDataFlavor(kJPGImageMime);
771
0
          (*aTransferable)->AddDataFlavor(kPNGImageMime);
772
0
          (*aTransferable)->AddDataFlavor(kGIFImageMime);
773
0
          break;
774
0
        case 1:  // prefer PNG over JPEG over GIF encoding (default)
775
0
        default:
776
0
          (*aTransferable)->AddDataFlavor(kPNGImageMime);
777
0
          (*aTransferable)->AddDataFlavor(kJPEGImageMime);
778
0
          (*aTransferable)->AddDataFlavor(kJPGImageMime);
779
0
          (*aTransferable)->AddDataFlavor(kGIFImageMime);
780
0
          break;
781
0
        case 2:  // prefer GIF over JPEG over PNG encoding
782
0
          (*aTransferable)->AddDataFlavor(kGIFImageMime);
783
0
          (*aTransferable)->AddDataFlavor(kJPEGImageMime);
784
0
          (*aTransferable)->AddDataFlavor(kJPGImageMime);
785
0
          (*aTransferable)->AddDataFlavor(kPNGImageMime);
786
0
          break;
787
0
      }
788
0
    }
789
0
    (*aTransferable)->AddDataFlavor(kUnicodeMime);
790
0
    (*aTransferable)->AddDataFlavor(kMozTextInternal);
791
0
  }
792
0
793
0
  return NS_OK;
794
0
}
795
796
bool
797
FindIntegerAfterString(const char* aLeadingString,
798
                       nsCString& aCStr,
799
                       int32_t& foundNumber)
800
0
{
801
0
  // first obtain offsets from cfhtml str
802
0
  int32_t numFront = aCStr.Find(aLeadingString);
803
0
  if (numFront == -1) {
804
0
    return false;
805
0
  }
806
0
  numFront += strlen(aLeadingString);
807
0
808
0
  int32_t numBack = aCStr.FindCharInSet(CRLF, numFront);
809
0
  if (numBack == -1) {
810
0
    return false;
811
0
  }
812
0
813
0
  nsAutoCString numStr(Substring(aCStr, numFront, numBack-numFront));
814
0
  nsresult errorCode;
815
0
  foundNumber = numStr.ToInteger(&errorCode);
816
0
  return true;
817
0
}
818
819
nsresult
820
RemoveFragComments(nsCString& aStr)
821
0
{
822
0
  // remove the StartFragment/EndFragment comments from the str, if present
823
0
  int32_t startCommentIndx = aStr.Find("<!--StartFragment");
824
0
  if (startCommentIndx >= 0) {
825
0
    int32_t startCommentEnd = aStr.Find("-->", false, startCommentIndx);
826
0
    if (startCommentEnd > startCommentIndx) {
827
0
      aStr.Cut(startCommentIndx, (startCommentEnd + 3) - startCommentIndx);
828
0
    }
829
0
  }
830
0
  int32_t endCommentIndx = aStr.Find("<!--EndFragment");
831
0
  if (endCommentIndx >= 0) {
832
0
    int32_t endCommentEnd = aStr.Find("-->", false, endCommentIndx);
833
0
    if (endCommentEnd > endCommentIndx) {
834
0
      aStr.Cut(endCommentIndx, (endCommentEnd + 3) - endCommentIndx);
835
0
    }
836
0
  }
837
0
  return NS_OK;
838
0
}
839
840
nsresult
841
HTMLEditor::ParseCFHTML(nsCString& aCfhtml,
842
                        char16_t** aStuffToPaste,
843
                        char16_t** aCfcontext)
844
0
{
845
0
  // First obtain offsets from cfhtml str.
846
0
  int32_t startHTML, endHTML, startFragment, endFragment;
847
0
  if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) ||
848
0
      startHTML < -1) {
849
0
    return NS_ERROR_FAILURE;
850
0
  }
851
0
  if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) ||
852
0
      endHTML < -1) {
853
0
    return NS_ERROR_FAILURE;
854
0
  }
855
0
  if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) ||
856
0
      startFragment < 0) {
857
0
    return NS_ERROR_FAILURE;
858
0
  }
859
0
  if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) ||
860
0
      startFragment < 0) {
861
0
    return NS_ERROR_FAILURE;
862
0
  }
863
0
864
0
  // The StartHTML and EndHTML markers are allowed to be -1 to include everything.
865
0
  //   See Reference: MSDN doc entitled "HTML Clipboard Format"
866
0
  //   http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
867
0
  if (startHTML == -1) {
868
0
    startHTML = aCfhtml.Find("<!--StartFragment-->");
869
0
    if (startHTML == -1) {
870
0
      return NS_OK;
871
0
    }
872
0
  }
873
0
  if (endHTML == -1) {
874
0
    const char endFragmentMarker[] = "<!--EndFragment-->";
875
0
    endHTML = aCfhtml.Find(endFragmentMarker);
876
0
    if (endHTML == -1) {
877
0
      return NS_OK;
878
0
    }
879
0
    endHTML += ArrayLength(endFragmentMarker) - 1;
880
0
  }
881
0
882
0
  // create context string
883
0
  nsAutoCString contextUTF8(Substring(aCfhtml, startHTML, startFragment - startHTML) +
884
0
                            NS_LITERAL_CSTRING("<!--" kInsertCookie "-->") +
885
0
                            Substring(aCfhtml, endFragment, endHTML - endFragment));
886
0
887
0
  // validate startFragment
888
0
  // make sure it's not in the middle of a HTML tag
889
0
  // see bug #228879 for more details
890
0
  int32_t curPos = startFragment;
891
0
  while (curPos > startHTML) {
892
0
    if (aCfhtml[curPos] == '>') {
893
0
      // working backwards, the first thing we see is the end of a tag
894
0
      // so StartFragment is good, so do nothing.
895
0
      break;
896
0
    }
897
0
    if (aCfhtml[curPos] == '<') {
898
0
      // if we are at the start, then we want to see the '<'
899
0
      if (curPos != startFragment) {
900
0
        // working backwards, the first thing we see is the start of a tag
901
0
        // so StartFragment is bad, so we need to update it.
902
0
        NS_ERROR("StartFragment byte count in the clipboard looks bad, see bug #228879");
903
0
        startFragment = curPos - 1;
904
0
      }
905
0
      break;
906
0
    }
907
0
    curPos--;
908
0
  }
909
0
910
0
  // create fragment string
911
0
  nsAutoCString fragmentUTF8(Substring(aCfhtml, startFragment, endFragment-startFragment));
912
0
913
0
  // remove the StartFragment/EndFragment comments from the fragment, if present
914
0
  RemoveFragComments(fragmentUTF8);
915
0
916
0
  // remove the StartFragment/EndFragment comments from the context, if present
917
0
  RemoveFragComments(contextUTF8);
918
0
919
0
  // convert both strings to usc2
920
0
  const nsString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8);
921
0
  const nsString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8);
922
0
923
0
  // translate platform linebreaks for fragment
924
0
  int32_t oldLengthInChars = fragUcs2Str.Length() + 1;  // +1 to include null terminator
925
0
  int32_t newLengthInChars = 0;
926
0
  *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(fragUcs2Str.get(),
927
0
                                                           nsLinebreakConverter::eLinebreakAny,
928
0
                                                           nsLinebreakConverter::eLinebreakContent,
929
0
                                                           oldLengthInChars, &newLengthInChars);
930
0
  NS_ENSURE_TRUE(*aStuffToPaste, NS_ERROR_FAILURE);
931
0
932
0
  // translate platform linebreaks for context
933
0
  oldLengthInChars = cntxtUcs2Str.Length() + 1;  // +1 to include null terminator
934
0
  newLengthInChars = 0;
935
0
  *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(cntxtUcs2Str.get(),
936
0
                                                           nsLinebreakConverter::eLinebreakAny,
937
0
                                                           nsLinebreakConverter::eLinebreakContent,
938
0
                                                           oldLengthInChars, &newLengthInChars);
939
0
  // it's ok for context to be empty.  frag might be whole doc and contain all its context.
940
0
941
0
  // we're done!
942
0
  return NS_OK;
943
0
}
944
945
static nsresult
946
ImgFromData(const nsACString& aType, const nsACString& aData, nsString& aOutput)
947
0
{
948
0
  nsAutoCString data64;
949
0
  nsresult rv = Base64Encode(aData, data64);
950
0
  NS_ENSURE_SUCCESS(rv, rv);
951
0
952
0
  aOutput.AssignLiteral("<IMG src=\"data:");
953
0
  AppendUTF8toUTF16(aType, aOutput);
954
0
  aOutput.AppendLiteral(";base64,");
955
0
  if (!AppendASCIItoUTF16(data64, aOutput, fallible_t())) {
956
0
    return NS_ERROR_OUT_OF_MEMORY;
957
0
  }
958
0
  aOutput.AppendLiteral("\" alt=\"\" >");
959
0
  return NS_OK;
960
0
}
961
962
NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor::BlobReader)
963
964
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLEditor::BlobReader)
965
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
966
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHTMLEditor)
967
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceDoc)
968
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDestinationNode)
969
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
970
971
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLEditor::BlobReader)
972
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
973
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHTMLEditor)
974
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceDoc)
975
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDestinationNode)
976
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
977
978
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLEditor::BlobReader, AddRef)
979
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLEditor::BlobReader, Release)
980
981
HTMLEditor::BlobReader::BlobReader(BlobImpl* aBlob,
982
                                   HTMLEditor* aHTMLEditor,
983
                                   bool aIsSafe,
984
                                   nsIDocument* aSourceDoc,
985
                                   nsINode* aDestinationNode,
986
                                   int32_t aDestOffset,
987
                                   bool aDoDeleteSelection)
988
  : mBlob(aBlob)
989
  , mHTMLEditor(aHTMLEditor)
990
  , mIsSafe(aIsSafe)
991
  , mSourceDoc(aSourceDoc)
992
  , mDestinationNode(aDestinationNode)
993
  , mDestOffset(aDestOffset)
994
  , mDoDeleteSelection(aDoDeleteSelection)
995
0
{
996
0
  MOZ_ASSERT(mBlob);
997
0
  MOZ_ASSERT(mHTMLEditor);
998
0
  MOZ_ASSERT(mDestinationNode);
999
0
}
1000
1001
nsresult
1002
HTMLEditor::BlobReader::OnResult(const nsACString& aResult)
1003
0
{
1004
0
  nsString blobType;
1005
0
  mBlob->GetType(blobType);
1006
0
1007
0
  NS_ConvertUTF16toUTF8 type(blobType);
1008
0
  nsAutoString stuffToPaste;
1009
0
  nsresult rv = ImgFromData(type, aResult, stuffToPaste);
1010
0
  NS_ENSURE_SUCCESS(rv, rv);
1011
0
1012
0
  AutoPlaceholderBatch beginBatching(mHTMLEditor);
1013
0
  rv = mHTMLEditor->DoInsertHTMLWithContext(stuffToPaste, EmptyString(),
1014
0
                                            EmptyString(),
1015
0
                                            NS_LITERAL_STRING(kFileMime),
1016
0
                                            mSourceDoc,
1017
0
                                            mDestinationNode, mDestOffset,
1018
0
                                            mDoDeleteSelection,
1019
0
                                            mIsSafe, false);
1020
0
  return rv;
1021
0
}
1022
1023
nsresult
1024
HTMLEditor::BlobReader::OnError(const nsAString& aError)
1025
0
{
1026
0
  const nsPromiseFlatString& flat = PromiseFlatString(aError);
1027
0
  const char16_t* error = flat.get();
1028
0
  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1029
0
                                  NS_LITERAL_CSTRING("Editor"),
1030
0
                                  mDestinationNode->OwnerDoc(),
1031
0
                                  nsContentUtils::eDOM_PROPERTIES,
1032
0
                                  "EditorFileDropFailed",
1033
0
                                  &error, 1);
1034
0
  return NS_OK;
1035
0
}
1036
1037
class SlurpBlobEventListener final : public nsIDOMEventListener
1038
{
1039
public:
1040
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
1041
  NS_DECL_CYCLE_COLLECTION_CLASS(SlurpBlobEventListener)
1042
1043
  explicit SlurpBlobEventListener(HTMLEditor::BlobReader* aListener)
1044
    : mListener(aListener)
1045
0
  { }
1046
1047
  NS_IMETHOD HandleEvent(Event* aEvent) override;
1048
1049
private:
1050
0
  ~SlurpBlobEventListener() = default;
1051
1052
  RefPtr<HTMLEditor::BlobReader> mListener;
1053
};
1054
1055
NS_IMPL_CYCLE_COLLECTION(SlurpBlobEventListener, mListener)
1056
1057
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SlurpBlobEventListener)
1058
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
1059
0
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
1060
0
NS_INTERFACE_MAP_END
1061
1062
NS_IMPL_CYCLE_COLLECTING_ADDREF(SlurpBlobEventListener)
1063
NS_IMPL_CYCLE_COLLECTING_RELEASE(SlurpBlobEventListener)
1064
1065
NS_IMETHODIMP
1066
SlurpBlobEventListener::HandleEvent(Event* aEvent)
1067
0
{
1068
0
  EventTarget* target = aEvent->GetTarget();
1069
0
  if (!target || !mListener) {
1070
0
    return NS_OK;
1071
0
  }
1072
0
1073
0
  RefPtr<FileReader> reader = do_QueryObject(target);
1074
0
  if (!reader) {
1075
0
    return NS_OK;
1076
0
  }
1077
0
1078
0
  EventMessage message = aEvent->WidgetEventPtr()->mMessage;
1079
0
1080
0
  if (message == eLoad) {
1081
0
    MOZ_ASSERT(reader->DataFormat() == FileReader::FILE_AS_BINARY);
1082
0
1083
0
    // The original data has been converted from Latin1 to UTF-16, this just
1084
0
    // undoes that conversion.
1085
0
    mListener->OnResult(NS_LossyConvertUTF16toASCII(reader->Result()));
1086
0
  } else if (message == eLoadError) {
1087
0
    nsAutoString errorMessage;
1088
0
    reader->GetError()->GetErrorMessage(errorMessage);
1089
0
    mListener->OnError(errorMessage);
1090
0
  }
1091
0
1092
0
  return NS_OK;
1093
0
}
1094
1095
// static
1096
nsresult
1097
HTMLEditor::SlurpBlob(Blob* aBlob, nsPIDOMWindowOuter* aWindow,
1098
                      BlobReader* aBlobReader)
1099
0
{
1100
0
  MOZ_ASSERT(aBlob);
1101
0
  MOZ_ASSERT(aWindow);
1102
0
  MOZ_ASSERT(aBlobReader);
1103
0
1104
0
  nsCOMPtr<nsPIDOMWindowInner> inner = aWindow->GetCurrentInnerWindow();
1105
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(inner);
1106
0
  RefPtr<WeakWorkerRef> workerRef;
1107
0
  RefPtr<FileReader> reader = new FileReader(global, workerRef);
1108
0
1109
0
  RefPtr<SlurpBlobEventListener> eventListener =
1110
0
    new SlurpBlobEventListener(aBlobReader);
1111
0
1112
0
  nsresult rv = reader->AddEventListener(NS_LITERAL_STRING("load"),
1113
0
                                         eventListener, false);
1114
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1115
0
    return rv;
1116
0
  }
1117
0
1118
0
  rv = reader->AddEventListener(NS_LITERAL_STRING("error"),
1119
0
                                eventListener, false);
1120
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1121
0
    return rv;
1122
0
  }
1123
0
1124
0
  ErrorResult result;
1125
0
  reader->ReadAsBinaryString(*aBlob, result);
1126
0
  if (result.Failed()) {
1127
0
    return result.StealNSResult();
1128
0
  }
1129
0
  return NS_OK;
1130
0
}
1131
1132
nsresult
1133
HTMLEditor::InsertObject(const nsACString& aType,
1134
                         nsISupports* aObject,
1135
                         bool aIsSafe,
1136
                         nsIDocument* aSourceDoc,
1137
                         nsINode* aDestinationNode,
1138
                         int32_t aDestOffset,
1139
                         bool aDoDeleteSelection)
1140
0
{
1141
0
  nsresult rv;
1142
0
1143
0
  if (nsCOMPtr<BlobImpl> blob = do_QueryInterface(aObject)) {
1144
0
    RefPtr<BlobReader> br = new BlobReader(blob, this, aIsSafe, aSourceDoc,
1145
0
                                           aDestinationNode, aDestOffset,
1146
0
                                           aDoDeleteSelection);
1147
0
    nsCOMPtr<nsINode> node = aDestinationNode;
1148
0
    MOZ_ASSERT(node);
1149
0
1150
0
    RefPtr<Blob> domBlob = Blob::Create(node->GetOwnerGlobal(), blob);
1151
0
    NS_ENSURE_TRUE(domBlob, NS_ERROR_FAILURE);
1152
0
1153
0
    return SlurpBlob(domBlob, node->OwnerDoc()->GetWindow(), br);
1154
0
  }
1155
0
1156
0
  nsAutoCString type(aType);
1157
0
1158
0
  // Check to see if we can insert an image file
1159
0
  bool insertAsImage = false;
1160
0
  nsCOMPtr<nsIFile> fileObj;
1161
0
  if (type.EqualsLiteral(kFileMime)) {
1162
0
    fileObj = do_QueryInterface(aObject);
1163
0
    if (fileObj) {
1164
0
      // Accept any image type fed to us
1165
0
      if (nsContentUtils::IsFileImage(fileObj, type)) {
1166
0
        insertAsImage = true;
1167
0
      } else {
1168
0
        // Reset type.
1169
0
        type.AssignLiteral(kFileMime);
1170
0
      }
1171
0
    }
1172
0
  }
1173
0
1174
0
  if (type.EqualsLiteral(kJPEGImageMime) ||
1175
0
      type.EqualsLiteral(kJPGImageMime) ||
1176
0
      type.EqualsLiteral(kPNGImageMime) ||
1177
0
      type.EqualsLiteral(kGIFImageMime) ||
1178
0
      insertAsImage) {
1179
0
    nsCString imageData;
1180
0
    if (insertAsImage) {
1181
0
      rv = nsContentUtils::SlurpFileToString(fileObj, imageData);
1182
0
      NS_ENSURE_SUCCESS(rv, rv);
1183
0
    } else {
1184
0
      nsCOMPtr<nsIInputStream> imageStream = do_QueryInterface(aObject);
1185
0
      NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE);
1186
0
1187
0
      rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
1188
0
      NS_ENSURE_SUCCESS(rv, rv);
1189
0
1190
0
      rv = imageStream->Close();
1191
0
      NS_ENSURE_SUCCESS(rv, rv);
1192
0
    }
1193
0
1194
0
    nsAutoString stuffToPaste;
1195
0
    rv = ImgFromData(type, imageData, stuffToPaste);
1196
0
    NS_ENSURE_SUCCESS(rv, rv);
1197
0
1198
0
    AutoPlaceholderBatch beginBatching(this);
1199
0
    rv = DoInsertHTMLWithContext(stuffToPaste, EmptyString(), EmptyString(),
1200
0
                                 NS_LITERAL_STRING(kFileMime),
1201
0
                                 aSourceDoc,
1202
0
                                 aDestinationNode, aDestOffset,
1203
0
                                 aDoDeleteSelection,
1204
0
                                 aIsSafe, false);
1205
0
  }
1206
0
1207
0
  return NS_OK;
1208
0
}
1209
1210
nsresult
1211
HTMLEditor::InsertFromTransferable(nsITransferable* transferable,
1212
                                   nsIDocument* aSourceDoc,
1213
                                   const nsAString& aContextStr,
1214
                                   const nsAString& aInfoStr,
1215
                                   bool havePrivateHTMLFlavor,
1216
                                   bool aDoDeleteSelection)
1217
0
{
1218
0
  nsresult rv = NS_OK;
1219
0
  nsAutoCString bestFlavor;
1220
0
  nsCOMPtr<nsISupports> genericDataObj;
1221
0
  uint32_t len = 0;
1222
0
  if (NS_SUCCEEDED(
1223
0
        transferable->GetAnyTransferData(bestFlavor,
1224
0
                                         getter_AddRefs(genericDataObj),
1225
0
                                         &len))) {
1226
0
    AutoTransactionsConserveSelection dontChangeMySelection(*this);
1227
0
    nsAutoString flavor;
1228
0
    CopyASCIItoUTF16(bestFlavor, flavor);
1229
0
    nsAutoString stuffToPaste;
1230
0
    bool isSafe = IsSafeToInsertData(aSourceDoc);
1231
0
1232
0
    if (bestFlavor.EqualsLiteral(kFileMime) ||
1233
0
        bestFlavor.EqualsLiteral(kJPEGImageMime) ||
1234
0
        bestFlavor.EqualsLiteral(kJPGImageMime) ||
1235
0
        bestFlavor.EqualsLiteral(kPNGImageMime) ||
1236
0
        bestFlavor.EqualsLiteral(kGIFImageMime)) {
1237
0
      rv = InsertObject(bestFlavor, genericDataObj, isSafe,
1238
0
                        aSourceDoc, nullptr, 0, aDoDeleteSelection);
1239
0
    } else if (bestFlavor.EqualsLiteral(kNativeHTMLMime)) {
1240
0
      // note cf_html uses utf8, hence use length = len, not len/2 as in flavors below
1241
0
      nsCOMPtr<nsISupportsCString> textDataObj = do_QueryInterface(genericDataObj);
1242
0
      if (textDataObj && len > 0) {
1243
0
        nsAutoCString cfhtml;
1244
0
        textDataObj->GetData(cfhtml);
1245
0
        NS_ASSERTION(cfhtml.Length() <= (len), "Invalid length!");
1246
0
        nsString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
1247
0
1248
0
        rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
1249
0
        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
1250
0
          AutoPlaceholderBatch beginBatching(this);
1251
0
          // If we have our private HTML flavor, we will only use the fragment
1252
0
          // from the CF_HTML. The rest comes from the clipboard.
1253
0
          if (havePrivateHTMLFlavor) {
1254
0
            rv = DoInsertHTMLWithContext(cffragment,
1255
0
                                         aContextStr, aInfoStr, flavor,
1256
0
                                         aSourceDoc,
1257
0
                                         nullptr, 0,
1258
0
                                         aDoDeleteSelection,
1259
0
                                         isSafe);
1260
0
          } else {
1261
0
            rv = DoInsertHTMLWithContext(cffragment,
1262
0
                                         cfcontext, cfselection, flavor,
1263
0
                                         aSourceDoc,
1264
0
                                         nullptr, 0,
1265
0
                                         aDoDeleteSelection,
1266
0
                                         isSafe);
1267
0
1268
0
          }
1269
0
        } else {
1270
0
          // In some platforms (like Linux), the clipboard might return data
1271
0
          // requested for unknown flavors (for example:
1272
0
          // application/x-moz-nativehtml).  In this case, treat the data
1273
0
          // to be pasted as mere HTML to get the best chance of pasting it
1274
0
          // correctly.
1275
0
          bestFlavor.AssignLiteral(kHTMLMime);
1276
0
          // Fall through the next case
1277
0
        }
1278
0
      }
1279
0
    }
1280
0
    if (bestFlavor.EqualsLiteral(kHTMLMime) ||
1281
0
        bestFlavor.EqualsLiteral(kUnicodeMime) ||
1282
0
        bestFlavor.EqualsLiteral(kMozTextInternal)) {
1283
0
      nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj);
1284
0
      if (textDataObj && len > 0) {
1285
0
        nsAutoString text;
1286
0
        textDataObj->GetData(text);
1287
0
        NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
1288
0
        stuffToPaste.Assign(text.get(), len / 2);
1289
0
      } else {
1290
0
        nsCOMPtr<nsISupportsCString> textDataObj(do_QueryInterface(genericDataObj));
1291
0
        if (textDataObj && len > 0) {
1292
0
          nsAutoCString text;
1293
0
          textDataObj->GetData(text);
1294
0
          NS_ASSERTION(text.Length() <= len, "Invalid length!");
1295
0
          stuffToPaste.Assign(NS_ConvertUTF8toUTF16(Substring(text, 0, len)));
1296
0
        }
1297
0
      }
1298
0
1299
0
      if (!stuffToPaste.IsEmpty()) {
1300
0
        AutoPlaceholderBatch beginBatching(this);
1301
0
        if (bestFlavor.EqualsLiteral(kHTMLMime)) {
1302
0
          rv = DoInsertHTMLWithContext(stuffToPaste,
1303
0
                                       aContextStr, aInfoStr, flavor,
1304
0
                                       aSourceDoc,
1305
0
                                       nullptr, 0,
1306
0
                                       aDoDeleteSelection,
1307
0
                                       isSafe);
1308
0
        } else {
1309
0
          rv = InsertTextAt(stuffToPaste, nullptr, 0, aDoDeleteSelection);
1310
0
        }
1311
0
      }
1312
0
    }
1313
0
  }
1314
0
1315
0
  // Try to scroll the selection into view if the paste succeeded
1316
0
  if (NS_SUCCEEDED(rv)) {
1317
0
    ScrollSelectionIntoView(false);
1318
0
  }
1319
0
  return rv;
1320
0
}
1321
1322
static void
1323
GetStringFromDataTransfer(DataTransfer* aDataTransfer,
1324
                          const nsAString& aType,
1325
                          int32_t aIndex,
1326
                          nsAString& aOutputString)
1327
0
{
1328
0
  nsCOMPtr<nsIVariant> variant;
1329
0
  aDataTransfer->GetDataAtNoSecurityCheck(aType, aIndex, getter_AddRefs(variant));
1330
0
  if (variant) {
1331
0
    variant->GetAsAString(aOutputString);
1332
0
  }
1333
0
}
1334
1335
nsresult
1336
HTMLEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
1337
                                   int32_t aIndex,
1338
                                   nsIDocument* aSourceDoc,
1339
                                   nsINode* aDestinationNode,
1340
                                   int32_t aDestOffset,
1341
                                   bool aDoDeleteSelection)
1342
0
{
1343
0
  ErrorResult rv;
1344
0
  RefPtr<DOMStringList> types =
1345
0
    aDataTransfer->MozTypesAt(aIndex, CallerType::System, rv);
1346
0
  if (rv.Failed()) {
1347
0
    return rv.StealNSResult();
1348
0
  }
1349
0
1350
0
  bool hasPrivateHTMLFlavor = types->Contains(NS_LITERAL_STRING(kHTMLContext));
1351
0
1352
0
  bool isText = IsPlaintextEditor();
1353
0
  bool isSafe = IsSafeToInsertData(aSourceDoc);
1354
0
1355
0
  uint32_t length = types->Length();
1356
0
  for (uint32_t t = 0; t < length; t++) {
1357
0
    nsAutoString type;
1358
0
    types->Item(t, type);
1359
0
1360
0
    if (!isText) {
1361
0
      if (type.EqualsLiteral(kFileMime) ||
1362
0
          type.EqualsLiteral(kJPEGImageMime) ||
1363
0
          type.EqualsLiteral(kJPGImageMime) ||
1364
0
          type.EqualsLiteral(kPNGImageMime) ||
1365
0
          type.EqualsLiteral(kGIFImageMime)) {
1366
0
        nsCOMPtr<nsIVariant> variant;
1367
0
        aDataTransfer->GetDataAtNoSecurityCheck(type, aIndex, getter_AddRefs(variant));
1368
0
        if (variant) {
1369
0
          nsCOMPtr<nsISupports> object;
1370
0
          variant->GetAsISupports(getter_AddRefs(object));
1371
0
          return InsertObject(NS_ConvertUTF16toUTF8(type), object, isSafe,
1372
0
                              aSourceDoc, aDestinationNode, aDestOffset,
1373
0
                              aDoDeleteSelection);
1374
0
        }
1375
0
      } else if (type.EqualsLiteral(kNativeHTMLMime)) {
1376
0
        // Windows only clipboard parsing.
1377
0
        nsAutoString text;
1378
0
        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
1379
0
        NS_ConvertUTF16toUTF8 cfhtml(text);
1380
0
1381
0
        nsString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
1382
0
1383
0
        nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
1384
0
        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
1385
0
          AutoPlaceholderBatch beginBatching(this);
1386
0
1387
0
          if (hasPrivateHTMLFlavor) {
1388
0
            // If we have our private HTML flavor, we will only use the fragment
1389
0
            // from the CF_HTML. The rest comes from the clipboard.
1390
0
            nsAutoString contextString, infoString;
1391
0
            GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), aIndex, contextString);
1392
0
            GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), aIndex, infoString);
1393
0
            return DoInsertHTMLWithContext(cffragment,
1394
0
                                           contextString, infoString, type,
1395
0
                                           aSourceDoc,
1396
0
                                           aDestinationNode, aDestOffset,
1397
0
                                           aDoDeleteSelection,
1398
0
                                           isSafe);
1399
0
          } else {
1400
0
            return DoInsertHTMLWithContext(cffragment,
1401
0
                                           cfcontext, cfselection, type,
1402
0
                                           aSourceDoc,
1403
0
                                           aDestinationNode, aDestOffset,
1404
0
                                           aDoDeleteSelection,
1405
0
                                           isSafe);
1406
0
          }
1407
0
        }
1408
0
      } else if (type.EqualsLiteral(kHTMLMime)) {
1409
0
        nsAutoString text, contextString, infoString;
1410
0
        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
1411
0
        GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), aIndex, contextString);
1412
0
        GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), aIndex, infoString);
1413
0
1414
0
        AutoPlaceholderBatch beginBatching(this);
1415
0
        if (type.EqualsLiteral(kHTMLMime)) {
1416
0
          return DoInsertHTMLWithContext(text,
1417
0
                                         contextString, infoString, type,
1418
0
                                         aSourceDoc,
1419
0
                                         aDestinationNode, aDestOffset,
1420
0
                                         aDoDeleteSelection,
1421
0
                                         isSafe);
1422
0
        }
1423
0
      }
1424
0
    }
1425
0
1426
0
    if (type.EqualsLiteral(kTextMime) ||
1427
0
        type.EqualsLiteral(kMozTextInternal)) {
1428
0
      nsAutoString text;
1429
0
      GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
1430
0
1431
0
      AutoPlaceholderBatch beginBatching(this);
1432
0
      return InsertTextAt(text, aDestinationNode, aDestOffset, aDoDeleteSelection);
1433
0
    }
1434
0
  }
1435
0
1436
0
  return NS_OK;
1437
0
}
1438
1439
bool
1440
HTMLEditor::HavePrivateHTMLFlavor(nsIClipboard* aClipboard)
1441
0
{
1442
0
  // check the clipboard for our special kHTMLContext flavor.  If that is there, we know
1443
0
  // we have our own internal html format on clipboard.
1444
0
1445
0
  NS_ENSURE_TRUE(aClipboard, false);
1446
0
  bool bHavePrivateHTMLFlavor = false;
1447
0
1448
0
  const char* flavArray[] = { kHTMLContext };
1449
0
1450
0
  if (NS_SUCCEEDED(
1451
0
        aClipboard->HasDataMatchingFlavors(flavArray,
1452
0
                                           ArrayLength(flavArray),
1453
0
                                           nsIClipboard::kGlobalClipboard,
1454
0
                                           &bHavePrivateHTMLFlavor))) {
1455
0
    return bHavePrivateHTMLFlavor;
1456
0
  }
1457
0
1458
0
  return false;
1459
0
}
1460
1461
nsresult
1462
HTMLEditor::PasteInternal(int32_t aClipboardType)
1463
0
{
1464
0
  if (!FireClipboardEvent(ePaste, aClipboardType)) {
1465
0
    return NS_OK;
1466
0
  }
1467
0
1468
0
  // Get Clipboard Service
1469
0
  nsresult rv;
1470
0
  nsCOMPtr<nsIClipboard> clipboard =
1471
0
    do_GetService("@mozilla.org/widget/clipboard;1", &rv);
1472
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1473
0
    return rv;
1474
0
  }
1475
0
1476
0
  // Get the nsITransferable interface for getting the data from the clipboard
1477
0
  nsCOMPtr<nsITransferable> transferable;
1478
0
  rv = PrepareHTMLTransferable(getter_AddRefs(transferable));
1479
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1480
0
    return rv;
1481
0
  }
1482
0
  if (NS_WARN_IF(!transferable)) {
1483
0
    return NS_ERROR_FAILURE;
1484
0
  }
1485
0
  // Get the Data from the clipboard
1486
0
  rv = clipboard->GetData(transferable, aClipboardType);
1487
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1488
0
    return rv;
1489
0
  }
1490
0
1491
0
  // XXX Why don't you check this first?
1492
0
  if (!IsModifiable()) {
1493
0
    return NS_OK;
1494
0
  }
1495
0
1496
0
  // also get additional html copy hints, if present
1497
0
  nsAutoString contextStr, infoStr;
1498
0
1499
0
  // If we have our internal html flavor on the clipboard, there is special
1500
0
  // context to use instead of cfhtml context.
1501
0
  bool bHavePrivateHTMLFlavor = HavePrivateHTMLFlavor(clipboard);
1502
0
  if (bHavePrivateHTMLFlavor) {
1503
0
    nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
1504
0
    uint32_t contextLen, infoLen;
1505
0
    nsCOMPtr<nsISupportsString> textDataObj;
1506
0
1507
0
    nsCOMPtr<nsITransferable> contextTransferable =
1508
0
      do_CreateInstance("@mozilla.org/widget/transferable;1");
1509
0
    if (NS_WARN_IF(!contextTransferable)) {
1510
0
      return NS_ERROR_FAILURE;
1511
0
    }
1512
0
    contextTransferable->Init(nullptr);
1513
0
    contextTransferable->AddDataFlavor(kHTMLContext);
1514
0
    clipboard->GetData(contextTransferable, aClipboardType);
1515
0
    contextTransferable->GetTransferData(kHTMLContext,
1516
0
                                         getter_AddRefs(contextDataObj),
1517
0
                                         &contextLen);
1518
0
1519
0
    nsCOMPtr<nsITransferable> infoTransferable =
1520
0
      do_CreateInstance("@mozilla.org/widget/transferable;1");
1521
0
    if (NS_WARN_IF(!infoTransferable)) {
1522
0
      return NS_ERROR_FAILURE;
1523
0
    }
1524
0
    infoTransferable->Init(nullptr);
1525
0
    infoTransferable->AddDataFlavor(kHTMLInfo);
1526
0
    clipboard->GetData(infoTransferable, aClipboardType);
1527
0
    infoTransferable->GetTransferData(kHTMLInfo,
1528
0
                                      getter_AddRefs(infoDataObj),
1529
0
                                      &infoLen);
1530
0
1531
0
    if (contextDataObj) {
1532
0
      nsAutoString text;
1533
0
      textDataObj = do_QueryInterface(contextDataObj);
1534
0
      textDataObj->GetData(text);
1535
0
      MOZ_ASSERT(text.Length() <= contextLen / 2);
1536
0
      contextStr.Assign(text.get(), contextLen / 2);
1537
0
    }
1538
0
1539
0
    if (infoDataObj) {
1540
0
      nsAutoString text;
1541
0
      textDataObj = do_QueryInterface(infoDataObj);
1542
0
      textDataObj->GetData(text);
1543
0
      MOZ_ASSERT(text.Length() <= infoLen / 2);
1544
0
      infoStr.Assign(text.get(), infoLen / 2);
1545
0
    }
1546
0
  }
1547
0
1548
0
  rv = InsertFromTransferable(transferable, nullptr, contextStr, infoStr,
1549
0
                              bHavePrivateHTMLFlavor, true);
1550
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1551
0
    return rv;
1552
0
  }
1553
0
  return NS_OK;
1554
0
}
1555
1556
nsresult
1557
HTMLEditor::PasteTransferable(nsITransferable* aTransferable)
1558
0
{
1559
0
  // Use an invalid value for the clipboard type as data comes from aTransferable
1560
0
  // and we don't currently implement a way to put that in the data transfer yet.
1561
0
  if (!FireClipboardEvent(ePaste, nsIClipboard::kGlobalClipboard)) {
1562
0
    return NS_OK;
1563
0
  }
1564
0
1565
0
  nsAutoString contextStr, infoStr;
1566
0
  return InsertFromTransferable(aTransferable, nullptr, contextStr, infoStr,
1567
0
                                false, true);
1568
0
}
1569
1570
/**
1571
 * HTML PasteNoFormatting. Ignore any HTML styles and formating in paste source.
1572
 */
1573
NS_IMETHODIMP
1574
HTMLEditor::PasteNoFormatting(int32_t aSelectionType)
1575
0
{
1576
0
  if (!FireClipboardEvent(ePasteNoFormatting, aSelectionType)) {
1577
0
    return NS_OK;
1578
0
  }
1579
0
1580
0
  CommitComposition();
1581
0
1582
0
  // Get Clipboard Service
1583
0
  nsresult rv;
1584
0
  nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1585
0
  NS_ENSURE_SUCCESS(rv, rv);
1586
0
1587
0
  // Get the nsITransferable interface for getting the data from the clipboard.
1588
0
  // use TextEditor::PrepareTransferable() to force unicode plaintext data.
1589
0
  nsCOMPtr<nsITransferable> trans;
1590
0
  rv = TextEditor::PrepareTransferable(getter_AddRefs(trans));
1591
0
  if (NS_SUCCEEDED(rv) && trans) {
1592
0
    // Get the Data from the clipboard
1593
0
    if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) &&
1594
0
        IsModifiable()) {
1595
0
      const nsString& empty = EmptyString();
1596
0
      rv = InsertFromTransferable(trans, nullptr, empty, empty, false, true);
1597
0
    }
1598
0
  }
1599
0
1600
0
  return rv;
1601
0
}
1602
1603
// The following arrays contain the MIME types that we can paste. The arrays
1604
// are used by CanPaste() and CanPasteTransferable() below.
1605
1606
static const char* textEditorFlavors[] = { kUnicodeMime };
1607
static const char* textHtmlEditorFlavors[] = { kUnicodeMime, kHTMLMime,
1608
                                               kJPEGImageMime, kJPGImageMime,
1609
                                               kPNGImageMime, kGIFImageMime };
1610
1611
NS_IMETHODIMP
1612
HTMLEditor::CanPaste(int32_t aSelectionType,
1613
                     bool* aCanPaste)
1614
0
{
1615
0
  NS_ENSURE_ARG_POINTER(aCanPaste);
1616
0
  *aCanPaste = false;
1617
0
1618
0
  // Always enable the paste command when inside of a HTML or XHTML document.
1619
0
  nsCOMPtr<nsIDocument> doc = GetDocument();
1620
0
  if (doc && doc->IsHTMLOrXHTML()) {
1621
0
    *aCanPaste = true;
1622
0
    return NS_OK;
1623
0
  }
1624
0
1625
0
  // can't paste if readonly
1626
0
  if (!IsModifiable()) {
1627
0
    return NS_OK;
1628
0
  }
1629
0
1630
0
  nsresult rv;
1631
0
  nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1632
0
  NS_ENSURE_SUCCESS(rv, rv);
1633
0
1634
0
  bool haveFlavors;
1635
0
1636
0
  // Use the flavors depending on the current editor mask
1637
0
  if (IsPlaintextEditor()) {
1638
0
    rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
1639
0
                                           ArrayLength(textEditorFlavors),
1640
0
                                           aSelectionType, &haveFlavors);
1641
0
  } else {
1642
0
    rv = clipboard->HasDataMatchingFlavors(textHtmlEditorFlavors,
1643
0
                                           ArrayLength(textHtmlEditorFlavors),
1644
0
                                           aSelectionType, &haveFlavors);
1645
0
  }
1646
0
  NS_ENSURE_SUCCESS(rv, rv);
1647
0
1648
0
  *aCanPaste = haveFlavors;
1649
0
  return NS_OK;
1650
0
}
1651
1652
bool
1653
HTMLEditor::CanPasteTransferable(nsITransferable* aTransferable)
1654
0
{
1655
0
  // can't paste if readonly
1656
0
  if (!IsModifiable()) {
1657
0
    return false;
1658
0
  }
1659
0
1660
0
  // If |aTransferable| is null, assume that a paste will succeed.
1661
0
  if (!aTransferable) {
1662
0
    return true;
1663
0
  }
1664
0
1665
0
  // Peek in |aTransferable| to see if it contains a supported MIME type.
1666
0
1667
0
  // Use the flavors depending on the current editor mask
1668
0
  const char ** flavors;
1669
0
  size_t length;
1670
0
  if (IsPlaintextEditor()) {
1671
0
    flavors = textEditorFlavors;
1672
0
    length = ArrayLength(textEditorFlavors);
1673
0
  } else {
1674
0
    flavors = textHtmlEditorFlavors;
1675
0
    length = ArrayLength(textHtmlEditorFlavors);
1676
0
  }
1677
0
1678
0
  for (size_t i = 0; i < length; i++, flavors++) {
1679
0
    nsCOMPtr<nsISupports> data;
1680
0
    uint32_t dataLen;
1681
0
    nsresult rv = aTransferable->GetTransferData(*flavors,
1682
0
                                                 getter_AddRefs(data),
1683
0
                                                 &dataLen);
1684
0
    if (NS_SUCCEEDED(rv) && data) {
1685
0
      return true;
1686
0
    }
1687
0
  }
1688
0
1689
0
  return false;
1690
0
}
1691
1692
nsresult
1693
HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType)
1694
0
{
1695
0
  MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
1696
0
             aClipboardType == nsIClipboard::kSelectionClipboard);
1697
0
1698
0
  if (IsPlaintextEditor()) {
1699
0
    return PasteAsPlaintextQuotation(aClipboardType);
1700
0
  }
1701
0
1702
0
  // If it's not in plain text edit mode, paste text into new
1703
0
  // <blockquote type="cite"> element after removing selection.
1704
0
1705
0
  AutoPlaceholderBatch beginBatching(this);
1706
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1707
0
                                      *this, EditSubAction::eInsertQuotation,
1708
0
                                      nsIEditor::eNext);
1709
0
1710
0
  RefPtr<Selection> selection = GetSelection();
1711
0
  if (NS_WARN_IF(!selection)) {
1712
0
    return NS_ERROR_FAILURE;
1713
0
  }
1714
0
1715
0
  // Adjust Selection and clear cached style before inserting <blockquote>.
1716
0
  EditSubActionInfo subActionInfo(EditSubAction::eInsertElement);
1717
0
  bool cancel, handled;
1718
0
  RefPtr<TextEditRules> rules(mRules);
1719
0
  nsresult rv =
1720
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1721
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1722
0
    return rv;
1723
0
  }
1724
0
  if (cancel || handled) {
1725
0
    return NS_OK;
1726
0
  }
1727
0
1728
0
  // Then, remove Selection and create <blockquote type="cite"> now.
1729
0
  // XXX Why don't we insert the <blockquote> into the DOM tree after
1730
0
  //     pasting the content in clipboard into it?
1731
0
  nsCOMPtr<Element> newNode =
1732
0
    DeleteSelectionAndCreateElement(*nsGkAtoms::blockquote);
1733
0
  if (NS_WARN_IF(!newNode)) {
1734
0
    return NS_ERROR_FAILURE;
1735
0
  }
1736
0
  newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1737
0
                   NS_LITERAL_STRING("cite"), true);
1738
0
1739
0
  // Collapse Selection in the new <blockquote> element.
1740
0
  rv = selection->Collapse(newNode, 0);
1741
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1742
0
    return rv;
1743
0
  }
1744
0
1745
0
  // XXX Why don't we call HTMLEditRules::DidDoAction() after Paste()?
1746
0
  rv = PasteInternal(aClipboardType);
1747
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1748
0
    return rv;
1749
0
  }
1750
0
  return NS_OK;
1751
0
}
1752
1753
/**
1754
 * Paste a plaintext quotation.
1755
 */
1756
nsresult
1757
HTMLEditor::PasteAsPlaintextQuotation(int32_t aSelectionType)
1758
0
{
1759
0
  // Get Clipboard Service
1760
0
  nsresult rv;
1761
0
  nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1762
0
  NS_ENSURE_SUCCESS(rv, rv);
1763
0
1764
0
  // Create generic Transferable for getting the data
1765
0
  nsCOMPtr<nsITransferable> trans =
1766
0
                 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
1767
0
  NS_ENSURE_SUCCESS(rv, rv);
1768
0
  NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
1769
0
1770
0
  nsCOMPtr<nsIDocument> destdoc = GetDocument();
1771
0
  nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
1772
0
  trans->Init(loadContext);
1773
0
1774
0
  // We only handle plaintext pastes here
1775
0
  trans->AddDataFlavor(kUnicodeMime);
1776
0
1777
0
  // Get the Data from the clipboard
1778
0
  clipboard->GetData(trans, aSelectionType);
1779
0
1780
0
  // Now we ask the transferable for the data
1781
0
  // it still owns the data, we just have a pointer to it.
1782
0
  // If it can't support a "text" output of the data the call will fail
1783
0
  nsCOMPtr<nsISupports> genericDataObj;
1784
0
  uint32_t len = 0;
1785
0
  nsAutoCString flav;
1786
0
  rv = trans->GetAnyTransferData(flav, getter_AddRefs(genericDataObj), &len);
1787
0
  NS_ENSURE_SUCCESS(rv, rv);
1788
0
1789
0
  if (flav.EqualsLiteral(kUnicodeMime)) {
1790
0
    nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj);
1791
0
    if (textDataObj && len > 0) {
1792
0
      nsAutoString stuffToPaste;
1793
0
      textDataObj->GetData(stuffToPaste);
1794
0
      NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
1795
0
      AutoPlaceholderBatch beginBatching(this);
1796
0
      rv = InsertAsPlaintextQuotation(stuffToPaste, true, 0);
1797
0
    }
1798
0
  }
1799
0
1800
0
  return rv;
1801
0
}
1802
1803
nsresult
1804
HTMLEditor::InsertTextWithQuotations(const nsAString& aStringToInsert)
1805
0
{
1806
0
  // The whole operation should be undoable in one transaction:
1807
0
  // XXX Why isn't enough to use only AutoPlaceholderBatch here?
1808
0
  AutoTransactionBatch bundleAllTransactions(*this);
1809
0
  AutoPlaceholderBatch beginBatching(this);
1810
0
1811
0
  nsresult rv = InsertTextWithQuotationsInternal(aStringToInsert);
1812
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1813
0
    return rv;
1814
0
  }
1815
0
  return NS_OK;
1816
0
}
1817
1818
nsresult
1819
HTMLEditor::InsertTextWithQuotationsInternal(const nsAString& aStringToInsert)
1820
0
{
1821
0
  // We're going to loop over the string, collecting up a "hunk"
1822
0
  // that's all the same type (quoted or not),
1823
0
  // Whenever the quotedness changes (or we reach the string's end)
1824
0
  // we will insert the hunk all at once, quoted or non.
1825
0
1826
0
  static const char16_t cite('>');
1827
0
  bool curHunkIsQuoted = (aStringToInsert.First() == cite);
1828
0
1829
0
  nsAString::const_iterator hunkStart, strEnd;
1830
0
  aStringToInsert.BeginReading(hunkStart);
1831
0
  aStringToInsert.EndReading(strEnd);
1832
0
1833
0
  // In the loop below, we only look for DOM newlines (\n),
1834
0
  // because we don't have a FindChars method that can look
1835
0
  // for both \r and \n.  \r is illegal in the dom anyway,
1836
0
  // but in debug builds, let's take the time to verify that
1837
0
  // there aren't any there:
1838
#ifdef DEBUG
1839
  nsAString::const_iterator dbgStart (hunkStart);
1840
  if (FindCharInReadable('\r', dbgStart, strEnd)) {
1841
    NS_ASSERTION(false,
1842
            "Return characters in DOM! InsertTextWithQuotations may be wrong");
1843
  }
1844
#endif /* DEBUG */
1845
1846
0
  // Loop over lines:
1847
0
  nsresult rv = NS_OK;
1848
0
  nsAString::const_iterator lineStart (hunkStart);
1849
0
  // We will break from inside when we run out of newlines.
1850
0
  for (;;) {
1851
0
    // Search for the end of this line (dom newlines, see above):
1852
0
    bool found = FindCharInReadable('\n', lineStart, strEnd);
1853
0
    bool quoted = false;
1854
0
    if (found) {
1855
0
      // if there's another newline, lineStart now points there.
1856
0
      // Loop over any consecutive newline chars:
1857
0
      nsAString::const_iterator firstNewline (lineStart);
1858
0
      while (*lineStart == '\n') {
1859
0
        ++lineStart;
1860
0
      }
1861
0
      quoted = (*lineStart == cite);
1862
0
      if (quoted == curHunkIsQuoted) {
1863
0
        continue;
1864
0
      }
1865
0
      // else we're changing state, so we need to insert
1866
0
      // from curHunk to lineStart then loop around.
1867
0
1868
0
      // But if the current hunk is quoted, then we want to make sure
1869
0
      // that any extra newlines on the end do not get included in
1870
0
      // the quoted section: blank lines flaking a quoted section
1871
0
      // should be considered unquoted, so that if the user clicks
1872
0
      // there and starts typing, the new text will be outside of
1873
0
      // the quoted block.
1874
0
      if (curHunkIsQuoted) {
1875
0
        lineStart = firstNewline;
1876
0
1877
0
        // 'firstNewline' points to the first '\n'. We want to
1878
0
        // ensure that this first newline goes into the hunk
1879
0
        // since quoted hunks can be displayed as blocks
1880
0
        // (and the newline should become invisible in this case).
1881
0
        // So the next line needs to start at the next character.
1882
0
        lineStart++;
1883
0
      }
1884
0
    }
1885
0
1886
0
    // If no newline found, lineStart is now strEnd and we can finish up,
1887
0
    // inserting from curHunk to lineStart then returning.
1888
0
    const nsAString &curHunk = Substring(hunkStart, lineStart);
1889
0
    nsCOMPtr<nsINode> dummyNode;
1890
0
    if (curHunkIsQuoted) {
1891
0
      rv = InsertAsPlaintextQuotation(curHunk, false,
1892
0
                                      getter_AddRefs(dummyNode));
1893
0
    } else {
1894
0
      rv = InsertTextAsSubAction(curHunk);
1895
0
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1896
0
        "Failed to insert a line of the quoted text");
1897
0
    }
1898
0
    if (!found) {
1899
0
      break;
1900
0
    }
1901
0
    curHunkIsQuoted = quoted;
1902
0
    hunkStart = lineStart;
1903
0
  }
1904
0
1905
0
  return rv;
1906
0
}
1907
1908
nsresult
1909
HTMLEditor::InsertAsQuotation(const nsAString& aQuotedText,
1910
                              nsINode** aNodeInserted)
1911
0
{
1912
0
  AutoPlaceholderBatch beginBatching(this);
1913
0
  if (IsPlaintextEditor()) {
1914
0
    return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
1915
0
  }
1916
0
1917
0
  nsAutoString citation;
1918
0
  return InsertAsCitedQuotation(aQuotedText, citation, false,
1919
0
                                aNodeInserted);
1920
0
}
1921
1922
// Insert plaintext as a quotation, with cite marks (e.g. "> ").
1923
// This differs from its corresponding method in TextEditor
1924
// in that here, quoted material is enclosed in a <pre> tag
1925
// in order to preserve the original line wrapping.
1926
nsresult
1927
HTMLEditor::InsertAsPlaintextQuotation(const nsAString& aQuotedText,
1928
                                       bool aAddCites,
1929
                                       nsINode** aNodeInserted)
1930
0
{
1931
0
  if (aNodeInserted) {
1932
0
    *aNodeInserted = nullptr;
1933
0
  }
1934
0
1935
0
  RefPtr<Selection> selection = GetSelection();
1936
0
  if (NS_WARN_IF(!selection)) {
1937
0
    return NS_ERROR_FAILURE;
1938
0
  }
1939
0
1940
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1941
0
                                      *this, EditSubAction::eInsertQuotation,
1942
0
                                      nsIEditor::eNext);
1943
0
1944
0
  // give rules a chance to handle or cancel
1945
0
  EditSubActionInfo subActionInfo(EditSubAction::eInsertElement);
1946
0
  bool cancel, handled;
1947
0
  // Protect the edit rules object from dying
1948
0
  RefPtr<TextEditRules> rules(mRules);
1949
0
  nsresult rv =
1950
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1951
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1952
0
    return rv;
1953
0
  }
1954
0
  if (cancel || handled) {
1955
0
    return NS_OK; // rules canceled the operation
1956
0
  }
1957
0
1958
0
  // Wrap the inserted quote in a <span> so we can distinguish it. If we're
1959
0
  // inserting into the <body>, we use a <span> which is displayed as a block
1960
0
  // and sized to the screen using 98 viewport width units.
1961
0
  // We could use 100vw, but 98vw avoids a horizontal scroll bar where possible.
1962
0
  // All this is done to wrap overlong lines to the screen and not to the
1963
0
  // container element, the width-restricted body.
1964
0
  RefPtr<Element> newNode =
1965
0
    DeleteSelectionAndCreateElement(*nsGkAtoms::span);
1966
0
1967
0
  // If this succeeded, then set selection inside the pre
1968
0
  // so the inserted text will end up there.
1969
0
  // If it failed, we don't care what the return value was,
1970
0
  // but we'll fall through and try to insert the text anyway.
1971
0
  if (newNode) {
1972
0
    // Add an attribute on the pre node so we'll know it's a quotation.
1973
0
    newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::mozquote,
1974
0
                     NS_LITERAL_STRING("true"), true);
1975
0
    // Allow wrapping on spans so long lines get wrapped to the screen.
1976
0
    nsCOMPtr<nsINode> parent = newNode->GetParentNode();
1977
0
    if (parent && parent->IsHTMLElement(nsGkAtoms::body)) {
1978
0
      newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
1979
0
        NS_LITERAL_STRING("white-space: pre-wrap; display: block; width: 98vw;"),
1980
0
        true);
1981
0
    } else {
1982
0
      newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
1983
0
        NS_LITERAL_STRING("white-space: pre-wrap;"), true);
1984
0
    }
1985
0
1986
0
    // and set the selection inside it:
1987
0
    DebugOnly<nsresult> rv = selection->Collapse(newNode, 0);
1988
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1989
0
      "Failed to collapse selection into the new node");
1990
0
  }
1991
0
1992
0
  if (aAddCites) {
1993
0
    rv = InsertWithQuotationsAsSubAction(aQuotedText);
1994
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1995
0
      "Failed to insert the text with quotations");
1996
0
  } else {
1997
0
    rv = InsertTextAsSubAction(aQuotedText);
1998
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1999
0
      "Failed to insert the quoted text as plain text");
2000
0
  }
2001
0
  // Note that if !aAddCites, aNodeInserted isn't set.
2002
0
  // That's okay because the routines that use aAddCites
2003
0
  // don't need to know the inserted node.
2004
0
2005
0
  if (aNodeInserted && NS_SUCCEEDED(rv)) {
2006
0
    newNode.forget(aNodeInserted);
2007
0
  }
2008
0
2009
0
  // Set the selection to just after the inserted node:
2010
0
  if (NS_SUCCEEDED(rv) && newNode) {
2011
0
    EditorRawDOMPoint afterNewNode(newNode);
2012
0
    if (afterNewNode.AdvanceOffset()) {
2013
0
      DebugOnly<nsresult> rv = selection->Collapse(afterNewNode);
2014
0
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2015
0
        "Failed to collapse after the new node");
2016
0
    }
2017
0
  }
2018
0
2019
0
  // XXX Why don't we call HTMLEditRules::DidDoAction() here?
2020
0
  return rv;
2021
0
}
2022
2023
NS_IMETHODIMP
2024
HTMLEditor::Rewrap(bool aRespectNewlines)
2025
0
{
2026
0
  // Rewrap makes no sense if there's no wrap column; default to 72.
2027
0
  int32_t wrapWidth = WrapWidth();
2028
0
  if (wrapWidth <= 0) {
2029
0
    wrapWidth = 72;
2030
0
  }
2031
0
2032
0
  nsAutoString current;
2033
0
  bool isCollapsed;
2034
0
  nsresult rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted |
2035
0
                                   nsIDocumentEncoder::OutputLFLineBreak,
2036
0
                                   &isCollapsed, current);
2037
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2038
0
    return rv;
2039
0
  }
2040
0
2041
0
  nsString wrapped;
2042
0
  uint32_t firstLineOffset = 0;   // XXX need to reset this if there is a
2043
0
                                  //     selection
2044
0
  rv = InternetCiter::Rewrap(current, wrapWidth, firstLineOffset,
2045
0
                             aRespectNewlines, wrapped);
2046
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2047
0
    return rv;
2048
0
  }
2049
0
2050
0
  if (isCollapsed) {
2051
0
    DebugOnly<nsresult> rv = SelectAllInternal();
2052
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),  "Failed to select all text");
2053
0
  }
2054
0
2055
0
  // The whole operation in InsertTextWithQuotationsInternal() should be
2056
0
  // undoable in one transaction.
2057
0
  // XXX Why isn't enough to use only AutoPlaceholderBatch here?
2058
0
  AutoTransactionBatch bundleAllTransactions(*this);
2059
0
  AutoPlaceholderBatch beginBatching(this);
2060
0
  rv = InsertTextWithQuotationsInternal(wrapped);
2061
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2062
0
    return rv;
2063
0
  }
2064
0
  return NS_OK;
2065
0
}
2066
2067
NS_IMETHODIMP
2068
HTMLEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
2069
                                   const nsAString& aCitation,
2070
                                   bool aInsertHTML,
2071
                                   nsINode** aNodeInserted)
2072
0
{
2073
0
  AutoPlaceholderBatch beginBatching(this);
2074
0
2075
0
  // Don't let anyone insert HTML when we're in plaintext mode.
2076
0
  if (IsPlaintextEditor()) {
2077
0
    NS_ASSERTION(!aInsertHTML,
2078
0
      "InsertAsCitedQuotation: trying to insert html into plaintext editor");
2079
0
    return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
2080
0
  }
2081
0
2082
0
  RefPtr<Selection> selection = GetSelection();
2083
0
  if (NS_WARN_IF(!selection)) {
2084
0
    return NS_ERROR_FAILURE;
2085
0
  }
2086
0
2087
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
2088
0
                                      *this, EditSubAction::eInsertQuotation,
2089
0
                                      nsIEditor::eNext);
2090
0
2091
0
  // give rules a chance to handle or cancel
2092
0
  EditSubActionInfo subActionInfo(EditSubAction::eInsertElement);
2093
0
  bool cancel, handled;
2094
0
  // Protect the edit rules object from dying
2095
0
  RefPtr<TextEditRules> rules(mRules);
2096
0
  nsresult rv =
2097
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
2098
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2099
0
    return rv;
2100
0
  }
2101
0
  if (cancel || handled) {
2102
0
    return NS_OK; // rules canceled the operation
2103
0
  }
2104
0
2105
0
  RefPtr<Element> newNode =
2106
0
    DeleteSelectionAndCreateElement(*nsGkAtoms::blockquote);
2107
0
  if (NS_WARN_IF(!newNode)) {
2108
0
    return NS_ERROR_FAILURE;
2109
0
  }
2110
0
2111
0
  // Try to set type=cite.  Ignore it if this fails.
2112
0
  newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
2113
0
                   NS_LITERAL_STRING("cite"), true);
2114
0
2115
0
  if (!aCitation.IsEmpty()) {
2116
0
    newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::cite, aCitation, true);
2117
0
  }
2118
0
2119
0
  // Set the selection inside the blockquote so aQuotedText will go there:
2120
0
  selection->Collapse(newNode, 0);
2121
0
2122
0
  if (aInsertHTML) {
2123
0
    rv = LoadHTML(aQuotedText);
2124
0
  } else {
2125
0
    rv = InsertTextAsSubAction(aQuotedText);  // XXX ignore charset
2126
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert the quoted text");
2127
0
  }
2128
0
2129
0
  if (aNodeInserted && NS_SUCCEEDED(rv)) {
2130
0
    *aNodeInserted = newNode;
2131
0
    NS_IF_ADDREF(*aNodeInserted);
2132
0
  }
2133
0
2134
0
  // Set the selection to just after the inserted node:
2135
0
  if (NS_SUCCEEDED(rv) && newNode) {
2136
0
    EditorRawDOMPoint afterNewNode(newNode);
2137
0
    if (afterNewNode.AdvanceOffset()) {
2138
0
      selection->Collapse(afterNewNode);
2139
0
    }
2140
0
  }
2141
0
  return rv;
2142
0
}
2143
2144
2145
void RemoveBodyAndHead(nsINode& aNode)
2146
0
{
2147
0
  nsCOMPtr<nsIContent> body, head;
2148
0
  // find the body and head nodes if any.
2149
0
  // look only at immediate children of aNode.
2150
0
  for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
2151
0
       child;
2152
0
       child = child->GetNextSibling()) {
2153
0
    if (child->IsHTMLElement(nsGkAtoms::body)) {
2154
0
      body = child;
2155
0
    } else if (child->IsHTMLElement(nsGkAtoms::head)) {
2156
0
      head = child;
2157
0
    }
2158
0
  }
2159
0
  if (head) {
2160
0
    ErrorResult ignored;
2161
0
    aNode.RemoveChild(*head, ignored);
2162
0
  }
2163
0
  if (body) {
2164
0
    nsCOMPtr<nsIContent> child = body->GetFirstChild();
2165
0
    while (child) {
2166
0
      ErrorResult ignored;
2167
0
      aNode.InsertBefore(*child, body, ignored);
2168
0
      child = body->GetFirstChild();
2169
0
    }
2170
0
2171
0
    ErrorResult ignored;
2172
0
    aNode.RemoveChild(*body, ignored);
2173
0
  }
2174
0
}
2175
2176
/**
2177
 * This function finds the target node that we will be pasting into. aStart is
2178
 * the context that we're given and aResult will be the target. Initially,
2179
 * *aResult must be nullptr.
2180
 *
2181
 * The target for a paste is found by either finding the node that contains
2182
 * the magical comment node containing kInsertCookie or, failing that, the
2183
 * firstChild of the firstChild (until we reach a leaf).
2184
 */
2185
nsresult FindTargetNode(nsINode *aStart, nsCOMPtr<nsINode> &aResult)
2186
0
{
2187
0
  NS_ENSURE_TRUE(aStart, NS_OK);
2188
0
2189
0
  nsCOMPtr<nsINode> child = aStart->GetFirstChild();
2190
0
2191
0
  if (!child) {
2192
0
    // If the current result is nullptr, then aStart is a leaf, and is the
2193
0
    // fallback result.
2194
0
    if (!aResult) {
2195
0
      aResult = aStart;
2196
0
    }
2197
0
    return NS_OK;
2198
0
  }
2199
0
2200
0
  do {
2201
0
    // Is this child the magical cookie?
2202
0
    if (auto* comment = Comment::FromNode(child)) {
2203
0
      nsAutoString data;
2204
0
      comment->GetData(data);
2205
0
2206
0
      if (data.EqualsLiteral(kInsertCookie)) {
2207
0
        // Yes it is! Return an error so we bubble out and short-circuit the
2208
0
        // search.
2209
0
        aResult = aStart;
2210
0
2211
0
        child->Remove();
2212
0
2213
0
        return NS_SUCCESS_EDITOR_FOUND_TARGET;
2214
0
      }
2215
0
    }
2216
0
2217
0
    nsresult rv = FindTargetNode(child, aResult);
2218
0
    NS_ENSURE_SUCCESS(rv, rv);
2219
0
2220
0
    if (rv == NS_SUCCESS_EDITOR_FOUND_TARGET) {
2221
0
      return NS_SUCCESS_EDITOR_FOUND_TARGET;
2222
0
    }
2223
0
2224
0
    child = child->GetNextSibling();
2225
0
  } while (child);
2226
0
2227
0
  return NS_OK;
2228
0
}
2229
2230
nsresult
2231
HTMLEditor::CreateDOMFragmentFromPaste(const nsAString& aInputString,
2232
                                       const nsAString& aContextStr,
2233
                                       const nsAString& aInfoStr,
2234
                                       nsCOMPtr<nsINode>* outFragNode,
2235
                                       nsCOMPtr<nsINode>* outStartNode,
2236
                                       nsCOMPtr<nsINode>* outEndNode,
2237
                                       int32_t* outStartOffset,
2238
                                       int32_t* outEndOffset,
2239
                                       bool aTrustedInput)
2240
0
{
2241
0
  NS_ENSURE_TRUE(outFragNode && outStartNode && outEndNode, NS_ERROR_NULL_POINTER);
2242
0
2243
0
  nsCOMPtr<nsIDocument> doc = GetDocument();
2244
0
  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2245
0
2246
0
  // if we have context info, create a fragment for that
2247
0
  nsresult rv = NS_OK;
2248
0
  nsCOMPtr<nsINode> contextLeaf;
2249
0
  RefPtr<DocumentFragment> contextAsNode;
2250
0
  if (!aContextStr.IsEmpty()) {
2251
0
    rv = ParseFragment(aContextStr, nullptr, doc, getter_AddRefs(contextAsNode),
2252
0
                       aTrustedInput);
2253
0
    NS_ENSURE_SUCCESS(rv, rv);
2254
0
    NS_ENSURE_TRUE(contextAsNode, NS_ERROR_FAILURE);
2255
0
2256
0
    rv = StripFormattingNodes(*contextAsNode);
2257
0
    NS_ENSURE_SUCCESS(rv, rv);
2258
0
2259
0
    RemoveBodyAndHead(*contextAsNode);
2260
0
2261
0
    rv = FindTargetNode(contextAsNode, contextLeaf);
2262
0
    NS_ENSURE_SUCCESS(rv, rv);
2263
0
  }
2264
0
2265
0
  nsCOMPtr<nsIContent> contextLeafAsContent = do_QueryInterface(contextLeaf);
2266
0
  MOZ_ASSERT_IF(contextLeaf, contextLeafAsContent);
2267
0
2268
0
  // create fragment for pasted html
2269
0
  nsAtom* contextAtom;
2270
0
  if (contextLeafAsContent) {
2271
0
    contextAtom = contextLeafAsContent->NodeInfo()->NameAtom();
2272
0
    if (contextLeafAsContent->IsHTMLElement(nsGkAtoms::html)) {
2273
0
      contextAtom = nsGkAtoms::body;
2274
0
    }
2275
0
  } else {
2276
0
    contextAtom = nsGkAtoms::body;
2277
0
  }
2278
0
  RefPtr<DocumentFragment> fragment;
2279
0
  rv = ParseFragment(aInputString,
2280
0
                     contextAtom,
2281
0
                     doc,
2282
0
                     getter_AddRefs(fragment),
2283
0
                     aTrustedInput);
2284
0
  NS_ENSURE_SUCCESS(rv, rv);
2285
0
  NS_ENSURE_TRUE(fragment, NS_ERROR_FAILURE);
2286
0
2287
0
  RemoveBodyAndHead(*fragment);
2288
0
2289
0
  if (contextAsNode) {
2290
0
    // unite the two trees
2291
0
    contextLeafAsContent->AppendChild(*fragment, IgnoreErrors());
2292
0
    fragment = contextAsNode;
2293
0
  }
2294
0
2295
0
  rv = StripFormattingNodes(*fragment, true);
2296
0
  NS_ENSURE_SUCCESS(rv, rv);
2297
0
2298
0
  // If there was no context, then treat all of the data we did get as the
2299
0
  // pasted data.
2300
0
  if (contextLeaf) {
2301
0
    *outEndNode = *outStartNode = contextLeaf;
2302
0
  } else {
2303
0
    *outEndNode = *outStartNode = fragment;
2304
0
  }
2305
0
2306
0
  *outFragNode = fragment.forget();
2307
0
  *outStartOffset = 0;
2308
0
2309
0
  // get the infoString contents
2310
0
  if (!aInfoStr.IsEmpty()) {
2311
0
    int32_t sep = aInfoStr.FindChar((char16_t)',');
2312
0
    nsAutoString numstr1(Substring(aInfoStr, 0, sep));
2313
0
    nsAutoString numstr2(Substring(aInfoStr, sep+1, aInfoStr.Length() - (sep+1)));
2314
0
2315
0
    // Move the start and end children.
2316
0
    nsresult err;
2317
0
    int32_t num = numstr1.ToInteger(&err);
2318
0
    while (num--) {
2319
0
      nsINode* tmp = (*outStartNode)->GetFirstChild();
2320
0
      NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2321
0
      *outStartNode = tmp;
2322
0
    }
2323
0
2324
0
    num = numstr2.ToInteger(&err);
2325
0
    while (num--) {
2326
0
      nsINode* tmp = (*outEndNode)->GetLastChild();
2327
0
      NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2328
0
      *outEndNode = tmp;
2329
0
    }
2330
0
  }
2331
0
2332
0
  *outEndOffset = (*outEndNode)->Length();
2333
0
  return NS_OK;
2334
0
}
2335
2336
2337
nsresult
2338
HTMLEditor::ParseFragment(const nsAString& aFragStr,
2339
                          nsAtom* aContextLocalName,
2340
                          nsIDocument* aTargetDocument,
2341
                          DocumentFragment** aFragment,
2342
                          bool aTrustedInput)
2343
0
{
2344
0
  nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;
2345
0
2346
0
  RefPtr<DocumentFragment> fragment =
2347
0
    new DocumentFragment(aTargetDocument->NodeInfoManager());
2348
0
  nsresult rv = nsContentUtils::ParseFragmentHTML(aFragStr,
2349
0
                                                  fragment,
2350
0
                                                  aContextLocalName ?
2351
0
                                                    aContextLocalName : nsGkAtoms::body,
2352
0
                                                    kNameSpaceID_XHTML,
2353
0
                                                  false,
2354
0
                                                  true);
2355
0
  if (!aTrustedInput) {
2356
0
    nsTreeSanitizer sanitizer(aContextLocalName ?
2357
0
                              nsIParserUtils::SanitizerAllowStyle :
2358
0
                              nsIParserUtils::SanitizerAllowComments);
2359
0
    sanitizer.Sanitize(fragment);
2360
0
  }
2361
0
  fragment.forget(aFragment);
2362
0
  return rv;
2363
0
}
2364
2365
void
2366
HTMLEditor::CreateListOfNodesToPaste(
2367
              DocumentFragment& aFragment,
2368
              nsTArray<OwningNonNull<nsINode>>& outNodeList,
2369
              nsINode* aStartContainer,
2370
              int32_t aStartOffset,
2371
              nsINode* aEndContainer,
2372
              int32_t aEndOffset)
2373
0
{
2374
0
  // If no info was provided about the boundary between context and stream,
2375
0
  // then assume all is stream.
2376
0
  if (!aStartContainer) {
2377
0
    aStartContainer = &aFragment;
2378
0
    aStartOffset = 0;
2379
0
    aEndContainer = &aFragment;
2380
0
    aEndOffset = aFragment.Length();
2381
0
  }
2382
0
2383
0
  RefPtr<nsRange> docFragRange;
2384
0
  nsresult rv = nsRange::CreateRange(aStartContainer, aStartOffset,
2385
0
                                     aEndContainer, aEndOffset,
2386
0
                                     getter_AddRefs(docFragRange));
2387
0
  MOZ_ASSERT(NS_SUCCEEDED(rv));
2388
0
  NS_ENSURE_SUCCESS(rv, );
2389
0
2390
0
  // Now use a subtree iterator over the range to create a list of nodes
2391
0
  TrivialFunctor functor;
2392
0
  DOMSubtreeIterator iter;
2393
0
  rv = iter.Init(*docFragRange);
2394
0
  NS_ENSURE_SUCCESS(rv, );
2395
0
  iter.AppendList(functor, outNodeList);
2396
0
}
2397
2398
void
2399
HTMLEditor::GetListAndTableParents(StartOrEnd aStartOrEnd,
2400
                                   nsTArray<OwningNonNull<nsINode>>& aNodeList,
2401
                                   nsTArray<OwningNonNull<Element>>& outArray)
2402
0
{
2403
0
  MOZ_ASSERT(aNodeList.Length());
2404
0
2405
0
  // Build up list of parents of first (or last) node in list that are either
2406
0
  // lists, or tables.
2407
0
  int32_t idx = aStartOrEnd == StartOrEnd::end ? aNodeList.Length() - 1 : 0;
2408
0
2409
0
  for (nsCOMPtr<nsINode> node = aNodeList[idx]; node;
2410
0
       node = node->GetParentNode()) {
2411
0
    if (HTMLEditUtils::IsList(node) || HTMLEditUtils::IsTable(node)) {
2412
0
      outArray.AppendElement(*node->AsElement());
2413
0
    }
2414
0
  }
2415
0
}
2416
2417
int32_t
2418
HTMLEditor::DiscoverPartialListsAndTables(
2419
              nsTArray<OwningNonNull<nsINode>>& aPasteNodes,
2420
              nsTArray<OwningNonNull<Element>>& aListsAndTables)
2421
0
{
2422
0
  int32_t ret = -1;
2423
0
  int32_t listAndTableParents = aListsAndTables.Length();
2424
0
2425
0
  // Scan insertion list for table elements (other than table).
2426
0
  for (auto& curNode : aPasteNodes) {
2427
0
    if (HTMLEditUtils::IsTableElement(curNode) &&
2428
0
        !curNode->IsHTMLElement(nsGkAtoms::table)) {
2429
0
      nsCOMPtr<Element> table = curNode->GetParentElement();
2430
0
      while (table && !table->IsHTMLElement(nsGkAtoms::table)) {
2431
0
        table = table->GetParentElement();
2432
0
      }
2433
0
      if (table) {
2434
0
        int32_t idx = aListsAndTables.IndexOf(table);
2435
0
        if (idx == -1) {
2436
0
          return ret;
2437
0
        }
2438
0
        ret = idx;
2439
0
        if (ret == listAndTableParents - 1) {
2440
0
          return ret;
2441
0
        }
2442
0
      }
2443
0
    }
2444
0
    if (HTMLEditUtils::IsListItem(curNode)) {
2445
0
      nsCOMPtr<Element> list = curNode->GetParentElement();
2446
0
      while (list && !HTMLEditUtils::IsList(list)) {
2447
0
        list = list->GetParentElement();
2448
0
      }
2449
0
      if (list) {
2450
0
        int32_t idx = aListsAndTables.IndexOf(list);
2451
0
        if (idx == -1) {
2452
0
          return ret;
2453
0
        }
2454
0
        ret = idx;
2455
0
        if (ret == listAndTableParents - 1) {
2456
0
          return ret;
2457
0
        }
2458
0
      }
2459
0
    }
2460
0
  }
2461
0
  return ret;
2462
0
}
2463
2464
nsINode*
2465
HTMLEditor::ScanForListAndTableStructure(
2466
              StartOrEnd aStartOrEnd,
2467
              nsTArray<OwningNonNull<nsINode>>& aNodes,
2468
              Element& aListOrTable)
2469
0
{
2470
0
  // Look upward from first/last paste node for a piece of this list/table
2471
0
  int32_t idx = aStartOrEnd == StartOrEnd::end ? aNodes.Length() - 1 : 0;
2472
0
  bool isList = HTMLEditUtils::IsList(&aListOrTable);
2473
0
2474
0
  for (nsCOMPtr<nsINode> node = aNodes[idx]; node;
2475
0
       node = node->GetParentNode()) {
2476
0
    if ((isList && HTMLEditUtils::IsListItem(node)) ||
2477
0
        (!isList && HTMLEditUtils::IsTableElement(node) &&
2478
0
                    !node->IsHTMLElement(nsGkAtoms::table))) {
2479
0
      nsCOMPtr<Element> structureNode = node->GetParentElement();
2480
0
      if (isList) {
2481
0
        while (structureNode && !HTMLEditUtils::IsList(structureNode)) {
2482
0
          structureNode = structureNode->GetParentElement();
2483
0
        }
2484
0
      } else {
2485
0
        while (structureNode &&
2486
0
               !structureNode->IsHTMLElement(nsGkAtoms::table)) {
2487
0
          structureNode = structureNode->GetParentElement();
2488
0
        }
2489
0
      }
2490
0
      if (structureNode == &aListOrTable) {
2491
0
        if (isList) {
2492
0
          return structureNode;
2493
0
        }
2494
0
        return node;
2495
0
      }
2496
0
    }
2497
0
  }
2498
0
  return nullptr;
2499
0
}
2500
2501
void
2502
HTMLEditor::ReplaceOrphanedStructure(
2503
              StartOrEnd aStartOrEnd,
2504
              nsTArray<OwningNonNull<nsINode>>& aNodeArray,
2505
              nsTArray<OwningNonNull<Element>>& aListAndTableArray,
2506
              int32_t aHighWaterMark)
2507
0
{
2508
0
  OwningNonNull<Element> curNode = aListAndTableArray[aHighWaterMark];
2509
0
2510
0
  // Find substructure of list or table that must be included in paste.
2511
0
  nsCOMPtr<nsINode> replaceNode =
2512
0
    ScanForListAndTableStructure(aStartOrEnd, aNodeArray, curNode);
2513
0
2514
0
  if (!replaceNode) {
2515
0
    return;
2516
0
  }
2517
0
2518
0
  // If we found substructure, paste it instead of its descendants.
2519
0
  // Postprocess list to remove any descendants of this node so that we don't
2520
0
  // insert them twice.
2521
0
  uint32_t removedCount = 0;
2522
0
  uint32_t originalLength = aNodeArray.Length();
2523
0
  for (uint32_t i = 0; i < originalLength; i++) {
2524
0
    uint32_t idx = aStartOrEnd == StartOrEnd::start ?
2525
0
      (i - removedCount) : (originalLength - i - 1);
2526
0
    OwningNonNull<nsINode> endpoint = aNodeArray[idx];
2527
0
    if (endpoint == replaceNode ||
2528
0
        EditorUtils::IsDescendantOf(*endpoint, *replaceNode)) {
2529
0
      aNodeArray.RemoveElementAt(idx);
2530
0
      removedCount++;
2531
0
    }
2532
0
  }
2533
0
2534
0
  // Now replace the removed nodes with the structural parent
2535
0
  if (aStartOrEnd == StartOrEnd::end) {
2536
0
    aNodeArray.AppendElement(*replaceNode);
2537
0
  } else {
2538
0
    aNodeArray.InsertElementAt(0, *replaceNode);
2539
0
  }
2540
0
}
2541
2542
} // namespace mozilla