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