1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/libeditor/text/nsPlaintextDataTransfer.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,457 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/ArrayUtils.h" 1.10 +#include "mozilla/MouseEvents.h" 1.11 +#include "nsAString.h" 1.12 +#include "nsCOMPtr.h" 1.13 +#include "nsCRT.h" 1.14 +#include "nsComponentManagerUtils.h" 1.15 +#include "nsContentUtils.h" 1.16 +#include "nsDebug.h" 1.17 +#include "nsEditor.h" 1.18 +#include "nsEditorUtils.h" 1.19 +#include "nsError.h" 1.20 +#include "nsIClipboard.h" 1.21 +#include "nsIContent.h" 1.22 +#include "nsIDOMDataTransfer.h" 1.23 +#include "nsIDOMDocument.h" 1.24 +#include "nsIDOMDragEvent.h" 1.25 +#include "nsIDOMEvent.h" 1.26 +#include "nsIDOMNode.h" 1.27 +#include "nsIDOMRange.h" 1.28 +#include "nsIDOMUIEvent.h" 1.29 +#include "nsIDocument.h" 1.30 +#include "nsIDragService.h" 1.31 +#include "nsIDragSession.h" 1.32 +#include "nsIEditor.h" 1.33 +#include "nsIEditorIMESupport.h" 1.34 +#include "nsIDocShell.h" 1.35 +#include "nsIDocShellTreeItem.h" 1.36 +#include "nsIPrincipal.h" 1.37 +#include "nsIFormControl.h" 1.38 +#include "nsIPlaintextEditor.h" 1.39 +#include "nsISelection.h" 1.40 +#include "nsISupportsPrimitives.h" 1.41 +#include "nsITransferable.h" 1.42 +#include "nsIVariant.h" 1.43 +#include "nsLiteralString.h" 1.44 +#include "nsPlaintextEditor.h" 1.45 +#include "nsSelectionState.h" 1.46 +#include "nsServiceManagerUtils.h" 1.47 +#include "nsString.h" 1.48 +#include "nsXPCOM.h" 1.49 +#include "nscore.h" 1.50 + 1.51 +class nsILoadContext; 1.52 +class nsISupports; 1.53 + 1.54 +using namespace mozilla; 1.55 +using namespace mozilla::dom; 1.56 + 1.57 +NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable) 1.58 +{ 1.59 + // Create generic Transferable for getting the data 1.60 + nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable); 1.61 + NS_ENSURE_SUCCESS(rv, rv); 1.62 + 1.63 + // Get the nsITransferable interface for getting the data from the clipboard 1.64 + if (transferable) { 1.65 + nsCOMPtr<nsIDocument> destdoc = GetDocument(); 1.66 + nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; 1.67 + (*transferable)->Init(loadContext); 1.68 + 1.69 + (*transferable)->AddDataFlavor(kUnicodeMime); 1.70 + (*transferable)->AddDataFlavor(kMozTextInternal); 1.71 + }; 1.72 + return NS_OK; 1.73 +} 1.74 + 1.75 +nsresult nsPlaintextEditor::InsertTextAt(const nsAString &aStringToInsert, 1.76 + nsIDOMNode *aDestinationNode, 1.77 + int32_t aDestOffset, 1.78 + bool aDoDeleteSelection) 1.79 +{ 1.80 + if (aDestinationNode) 1.81 + { 1.82 + nsresult res; 1.83 + nsCOMPtr<nsISelection>selection; 1.84 + res = GetSelection(getter_AddRefs(selection)); 1.85 + NS_ENSURE_SUCCESS(res, res); 1.86 + 1.87 + nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode; 1.88 + int32_t targetOffset = aDestOffset; 1.89 + 1.90 + if (aDoDeleteSelection) 1.91 + { 1.92 + // Use an auto tracker so that our drop point is correctly 1.93 + // positioned after the delete. 1.94 + nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset); 1.95 + res = DeleteSelection(eNone, eStrip); 1.96 + NS_ENSURE_SUCCESS(res, res); 1.97 + } 1.98 + 1.99 + res = selection->Collapse(targetNode, targetOffset); 1.100 + NS_ENSURE_SUCCESS(res, res); 1.101 + } 1.102 + 1.103 + return InsertText(aStringToInsert); 1.104 +} 1.105 + 1.106 +NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTransferable, 1.107 + nsIDOMNode *aDestinationNode, 1.108 + int32_t aDestOffset, 1.109 + bool aDoDeleteSelection) 1.110 +{ 1.111 + nsresult rv = NS_OK; 1.112 + char* bestFlavor = nullptr; 1.113 + nsCOMPtr<nsISupports> genericDataObj; 1.114 + uint32_t len = 0; 1.115 + if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len)) 1.116 + && bestFlavor && (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) || 1.117 + 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal))) 1.118 + { 1.119 + nsAutoTxnsConserveSelection dontSpazMySelection(this); 1.120 + nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) ); 1.121 + if (textDataObj && len > 0) 1.122 + { 1.123 + nsAutoString stuffToPaste; 1.124 + textDataObj->GetData(stuffToPaste); 1.125 + NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!"); 1.126 + 1.127 + // Sanitize possible carriage returns in the string to be inserted 1.128 + nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste); 1.129 + 1.130 + nsAutoEditBatch beginBatching(this); 1.131 + rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection); 1.132 + } 1.133 + } 1.134 + NS_Free(bestFlavor); 1.135 + 1.136 + // Try to scroll the selection into view if the paste/drop succeeded 1.137 + 1.138 + if (NS_SUCCEEDED(rv)) 1.139 + ScrollSelectionIntoView(false); 1.140 + 1.141 + return rv; 1.142 +} 1.143 + 1.144 +nsresult nsPlaintextEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer, 1.145 + int32_t aIndex, 1.146 + nsIDOMDocument *aSourceDoc, 1.147 + nsIDOMNode *aDestinationNode, 1.148 + int32_t aDestOffset, 1.149 + bool aDoDeleteSelection) 1.150 +{ 1.151 + nsCOMPtr<nsIVariant> data; 1.152 + aDataTransfer->MozGetDataAt(NS_LITERAL_STRING("text/plain"), aIndex, 1.153 + getter_AddRefs(data)); 1.154 + if (data) { 1.155 + nsAutoString insertText; 1.156 + data->GetAsAString(insertText); 1.157 + nsContentUtils::PlatformToDOMLineBreaks(insertText); 1.158 + 1.159 + nsAutoEditBatch beginBatching(this); 1.160 + return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection); 1.161 + } 1.162 + 1.163 + return NS_OK; 1.164 +} 1.165 + 1.166 +nsresult nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) 1.167 +{ 1.168 + ForceCompositionEnd(); 1.169 + 1.170 + nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent)); 1.171 + NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE); 1.172 + 1.173 + nsCOMPtr<nsIDOMDataTransfer> domDataTransfer; 1.174 + dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer)); 1.175 + nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer); 1.176 + NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE); 1.177 + 1.178 + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); 1.179 + NS_ASSERTION(dragSession, "No drag session"); 1.180 + 1.181 + nsCOMPtr<nsIDOMNode> sourceNode; 1.182 + dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode)); 1.183 + 1.184 + nsCOMPtr<nsIDOMDocument> srcdomdoc; 1.185 + if (sourceNode) { 1.186 + sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc)); 1.187 + NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE); 1.188 + } 1.189 + 1.190 + if (nsContentUtils::CheckForSubFrameDrop(dragSession, 1.191 + aDropEvent->GetInternalNSEvent()->AsDragEvent())) { 1.192 + // Don't allow drags from subframe documents with different origins than 1.193 + // the drop destination. 1.194 + if (srcdomdoc && !IsSafeToInsertData(srcdomdoc)) 1.195 + return NS_OK; 1.196 + } 1.197 + 1.198 + // Current doc is destination 1.199 + nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument(); 1.200 + NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED); 1.201 + 1.202 + uint32_t numItems = 0; 1.203 + nsresult rv = dataTransfer->GetMozItemCount(&numItems); 1.204 + NS_ENSURE_SUCCESS(rv, rv); 1.205 + if (numItems < 1) return NS_ERROR_FAILURE; // nothing to drop? 1.206 + 1.207 + // Combine any deletion and drop insertion into one transaction 1.208 + nsAutoEditBatch beginBatching(this); 1.209 + 1.210 + bool deleteSelection = false; 1.211 + 1.212 + // We have to figure out whether to delete and relocate caret only once 1.213 + // Parent and offset are under the mouse cursor 1.214 + nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent); 1.215 + NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE); 1.216 + 1.217 + nsCOMPtr<nsIDOMNode> newSelectionParent; 1.218 + rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent)); 1.219 + NS_ENSURE_SUCCESS(rv, rv); 1.220 + NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE); 1.221 + 1.222 + int32_t newSelectionOffset; 1.223 + rv = uiEvent->GetRangeOffset(&newSelectionOffset); 1.224 + NS_ENSURE_SUCCESS(rv, rv); 1.225 + 1.226 + nsCOMPtr<nsISelection> selection; 1.227 + rv = GetSelection(getter_AddRefs(selection)); 1.228 + NS_ENSURE_SUCCESS(rv, rv); 1.229 + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); 1.230 + 1.231 + bool isCollapsed = selection->Collapsed(); 1.232 + 1.233 + // Only the nsHTMLEditor::FindUserSelectAllNode returns a node. 1.234 + nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent); 1.235 + if (userSelectNode) 1.236 + { 1.237 + // The drop is happening over a "-moz-user-select: all" 1.238 + // subtree so make sure the content we insert goes before 1.239 + // the root of the subtree. 1.240 + // 1.241 + // XXX: Note that inserting before the subtree matches the 1.242 + // current behavior when dropping on top of an image. 1.243 + // The decision for dropping before or after the 1.244 + // subtree should really be done based on coordinates. 1.245 + 1.246 + newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset); 1.247 + 1.248 + NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE); 1.249 + } 1.250 + 1.251 + // Check if mouse is in the selection 1.252 + // if so, jump through some hoops to determine if mouse is over selection (bail) 1.253 + // and whether user wants to copy selection or delete it 1.254 + if (!isCollapsed) 1.255 + { 1.256 + // We never have to delete if selection is already collapsed 1.257 + bool cursorIsInSelection = false; 1.258 + 1.259 + int32_t rangeCount; 1.260 + rv = selection->GetRangeCount(&rangeCount); 1.261 + NS_ENSURE_SUCCESS(rv, rv); 1.262 + 1.263 + for (int32_t j = 0; j < rangeCount; j++) 1.264 + { 1.265 + nsCOMPtr<nsIDOMRange> range; 1.266 + rv = selection->GetRangeAt(j, getter_AddRefs(range)); 1.267 + if (NS_FAILED(rv) || !range) 1.268 + continue; // don't bail yet, iterate through them all 1.269 + 1.270 + rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection); 1.271 + if (cursorIsInSelection) 1.272 + break; 1.273 + } 1.274 + 1.275 + if (cursorIsInSelection) 1.276 + { 1.277 + // Dragging within same doc can't drop on itself -- leave! 1.278 + if (srcdomdoc == destdomdoc) 1.279 + return NS_OK; 1.280 + 1.281 + // Dragging from another window onto a selection 1.282 + // XXX Decision made to NOT do this, 1.283 + // note that 4.x does replace if dropped on 1.284 + //deleteSelection = true; 1.285 + } 1.286 + else 1.287 + { 1.288 + // We are NOT over the selection 1.289 + if (srcdomdoc == destdomdoc) 1.290 + { 1.291 + // Within the same doc: delete if user doesn't want to copy 1.292 + uint32_t dropEffect; 1.293 + dataTransfer->GetDropEffectInt(&dropEffect); 1.294 + deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY); 1.295 + } 1.296 + else 1.297 + { 1.298 + // Different source doc: Don't delete 1.299 + deleteSelection = false; 1.300 + } 1.301 + } 1.302 + } 1.303 + 1.304 + if (IsPlaintextEditor()) { 1.305 + nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent); 1.306 + while (content) { 1.307 + nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content)); 1.308 + if (formControl && !formControl->AllowDrop()) { 1.309 + // Don't allow dropping into a form control that doesn't allow being 1.310 + // dropped into. 1.311 + return NS_OK; 1.312 + } 1.313 + content = content->GetParent(); 1.314 + } 1.315 + } 1.316 + 1.317 + for (uint32_t i = 0; i < numItems; ++i) { 1.318 + InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent, 1.319 + newSelectionOffset, deleteSelection); 1.320 + } 1.321 + 1.322 + if (NS_SUCCEEDED(rv)) 1.323 + ScrollSelectionIntoView(false); 1.324 + 1.325 + return rv; 1.326 +} 1.327 + 1.328 +NS_IMETHODIMP nsPlaintextEditor::Paste(int32_t aSelectionType) 1.329 +{ 1.330 + if (!FireClipboardEvent(NS_PASTE, aSelectionType)) 1.331 + return NS_OK; 1.332 + 1.333 + // Get Clipboard Service 1.334 + nsresult rv; 1.335 + nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); 1.336 + if ( NS_FAILED(rv) ) 1.337 + return rv; 1.338 + 1.339 + // Get the nsITransferable interface for getting the data from the clipboard 1.340 + nsCOMPtr<nsITransferable> trans; 1.341 + rv = PrepareTransferable(getter_AddRefs(trans)); 1.342 + if (NS_SUCCEEDED(rv) && trans) 1.343 + { 1.344 + // Get the Data from the clipboard 1.345 + if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable()) 1.346 + { 1.347 + // handle transferable hooks 1.348 + nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument(); 1.349 + if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) 1.350 + return NS_OK; 1.351 + 1.352 + rv = InsertTextFromTransferable(trans, nullptr, 0, true); 1.353 + } 1.354 + } 1.355 + 1.356 + return rv; 1.357 +} 1.358 + 1.359 +NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable) 1.360 +{ 1.361 + // Use an invalid value for the clipboard type as data comes from aTransferable 1.362 + // and we don't currently implement a way to put that in the data transfer yet. 1.363 + if (!FireClipboardEvent(NS_PASTE, -1)) 1.364 + return NS_OK; 1.365 + 1.366 + if (!IsModifiable()) 1.367 + return NS_OK; 1.368 + 1.369 + // handle transferable hooks 1.370 + nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument(); 1.371 + if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) 1.372 + return NS_OK; 1.373 + 1.374 + return InsertTextFromTransferable(aTransferable, nullptr, 0, true); 1.375 +} 1.376 + 1.377 +NS_IMETHODIMP nsPlaintextEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste) 1.378 +{ 1.379 + NS_ENSURE_ARG_POINTER(aCanPaste); 1.380 + *aCanPaste = false; 1.381 + 1.382 + // can't paste if readonly 1.383 + if (!IsModifiable()) 1.384 + return NS_OK; 1.385 + 1.386 + nsresult rv; 1.387 + nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); 1.388 + NS_ENSURE_SUCCESS(rv, rv); 1.389 + 1.390 + // the flavors that we can deal with 1.391 + const char* textEditorFlavors[] = { kUnicodeMime }; 1.392 + 1.393 + bool haveFlavors; 1.394 + rv = clipboard->HasDataMatchingFlavors(textEditorFlavors, 1.395 + ArrayLength(textEditorFlavors), 1.396 + aSelectionType, &haveFlavors); 1.397 + NS_ENSURE_SUCCESS(rv, rv); 1.398 + 1.399 + *aCanPaste = haveFlavors; 1.400 + return NS_OK; 1.401 +} 1.402 + 1.403 + 1.404 +NS_IMETHODIMP nsPlaintextEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste) 1.405 +{ 1.406 + NS_ENSURE_ARG_POINTER(aCanPaste); 1.407 + 1.408 + // can't paste if readonly 1.409 + if (!IsModifiable()) { 1.410 + *aCanPaste = false; 1.411 + return NS_OK; 1.412 + } 1.413 + 1.414 + // If |aTransferable| is null, assume that a paste will succeed. 1.415 + if (!aTransferable) { 1.416 + *aCanPaste = true; 1.417 + return NS_OK; 1.418 + } 1.419 + 1.420 + nsCOMPtr<nsISupports> data; 1.421 + uint32_t dataLen; 1.422 + nsresult rv = aTransferable->GetTransferData(kUnicodeMime, 1.423 + getter_AddRefs(data), 1.424 + &dataLen); 1.425 + if (NS_SUCCEEDED(rv) && data) 1.426 + *aCanPaste = true; 1.427 + else 1.428 + *aCanPaste = false; 1.429 + 1.430 + return NS_OK; 1.431 +} 1.432 + 1.433 +bool nsPlaintextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc) 1.434 +{ 1.435 + // Try to determine whether we should use a sanitizing fragment sink 1.436 + bool isSafe = false; 1.437 + 1.438 + nsCOMPtr<nsIDocument> destdoc = GetDocument(); 1.439 + NS_ASSERTION(destdoc, "Where is our destination doc?"); 1.440 + nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell(); 1.441 + nsCOMPtr<nsIDocShellTreeItem> root; 1.442 + if (dsti) 1.443 + dsti->GetRootTreeItem(getter_AddRefs(root)); 1.444 + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root); 1.445 + uint32_t appType; 1.446 + if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) 1.447 + isSafe = appType == nsIDocShell::APP_TYPE_EDITOR; 1.448 + if (!isSafe && aSourceDoc) { 1.449 + nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc); 1.450 + NS_ASSERTION(srcdoc, "Where is our source doc?"); 1.451 + 1.452 + nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal(); 1.453 + nsIPrincipal* destPrincipal = destdoc->NodePrincipal(); 1.454 + NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?"); 1.455 + srcPrincipal->Subsumes(destPrincipal, &isSafe); 1.456 + } 1.457 + 1.458 + return isSafe; 1.459 +} 1.460 +