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/. */
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"
20 #include "nsCRTGlue.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsContentUtils.h"
24 #include "nsDependentSubstring.h"
25 #include "nsEditRules.h"
27 #include "nsEditorUtils.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"
56 #include "nsIInputStream.h"
57 #include "nsIMIMEService.h"
58 #include "nsNameSpaceManager.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"
67 #include "nsIVariant.h"
68 #include "nsLinebreakConverter.h"
69 #include "nsLiteralString.h"
70 #include "nsNetUtil.h"
71 #include "nsPlaintextEditor.h"
73 #include "nsReadableUtils.h"
74 #include "nsSelectionState.h"
75 #include "nsServiceManagerUtils.h"
76 #include "nsStreamUtils.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"
87 #include "nsContentUtils.h"
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
);
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
);
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
);
126 return NS_OK
; // rules canceled the operation
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
);
153 rv
= range
->GetStartOffset(&childOffset
);
154 NS_ENSURE_SUCCESS(rv
, rv
);
156 nsCOMPtr
<nsIDOMNode
> nodeToInsert
;
157 docfrag
->GetFirstChild(getter_AddRefs(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);
180 nsHTMLEditor::InsertHTMLWithContext(const nsAString
& aInputString
,
181 const nsAString
& aContextStr
,
182 const nsAString
& aInfoStr
,
183 const nsAString
& aFlavor
,
184 nsIDOMDocument
*aSourceDoc
,
185 nsIDOMNode
*aDestNode
,
187 bool aDeleteSelection
)
189 return DoInsertHTMLWithContext(aInputString
, aContextStr
, aInfoStr
,
190 aFlavor
, aSourceDoc
, aDestNode
, aDestOffset
, aDeleteSelection
,
191 /* trusted input */ true, /* clear style */ false);
195 nsHTMLEditor::DoInsertHTMLWithContext(const nsAString
& aInputString
,
196 const nsAString
& aContextStr
,
197 const nsAString
& aInfoStr
,
198 const nsAString
& aFlavor
,
199 nsIDOMDocument
*aSourceDoc
,
200 nsIDOMNode
*aDestNode
,
202 bool aDeleteSelection
,
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
);
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
),
231 NS_ENSURE_SUCCESS(rv
, rv
);
233 nsCOMPtr
<nsIDOMNode
> targetNode
, tempNode
;
234 int32_t targetOffset
=0;
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
;
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
),
258 (nsIDOMNode
**)address_of(streamEndParent
),
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)
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()),
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
);
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
335 if (!nsHTMLEditUtils::IsTableElement(nodeList
[0])) {
336 cellSelectionMode
= false;
340 if (!cellSelectionMode
)
342 rv
= DeleteSelectionAndPrepareToCreateNode();
343 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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
);
373 return NS_OK
; // rules canceled the operation
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
,
409 NS_ENSURE_STATE(offsetOfNewNode
!= -1);
410 nsCOMPtr
<nsIDOMNode
> temp
;
411 rv
= parentNode
->GetParentNode(getter_AddRefs(temp
));
412 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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();
460 if (IsBlockNode(parentNode
))
461 parentBlock
= parentNode
;
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
))
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
));
492 rv
= InsertNodeAtPoint(child
, address_of(parentNode
), &offsetOfNewNode
, true);
497 lastInsertNode
= child
;
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
));
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
))
521 rv
= IsEmptyNode(parentNode
, &isEmpty
, true);
522 if (NS_SUCCEEDED(rv
) && isEmpty
)
525 nsCOMPtr
<nsIDOMNode
> listNode
= GetNodeLocation(parentNode
, &newOffset
);
528 DeleteNode(parentNode
);
529 parentNode
= listNode
;
530 offsetOfNewNode
= newOffset
;
534 rv
= InsertNodeAtPoint(child
, address_of(parentNode
), &offsetOfNewNode
, true);
539 lastInsertNode
= child
;
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
));
556 rv
= InsertNodeAtPoint(child
, address_of(parentNode
), &offsetOfNewNode
, true);
561 lastInsertNode
= child
;
564 curNode
->GetFirstChild(getter_AddRefs(child
));
568 if (!bDidInsert
|| NS_FAILED(rv
))
571 rv
= InsertNodeAtPoint(curNode
, address_of(parentNode
), &offsetOfNewNode
, true);
572 if (NS_SUCCEEDED(rv
))
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
))
590 insertedContextParent
= parent
;
591 lastInsertNode
= GetChildAt(parentNode
, offsetOfNewNode
);
599 parentNode
= GetNodeLocation(lastInsertNode
, &offsetOfNewNode
);
604 // Now collapse the selection to the end of what we just inserted:
607 // set selection to the end of what we just pasted.
608 nsCOMPtr
<nsIDOMNode
> selNode
, tmp
, highTable
;
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_
));
618 while (tmp
&& (tmp
!= lastInsertNode
))
620 if (nsHTMLEditUtils::IsTable(tmp
))
622 nsCOMPtr
<nsIDOMNode
> parent
= tmp
;
623 tmp
->GetParentNode(getter_AddRefs(parent
));
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.
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;
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
);
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
));
698 selNode
= GetNodeLocation(GetAsDOMNode(leftLink
), &selOffset
);
699 selection
->Collapse(selNode
, selOffset
+1);
705 return mRules
->DidDoAction(selection
, &ruleInfo
, rv
);
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
);
722 nsHTMLEditor::RemoveInsertionListener(nsIContentFilter
*aListener
)
724 NS_ENSURE_TRUE(aListener
, NS_ERROR_FAILURE
);
726 mContentFilters
.RemoveElement(aListener
);
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
,
746 for (auto& listener
: mContentFilters
) {
750 listener
->NotifyOfInsertion(aFlavor
, nullptr, sourceDoc
,
751 aWillDeleteSelection
, aFragmentAsNode
,
752 aFragStartNode
, aFragStartOffset
,
753 aFragEndNode
, aFragEndOffset
, aTargetNode
,
754 aTargetOffset
, aDoContinue
);
761 nsHTMLEditor::IsInLink(nsIDOMNode
*aNode
, nsCOMPtr
<nsIDOMNode
> *outLink
)
763 NS_ENSURE_TRUE(aNode
, false);
766 nsCOMPtr
<nsIDOMNode
> tmp
, node
= aNode
;
769 if (nsHTMLEditUtils::IsLink(node
))
776 tmp
->GetParentNode(getter_AddRefs(node
));
783 nsHTMLEditor::StripFormattingNodes(nsIContent
& aNode
, bool aListOnly
)
785 if (aNode
.TextIsOnlyWhitespace()) {
786 nsCOMPtr
<nsINode
> parent
= aNode
.GetParentNode();
788 if (!aListOnly
|| nsHTMLEditUtils::IsList(parent
)) {
790 parent
->RemoveChild(aNode
, rv
);
791 return rv
.StealNSResult();
797 if (!aNode
.IsHTMLElement(nsGkAtoms::pre
)) {
798 nsCOMPtr
<nsIContent
> child
= aNode
.GetLastChild();
800 nsCOMPtr
<nsIContent
> previous
= child
->GetPreviousSibling();
801 nsresult rv
= StripFormattingNodes(*child
, aListOnly
);
802 NS_ENSURE_SUCCESS(rv
, rv
);
803 child
= previous
.forget();
809 NS_IMETHODIMP
nsHTMLEditor::PrepareTransferable(nsITransferable
**transferable
)
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
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
);
845 case 1: // prefer PNG over JPEG over GIF encoding (default)
847 (*aTransferable
)->AddDataFlavor(kPNGImageMime
);
848 (*aTransferable
)->AddDataFlavor(kJPEGImageMime
);
849 (*aTransferable
)->AddDataFlavor(kJPGImageMime
);
850 (*aTransferable
)->AddDataFlavor(kGIFImageMime
);
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
);
860 (*aTransferable
)->AddDataFlavor(kUnicodeMime
);
861 (*aTransferable
)->AddDataFlavor(kMozTextInternal
);
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
);
875 numFront
+= strlen(aLeadingString
);
877 int32_t numBack
= aCStr
.FindCharInSet(CRLF
, numFront
);
881 nsAutoCString
numStr(Substring(aCStr
, numFront
, numBack
-numFront
));
883 foundNumber
= numStr
.ToInteger(&errorCode
);
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
);
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
) ||
915 return NS_ERROR_FAILURE
;
916 if (!FindIntegerAfterString("EndHTML:", aCfhtml
, endHTML
) ||
918 return NS_ERROR_FAILURE
;
919 if (!FindIntegerAfterString("StartFragment:", aCfhtml
, startFragment
) ||
921 return NS_ERROR_FAILURE
;
922 if (!FindIntegerAfterString("EndFragment:", aCfhtml
, endFragment
) ||
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-->");
935 const char endFragmentMarker
[] = "<!--EndFragment-->";
936 endHTML
= aCfhtml
.Find(endFragmentMarker
);
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.
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;
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.
1012 nsresult
nsHTMLEditor::InsertObject(const char* aType
, nsISupports
* aObject
, bool aIsSafe
,
1013 nsIDOMDocument
*aSourceDoc
,
1014 nsIDOMNode
*aDestinationNode
,
1015 int32_t aDestOffset
,
1016 bool aDoDeleteSelection
)
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
);
1030 // Accept any image type fed to us
1031 if (nsContentUtils::IsFileImage(fileObj
, type
))
1033 insertAsImage
= true;
1038 type
.AssignLiteral(kFileMime
);
1043 if (type
.EqualsLiteral(kJPEGImageMime
) ||
1044 type
.EqualsLiteral(kJPGImageMime
) ||
1045 type
.EqualsLiteral(kPNGImageMime
) ||
1046 type
.EqualsLiteral(kGIFImageMime
) ||
1049 nsCString imageData
;
1052 rv
= nsContentUtils::SlurpFileToString(fileObj
, imageData
);
1053 NS_ENSURE_SUCCESS(rv
, rv
);
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
),
1081 aDestinationNode
, aDestOffset
,
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
;
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
,
1140 aDestinationNode
, aDestOffset
,
1144 rv
= DoInsertHTMLWithContext(cffragment
,
1145 cfcontext
, cfselection
, flavor
,
1147 aDestinationNode
, aDestOffset
,
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
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) {
1169 textDataObj
->GetData(text
);
1170 NS_ASSERTION(text
.Length() <= (len
/2), "Invalid length!");
1171 stuffToPaste
.Assign(text
.get(), len
/ 2);
1173 nsCOMPtr
<nsISupportsCString
> textDataObj(do_QueryInterface(genericDataObj
));
1174 if (textDataObj
&& len
> 0) {
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
,
1188 aDestinationNode
, aDestOffset
,
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);
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
));
1212 variant
->GetAsAString(aOutputString
);
1215 nsresult
nsHTMLEditor::InsertFromDataTransfer(DataTransfer
*aDataTransfer
,
1217 nsIDOMDocument
*aSourceDoc
,
1218 nsIDOMNode
*aDestinationNode
,
1219 int32_t aDestOffset
,
1220 bool aDoDeleteSelection
)
1223 RefPtr
<DOMStringList
> types
= aDataTransfer
->MozTypesAt(aIndex
, rv
);
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
++) {
1236 types
->Item(t
, type
);
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
));
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.
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
,
1275 aDestinationNode
, aDestOffset
,
1280 return DoInsertHTMLWithContext(cffragment
,
1281 cfcontext
, cfselection
, type
,
1283 aDestinationNode
, aDestOffset
,
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
,
1300 aDestinationNode
, aDestOffset
,
1307 if (type
.EqualsLiteral(kTextMime
) ||
1308 type
.EqualsLiteral(kMozTextInternal
)) {
1310 GetStringFromDataTransfer(aDataTransfer
, type
, aIndex
, text
);
1312 nsAutoEditBatch
beginBatching(this);
1313 return InsertTextAt(text
, aDestinationNode
, aDestOffset
, aDoDeleteSelection
);
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
;
1339 NS_IMETHODIMP
nsHTMLEditor::Paste(int32_t aSelectionType
)
1341 if (!FireClipboardEvent(ePaste
, aSelectionType
)) {
1345 // Get Clipboard Service
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()) {
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
);
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);
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
))
1415 return InsertFromTransferable(trans
, nullptr, contextStr
, infoStr
, bHavePrivateHTMLFlavor
,
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
)) {
1427 // handle transferable hooks
1428 nsCOMPtr
<nsIDOMDocument
> domdoc
= GetDOMDocument();
1429 if (!nsEditorHookUtils::DoInsertionHook(domdoc
, nullptr, aTransferable
))
1432 nsAutoString contextStr
, infoStr
;
1433 return InsertFromTransferable(aTransferable
, nullptr, contextStr
, infoStr
, false,
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
)) {
1446 ForceCompositionEnd();
1448 // Get Clipboard Service
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,
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
);
1485 // can't paste if readonly
1486 if (!IsModifiable()) {
1491 nsCOMPtr
<nsIClipboard
> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv
));
1492 NS_ENSURE_SUCCESS(rv
, rv
);
1496 // Use the flavors depending on the current editor mask
1497 if (IsPlaintextEditor())
1498 rv
= clipboard
->HasDataMatchingFlavors(textEditorFlavors
,
1499 ArrayLength(textEditorFlavors
),
1500 aSelectionType
, &haveFlavors
);
1502 rv
= clipboard
->HasDataMatchingFlavors(textHtmlEditorFlavors
,
1503 ArrayLength(textHtmlEditorFlavors
),
1504 aSelectionType
, &haveFlavors
);
1506 NS_ENSURE_SUCCESS(rv
, rv
);
1508 *aCanPaste
= haveFlavors
;
1512 NS_IMETHODIMP
nsHTMLEditor::CanPasteTransferable(nsITransferable
*aTransferable
, bool *aCanPaste
)
1514 NS_ENSURE_ARG_POINTER(aCanPaste
);
1516 // can't paste if readonly
1517 if (!IsModifiable()) {
1522 // If |aTransferable| is null, assume that a paste will succeed.
1523 if (!aTransferable
) {
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
;
1533 if (IsPlaintextEditor()) {
1534 flavors
= textEditorFlavors
;
1535 length
= ArrayLength(textEditorFlavors
);
1537 flavors
= textHtmlEditorFlavors
;
1538 length
= ArrayLength(textHtmlEditorFlavors
);
1541 for (unsigned int i
= 0; i
< length
; i
++, flavors
++) {
1542 nsCOMPtr
<nsISupports
> data
;
1544 nsresult rv
= aTransferable
->GetTransferData(*flavors
,
1545 getter_AddRefs(data
),
1547 if (NS_SUCCEEDED(rv
) && data
) {
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
);
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
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
;
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);
1659 nsHTMLEditor::InsertTextWithQuotations(const nsAString
&aStringToInsert
)
1662 return InsertText(aStringToInsert
);
1664 // The whole operation should be undoable in one transaction:
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:
1685 nsAString::const_iterator
dbgStart (hunkStart
);
1686 if (FindCharInReadable('\r', dbgStart
, strEnd
))
1688 "Return characters in DOM! InsertTextWithQuotations may be wrong");
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;
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')
1706 quoted
= (*lineStart
== cite
);
1707 if (quoted
== curHunkIsQuoted
)
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
));
1730 rv
= InsertText(curHunk
);
1735 curHunkIsQuoted
= quoted
;
1736 hunkStart
= lineStart
;
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,
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.
1760 nsHTMLEditor::InsertAsPlaintextQuotation(const nsAString
& aQuotedText
,
1762 nsIDOMNode
**aNodeInserted
)
1765 return nsPlaintextEditor::InsertAsQuotation(aQuotedText
, aNodeInserted
);
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.
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);
1807 rv
= nsPlaintextEditor::InsertAsQuotation(aQuotedText
, aNodeInserted
);
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;
1826 selection
->Collapse(parent
, offset
+ 1);
1833 nsHTMLEditor::StripCites()
1835 return nsPlaintextEditor::StripCites();
1839 nsHTMLEditor::Rewrap(bool aRespectNewlines
)
1841 return nsPlaintextEditor::Rewrap(aRespectNewlines
);
1845 nsHTMLEditor::InsertAsCitedQuotation(const nsAString
& aQuotedText
,
1846 const nsAString
& aCitation
,
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
);
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);
1891 rv
= LoadHTML(aQuotedText
);
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;
1907 selection
->Collapse(parent
, offset
+ 1);
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();
1921 child
= child
->GetNextSibling()) {
1922 if (child
->IsHTMLElement(nsGkAtoms::body
)) {
1924 } else if (child
->IsHTMLElement(nsGkAtoms::head
)) {
1929 ErrorResult ignored
;
1930 aNode
.RemoveChild(*head
, ignored
);
1933 nsCOMPtr
<nsIContent
> child
= body
->GetFirstChild();
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
);
1965 // If the current result is nullptr, then aStart is a leaf, and is the
1975 // Is this child the magical cookie?
1976 nsCOMPtr
<nsIDOMComment
> comment
= do_QueryInterface(child
);
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
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
1999 rv
= FindTargetNode(child
, aResult
);
2000 NS_ENSURE_SUCCESS(rv
, rv
);
2002 rv
= child
->GetNextSibling(getter_AddRefs(tmp
));
2003 NS_ENSURE_SUCCESS(rv
, rv
);
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
,
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
),
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
) {
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
;
2058 contextAtom
= nsGkAtoms::body
;
2060 RefPtr
<DocumentFragment
> fragment
;
2061 rv
= ParseFragment(aInputString
,
2064 getter_AddRefs(fragment
),
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
2084 *outEndNode
= *outStartNode
= contextLeaf
;
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.
2100 int32_t num
= numstr1
.ToInteger(&err
);
2102 nsCOMPtr
<nsIDOMNode
> tmp
;
2104 (*outStartNode
)->GetFirstChild(getter_AddRefs(tmp
));
2105 NS_ENSURE_TRUE(tmp
, NS_ERROR_FAILURE
);
2106 tmp
.swap(*outStartNode
);
2109 num
= numstr2
.ToInteger(&err
);
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();
2123 nsresult
nsHTMLEditor::ParseFragment(const nsAString
& aFragStr
,
2124 nsIAtom
* aContextLocalName
,
2125 nsIDocument
* aTargetDocument
,
2126 DocumentFragment
** aFragment
,
2129 nsAutoScriptBlockerSuppressNodeRemoved autoBlocker
;
2131 RefPtr
<DocumentFragment
> fragment
=
2132 new DocumentFragment(aTargetDocument
->NodeInfoManager());
2133 nsresult rv
= nsContentUtils::ParseFragmentHTML(aFragStr
,
2136 aContextLocalName
: nsGkAtoms::body
,
2140 if (!aTrustedInput
) {
2141 nsTreeSanitizer
sanitizer(aContextLocalName
?
2142 nsIParserUtils::SanitizerAllowStyle
:
2143 nsIParserUtils::SanitizerAllowComments
);
2144 sanitizer
.Sanitize(fragment
);
2146 fragment
.forget(aFragment
);
2151 nsHTMLEditor::CreateListOfNodesToPaste(DocumentFragment
& aFragment
,
2152 nsTArray
<OwningNonNull
<nsINode
>>& outNodeList
,
2153 nsINode
* aStartNode
,
2154 int32_t aStartOffset
,
2158 // If no info was provided about the boundary between context and stream,
2159 // then assume all is stream.
2161 aStartNode
= &aFragment
;
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
);
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());
2202 nsHTMLEditor::DiscoverPartialListsAndTables(nsTArray
<OwningNonNull
<nsINode
>>& aPasteNodes
,
2203 nsTArray
<OwningNonNull
<Element
>>& aListsAndTables
)
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();
2217 int32_t idx
= aListsAndTables
.IndexOf(table
);
2222 if (ret
== listAndTableParents
- 1) {
2227 if (nsHTMLEditUtils::IsListItem(curNode
)) {
2228 nsCOMPtr
<Element
> list
= curNode
->GetParentElement();
2229 while (list
&& !nsHTMLEditUtils::IsList(list
)) {
2230 list
= list
->GetParentElement();
2233 int32_t idx
= aListsAndTables
.IndexOf(list
);
2238 if (ret
== listAndTableParents
- 1) {
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();
2263 while (structureNode
&& !nsHTMLEditUtils::IsList(structureNode
)) {
2264 structureNode
= structureNode
->GetParentElement();
2267 while (structureNode
&&
2268 !structureNode
->IsHTMLElement(nsGkAtoms::table
)) {
2269 structureNode
= structureNode
->GetParentElement();
2272 if (structureNode
== &aListOrTable
) {
2274 return structureNode
;
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
);
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
)) {
2309 aNodeArray
.RemoveElementAt(idx
);
2312 // Now replace the removed nodes with the structural parent
2313 if (aStartOrEnd
== StartOrEnd::end
) {
2314 aNodeArray
.AppendElement(*replaceNode
);
2316 aNodeArray
.InsertElementAt(0, *replaceNode
);