Bug 1247796. Use keyboardFocusIndicatorColor for ActiveBorder system color keyword...
[gecko.git] / editor / libeditor / nsHTMLDataTransfer.cpp
blobb062ca3860192ecad8b0d5b069d1acff740ef619
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/. */
7 #include <string.h>
9 #include "mozilla/dom/DocumentFragment.h"
10 #include "mozilla/OwningNonNull.h"
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/Base64.h"
13 #include "mozilla/BasicEvents.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/dom/Selection.h"
16 #include "nsAString.h"
17 #include "nsAutoPtr.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 "nsEditRules.h"
26 #include "nsEditor.h"
27 #include "nsEditorUtils.h"
28 #include "nsError.h"
29 #include "nsGkAtoms.h"
30 #include "nsHTMLEditUtils.h"
31 #include "nsHTMLEditor.h"
32 #include "nsIClipboard.h"
33 #include "nsIContent.h"
34 #include "nsIContentFilter.h"
35 #include "nsIDOMComment.h"
36 #include "mozilla/dom/DOMStringList.h"
37 #include "mozilla/dom/DataTransfer.h"
38 #include "nsIDOMDocument.h"
39 #include "nsIDOMDocumentFragment.h"
40 #include "nsIDOMElement.h"
41 #include "nsIDOMHTMLAnchorElement.h"
42 #include "nsIDOMHTMLEmbedElement.h"
43 #include "nsIDOMHTMLFrameElement.h"
44 #include "nsIDOMHTMLIFrameElement.h"
45 #include "nsIDOMHTMLImageElement.h"
46 #include "nsIDOMHTMLInputElement.h"
47 #include "nsIDOMHTMLLinkElement.h"
48 #include "nsIDOMHTMLObjectElement.h"
49 #include "nsIDOMHTMLScriptElement.h"
50 #include "nsIDOMNode.h"
51 #include "nsIDocument.h"
52 #include "nsIEditor.h"
53 #include "nsIEditorIMESupport.h"
54 #include "nsIEditorMailSupport.h"
55 #include "nsIFile.h"
56 #include "nsIInputStream.h"
57 #include "nsIMIMEService.h"
58 #include "nsNameSpaceManager.h"
59 #include "nsINode.h"
60 #include "nsIParserUtils.h"
61 #include "nsIPlaintextEditor.h"
62 #include "nsISupportsImpl.h"
63 #include "nsISupportsPrimitives.h"
64 #include "nsISupportsUtils.h"
65 #include "nsITransferable.h"
66 #include "nsIURI.h"
67 #include "nsIVariant.h"
68 #include "nsLinebreakConverter.h"
69 #include "nsLiteralString.h"
70 #include "nsNetUtil.h"
71 #include "nsPlaintextEditor.h"
72 #include "nsRange.h"
73 #include "nsReadableUtils.h"
74 #include "nsSelectionState.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 "nsTextEditRules.h"
82 #include "nsTextEditUtils.h"
83 #include "nsTreeSanitizer.h"
84 #include "nsWSRunObject.h"
85 #include "nsXPCOM.h"
86 #include "nscore.h"
87 #include "nsContentUtils.h"
89 class nsIAtom;
90 class nsILoadContext;
91 class nsISupports;
93 using namespace mozilla;
94 using namespace mozilla::dom;
96 #define kInsertCookie "_moz_Insert Here_moz_"
98 // some little helpers
99 static bool FindIntegerAfterString(const char *aLeadingString,
100 nsCString &aCStr, int32_t &foundNumber);
101 static nsresult RemoveFragComments(nsCString &theStr);
102 static void RemoveBodyAndHead(nsINode& aNode);
103 static nsresult FindTargetNode(nsIDOMNode *aStart, nsCOMPtr<nsIDOMNode> &aResult);
105 nsresult
106 nsHTMLEditor::LoadHTML(const nsAString & aInputString)
108 NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
110 // force IME commit; set up rules sniffing and batching
111 ForceCompositionEnd();
112 nsAutoEditBatch beginBatching(this);
113 nsAutoRules beginRulesSniffing(this, EditAction::loadHTML, nsIEditor::eNext);
115 // Get selection
116 RefPtr<Selection> selection = GetSelection();
117 NS_ENSURE_STATE(selection);
119 nsTextRulesInfo ruleInfo(EditAction::loadHTML);
120 bool cancel, handled;
121 // Protect the edit rules object from dying
122 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
123 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
124 NS_ENSURE_SUCCESS(rv, rv);
125 if (cancel) {
126 return NS_OK; // rules canceled the operation
129 if (!handled)
131 // Delete Selection, but only if it isn't collapsed, see bug #106269
132 if (!selection->Collapsed()) {
133 rv = DeleteSelection(eNone, eStrip);
134 NS_ENSURE_SUCCESS(rv, rv);
137 // Get the first range in the selection, for context:
138 RefPtr<nsRange> range = selection->GetRangeAt(0);
139 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
141 // create fragment for pasted html
142 nsCOMPtr<nsIDOMDocumentFragment> docfrag;
144 rv = range->CreateContextualFragment(aInputString, getter_AddRefs(docfrag));
145 NS_ENSURE_SUCCESS(rv, rv);
147 // put the fragment into the document
148 nsCOMPtr<nsIDOMNode> parent, junk;
149 rv = range->GetStartContainer(getter_AddRefs(parent));
150 NS_ENSURE_SUCCESS(rv, rv);
151 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
152 int32_t childOffset;
153 rv = range->GetStartOffset(&childOffset);
154 NS_ENSURE_SUCCESS(rv, rv);
156 nsCOMPtr<nsIDOMNode> nodeToInsert;
157 docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
158 while (nodeToInsert)
160 rv = InsertNode(nodeToInsert, parent, childOffset++);
161 NS_ENSURE_SUCCESS(rv, rv);
162 docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
166 return mRules->DidDoAction(selection, &ruleInfo, rv);
170 NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsAString & aInString)
172 const nsAFlatString& empty = EmptyString();
174 return InsertHTMLWithContext(aInString, empty, empty, empty,
175 nullptr, nullptr, 0, true);
179 nsresult
180 nsHTMLEditor::InsertHTMLWithContext(const nsAString & aInputString,
181 const nsAString & aContextStr,
182 const nsAString & aInfoStr,
183 const nsAString & aFlavor,
184 nsIDOMDocument *aSourceDoc,
185 nsIDOMNode *aDestNode,
186 int32_t aDestOffset,
187 bool aDeleteSelection)
189 return DoInsertHTMLWithContext(aInputString, aContextStr, aInfoStr,
190 aFlavor, aSourceDoc, aDestNode, aDestOffset, aDeleteSelection,
191 /* trusted input */ true, /* clear style */ false);
194 nsresult
195 nsHTMLEditor::DoInsertHTMLWithContext(const nsAString & aInputString,
196 const nsAString & aContextStr,
197 const nsAString & aInfoStr,
198 const nsAString & aFlavor,
199 nsIDOMDocument *aSourceDoc,
200 nsIDOMNode *aDestNode,
201 int32_t aDestOffset,
202 bool aDeleteSelection,
203 bool aTrustedInput,
204 bool aClearStyle)
206 NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
208 // Prevent the edit rules object from dying
209 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
211 // force IME commit; set up rules sniffing and batching
212 ForceCompositionEnd();
213 nsAutoEditBatch beginBatching(this);
214 nsAutoRules beginRulesSniffing(this, EditAction::htmlPaste, nsIEditor::eNext);
216 // Get selection
217 RefPtr<Selection> selection = GetSelection();
218 NS_ENSURE_STATE(selection);
220 // create a dom document fragment that represents the structure to paste
221 nsCOMPtr<nsIDOMNode> fragmentAsNode, streamStartParent, streamEndParent;
222 int32_t streamStartOffset = 0, streamEndOffset = 0;
224 nsresult rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr,
225 address_of(fragmentAsNode),
226 address_of(streamStartParent),
227 address_of(streamEndParent),
228 &streamStartOffset,
229 &streamEndOffset,
230 aTrustedInput);
231 NS_ENSURE_SUCCESS(rv, rv);
233 nsCOMPtr<nsIDOMNode> targetNode, tempNode;
234 int32_t targetOffset=0;
236 if (!aDestNode)
238 // if caller didn't provide the destination/target node,
239 // fetch the paste insertion point from our selection
240 rv = GetStartNodeAndOffset(selection, getter_AddRefs(targetNode), &targetOffset);
241 NS_ENSURE_SUCCESS(rv, rv);
242 if (!targetNode || !IsEditable(targetNode)) {
243 return NS_ERROR_FAILURE;
246 else
248 targetNode = aDestNode;
249 targetOffset = aDestOffset;
252 bool doContinue = true;
254 rv = DoContentFilterCallback(aFlavor, aSourceDoc, aDeleteSelection,
255 (nsIDOMNode **)address_of(fragmentAsNode),
256 (nsIDOMNode **)address_of(streamStartParent),
257 &streamStartOffset,
258 (nsIDOMNode **)address_of(streamEndParent),
259 &streamEndOffset,
260 (nsIDOMNode **)address_of(targetNode),
261 &targetOffset, &doContinue);
263 NS_ENSURE_SUCCESS(rv, rv);
264 NS_ENSURE_TRUE(doContinue, NS_OK);
266 // if we have a destination / target node, we want to insert there
267 // rather than in place of the selection
268 // ignore aDeleteSelection here if no aDestNode since deletion will
269 // also occur later; this block is intended to cover the various
270 // scenarios where we are dropping in an editor (and may want to delete
271 // the selection before collapsing the selection in the new destination)
272 if (aDestNode)
274 if (aDeleteSelection)
276 // Use an auto tracker so that our drop point is correctly
277 // positioned after the delete.
278 nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
279 rv = DeleteSelection(eNone, eStrip);
280 NS_ENSURE_SUCCESS(rv, rv);
283 rv = selection->Collapse(targetNode, targetOffset);
284 NS_ENSURE_SUCCESS(rv, rv);
287 // we need to recalculate various things based on potentially new offsets
288 // this is work to be completed at a later date (probably by jfrancis)
290 // make a list of what nodes in docFrag we need to move
291 nsTArray<OwningNonNull<nsINode>> nodeList;
292 nsCOMPtr<nsINode> fragmentAsNodeNode = do_QueryInterface(fragmentAsNode);
293 NS_ENSURE_STATE(fragmentAsNodeNode || !fragmentAsNode);
294 nsCOMPtr<nsINode> streamStartParentNode =
295 do_QueryInterface(streamStartParent);
296 NS_ENSURE_STATE(streamStartParentNode || !streamStartParent);
297 nsCOMPtr<nsINode> streamEndParentNode =
298 do_QueryInterface(streamEndParent);
299 NS_ENSURE_STATE(streamEndParentNode || !streamEndParent);
300 CreateListOfNodesToPaste(*static_cast<DocumentFragment*>(fragmentAsNodeNode.get()),
301 nodeList,
302 streamStartParentNode, streamStartOffset,
303 streamEndParentNode, streamEndOffset);
305 if (nodeList.Length() == 0) {
306 // We aren't inserting anything, but if aDeleteSelection is set, we do want
307 // to delete everything.
308 if (aDeleteSelection) {
309 return DeleteSelection(eNone, eStrip);
311 return NS_OK;
314 // Are there any table elements in the list?
315 // node and offset for insertion
316 nsCOMPtr<nsIDOMNode> parentNode;
317 int32_t offsetOfNewNode;
319 // check for table cell selection mode
320 bool cellSelectionMode = false;
321 nsCOMPtr<nsIDOMElement> cell;
322 rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
323 if (NS_SUCCEEDED(rv) && cell)
325 cellSelectionMode = true;
328 if (cellSelectionMode)
330 // do we have table content to paste? If so, we want to delete
331 // the selected table cells and replace with new table elements;
332 // but if not we want to delete _contents_ of cells and replace
333 // with non-table elements. Use cellSelectionMode bool to
334 // indicate results.
335 if (!nsHTMLEditUtils::IsTableElement(nodeList[0])) {
336 cellSelectionMode = false;
340 if (!cellSelectionMode)
342 rv = DeleteSelectionAndPrepareToCreateNode();
343 NS_ENSURE_SUCCESS(rv, rv);
345 if (aClearStyle) {
346 // pasting does not inherit local inline styles
347 nsCOMPtr<nsIDOMNode> tmpNode =
348 do_QueryInterface(selection->GetAnchorNode());
349 int32_t tmpOffset = static_cast<int32_t>(selection->AnchorOffset());
350 rv = ClearStyle(address_of(tmpNode), &tmpOffset, nullptr, nullptr);
351 NS_ENSURE_SUCCESS(rv, rv);
354 else
356 // delete whole cells: we will replace with new table content
357 { // Braces for artificial block to scope nsAutoSelectionReset.
358 // Save current selection since DeleteTableCell perturbs it
359 nsAutoSelectionReset selectionResetter(selection, this);
360 rv = DeleteTableCell(1);
361 NS_ENSURE_SUCCESS(rv, rv);
363 // collapse selection to beginning of deleted table content
364 selection->CollapseToStart();
367 // give rules a chance to handle or cancel
368 nsTextRulesInfo ruleInfo(EditAction::insertElement);
369 bool cancel, handled;
370 rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
371 NS_ENSURE_SUCCESS(rv, rv);
372 if (cancel) {
373 return NS_OK; // rules canceled the operation
376 if (!handled)
378 // The rules code (WillDoAction above) might have changed the selection.
379 // refresh our memory...
380 rv = GetStartNodeAndOffset(selection, getter_AddRefs(parentNode), &offsetOfNewNode);
381 NS_ENSURE_SUCCESS(rv, rv);
382 NS_ENSURE_TRUE(parentNode, NS_ERROR_FAILURE);
384 // Adjust position based on the first node we are going to insert.
385 NormalizeEOLInsertPosition(GetAsDOMNode(nodeList[0]),
386 address_of(parentNode), &offsetOfNewNode);
388 // if there are any invisible br's after our insertion point, remove them.
389 // this is because if there is a br at end of what we paste, it will make
390 // the invisible br visible.
391 nsWSRunObject wsObj(this, parentNode, offsetOfNewNode);
392 if (wsObj.mEndReasonNode &&
393 nsTextEditUtils::IsBreak(wsObj.mEndReasonNode) &&
394 !IsVisBreak(wsObj.mEndReasonNode)) {
395 rv = DeleteNode(wsObj.mEndReasonNode);
396 NS_ENSURE_SUCCESS(rv, rv);
399 // Remember if we are in a link.
400 bool bStartedInLink = IsInLink(parentNode);
402 // Are we in a text node? If so, split it.
403 if (IsTextNode(parentNode))
405 nsCOMPtr<nsIContent> parentContent = do_QueryInterface(parentNode);
406 NS_ENSURE_STATE(parentContent || !parentNode);
407 offsetOfNewNode = SplitNodeDeep(*parentContent, *parentContent,
408 offsetOfNewNode);
409 NS_ENSURE_STATE(offsetOfNewNode != -1);
410 nsCOMPtr<nsIDOMNode> temp;
411 rv = parentNode->GetParentNode(getter_AddRefs(temp));
412 NS_ENSURE_SUCCESS(rv, rv);
413 parentNode = temp;
416 // build up list of parents of first node in list that are either
417 // lists or tables. First examine front of paste node list.
418 nsTArray<OwningNonNull<Element>> startListAndTableArray;
419 GetListAndTableParents(StartOrEnd::start, nodeList,
420 startListAndTableArray);
422 // remember number of lists and tables above us
423 int32_t highWaterMark = -1;
424 if (startListAndTableArray.Length() > 0) {
425 highWaterMark = DiscoverPartialListsAndTables(nodeList,
426 startListAndTableArray);
429 // if we have pieces of tables or lists to be inserted, let's force the paste
430 // to deal with table elements right away, so that it doesn't orphan some
431 // table or list contents outside the table or list.
432 if (highWaterMark >= 0)
434 ReplaceOrphanedStructure(StartOrEnd::start, nodeList,
435 startListAndTableArray, highWaterMark);
438 // Now go through the same process again for the end of the paste node list.
439 nsTArray<OwningNonNull<Element>> endListAndTableArray;
440 GetListAndTableParents(StartOrEnd::end, nodeList, endListAndTableArray);
441 highWaterMark = -1;
443 // remember number of lists and tables above us
444 if (endListAndTableArray.Length() > 0) {
445 highWaterMark = DiscoverPartialListsAndTables(nodeList,
446 endListAndTableArray);
449 // don't orphan partial list or table structure
450 if (highWaterMark >= 0)
452 ReplaceOrphanedStructure(StartOrEnd::end, nodeList,
453 endListAndTableArray, highWaterMark);
456 // Loop over the node list and paste the nodes:
457 nsCOMPtr<nsIDOMNode> parentBlock, lastInsertNode, insertedContextParent;
458 int32_t listCount = nodeList.Length();
459 int32_t j;
460 if (IsBlockNode(parentNode))
461 parentBlock = parentNode;
462 else
463 parentBlock = GetBlockNodeParent(parentNode);
465 for (j=0; j<listCount; j++)
467 bool bDidInsert = false;
468 nsCOMPtr<nsIDOMNode> curNode = nodeList[j]->AsDOMNode();
470 NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
471 NS_ENSURE_TRUE(curNode != fragmentAsNode, NS_ERROR_FAILURE);
472 NS_ENSURE_TRUE(!nsTextEditUtils::IsBody(curNode), NS_ERROR_FAILURE);
474 if (insertedContextParent)
476 // if we had to insert something higher up in the paste hierarchy, we want to
477 // skip any further paste nodes that descend from that. Else we will paste twice.
478 if (nsEditorUtils::IsDescendantOf(curNode, insertedContextParent))
479 continue;
482 // give the user a hand on table element insertion. if they have
483 // a table or table row on the clipboard, and are trying to insert
484 // into a table or table row, insert the appropriate children instead.
485 if ( (nsHTMLEditUtils::IsTableRow(curNode) && nsHTMLEditUtils::IsTableRow(parentNode))
486 && (nsHTMLEditUtils::IsTable(curNode) || nsHTMLEditUtils::IsTable(parentNode)) )
488 nsCOMPtr<nsIDOMNode> child;
489 curNode->GetFirstChild(getter_AddRefs(child));
490 while (child)
492 rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true);
493 if (NS_FAILED(rv))
494 break;
496 bDidInsert = true;
497 lastInsertNode = child;
498 offsetOfNewNode++;
500 curNode->GetFirstChild(getter_AddRefs(child));
503 // give the user a hand on list insertion. if they have
504 // a list on the clipboard, and are trying to insert
505 // into a list or list item, insert the appropriate children instead,
506 // ie, merge the lists instead of pasting in a sublist.
507 else if (nsHTMLEditUtils::IsList(curNode) &&
508 (nsHTMLEditUtils::IsList(parentNode) || nsHTMLEditUtils::IsListItem(parentNode)) )
510 nsCOMPtr<nsIDOMNode> child, tmp;
511 curNode->GetFirstChild(getter_AddRefs(child));
512 while (child)
514 if (nsHTMLEditUtils::IsListItem(child) || nsHTMLEditUtils::IsList(child))
516 // Check if we are pasting into empty list item. If so
517 // delete it and paste into parent list instead.
518 if (nsHTMLEditUtils::IsListItem(parentNode))
520 bool isEmpty;
521 rv = IsEmptyNode(parentNode, &isEmpty, true);
522 if (NS_SUCCEEDED(rv) && isEmpty)
524 int32_t newOffset;
525 nsCOMPtr<nsIDOMNode> listNode = GetNodeLocation(parentNode, &newOffset);
526 if (listNode)
528 DeleteNode(parentNode);
529 parentNode = listNode;
530 offsetOfNewNode = newOffset;
534 rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true);
535 if (NS_FAILED(rv))
536 break;
538 bDidInsert = true;
539 lastInsertNode = child;
540 offsetOfNewNode++;
542 else
544 curNode->RemoveChild(child, getter_AddRefs(tmp));
546 curNode->GetFirstChild(getter_AddRefs(child));
549 } else if (parentBlock && nsHTMLEditUtils::IsPre(parentBlock) &&
550 nsHTMLEditUtils::IsPre(curNode)) {
551 // Check for pre's going into pre's.
552 nsCOMPtr<nsIDOMNode> child, tmp;
553 curNode->GetFirstChild(getter_AddRefs(child));
554 while (child)
556 rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true);
557 if (NS_FAILED(rv))
558 break;
560 bDidInsert = true;
561 lastInsertNode = child;
562 offsetOfNewNode++;
564 curNode->GetFirstChild(getter_AddRefs(child));
568 if (!bDidInsert || NS_FAILED(rv))
570 // try to insert
571 rv = InsertNodeAtPoint(curNode, address_of(parentNode), &offsetOfNewNode, true);
572 if (NS_SUCCEEDED(rv))
574 bDidInsert = true;
575 lastInsertNode = curNode;
578 // Assume failure means no legal parent in the document hierarchy,
579 // try again with the parent of curNode in the paste hierarchy.
580 nsCOMPtr<nsIDOMNode> parent;
581 while (NS_FAILED(rv) && curNode)
583 curNode->GetParentNode(getter_AddRefs(parent));
584 if (parent && !nsTextEditUtils::IsBody(parent))
586 rv = InsertNodeAtPoint(parent, address_of(parentNode), &offsetOfNewNode, true);
587 if (NS_SUCCEEDED(rv))
589 bDidInsert = true;
590 insertedContextParent = parent;
591 lastInsertNode = GetChildAt(parentNode, offsetOfNewNode);
594 curNode = parent;
597 if (lastInsertNode)
599 parentNode = GetNodeLocation(lastInsertNode, &offsetOfNewNode);
600 offsetOfNewNode++;
604 // Now collapse the selection to the end of what we just inserted:
605 if (lastInsertNode)
607 // set selection to the end of what we just pasted.
608 nsCOMPtr<nsIDOMNode> selNode, tmp, highTable;
609 int32_t selOffset;
611 // but don't cross tables
612 if (!nsHTMLEditUtils::IsTable(lastInsertNode))
614 nsCOMPtr<nsINode> lastInsertNode_ = do_QueryInterface(lastInsertNode);
615 NS_ENSURE_STATE(lastInsertNode_ || !lastInsertNode);
616 selNode = GetAsDOMNode(GetLastEditableLeaf(*lastInsertNode_));
617 tmp = selNode;
618 while (tmp && (tmp != lastInsertNode))
620 if (nsHTMLEditUtils::IsTable(tmp))
621 highTable = tmp;
622 nsCOMPtr<nsIDOMNode> parent = tmp;
623 tmp->GetParentNode(getter_AddRefs(parent));
624 tmp = parent;
626 if (highTable)
627 selNode = highTable;
629 if (!selNode)
630 selNode = lastInsertNode;
631 if (IsTextNode(selNode) || (IsContainer(selNode) && !nsHTMLEditUtils::IsTable(selNode)))
633 rv = GetLengthOfDOMNode(selNode, (uint32_t&)selOffset);
634 NS_ENSURE_SUCCESS(rv, rv);
636 else // we need to find a container for selection. Look up.
638 tmp = selNode;
639 selNode = GetNodeLocation(tmp, &selOffset);
640 // selNode might be null in case a mutation listener removed
641 // the stuff we just inserted from the DOM.
642 NS_ENSURE_STATE(selNode);
643 ++selOffset; // want to be *after* last leaf node in paste
646 // make sure we don't end up with selection collapsed after an invisible break node
647 nsWSRunObject wsRunObj(this, selNode, selOffset);
648 nsCOMPtr<nsINode> visNode;
649 int32_t outVisOffset=0;
650 WSType visType;
651 nsCOMPtr<nsINode> selNode_(do_QueryInterface(selNode));
652 wsRunObj.PriorVisibleNode(selNode_, selOffset, address_of(visNode),
653 &outVisOffset, &visType);
654 if (visType == WSType::br) {
655 // we are after a break. Is it visible? Despite the name,
656 // PriorVisibleNode does not make that determination for breaks.
657 // It also may not return the break in visNode. We have to pull it
658 // out of the nsWSRunObject's state.
659 if (!IsVisBreak(wsRunObj.mStartReasonNode))
661 // don't leave selection past an invisible break;
662 // reset {selNode,selOffset} to point before break
663 selNode = GetNodeLocation(GetAsDOMNode(wsRunObj.mStartReasonNode), &selOffset);
664 // we want to be inside any inline style prior to break
665 nsWSRunObject wsRunObj(this, selNode, selOffset);
666 selNode_ = do_QueryInterface(selNode);
667 wsRunObj.PriorVisibleNode(selNode_, selOffset, address_of(visNode),
668 &outVisOffset, &visType);
669 if (visType == WSType::text || visType == WSType::normalWS) {
670 selNode = GetAsDOMNode(visNode);
671 selOffset = outVisOffset; // PriorVisibleNode already set offset to _after_ the text or ws
672 } else if (visType == WSType::special) {
673 // prior visible thing is an image or some other non-text thingy.
674 // We want to be right after it.
675 selNode = GetNodeLocation(GetAsDOMNode(wsRunObj.mStartReasonNode), &selOffset);
676 ++selOffset;
680 selection->Collapse(selNode, selOffset);
682 // if we just pasted a link, discontinue link style
683 nsCOMPtr<nsIDOMNode> link;
684 if (!bStartedInLink && IsInLink(selNode, address_of(link)))
686 // so, if we just pasted a link, I split it. Why do that instead of just
687 // nudging selection point beyond it? Because it might have ended in a BR
688 // that is not visible. If so, the code above just placed selection
689 // inside that. So I split it instead.
690 nsCOMPtr<nsIContent> linkContent = do_QueryInterface(link);
691 NS_ENSURE_STATE(linkContent || !link);
692 nsCOMPtr<nsIContent> selContent = do_QueryInterface(selNode);
693 NS_ENSURE_STATE(selContent || !selNode);
694 nsCOMPtr<nsIContent> leftLink;
695 SplitNodeDeep(*linkContent, *selContent, selOffset,
696 EmptyContainers::no, getter_AddRefs(leftLink));
697 if (leftLink) {
698 selNode = GetNodeLocation(GetAsDOMNode(leftLink), &selOffset);
699 selection->Collapse(selNode, selOffset+1);
705 return mRules->DidDoAction(selection, &ruleInfo, rv);
708 NS_IMETHODIMP
709 nsHTMLEditor::AddInsertionListener(nsIContentFilter *aListener)
711 NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
713 // don't let a listener be added more than once
714 if (!mContentFilters.Contains(aListener)) {
715 mContentFilters.AppendElement(*aListener);
718 return NS_OK;
721 NS_IMETHODIMP
722 nsHTMLEditor::RemoveInsertionListener(nsIContentFilter *aListener)
724 NS_ENSURE_TRUE(aListener, NS_ERROR_FAILURE);
726 mContentFilters.RemoveElement(aListener);
728 return NS_OK;
731 nsresult
732 nsHTMLEditor::DoContentFilterCallback(const nsAString &aFlavor,
733 nsIDOMDocument *sourceDoc,
734 bool aWillDeleteSelection,
735 nsIDOMNode **aFragmentAsNode,
736 nsIDOMNode **aFragStartNode,
737 int32_t *aFragStartOffset,
738 nsIDOMNode **aFragEndNode,
739 int32_t *aFragEndOffset,
740 nsIDOMNode **aTargetNode,
741 int32_t *aTargetOffset,
742 bool *aDoContinue)
744 *aDoContinue = true;
746 for (auto& listener : mContentFilters) {
747 if (!*aDoContinue) {
748 break;
750 listener->NotifyOfInsertion(aFlavor, nullptr, sourceDoc,
751 aWillDeleteSelection, aFragmentAsNode,
752 aFragStartNode, aFragStartOffset,
753 aFragEndNode, aFragEndOffset, aTargetNode,
754 aTargetOffset, aDoContinue);
757 return NS_OK;
760 bool
761 nsHTMLEditor::IsInLink(nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *outLink)
763 NS_ENSURE_TRUE(aNode, false);
764 if (outLink)
765 *outLink = nullptr;
766 nsCOMPtr<nsIDOMNode> tmp, node = aNode;
767 while (node)
769 if (nsHTMLEditUtils::IsLink(node))
771 if (outLink)
772 *outLink = node;
773 return true;
775 tmp = node;
776 tmp->GetParentNode(getter_AddRefs(node));
778 return false;
782 nsresult
783 nsHTMLEditor::StripFormattingNodes(nsIContent& aNode, bool aListOnly)
785 if (aNode.TextIsOnlyWhitespace()) {
786 nsCOMPtr<nsINode> parent = aNode.GetParentNode();
787 if (parent) {
788 if (!aListOnly || nsHTMLEditUtils::IsList(parent)) {
789 ErrorResult rv;
790 parent->RemoveChild(aNode, rv);
791 return rv.StealNSResult();
793 return NS_OK;
797 if (!aNode.IsHTMLElement(nsGkAtoms::pre)) {
798 nsCOMPtr<nsIContent> child = aNode.GetLastChild();
799 while (child) {
800 nsCOMPtr<nsIContent> previous = child->GetPreviousSibling();
801 nsresult rv = StripFormattingNodes(*child, aListOnly);
802 NS_ENSURE_SUCCESS(rv, rv);
803 child = previous.forget();
806 return NS_OK;
809 NS_IMETHODIMP nsHTMLEditor::PrepareTransferable(nsITransferable **transferable)
811 return NS_OK;
814 nsresult
815 nsHTMLEditor::PrepareHTMLTransferable(nsITransferable **aTransferable)
817 // Create generic Transferable for getting the data
818 nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", aTransferable);
819 NS_ENSURE_SUCCESS(rv, rv);
821 // Get the nsITransferable interface for getting the data from the clipboard
822 if (aTransferable)
824 nsCOMPtr<nsIDocument> destdoc = GetDocument();
825 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
826 (*aTransferable)->Init(loadContext);
828 // Create the desired DataFlavor for the type of data
829 // we want to get out of the transferable
830 // This should only happen in html editors, not plaintext
831 if (!IsPlaintextEditor())
833 (*aTransferable)->AddDataFlavor(kNativeHTMLMime);
834 (*aTransferable)->AddDataFlavor(kHTMLMime);
835 (*aTransferable)->AddDataFlavor(kFileMime);
837 switch (Preferences::GetInt("clipboard.paste_image_type", 1))
839 case 0: // prefer JPEG over PNG over GIF encoding
840 (*aTransferable)->AddDataFlavor(kJPEGImageMime);
841 (*aTransferable)->AddDataFlavor(kJPGImageMime);
842 (*aTransferable)->AddDataFlavor(kPNGImageMime);
843 (*aTransferable)->AddDataFlavor(kGIFImageMime);
844 break;
845 case 1: // prefer PNG over JPEG over GIF encoding (default)
846 default:
847 (*aTransferable)->AddDataFlavor(kPNGImageMime);
848 (*aTransferable)->AddDataFlavor(kJPEGImageMime);
849 (*aTransferable)->AddDataFlavor(kJPGImageMime);
850 (*aTransferable)->AddDataFlavor(kGIFImageMime);
851 break;
852 case 2: // prefer GIF over JPEG over PNG encoding
853 (*aTransferable)->AddDataFlavor(kGIFImageMime);
854 (*aTransferable)->AddDataFlavor(kJPEGImageMime);
855 (*aTransferable)->AddDataFlavor(kJPGImageMime);
856 (*aTransferable)->AddDataFlavor(kPNGImageMime);
857 break;
860 (*aTransferable)->AddDataFlavor(kUnicodeMime);
861 (*aTransferable)->AddDataFlavor(kMozTextInternal);
864 return NS_OK;
867 bool
868 FindIntegerAfterString(const char *aLeadingString,
869 nsCString &aCStr, int32_t &foundNumber)
871 // first obtain offsets from cfhtml str
872 int32_t numFront = aCStr.Find(aLeadingString);
873 if (numFront == -1)
874 return false;
875 numFront += strlen(aLeadingString);
877 int32_t numBack = aCStr.FindCharInSet(CRLF, numFront);
878 if (numBack == -1)
879 return false;
881 nsAutoCString numStr(Substring(aCStr, numFront, numBack-numFront));
882 nsresult errorCode;
883 foundNumber = numStr.ToInteger(&errorCode);
884 return true;
887 nsresult
888 RemoveFragComments(nsCString & aStr)
890 // remove the StartFragment/EndFragment comments from the str, if present
891 int32_t startCommentIndx = aStr.Find("<!--StartFragment");
892 if (startCommentIndx >= 0)
894 int32_t startCommentEnd = aStr.Find("-->", false, startCommentIndx);
895 if (startCommentEnd > startCommentIndx)
896 aStr.Cut(startCommentIndx, (startCommentEnd+3)-startCommentIndx);
898 int32_t endCommentIndx = aStr.Find("<!--EndFragment");
899 if (endCommentIndx >= 0)
901 int32_t endCommentEnd = aStr.Find("-->", false, endCommentIndx);
902 if (endCommentEnd > endCommentIndx)
903 aStr.Cut(endCommentIndx, (endCommentEnd+3)-endCommentIndx);
905 return NS_OK;
908 nsresult
909 nsHTMLEditor::ParseCFHTML(nsCString & aCfhtml, char16_t **aStuffToPaste, char16_t **aCfcontext)
911 // First obtain offsets from cfhtml str.
912 int32_t startHTML, endHTML, startFragment, endFragment;
913 if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) ||
914 startHTML < -1)
915 return NS_ERROR_FAILURE;
916 if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) ||
917 endHTML < -1)
918 return NS_ERROR_FAILURE;
919 if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) ||
920 startFragment < 0)
921 return NS_ERROR_FAILURE;
922 if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) ||
923 startFragment < 0)
924 return NS_ERROR_FAILURE;
926 // The StartHTML and EndHTML markers are allowed to be -1 to include everything.
927 // See Reference: MSDN doc entitled "HTML Clipboard Format"
928 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
929 if (startHTML == -1) {
930 startHTML = aCfhtml.Find("<!--StartFragment-->");
931 if (startHTML == -1)
932 return NS_OK;
934 if (endHTML == -1) {
935 const char endFragmentMarker[] = "<!--EndFragment-->";
936 endHTML = aCfhtml.Find(endFragmentMarker);
937 if (endHTML == -1)
938 return NS_OK;
939 endHTML += ArrayLength(endFragmentMarker) - 1;
942 // create context string
943 nsAutoCString contextUTF8(Substring(aCfhtml, startHTML, startFragment - startHTML) +
944 NS_LITERAL_CSTRING("<!--" kInsertCookie "-->") +
945 Substring(aCfhtml, endFragment, endHTML - endFragment));
947 // validate startFragment
948 // make sure it's not in the middle of a HTML tag
949 // see bug #228879 for more details
950 int32_t curPos = startFragment;
951 while (curPos > startHTML)
953 if (aCfhtml[curPos] == '>')
955 // working backwards, the first thing we see is the end of a tag
956 // so StartFragment is good, so do nothing.
957 break;
959 else if (aCfhtml[curPos] == '<')
961 // if we are at the start, then we want to see the '<'
962 if (curPos != startFragment)
964 // working backwards, the first thing we see is the start of a tag
965 // so StartFragment is bad, so we need to update it.
966 NS_ERROR("StartFragment byte count in the clipboard looks bad, see bug #228879");
967 startFragment = curPos - 1;
969 break;
971 else
973 curPos--;
977 // create fragment string
978 nsAutoCString fragmentUTF8(Substring(aCfhtml, startFragment, endFragment-startFragment));
980 // remove the StartFragment/EndFragment comments from the fragment, if present
981 RemoveFragComments(fragmentUTF8);
983 // remove the StartFragment/EndFragment comments from the context, if present
984 RemoveFragComments(contextUTF8);
986 // convert both strings to usc2
987 const nsAFlatString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8);
988 const nsAFlatString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8);
990 // translate platform linebreaks for fragment
991 int32_t oldLengthInChars = fragUcs2Str.Length() + 1; // +1 to include null terminator
992 int32_t newLengthInChars = 0;
993 *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(fragUcs2Str.get(),
994 nsLinebreakConverter::eLinebreakAny,
995 nsLinebreakConverter::eLinebreakContent,
996 oldLengthInChars, &newLengthInChars);
997 NS_ENSURE_TRUE(*aStuffToPaste, NS_ERROR_FAILURE);
999 // translate platform linebreaks for context
1000 oldLengthInChars = cntxtUcs2Str.Length() + 1; // +1 to include null terminator
1001 newLengthInChars = 0;
1002 *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(cntxtUcs2Str.get(),
1003 nsLinebreakConverter::eLinebreakAny,
1004 nsLinebreakConverter::eLinebreakContent,
1005 oldLengthInChars, &newLengthInChars);
1006 // it's ok for context to be empty. frag might be whole doc and contain all its context.
1008 // we're done!
1009 return NS_OK;
1012 nsresult nsHTMLEditor::InsertObject(const char* aType, nsISupports* aObject, bool aIsSafe,
1013 nsIDOMDocument *aSourceDoc,
1014 nsIDOMNode *aDestinationNode,
1015 int32_t aDestOffset,
1016 bool aDoDeleteSelection)
1018 nsresult rv;
1020 nsAutoCString type(aType);
1022 // Check to see if we can insert an image file
1023 bool insertAsImage = false;
1024 nsCOMPtr<nsIFile> fileObj;
1025 if (type.EqualsLiteral(kFileMime))
1027 fileObj = do_QueryInterface(aObject);
1028 if (fileObj)
1030 // Accept any image type fed to us
1031 if (nsContentUtils::IsFileImage(fileObj, type))
1033 insertAsImage = true;
1035 else
1037 // Reset type.
1038 type.AssignLiteral(kFileMime);
1043 if (type.EqualsLiteral(kJPEGImageMime) ||
1044 type.EqualsLiteral(kJPGImageMime) ||
1045 type.EqualsLiteral(kPNGImageMime) ||
1046 type.EqualsLiteral(kGIFImageMime) ||
1047 insertAsImage)
1049 nsCString imageData;
1050 if (insertAsImage)
1052 rv = nsContentUtils::SlurpFileToString(fileObj, imageData);
1053 NS_ENSURE_SUCCESS(rv, rv);
1055 else
1057 nsCOMPtr<nsIInputStream> imageStream = do_QueryInterface(aObject);
1058 NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE);
1060 rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
1061 NS_ENSURE_SUCCESS(rv, rv);
1063 rv = imageStream->Close();
1064 NS_ENSURE_SUCCESS(rv, rv);
1067 nsAutoCString data64;
1068 rv = Base64Encode(imageData, data64);
1069 NS_ENSURE_SUCCESS(rv, rv);
1071 nsAutoString stuffToPaste;
1072 stuffToPaste.AssignLiteral("<IMG src=\"data:");
1073 AppendUTF8toUTF16(type, stuffToPaste);
1074 stuffToPaste.AppendLiteral(";base64,");
1075 AppendUTF8toUTF16(data64, stuffToPaste);
1076 stuffToPaste.AppendLiteral("\" alt=\"\" >");
1077 nsAutoEditBatch beginBatching(this);
1078 rv = DoInsertHTMLWithContext(stuffToPaste, EmptyString(), EmptyString(),
1079 NS_LITERAL_STRING(kFileMime),
1080 aSourceDoc,
1081 aDestinationNode, aDestOffset,
1082 aDoDeleteSelection,
1083 aIsSafe, false);
1086 return NS_OK;
1089 nsresult
1090 nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable,
1091 nsIDOMDocument *aSourceDoc,
1092 const nsAString & aContextStr,
1093 const nsAString & aInfoStr,
1094 bool havePrivateHTMLFlavor,
1095 nsIDOMNode *aDestinationNode,
1096 int32_t aDestOffset,
1097 bool aDoDeleteSelection)
1099 nsresult rv = NS_OK;
1100 nsXPIDLCString bestFlavor;
1101 nsCOMPtr<nsISupports> genericDataObj;
1102 uint32_t len = 0;
1103 if (NS_SUCCEEDED(transferable->GetAnyTransferData(getter_Copies(bestFlavor), getter_AddRefs(genericDataObj), &len)))
1105 nsAutoTxnsConserveSelection dontSpazMySelection(this);
1106 nsAutoString flavor;
1107 flavor.AssignWithConversion(bestFlavor);
1108 nsAutoString stuffToPaste;
1109 bool isSafe = IsSafeToInsertData(aSourceDoc);
1111 if (0 == nsCRT::strcmp(bestFlavor, kFileMime) ||
1112 0 == nsCRT::strcmp(bestFlavor, kJPEGImageMime) ||
1113 0 == nsCRT::strcmp(bestFlavor, kJPGImageMime) ||
1114 0 == nsCRT::strcmp(bestFlavor, kPNGImageMime) ||
1115 0 == nsCRT::strcmp(bestFlavor, kGIFImageMime)) {
1116 rv = InsertObject(bestFlavor, genericDataObj, isSafe,
1117 aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection);
1119 else if (0 == nsCRT::strcmp(bestFlavor, kNativeHTMLMime))
1121 // note cf_html uses utf8, hence use length = len, not len/2 as in flavors below
1122 nsCOMPtr<nsISupportsCString> textDataObj = do_QueryInterface(genericDataObj);
1123 if (textDataObj && len > 0)
1125 nsAutoCString cfhtml;
1126 textDataObj->GetData(cfhtml);
1127 NS_ASSERTION(cfhtml.Length() <= (len), "Invalid length!");
1128 nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
1130 rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
1131 if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty())
1133 nsAutoEditBatch beginBatching(this);
1134 // If we have our private HTML flavor, we will only use the fragment
1135 // from the CF_HTML. The rest comes from the clipboard.
1136 if (havePrivateHTMLFlavor) {
1137 rv = DoInsertHTMLWithContext(cffragment,
1138 aContextStr, aInfoStr, flavor,
1139 aSourceDoc,
1140 aDestinationNode, aDestOffset,
1141 aDoDeleteSelection,
1142 isSafe);
1143 } else {
1144 rv = DoInsertHTMLWithContext(cffragment,
1145 cfcontext, cfselection, flavor,
1146 aSourceDoc,
1147 aDestinationNode, aDestOffset,
1148 aDoDeleteSelection,
1149 isSafe);
1152 } else {
1153 // In some platforms (like Linux), the clipboard might return data
1154 // requested for unknown flavors (for example:
1155 // application/x-moz-nativehtml). In this case, treat the data
1156 // to be pasted as mere HTML to get the best chance of pasting it
1157 // correctly.
1158 bestFlavor.AssignLiteral(kHTMLMime);
1159 // Fall through the next case
1163 if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime) ||
1164 0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) ||
1165 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)) {
1166 nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj);
1167 if (textDataObj && len > 0) {
1168 nsAutoString text;
1169 textDataObj->GetData(text);
1170 NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
1171 stuffToPaste.Assign(text.get(), len / 2);
1172 } else {
1173 nsCOMPtr<nsISupportsCString> textDataObj(do_QueryInterface(genericDataObj));
1174 if (textDataObj && len > 0) {
1175 nsAutoCString text;
1176 textDataObj->GetData(text);
1177 NS_ASSERTION(text.Length() <= len, "Invalid length!");
1178 stuffToPaste.Assign(NS_ConvertUTF8toUTF16(Substring(text, 0, len)));
1182 if (!stuffToPaste.IsEmpty()) {
1183 nsAutoEditBatch beginBatching(this);
1184 if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime)) {
1185 rv = DoInsertHTMLWithContext(stuffToPaste,
1186 aContextStr, aInfoStr, flavor,
1187 aSourceDoc,
1188 aDestinationNode, aDestOffset,
1189 aDoDeleteSelection,
1190 isSafe);
1191 } else {
1192 rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
1198 // Try to scroll the selection into view if the paste succeeded
1199 if (NS_SUCCEEDED(rv))
1200 ScrollSelectionIntoView(false);
1202 return rv;
1205 static void
1206 GetStringFromDataTransfer(nsIDOMDataTransfer *aDataTransfer, const nsAString& aType,
1207 int32_t aIndex, nsAString& aOutputString)
1209 nsCOMPtr<nsIVariant> variant;
1210 DataTransfer::Cast(aDataTransfer)->GetDataAtNoSecurityCheck(aType, aIndex, getter_AddRefs(variant));
1211 if (variant)
1212 variant->GetAsAString(aOutputString);
1215 nsresult nsHTMLEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer,
1216 int32_t aIndex,
1217 nsIDOMDocument *aSourceDoc,
1218 nsIDOMNode *aDestinationNode,
1219 int32_t aDestOffset,
1220 bool aDoDeleteSelection)
1222 ErrorResult rv;
1223 RefPtr<DOMStringList> types = aDataTransfer->MozTypesAt(aIndex, rv);
1224 if (rv.Failed()) {
1225 return rv.StealNSResult();
1228 bool hasPrivateHTMLFlavor = types->Contains(NS_LITERAL_STRING(kHTMLContext));
1230 bool isText = IsPlaintextEditor();
1231 bool isSafe = IsSafeToInsertData(aSourceDoc);
1233 uint32_t length = types->Length();
1234 for (uint32_t t = 0; t < length; t++) {
1235 nsAutoString type;
1236 types->Item(t, type);
1238 if (!isText) {
1239 if (type.EqualsLiteral(kFileMime) ||
1240 type.EqualsLiteral(kJPEGImageMime) ||
1241 type.EqualsLiteral(kJPGImageMime) ||
1242 type.EqualsLiteral(kPNGImageMime) ||
1243 type.EqualsLiteral(kGIFImageMime)) {
1244 nsCOMPtr<nsIVariant> variant;
1245 DataTransfer::Cast(aDataTransfer)->GetDataAtNoSecurityCheck(type, aIndex, getter_AddRefs(variant));
1246 if (variant) {
1247 nsCOMPtr<nsISupports> object;
1248 variant->GetAsISupports(getter_AddRefs(object));
1249 return InsertObject(NS_ConvertUTF16toUTF8(type).get(), object, isSafe,
1250 aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection);
1253 else if (type.EqualsLiteral(kNativeHTMLMime)) {
1254 // Windows only clipboard parsing.
1255 nsAutoString text;
1256 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
1257 NS_ConvertUTF16toUTF8 cfhtml(text);
1259 nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
1261 nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
1262 if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty())
1264 nsAutoEditBatch beginBatching(this);
1266 if (hasPrivateHTMLFlavor) {
1267 // If we have our private HTML flavor, we will only use the fragment
1268 // from the CF_HTML. The rest comes from the clipboard.
1269 nsAutoString contextString, infoString;
1270 GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), aIndex, contextString);
1271 GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), aIndex, infoString);
1272 return DoInsertHTMLWithContext(cffragment,
1273 contextString, infoString, type,
1274 aSourceDoc,
1275 aDestinationNode, aDestOffset,
1276 aDoDeleteSelection,
1277 isSafe);
1279 else {
1280 return DoInsertHTMLWithContext(cffragment,
1281 cfcontext, cfselection, type,
1282 aSourceDoc,
1283 aDestinationNode, aDestOffset,
1284 aDoDeleteSelection,
1285 isSafe);
1289 else if (type.EqualsLiteral(kHTMLMime)) {
1290 nsAutoString text, contextString, infoString;
1291 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
1292 GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), aIndex, contextString);
1293 GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), aIndex, infoString);
1295 nsAutoEditBatch beginBatching(this);
1296 if (type.EqualsLiteral(kHTMLMime)) {
1297 return DoInsertHTMLWithContext(text,
1298 contextString, infoString, type,
1299 aSourceDoc,
1300 aDestinationNode, aDestOffset,
1301 aDoDeleteSelection,
1302 isSafe);
1307 if (type.EqualsLiteral(kTextMime) ||
1308 type.EqualsLiteral(kMozTextInternal)) {
1309 nsAutoString text;
1310 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
1312 nsAutoEditBatch beginBatching(this);
1313 return InsertTextAt(text, aDestinationNode, aDestOffset, aDoDeleteSelection);
1317 return NS_OK;
1320 bool nsHTMLEditor::HavePrivateHTMLFlavor(nsIClipboard *aClipboard)
1322 // check the clipboard for our special kHTMLContext flavor. If that is there, we know
1323 // we have our own internal html format on clipboard.
1325 NS_ENSURE_TRUE(aClipboard, false);
1326 bool bHavePrivateHTMLFlavor = false;
1328 const char* flavArray[] = { kHTMLContext };
1330 if (NS_SUCCEEDED(aClipboard->HasDataMatchingFlavors(flavArray,
1331 ArrayLength(flavArray), nsIClipboard::kGlobalClipboard,
1332 &bHavePrivateHTMLFlavor)))
1333 return bHavePrivateHTMLFlavor;
1335 return false;
1339 NS_IMETHODIMP nsHTMLEditor::Paste(int32_t aSelectionType)
1341 if (!FireClipboardEvent(ePaste, aSelectionType)) {
1342 return NS_OK;
1345 // Get Clipboard Service
1346 nsresult rv;
1347 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1348 NS_ENSURE_SUCCESS(rv, rv);
1350 // Get the nsITransferable interface for getting the data from the clipboard
1351 nsCOMPtr<nsITransferable> trans;
1352 rv = PrepareHTMLTransferable(getter_AddRefs(trans));
1353 NS_ENSURE_SUCCESS(rv, rv);
1354 NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
1355 // Get the Data from the clipboard
1356 rv = clipboard->GetData(trans, aSelectionType);
1357 NS_ENSURE_SUCCESS(rv, rv);
1358 if (!IsModifiable()) {
1359 return NS_OK;
1362 // also get additional html copy hints, if present
1363 nsAutoString contextStr, infoStr;
1365 // If we have our internal html flavor on the clipboard, there is special
1366 // context to use instead of cfhtml context.
1367 bool bHavePrivateHTMLFlavor = HavePrivateHTMLFlavor(clipboard);
1368 if (bHavePrivateHTMLFlavor)
1370 nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
1371 uint32_t contextLen, infoLen;
1372 nsCOMPtr<nsISupportsString> textDataObj;
1374 nsCOMPtr<nsITransferable> contextTrans =
1375 do_CreateInstance("@mozilla.org/widget/transferable;1");
1376 NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER);
1377 contextTrans->Init(nullptr);
1378 contextTrans->AddDataFlavor(kHTMLContext);
1379 clipboard->GetData(contextTrans, aSelectionType);
1380 contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen);
1382 nsCOMPtr<nsITransferable> infoTrans =
1383 do_CreateInstance("@mozilla.org/widget/transferable;1");
1384 NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER);
1385 infoTrans->Init(nullptr);
1386 infoTrans->AddDataFlavor(kHTMLInfo);
1387 clipboard->GetData(infoTrans, aSelectionType);
1388 infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen);
1390 if (contextDataObj)
1392 nsAutoString text;
1393 textDataObj = do_QueryInterface(contextDataObj);
1394 textDataObj->GetData(text);
1395 NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!");
1396 contextStr.Assign(text.get(), contextLen / 2);
1399 if (infoDataObj)
1401 nsAutoString text;
1402 textDataObj = do_QueryInterface(infoDataObj);
1403 textDataObj->GetData(text);
1404 NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!");
1405 infoStr.Assign(text.get(), infoLen / 2);
1409 // handle transferable hooks
1410 nsCOMPtr<nsIDOMDocument> domdoc;
1411 GetDocument(getter_AddRefs(domdoc));
1412 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans))
1413 return NS_OK;
1415 return InsertFromTransferable(trans, nullptr, contextStr, infoStr, bHavePrivateHTMLFlavor,
1416 nullptr, 0, true);
1419 NS_IMETHODIMP nsHTMLEditor::PasteTransferable(nsITransferable *aTransferable)
1421 // Use an invalid value for the clipboard type as data comes from aTransferable
1422 // and we don't currently implement a way to put that in the data transfer yet.
1423 if (!FireClipboardEvent(ePaste, nsIClipboard::kGlobalClipboard)) {
1424 return NS_OK;
1427 // handle transferable hooks
1428 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
1429 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable))
1430 return NS_OK;
1432 nsAutoString contextStr, infoStr;
1433 return InsertFromTransferable(aTransferable, nullptr, contextStr, infoStr, false,
1434 nullptr, 0, true);
1438 // HTML PasteNoFormatting. Ignore any HTML styles and formating in paste source
1440 NS_IMETHODIMP nsHTMLEditor::PasteNoFormatting(int32_t aSelectionType)
1442 if (!FireClipboardEvent(ePaste, aSelectionType)) {
1443 return NS_OK;
1446 ForceCompositionEnd();
1448 // Get Clipboard Service
1449 nsresult rv;
1450 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1451 NS_ENSURE_SUCCESS(rv, rv);
1453 // Get the nsITransferable interface for getting the data from the clipboard.
1454 // use nsPlaintextEditor::PrepareTransferable() to force unicode plaintext data.
1455 nsCOMPtr<nsITransferable> trans;
1456 rv = nsPlaintextEditor::PrepareTransferable(getter_AddRefs(trans));
1457 if (NS_SUCCEEDED(rv) && trans)
1459 // Get the Data from the clipboard
1460 if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
1462 const nsAFlatString& empty = EmptyString();
1463 rv = InsertFromTransferable(trans, nullptr, empty, empty, false, nullptr, 0,
1464 true);
1468 return rv;
1472 // The following arrays contain the MIME types that we can paste. The arrays
1473 // are used by CanPaste() and CanPasteTransferable() below.
1475 static const char* textEditorFlavors[] = { kUnicodeMime };
1476 static const char* textHtmlEditorFlavors[] = { kUnicodeMime, kHTMLMime,
1477 kJPEGImageMime, kJPGImageMime,
1478 kPNGImageMime, kGIFImageMime };
1480 NS_IMETHODIMP nsHTMLEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste)
1482 NS_ENSURE_ARG_POINTER(aCanPaste);
1483 *aCanPaste = false;
1485 // can't paste if readonly
1486 if (!IsModifiable()) {
1487 return NS_OK;
1490 nsresult rv;
1491 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1492 NS_ENSURE_SUCCESS(rv, rv);
1494 bool haveFlavors;
1496 // Use the flavors depending on the current editor mask
1497 if (IsPlaintextEditor())
1498 rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
1499 ArrayLength(textEditorFlavors),
1500 aSelectionType, &haveFlavors);
1501 else
1502 rv = clipboard->HasDataMatchingFlavors(textHtmlEditorFlavors,
1503 ArrayLength(textHtmlEditorFlavors),
1504 aSelectionType, &haveFlavors);
1506 NS_ENSURE_SUCCESS(rv, rv);
1508 *aCanPaste = haveFlavors;
1509 return NS_OK;
1512 NS_IMETHODIMP nsHTMLEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste)
1514 NS_ENSURE_ARG_POINTER(aCanPaste);
1516 // can't paste if readonly
1517 if (!IsModifiable()) {
1518 *aCanPaste = false;
1519 return NS_OK;
1522 // If |aTransferable| is null, assume that a paste will succeed.
1523 if (!aTransferable) {
1524 *aCanPaste = true;
1525 return NS_OK;
1528 // Peek in |aTransferable| to see if it contains a supported MIME type.
1530 // Use the flavors depending on the current editor mask
1531 const char ** flavors;
1532 unsigned length;
1533 if (IsPlaintextEditor()) {
1534 flavors = textEditorFlavors;
1535 length = ArrayLength(textEditorFlavors);
1536 } else {
1537 flavors = textHtmlEditorFlavors;
1538 length = ArrayLength(textHtmlEditorFlavors);
1541 for (unsigned int i = 0; i < length; i++, flavors++) {
1542 nsCOMPtr<nsISupports> data;
1543 uint32_t dataLen;
1544 nsresult rv = aTransferable->GetTransferData(*flavors,
1545 getter_AddRefs(data),
1546 &dataLen);
1547 if (NS_SUCCEEDED(rv) && data) {
1548 *aCanPaste = true;
1549 return NS_OK;
1553 *aCanPaste = false;
1554 return NS_OK;
1559 // HTML PasteAsQuotation: Paste in a blockquote type=cite
1561 NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation(int32_t aSelectionType)
1563 if (IsPlaintextEditor())
1564 return PasteAsPlaintextQuotation(aSelectionType);
1566 nsAutoString citation;
1567 return PasteAsCitedQuotation(citation, aSelectionType);
1570 NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsAString & aCitation,
1571 int32_t aSelectionType)
1573 nsAutoEditBatch beginBatching(this);
1574 nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
1576 // get selection
1577 RefPtr<Selection> selection = GetSelection();
1578 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1580 // give rules a chance to handle or cancel
1581 nsTextRulesInfo ruleInfo(EditAction::insertElement);
1582 bool cancel, handled;
1583 // Protect the edit rules object from dying
1584 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1585 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1586 NS_ENSURE_SUCCESS(rv, rv);
1587 if (cancel || handled) {
1588 return NS_OK; // rules canceled the operation
1591 nsCOMPtr<Element> newNode =
1592 DeleteSelectionAndCreateElement(*nsGkAtoms::blockquote);
1593 NS_ENSURE_TRUE(newNode, NS_ERROR_NULL_POINTER);
1595 // Try to set type=cite. Ignore it if this fails.
1596 newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1597 NS_LITERAL_STRING("cite"), true);
1599 // Set the selection to the underneath the node we just inserted:
1600 rv = selection->Collapse(newNode, 0);
1601 NS_ENSURE_SUCCESS(rv, rv);
1603 return Paste(aSelectionType);
1607 // Paste a plaintext quotation
1609 NS_IMETHODIMP nsHTMLEditor::PasteAsPlaintextQuotation(int32_t aSelectionType)
1611 // Get Clipboard Service
1612 nsresult rv;
1613 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1614 NS_ENSURE_SUCCESS(rv, rv);
1616 // Create generic Transferable for getting the data
1617 nsCOMPtr<nsITransferable> trans =
1618 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
1619 NS_ENSURE_SUCCESS(rv, rv);
1620 NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
1622 nsCOMPtr<nsIDocument> destdoc = GetDocument();
1623 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
1624 trans->Init(loadContext);
1626 // We only handle plaintext pastes here
1627 trans->AddDataFlavor(kUnicodeMime);
1629 // Get the Data from the clipboard
1630 clipboard->GetData(trans, aSelectionType);
1632 // Now we ask the transferable for the data
1633 // it still owns the data, we just have a pointer to it.
1634 // If it can't support a "text" output of the data the call will fail
1635 nsCOMPtr<nsISupports> genericDataObj;
1636 uint32_t len = 0;
1637 char* flav = 0;
1638 rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj), &len);
1639 NS_ENSURE_SUCCESS(rv, rv);
1641 if (flav && 0 == nsCRT::strcmp(flav, kUnicodeMime))
1643 nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj);
1644 if (textDataObj && len > 0)
1646 nsAutoString stuffToPaste;
1647 textDataObj->GetData(stuffToPaste);
1648 NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
1649 nsAutoEditBatch beginBatching(this);
1650 rv = InsertAsPlaintextQuotation(stuffToPaste, true, 0);
1653 free(flav);
1655 return rv;
1658 NS_IMETHODIMP
1659 nsHTMLEditor::InsertTextWithQuotations(const nsAString &aStringToInsert)
1661 if (mWrapToWindow)
1662 return InsertText(aStringToInsert);
1664 // The whole operation should be undoable in one transaction:
1665 BeginTransaction();
1667 // We're going to loop over the string, collecting up a "hunk"
1668 // that's all the same type (quoted or not),
1669 // Whenever the quotedness changes (or we reach the string's end)
1670 // we will insert the hunk all at once, quoted or non.
1672 static const char16_t cite('>');
1673 bool curHunkIsQuoted = (aStringToInsert.First() == cite);
1675 nsAString::const_iterator hunkStart, strEnd;
1676 aStringToInsert.BeginReading(hunkStart);
1677 aStringToInsert.EndReading(strEnd);
1679 // In the loop below, we only look for DOM newlines (\n),
1680 // because we don't have a FindChars method that can look
1681 // for both \r and \n. \r is illegal in the dom anyway,
1682 // but in debug builds, let's take the time to verify that
1683 // there aren't any there:
1684 #ifdef DEBUG
1685 nsAString::const_iterator dbgStart (hunkStart);
1686 if (FindCharInReadable('\r', dbgStart, strEnd))
1687 NS_ASSERTION(false,
1688 "Return characters in DOM! InsertTextWithQuotations may be wrong");
1689 #endif /* DEBUG */
1691 // Loop over lines:
1692 nsresult rv = NS_OK;
1693 nsAString::const_iterator lineStart (hunkStart);
1694 while (1) // we will break from inside when we run out of newlines
1696 // Search for the end of this line (dom newlines, see above):
1697 bool found = FindCharInReadable('\n', lineStart, strEnd);
1698 bool quoted = false;
1699 if (found)
1701 // if there's another newline, lineStart now points there.
1702 // Loop over any consecutive newline chars:
1703 nsAString::const_iterator firstNewline (lineStart);
1704 while (*lineStart == '\n')
1705 ++lineStart;
1706 quoted = (*lineStart == cite);
1707 if (quoted == curHunkIsQuoted)
1708 continue;
1709 // else we're changing state, so we need to insert
1710 // from curHunk to lineStart then loop around.
1712 // But if the current hunk is quoted, then we want to make sure
1713 // that any extra newlines on the end do not get included in
1714 // the quoted section: blank lines flaking a quoted section
1715 // should be considered unquoted, so that if the user clicks
1716 // there and starts typing, the new text will be outside of
1717 // the quoted block.
1718 if (curHunkIsQuoted)
1719 lineStart = firstNewline;
1722 // If no newline found, lineStart is now strEnd and we can finish up,
1723 // inserting from curHunk to lineStart then returning.
1724 const nsAString &curHunk = Substring(hunkStart, lineStart);
1725 nsCOMPtr<nsIDOMNode> dummyNode;
1726 if (curHunkIsQuoted)
1727 rv = InsertAsPlaintextQuotation(curHunk, false,
1728 getter_AddRefs(dummyNode));
1729 else
1730 rv = InsertText(curHunk);
1732 if (!found)
1733 break;
1735 curHunkIsQuoted = quoted;
1736 hunkStart = lineStart;
1739 EndTransaction();
1741 return rv;
1744 NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsAString & aQuotedText,
1745 nsIDOMNode **aNodeInserted)
1747 if (IsPlaintextEditor())
1748 return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
1750 nsAutoString citation;
1751 return InsertAsCitedQuotation(aQuotedText, citation, false,
1752 aNodeInserted);
1755 // Insert plaintext as a quotation, with cite marks (e.g. "> ").
1756 // This differs from its corresponding method in nsPlaintextEditor
1757 // in that here, quoted material is enclosed in a <pre> tag
1758 // in order to preserve the original line wrapping.
1759 NS_IMETHODIMP
1760 nsHTMLEditor::InsertAsPlaintextQuotation(const nsAString & aQuotedText,
1761 bool aAddCites,
1762 nsIDOMNode **aNodeInserted)
1764 if (mWrapToWindow)
1765 return nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
1767 // get selection
1768 RefPtr<Selection> selection = GetSelection();
1769 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1771 nsAutoEditBatch beginBatching(this);
1772 nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
1774 // give rules a chance to handle or cancel
1775 nsTextRulesInfo ruleInfo(EditAction::insertElement);
1776 bool cancel, handled;
1777 // Protect the edit rules object from dying
1778 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1779 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1780 NS_ENSURE_SUCCESS(rv, rv);
1781 if (cancel || handled) {
1782 return NS_OK; // rules canceled the operation
1785 // Wrap the inserted quote in a <span> so it won't be wrapped:
1786 nsCOMPtr<Element> newNode =
1787 DeleteSelectionAndCreateElement(*nsGkAtoms::span);
1789 // If this succeeded, then set selection inside the pre
1790 // so the inserted text will end up there.
1791 // If it failed, we don't care what the return value was,
1792 // but we'll fall through and try to insert the text anyway.
1793 if (newNode) {
1794 // Add an attribute on the pre node so we'll know it's a quotation.
1795 // Do this after the insertion, so that
1796 newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::mozquote,
1797 NS_LITERAL_STRING("true"), true);
1798 // turn off wrapping on spans
1799 newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
1800 NS_LITERAL_STRING("white-space: pre;"), true);
1802 // and set the selection inside it:
1803 selection->Collapse(newNode, 0);
1806 if (aAddCites)
1807 rv = nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
1808 else
1809 rv = nsPlaintextEditor::InsertText(aQuotedText);
1810 // Note that if !aAddCites, aNodeInserted isn't set.
1811 // That's okay because the routines that use aAddCites
1812 // don't need to know the inserted node.
1814 if (aNodeInserted && NS_SUCCEEDED(rv))
1816 *aNodeInserted = GetAsDOMNode(newNode);
1817 NS_IF_ADDREF(*aNodeInserted);
1820 // Set the selection to just after the inserted node:
1821 if (NS_SUCCEEDED(rv) && newNode)
1823 nsCOMPtr<nsINode> parent = newNode->GetParentNode();
1824 int32_t offset = parent ? parent->IndexOf(newNode) : -1;
1825 if (parent) {
1826 selection->Collapse(parent, offset + 1);
1829 return rv;
1832 NS_IMETHODIMP
1833 nsHTMLEditor::StripCites()
1835 return nsPlaintextEditor::StripCites();
1838 NS_IMETHODIMP
1839 nsHTMLEditor::Rewrap(bool aRespectNewlines)
1841 return nsPlaintextEditor::Rewrap(aRespectNewlines);
1844 NS_IMETHODIMP
1845 nsHTMLEditor::InsertAsCitedQuotation(const nsAString & aQuotedText,
1846 const nsAString & aCitation,
1847 bool aInsertHTML,
1848 nsIDOMNode **aNodeInserted)
1850 // Don't let anyone insert html into a "plaintext" editor:
1851 if (IsPlaintextEditor())
1853 NS_ASSERTION(!aInsertHTML, "InsertAsCitedQuotation: trying to insert html into plaintext editor");
1854 return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
1857 // get selection
1858 RefPtr<Selection> selection = GetSelection();
1859 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1861 nsAutoEditBatch beginBatching(this);
1862 nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
1864 // give rules a chance to handle or cancel
1865 nsTextRulesInfo ruleInfo(EditAction::insertElement);
1866 bool cancel, handled;
1867 // Protect the edit rules object from dying
1868 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1869 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1870 NS_ENSURE_SUCCESS(rv, rv);
1871 if (cancel || handled) {
1872 return NS_OK; // rules canceled the operation
1875 nsCOMPtr<Element> newNode =
1876 DeleteSelectionAndCreateElement(*nsGkAtoms::blockquote);
1877 NS_ENSURE_TRUE(newNode, NS_ERROR_NULL_POINTER);
1879 // Try to set type=cite. Ignore it if this fails.
1880 newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1881 NS_LITERAL_STRING("cite"), true);
1883 if (!aCitation.IsEmpty()) {
1884 newNode->SetAttr(kNameSpaceID_None, nsGkAtoms::cite, aCitation, true);
1887 // Set the selection inside the blockquote so aQuotedText will go there:
1888 selection->Collapse(newNode, 0);
1890 if (aInsertHTML)
1891 rv = LoadHTML(aQuotedText);
1892 else
1893 rv = InsertText(aQuotedText); // XXX ignore charset
1895 if (aNodeInserted && NS_SUCCEEDED(rv))
1897 *aNodeInserted = GetAsDOMNode(newNode);
1898 NS_IF_ADDREF(*aNodeInserted);
1901 // Set the selection to just after the inserted node:
1902 if (NS_SUCCEEDED(rv) && newNode)
1904 nsCOMPtr<nsINode> parent = newNode->GetParentNode();
1905 int32_t offset = parent ? parent->IndexOf(newNode) : -1;
1906 if (parent) {
1907 selection->Collapse(parent, offset + 1);
1910 return rv;
1914 void RemoveBodyAndHead(nsINode& aNode)
1916 nsCOMPtr<nsIContent> body, head;
1917 // find the body and head nodes if any.
1918 // look only at immediate children of aNode.
1919 for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
1920 child;
1921 child = child->GetNextSibling()) {
1922 if (child->IsHTMLElement(nsGkAtoms::body)) {
1923 body = child;
1924 } else if (child->IsHTMLElement(nsGkAtoms::head)) {
1925 head = child;
1928 if (head) {
1929 ErrorResult ignored;
1930 aNode.RemoveChild(*head, ignored);
1932 if (body) {
1933 nsCOMPtr<nsIContent> child = body->GetFirstChild();
1934 while (child) {
1935 ErrorResult ignored;
1936 aNode.InsertBefore(*child, body, ignored);
1937 child = body->GetFirstChild();
1940 ErrorResult ignored;
1941 aNode.RemoveChild(*body, ignored);
1946 * This function finds the target node that we will be pasting into. aStart is
1947 * the context that we're given and aResult will be the target. Initially,
1948 * *aResult must be nullptr.
1950 * The target for a paste is found by either finding the node that contains
1951 * the magical comment node containing kInsertCookie or, failing that, the
1952 * firstChild of the firstChild (until we reach a leaf).
1954 nsresult FindTargetNode(nsIDOMNode *aStart, nsCOMPtr<nsIDOMNode> &aResult)
1956 NS_ENSURE_TRUE(aStart, NS_OK);
1958 nsCOMPtr<nsIDOMNode> child, tmp;
1960 nsresult rv = aStart->GetFirstChild(getter_AddRefs(child));
1961 NS_ENSURE_SUCCESS(rv, rv);
1963 if (!child)
1965 // If the current result is nullptr, then aStart is a leaf, and is the
1966 // fallback result.
1967 if (!aResult)
1968 aResult = aStart;
1970 return NS_OK;
1975 // Is this child the magical cookie?
1976 nsCOMPtr<nsIDOMComment> comment = do_QueryInterface(child);
1977 if (comment)
1979 nsAutoString data;
1980 rv = comment->GetData(data);
1981 NS_ENSURE_SUCCESS(rv, rv);
1983 if (data.EqualsLiteral(kInsertCookie))
1985 // Yes it is! Return an error so we bubble out and short-circuit the
1986 // search.
1987 aResult = aStart;
1989 // Note: it doesn't matter if this fails.
1990 aStart->RemoveChild(child, getter_AddRefs(tmp));
1992 return NS_FOUND_TARGET;
1996 // Note: Don't use NS_ENSURE_* here since we return a failure result to
1997 // inicate that we found the magical cookie and we don't want to spam the
1998 // console.
1999 rv = FindTargetNode(child, aResult);
2000 NS_ENSURE_SUCCESS(rv, rv);
2002 rv = child->GetNextSibling(getter_AddRefs(tmp));
2003 NS_ENSURE_SUCCESS(rv, rv);
2005 child = tmp;
2006 } while (child);
2008 return NS_OK;
2011 nsresult nsHTMLEditor::CreateDOMFragmentFromPaste(const nsAString &aInputString,
2012 const nsAString & aContextStr,
2013 const nsAString & aInfoStr,
2014 nsCOMPtr<nsIDOMNode> *outFragNode,
2015 nsCOMPtr<nsIDOMNode> *outStartNode,
2016 nsCOMPtr<nsIDOMNode> *outEndNode,
2017 int32_t *outStartOffset,
2018 int32_t *outEndOffset,
2019 bool aTrustedInput)
2021 NS_ENSURE_TRUE(outFragNode && outStartNode && outEndNode, NS_ERROR_NULL_POINTER);
2023 nsCOMPtr<nsIDocument> doc = GetDocument();
2024 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2026 // if we have context info, create a fragment for that
2027 nsresult rv = NS_OK;
2028 nsCOMPtr<nsIDOMNode> contextLeaf;
2029 RefPtr<DocumentFragment> contextAsNode;
2030 if (!aContextStr.IsEmpty()) {
2031 rv = ParseFragment(aContextStr, nullptr, doc, getter_AddRefs(contextAsNode),
2032 aTrustedInput);
2033 NS_ENSURE_SUCCESS(rv, rv);
2034 NS_ENSURE_TRUE(contextAsNode, NS_ERROR_FAILURE);
2036 rv = StripFormattingNodes(*contextAsNode);
2037 NS_ENSURE_SUCCESS(rv, rv);
2039 RemoveBodyAndHead(*contextAsNode);
2041 rv = FindTargetNode(contextAsNode, contextLeaf);
2042 if (rv == NS_FOUND_TARGET) {
2043 rv = NS_OK;
2045 NS_ENSURE_SUCCESS(rv, rv);
2048 nsCOMPtr<nsIContent> contextLeafAsContent = do_QueryInterface(contextLeaf);
2050 // create fragment for pasted html
2051 nsIAtom* contextAtom;
2052 if (contextLeafAsContent) {
2053 contextAtom = contextLeafAsContent->NodeInfo()->NameAtom();
2054 if (contextLeafAsContent->IsHTMLElement(nsGkAtoms::html)) {
2055 contextAtom = nsGkAtoms::body;
2057 } else {
2058 contextAtom = nsGkAtoms::body;
2060 RefPtr<DocumentFragment> fragment;
2061 rv = ParseFragment(aInputString,
2062 contextAtom,
2063 doc,
2064 getter_AddRefs(fragment),
2065 aTrustedInput);
2066 NS_ENSURE_SUCCESS(rv, rv);
2067 NS_ENSURE_TRUE(fragment, NS_ERROR_FAILURE);
2069 RemoveBodyAndHead(*fragment);
2071 if (contextAsNode) {
2072 // unite the two trees
2073 nsCOMPtr<nsIDOMNode> junk;
2074 contextLeaf->AppendChild(fragment, getter_AddRefs(junk));
2075 fragment = contextAsNode;
2078 rv = StripFormattingNodes(*fragment, true);
2079 NS_ENSURE_SUCCESS(rv, rv);
2081 // If there was no context, then treat all of the data we did get as the
2082 // pasted data.
2083 if (contextLeaf) {
2084 *outEndNode = *outStartNode = contextLeaf;
2085 } else {
2086 *outEndNode = *outStartNode = fragment;
2089 *outFragNode = fragment.forget();
2090 *outStartOffset = 0;
2092 // get the infoString contents
2093 if (!aInfoStr.IsEmpty()) {
2094 int32_t sep = aInfoStr.FindChar((char16_t)',');
2095 nsAutoString numstr1(Substring(aInfoStr, 0, sep));
2096 nsAutoString numstr2(Substring(aInfoStr, sep+1, aInfoStr.Length() - (sep+1)));
2098 // Move the start and end children.
2099 nsresult err;
2100 int32_t num = numstr1.ToInteger(&err);
2102 nsCOMPtr<nsIDOMNode> tmp;
2103 while (num--) {
2104 (*outStartNode)->GetFirstChild(getter_AddRefs(tmp));
2105 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2106 tmp.swap(*outStartNode);
2109 num = numstr2.ToInteger(&err);
2110 while (num--) {
2111 (*outEndNode)->GetLastChild(getter_AddRefs(tmp));
2112 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2113 tmp.swap(*outEndNode);
2117 nsCOMPtr<nsINode> node = do_QueryInterface(*outEndNode);
2118 *outEndOffset = node->Length();
2119 return NS_OK;
2123 nsresult nsHTMLEditor::ParseFragment(const nsAString & aFragStr,
2124 nsIAtom* aContextLocalName,
2125 nsIDocument* aTargetDocument,
2126 DocumentFragment** aFragment,
2127 bool aTrustedInput)
2129 nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;
2131 RefPtr<DocumentFragment> fragment =
2132 new DocumentFragment(aTargetDocument->NodeInfoManager());
2133 nsresult rv = nsContentUtils::ParseFragmentHTML(aFragStr,
2134 fragment,
2135 aContextLocalName ?
2136 aContextLocalName : nsGkAtoms::body,
2137 kNameSpaceID_XHTML,
2138 false,
2139 true);
2140 if (!aTrustedInput) {
2141 nsTreeSanitizer sanitizer(aContextLocalName ?
2142 nsIParserUtils::SanitizerAllowStyle :
2143 nsIParserUtils::SanitizerAllowComments);
2144 sanitizer.Sanitize(fragment);
2146 fragment.forget(aFragment);
2147 return rv;
2150 void
2151 nsHTMLEditor::CreateListOfNodesToPaste(DocumentFragment& aFragment,
2152 nsTArray<OwningNonNull<nsINode>>& outNodeList,
2153 nsINode* aStartNode,
2154 int32_t aStartOffset,
2155 nsINode* aEndNode,
2156 int32_t aEndOffset)
2158 // If no info was provided about the boundary between context and stream,
2159 // then assume all is stream.
2160 if (!aStartNode) {
2161 aStartNode = &aFragment;
2162 aStartOffset = 0;
2163 aEndNode = &aFragment;
2164 aEndOffset = aFragment.Length();
2167 RefPtr<nsRange> docFragRange;
2168 nsresult rv = nsRange::CreateRange(aStartNode, aStartOffset,
2169 aEndNode, aEndOffset,
2170 getter_AddRefs(docFragRange));
2171 MOZ_ASSERT(NS_SUCCEEDED(rv));
2172 NS_ENSURE_SUCCESS(rv, );
2174 // Now use a subtree iterator over the range to create a list of nodes
2175 nsTrivialFunctor functor;
2176 nsDOMSubtreeIterator iter;
2177 rv = iter.Init(*docFragRange);
2178 NS_ENSURE_SUCCESS(rv, );
2179 iter.AppendList(functor, outNodeList);
2182 void
2183 nsHTMLEditor::GetListAndTableParents(StartOrEnd aStartOrEnd,
2184 nsTArray<OwningNonNull<nsINode>>& aNodeList,
2185 nsTArray<OwningNonNull<Element>>& outArray)
2187 MOZ_ASSERT(aNodeList.Length());
2189 // Build up list of parents of first (or last) node in list that are either
2190 // lists, or tables.
2191 int32_t idx = aStartOrEnd == StartOrEnd::end ? aNodeList.Length() - 1 : 0;
2193 for (nsCOMPtr<nsINode> node = aNodeList[idx]; node;
2194 node = node->GetParentNode()) {
2195 if (nsHTMLEditUtils::IsList(node) || nsHTMLEditUtils::IsTable(node)) {
2196 outArray.AppendElement(*node->AsElement());
2201 int32_t
2202 nsHTMLEditor::DiscoverPartialListsAndTables(nsTArray<OwningNonNull<nsINode>>& aPasteNodes,
2203 nsTArray<OwningNonNull<Element>>& aListsAndTables)
2205 int32_t ret = -1;
2206 int32_t listAndTableParents = aListsAndTables.Length();
2208 // Scan insertion list for table elements (other than table).
2209 for (auto& curNode : aPasteNodes) {
2210 if (nsHTMLEditUtils::IsTableElement(curNode) &&
2211 !curNode->IsHTMLElement(nsGkAtoms::table)) {
2212 nsCOMPtr<Element> table = curNode->GetParentElement();
2213 while (table && !table->IsHTMLElement(nsGkAtoms::table)) {
2214 table = table->GetParentElement();
2216 if (table) {
2217 int32_t idx = aListsAndTables.IndexOf(table);
2218 if (idx == -1) {
2219 return ret;
2221 ret = idx;
2222 if (ret == listAndTableParents - 1) {
2223 return ret;
2227 if (nsHTMLEditUtils::IsListItem(curNode)) {
2228 nsCOMPtr<Element> list = curNode->GetParentElement();
2229 while (list && !nsHTMLEditUtils::IsList(list)) {
2230 list = list->GetParentElement();
2232 if (list) {
2233 int32_t idx = aListsAndTables.IndexOf(list);
2234 if (idx == -1) {
2235 return ret;
2237 ret = idx;
2238 if (ret == listAndTableParents - 1) {
2239 return ret;
2244 return ret;
2247 nsINode*
2248 nsHTMLEditor::ScanForListAndTableStructure(StartOrEnd aStartOrEnd,
2249 nsTArray<OwningNonNull<nsINode>>& aNodes,
2250 Element& aListOrTable)
2252 // Look upward from first/last paste node for a piece of this list/table
2253 int32_t idx = aStartOrEnd == StartOrEnd::end ? aNodes.Length() - 1 : 0;
2254 bool isList = nsHTMLEditUtils::IsList(&aListOrTable);
2256 for (nsCOMPtr<nsINode> node = aNodes[idx]; node;
2257 node = node->GetParentNode()) {
2258 if ((isList && nsHTMLEditUtils::IsListItem(node)) ||
2259 (!isList && nsHTMLEditUtils::IsTableElement(node) &&
2260 !node->IsHTMLElement(nsGkAtoms::table))) {
2261 nsCOMPtr<Element> structureNode = node->GetParentElement();
2262 if (isList) {
2263 while (structureNode && !nsHTMLEditUtils::IsList(structureNode)) {
2264 structureNode = structureNode->GetParentElement();
2266 } else {
2267 while (structureNode &&
2268 !structureNode->IsHTMLElement(nsGkAtoms::table)) {
2269 structureNode = structureNode->GetParentElement();
2272 if (structureNode == &aListOrTable) {
2273 if (isList) {
2274 return structureNode;
2276 return node;
2280 return nullptr;
2283 void
2284 nsHTMLEditor::ReplaceOrphanedStructure(StartOrEnd aStartOrEnd,
2285 nsTArray<OwningNonNull<nsINode>>& aNodeArray,
2286 nsTArray<OwningNonNull<Element>>& aListAndTableArray,
2287 int32_t aHighWaterMark)
2289 OwningNonNull<Element> curNode = aListAndTableArray[aHighWaterMark];
2291 // Find substructure of list or table that must be included in paste.
2292 nsCOMPtr<nsINode> replaceNode =
2293 ScanForListAndTableStructure(aStartOrEnd, aNodeArray, curNode);
2295 if (!replaceNode) {
2296 return;
2299 // If we found substructure, paste it instead of its descendants.
2300 // Postprocess list to remove any descendants of this node so that we don't
2301 // insert them twice.
2302 while (aNodeArray.Length()) {
2303 int32_t idx = aStartOrEnd == StartOrEnd::start ? 0
2304 : aNodeArray.Length() - 1;
2305 OwningNonNull<nsINode> endpoint = aNodeArray[idx];
2306 if (!nsEditorUtils::IsDescendantOf(endpoint, replaceNode)) {
2307 break;
2309 aNodeArray.RemoveElementAt(idx);
2312 // Now replace the removed nodes with the structural parent
2313 if (aStartOrEnd == StartOrEnd::end) {
2314 aNodeArray.AppendElement(*replaceNode);
2315 } else {
2316 aNodeArray.InsertElementAt(0, *replaceNode);