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