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