Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "mozilla/TextEditor.h"
7 :
8 : #include "mozilla/ArrayUtils.h"
9 : #include "mozilla/EditorUtils.h"
10 : #include "mozilla/MouseEvents.h"
11 : #include "mozilla/SelectionState.h"
12 : #include "mozilla/dom/Selection.h"
13 : #include "nsAString.h"
14 : #include "nsCOMPtr.h"
15 : #include "nsComponentManagerUtils.h"
16 : #include "nsContentUtils.h"
17 : #include "nsDebug.h"
18 : #include "nsError.h"
19 : #include "nsIClipboard.h"
20 : #include "nsIContent.h"
21 : #include "nsIDOMDataTransfer.h"
22 : #include "nsIDOMDocument.h"
23 : #include "nsIDOMDragEvent.h"
24 : #include "nsIDOMEvent.h"
25 : #include "nsIDOMNode.h"
26 : #include "nsIDOMUIEvent.h"
27 : #include "nsIDocument.h"
28 : #include "nsIDragService.h"
29 : #include "nsIDragSession.h"
30 : #include "nsIEditor.h"
31 : #include "nsIDocShell.h"
32 : #include "nsIDocShellTreeItem.h"
33 : #include "nsIPrincipal.h"
34 : #include "nsIFormControl.h"
35 : #include "nsIPlaintextEditor.h"
36 : #include "nsISupportsPrimitives.h"
37 : #include "nsITransferable.h"
38 : #include "nsIVariant.h"
39 : #include "nsLiteralString.h"
40 : #include "nsRange.h"
41 : #include "nsServiceManagerUtils.h"
42 : #include "nsString.h"
43 : #include "nsXPCOM.h"
44 : #include "nscore.h"
45 :
46 : class nsILoadContext;
47 : class nsISupports;
48 :
49 : namespace mozilla {
50 :
51 : using namespace dom;
52 :
53 : NS_IMETHODIMP
54 0 : TextEditor::PrepareTransferable(nsITransferable** transferable)
55 : {
56 : // Create generic Transferable for getting the data
57 0 : nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable);
58 0 : NS_ENSURE_SUCCESS(rv, rv);
59 :
60 : // Get the nsITransferable interface for getting the data from the clipboard
61 0 : if (transferable) {
62 0 : nsCOMPtr<nsIDocument> destdoc = GetDocument();
63 0 : nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
64 0 : (*transferable)->Init(loadContext);
65 :
66 0 : (*transferable)->AddDataFlavor(kUnicodeMime);
67 0 : (*transferable)->AddDataFlavor(kMozTextInternal);
68 : };
69 0 : return NS_OK;
70 : }
71 :
72 : nsresult
73 0 : TextEditor::InsertTextAt(const nsAString& aStringToInsert,
74 : nsIDOMNode* aDestinationNode,
75 : int32_t aDestOffset,
76 : bool aDoDeleteSelection)
77 : {
78 0 : if (aDestinationNode) {
79 0 : RefPtr<Selection> selection = GetSelection();
80 0 : NS_ENSURE_STATE(selection);
81 :
82 0 : nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode;
83 0 : int32_t targetOffset = aDestOffset;
84 :
85 0 : if (aDoDeleteSelection) {
86 : // Use an auto tracker so that our drop point is correctly
87 : // positioned after the delete.
88 0 : AutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
89 0 : nsresult rv = DeleteSelection(eNone, eStrip);
90 0 : NS_ENSURE_SUCCESS(rv, rv);
91 : }
92 :
93 0 : nsresult rv = selection->Collapse(targetNode, targetOffset);
94 0 : NS_ENSURE_SUCCESS(rv, rv);
95 : }
96 :
97 0 : return InsertText(aStringToInsert);
98 : }
99 :
100 : nsresult
101 0 : TextEditor::InsertTextFromTransferable(nsITransferable* aTransferable,
102 : nsIDOMNode* aDestinationNode,
103 : int32_t aDestOffset,
104 : bool aDoDeleteSelection)
105 : {
106 0 : nsresult rv = NS_OK;
107 0 : nsAutoCString bestFlavor;
108 0 : nsCOMPtr<nsISupports> genericDataObj;
109 0 : uint32_t len = 0;
110 0 : if (NS_SUCCEEDED(
111 : aTransferable->GetAnyTransferData(bestFlavor,
112 : getter_AddRefs(genericDataObj),
113 0 : &len)) &&
114 0 : (bestFlavor.EqualsLiteral(kUnicodeMime) ||
115 0 : bestFlavor.EqualsLiteral(kMozTextInternal))) {
116 0 : AutoTransactionsConserveSelection dontSpazMySelection(this);
117 0 : nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
118 0 : if (textDataObj && len > 0) {
119 0 : nsAutoString stuffToPaste;
120 0 : textDataObj->GetData(stuffToPaste);
121 0 : NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
122 :
123 : // Sanitize possible carriage returns in the string to be inserted
124 0 : nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
125 :
126 0 : AutoEditBatch beginBatching(this);
127 0 : rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
128 : }
129 : }
130 :
131 : // Try to scroll the selection into view if the paste/drop succeeded
132 :
133 0 : if (NS_SUCCEEDED(rv)) {
134 0 : ScrollSelectionIntoView(false);
135 : }
136 :
137 0 : return rv;
138 : }
139 :
140 : nsresult
141 0 : TextEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer,
142 : int32_t aIndex,
143 : nsIDOMDocument* aSourceDoc,
144 : nsIDOMNode* aDestinationNode,
145 : int32_t aDestOffset,
146 : bool aDoDeleteSelection)
147 : {
148 0 : nsCOMPtr<nsIVariant> data;
149 0 : DataTransfer::Cast(aDataTransfer)->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"), aIndex,
150 0 : getter_AddRefs(data));
151 0 : if (data) {
152 0 : nsAutoString insertText;
153 0 : data->GetAsAString(insertText);
154 0 : nsContentUtils::PlatformToDOMLineBreaks(insertText);
155 :
156 0 : AutoEditBatch beginBatching(this);
157 0 : return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection);
158 : }
159 :
160 0 : return NS_OK;
161 : }
162 :
163 : nsresult
164 0 : TextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
165 : {
166 0 : ForceCompositionEnd();
167 :
168 0 : nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent));
169 0 : NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE);
170 :
171 0 : nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
172 0 : dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
173 0 : nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
174 0 : NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
175 :
176 0 : nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
177 0 : NS_ASSERTION(dragSession, "No drag session");
178 :
179 0 : nsCOMPtr<nsIDOMNode> sourceNode;
180 0 : dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
181 :
182 0 : nsCOMPtr<nsIDOMDocument> srcdomdoc;
183 0 : if (sourceNode) {
184 0 : sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc));
185 0 : NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE);
186 : }
187 :
188 0 : if (nsContentUtils::CheckForSubFrameDrop(dragSession,
189 0 : aDropEvent->WidgetEventPtr()->AsDragEvent())) {
190 : // Don't allow drags from subframe documents with different origins than
191 : // the drop destination.
192 0 : if (srcdomdoc && !IsSafeToInsertData(srcdomdoc)) {
193 0 : return NS_OK;
194 : }
195 : }
196 :
197 : // Current doc is destination
198 0 : nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument();
199 0 : NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED);
200 :
201 0 : uint32_t numItems = 0;
202 0 : nsresult rv = dataTransfer->GetMozItemCount(&numItems);
203 0 : NS_ENSURE_SUCCESS(rv, rv);
204 0 : if (numItems < 1) {
205 0 : return NS_ERROR_FAILURE; // Nothing to drop?
206 : }
207 :
208 : // Combine any deletion and drop insertion into one transaction
209 0 : AutoEditBatch beginBatching(this);
210 :
211 0 : bool deleteSelection = false;
212 :
213 : // We have to figure out whether to delete and relocate caret only once
214 : // Parent and offset are under the mouse cursor
215 0 : nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent);
216 0 : NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE);
217 :
218 0 : nsCOMPtr<nsIDOMNode> newSelectionParent;
219 0 : rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
220 0 : NS_ENSURE_SUCCESS(rv, rv);
221 0 : NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
222 :
223 : int32_t newSelectionOffset;
224 0 : rv = uiEvent->GetRangeOffset(&newSelectionOffset);
225 0 : NS_ENSURE_SUCCESS(rv, rv);
226 :
227 0 : RefPtr<Selection> selection = GetSelection();
228 0 : NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
229 :
230 0 : bool isCollapsed = selection->Collapsed();
231 :
232 : // Check if mouse is in the selection
233 : // if so, jump through some hoops to determine if mouse is over selection (bail)
234 : // and whether user wants to copy selection or delete it
235 0 : if (!isCollapsed) {
236 : // We never have to delete if selection is already collapsed
237 0 : bool cursorIsInSelection = false;
238 :
239 : int32_t rangeCount;
240 0 : rv = selection->GetRangeCount(&rangeCount);
241 0 : NS_ENSURE_SUCCESS(rv, rv);
242 :
243 0 : for (int32_t j = 0; j < rangeCount; j++) {
244 0 : RefPtr<nsRange> range = selection->GetRangeAt(j);
245 0 : if (!range) {
246 : // don't bail yet, iterate through them all
247 0 : continue;
248 : }
249 :
250 0 : rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
251 0 : if (cursorIsInSelection) {
252 0 : break;
253 : }
254 : }
255 :
256 0 : if (cursorIsInSelection) {
257 : // Dragging within same doc can't drop on itself -- leave!
258 0 : if (srcdomdoc == destdomdoc) {
259 0 : return NS_OK;
260 : }
261 :
262 : // Dragging from another window onto a selection
263 : // XXX Decision made to NOT do this,
264 : // note that 4.x does replace if dropped on
265 : //deleteSelection = true;
266 : } else {
267 : // We are NOT over the selection
268 0 : if (srcdomdoc == destdomdoc) {
269 : // Within the same doc: delete if user doesn't want to copy
270 : uint32_t dropEffect;
271 0 : dataTransfer->GetDropEffectInt(&dropEffect);
272 0 : deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
273 : } else {
274 : // Different source doc: Don't delete
275 0 : deleteSelection = false;
276 : }
277 : }
278 : }
279 :
280 0 : if (IsPlaintextEditor()) {
281 0 : nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent);
282 0 : while (content) {
283 0 : nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
284 0 : if (formControl && !formControl->AllowDrop()) {
285 : // Don't allow dropping into a form control that doesn't allow being
286 : // dropped into.
287 0 : return NS_OK;
288 : }
289 0 : content = content->GetParent();
290 : }
291 : }
292 :
293 0 : for (uint32_t i = 0; i < numItems; ++i) {
294 0 : InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent,
295 0 : newSelectionOffset, deleteSelection);
296 : }
297 :
298 0 : if (NS_SUCCEEDED(rv)) {
299 0 : ScrollSelectionIntoView(false);
300 : }
301 :
302 0 : return rv;
303 : }
304 :
305 : NS_IMETHODIMP
306 0 : TextEditor::Paste(int32_t aSelectionType)
307 : {
308 0 : if (!FireClipboardEvent(ePaste, aSelectionType)) {
309 0 : return NS_OK;
310 : }
311 :
312 : // Get Clipboard Service
313 : nsresult rv;
314 0 : nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
315 0 : if (NS_FAILED(rv)) {
316 0 : return rv;
317 : }
318 :
319 : // Get the nsITransferable interface for getting the data from the clipboard
320 0 : nsCOMPtr<nsITransferable> trans;
321 0 : rv = PrepareTransferable(getter_AddRefs(trans));
322 0 : if (NS_SUCCEEDED(rv) && trans) {
323 : // Get the Data from the clipboard
324 0 : if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) &&
325 0 : IsModifiable()) {
326 : // handle transferable hooks
327 0 : nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
328 0 : if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) {
329 0 : return NS_OK;
330 : }
331 :
332 0 : rv = InsertTextFromTransferable(trans, nullptr, 0, true);
333 : }
334 : }
335 :
336 0 : return rv;
337 : }
338 :
339 : NS_IMETHODIMP
340 0 : TextEditor::PasteTransferable(nsITransferable* aTransferable)
341 : {
342 : // Use an invalid value for the clipboard type as data comes from aTransferable
343 : // and we don't currently implement a way to put that in the data transfer yet.
344 0 : if (!FireClipboardEvent(ePaste, -1)) {
345 0 : return NS_OK;
346 : }
347 :
348 0 : if (!IsModifiable()) {
349 0 : return NS_OK;
350 : }
351 :
352 : // handle transferable hooks
353 0 : nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
354 0 : if (!EditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) {
355 0 : return NS_OK;
356 : }
357 :
358 0 : return InsertTextFromTransferable(aTransferable, nullptr, 0, true);
359 : }
360 :
361 : NS_IMETHODIMP
362 0 : TextEditor::CanPaste(int32_t aSelectionType,
363 : bool* aCanPaste)
364 : {
365 0 : NS_ENSURE_ARG_POINTER(aCanPaste);
366 0 : *aCanPaste = false;
367 :
368 : // Always enable the paste command when inside of a HTML or XHTML document.
369 0 : nsCOMPtr<nsIDocument> doc = GetDocument();
370 0 : if (doc && doc->IsHTMLOrXHTML()) {
371 0 : *aCanPaste = true;
372 0 : return NS_OK;
373 : }
374 :
375 : // can't paste if readonly
376 0 : if (!IsModifiable()) {
377 0 : return NS_OK;
378 : }
379 :
380 : nsresult rv;
381 0 : nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
382 0 : NS_ENSURE_SUCCESS(rv, rv);
383 :
384 : // the flavors that we can deal with
385 0 : const char* textEditorFlavors[] = { kUnicodeMime };
386 :
387 : bool haveFlavors;
388 0 : rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
389 0 : ArrayLength(textEditorFlavors),
390 0 : aSelectionType, &haveFlavors);
391 0 : NS_ENSURE_SUCCESS(rv, rv);
392 :
393 0 : *aCanPaste = haveFlavors;
394 0 : return NS_OK;
395 : }
396 :
397 :
398 : NS_IMETHODIMP
399 0 : TextEditor::CanPasteTransferable(nsITransferable* aTransferable,
400 : bool* aCanPaste)
401 : {
402 0 : NS_ENSURE_ARG_POINTER(aCanPaste);
403 :
404 : // can't paste if readonly
405 0 : if (!IsModifiable()) {
406 0 : *aCanPaste = false;
407 0 : return NS_OK;
408 : }
409 :
410 : // If |aTransferable| is null, assume that a paste will succeed.
411 0 : if (!aTransferable) {
412 0 : *aCanPaste = true;
413 0 : return NS_OK;
414 : }
415 :
416 0 : nsCOMPtr<nsISupports> data;
417 : uint32_t dataLen;
418 0 : nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
419 0 : getter_AddRefs(data),
420 0 : &dataLen);
421 0 : if (NS_SUCCEEDED(rv) && data) {
422 0 : *aCanPaste = true;
423 : } else {
424 0 : *aCanPaste = false;
425 : }
426 :
427 0 : return NS_OK;
428 : }
429 :
430 : bool
431 0 : TextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc)
432 : {
433 : // Try to determine whether we should use a sanitizing fragment sink
434 0 : bool isSafe = false;
435 :
436 0 : nsCOMPtr<nsIDocument> destdoc = GetDocument();
437 0 : NS_ASSERTION(destdoc, "Where is our destination doc?");
438 0 : nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell();
439 0 : nsCOMPtr<nsIDocShellTreeItem> root;
440 0 : if (dsti) {
441 0 : dsti->GetRootTreeItem(getter_AddRefs(root));
442 : }
443 0 : nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root);
444 : uint32_t appType;
445 0 : if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) {
446 0 : isSafe = appType == nsIDocShell::APP_TYPE_EDITOR;
447 : }
448 0 : if (!isSafe && aSourceDoc) {
449 0 : nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc);
450 0 : NS_ASSERTION(srcdoc, "Where is our source doc?");
451 :
452 0 : nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal();
453 0 : nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
454 0 : NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?");
455 0 : srcPrincipal->Subsumes(destPrincipal, &isSafe);
456 : }
457 :
458 0 : return isSafe;
459 : }
460 :
461 : } // namespace mozilla
|