editor/libeditor/html/nsHTMLDataTransfer.cpp

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

mercurial