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