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