michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "nsAString.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsCRT.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDebug.h" michael@0: #include "nsEditor.h" michael@0: #include "nsEditorUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsIClipboard.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMDataTransfer.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMDragEvent.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIDOMUIEvent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDragService.h" michael@0: #include "nsIDragSession.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIEditorIMESupport.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIFormControl.h" michael@0: #include "nsIPlaintextEditor.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsITransferable.h" michael@0: #include "nsIVariant.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsPlaintextEditor.h" michael@0: #include "nsSelectionState.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nscore.h" michael@0: michael@0: class nsILoadContext; michael@0: class nsISupports; michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable) michael@0: { michael@0: // Create generic Transferable for getting the data michael@0: nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get the nsITransferable interface for getting the data from the clipboard michael@0: if (transferable) { michael@0: nsCOMPtr destdoc = GetDocument(); michael@0: nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; michael@0: (*transferable)->Init(loadContext); michael@0: michael@0: (*transferable)->AddDataFlavor(kUnicodeMime); michael@0: (*transferable)->AddDataFlavor(kMozTextInternal); michael@0: }; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsPlaintextEditor::InsertTextAt(const nsAString &aStringToInsert, michael@0: nsIDOMNode *aDestinationNode, michael@0: int32_t aDestOffset, michael@0: bool aDoDeleteSelection) michael@0: { michael@0: if (aDestinationNode) michael@0: { michael@0: nsresult res; michael@0: nsCOMPtrselection; michael@0: res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr targetNode = aDestinationNode; michael@0: int32_t targetOffset = aDestOffset; michael@0: michael@0: if (aDoDeleteSelection) michael@0: { michael@0: // Use an auto tracker so that our drop point is correctly michael@0: // positioned after the delete. michael@0: nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset); michael@0: res = DeleteSelection(eNone, eStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: res = selection->Collapse(targetNode, targetOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: return InsertText(aStringToInsert); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTransferable, michael@0: nsIDOMNode *aDestinationNode, michael@0: int32_t aDestOffset, michael@0: bool aDoDeleteSelection) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: char* bestFlavor = nullptr; michael@0: nsCOMPtr genericDataObj; michael@0: uint32_t len = 0; michael@0: if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len)) michael@0: && bestFlavor && (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) || michael@0: 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal))) michael@0: { michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: nsCOMPtr textDataObj ( do_QueryInterface(genericDataObj) ); michael@0: if (textDataObj && len > 0) michael@0: { michael@0: nsAutoString stuffToPaste; michael@0: textDataObj->GetData(stuffToPaste); michael@0: NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!"); michael@0: michael@0: // Sanitize possible carriage returns in the string to be inserted michael@0: nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection); michael@0: } michael@0: } michael@0: NS_Free(bestFlavor); michael@0: michael@0: // Try to scroll the selection into view if the paste/drop succeeded michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: ScrollSelectionIntoView(false); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsPlaintextEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer, michael@0: int32_t aIndex, michael@0: nsIDOMDocument *aSourceDoc, michael@0: nsIDOMNode *aDestinationNode, michael@0: int32_t aDestOffset, michael@0: bool aDoDeleteSelection) michael@0: { michael@0: nsCOMPtr data; michael@0: aDataTransfer->MozGetDataAt(NS_LITERAL_STRING("text/plain"), aIndex, michael@0: getter_AddRefs(data)); michael@0: if (data) { michael@0: nsAutoString insertText; michael@0: data->GetAsAString(insertText); michael@0: nsContentUtils::PlatformToDOMLineBreaks(insertText); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) michael@0: { michael@0: ForceCompositionEnd(); michael@0: michael@0: nsCOMPtr dragEvent(do_QueryInterface(aDropEvent)); michael@0: NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr domDataTransfer; michael@0: dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer)); michael@0: nsCOMPtr dataTransfer = do_QueryInterface(domDataTransfer); michael@0: NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr dragSession = nsContentUtils::GetDragSession(); michael@0: NS_ASSERTION(dragSession, "No drag session"); michael@0: michael@0: nsCOMPtr sourceNode; michael@0: dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode)); michael@0: michael@0: nsCOMPtr srcdomdoc; michael@0: if (sourceNode) { michael@0: sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc)); michael@0: NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: if (nsContentUtils::CheckForSubFrameDrop(dragSession, michael@0: aDropEvent->GetInternalNSEvent()->AsDragEvent())) { michael@0: // Don't allow drags from subframe documents with different origins than michael@0: // the drop destination. michael@0: if (srcdomdoc && !IsSafeToInsertData(srcdomdoc)) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Current doc is destination michael@0: nsCOMPtr destdomdoc = GetDOMDocument(); michael@0: NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: uint32_t numItems = 0; michael@0: nsresult rv = dataTransfer->GetMozItemCount(&numItems); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (numItems < 1) return NS_ERROR_FAILURE; // nothing to drop? michael@0: michael@0: // Combine any deletion and drop insertion into one transaction michael@0: nsAutoEditBatch beginBatching(this); michael@0: michael@0: bool deleteSelection = false; michael@0: michael@0: // We have to figure out whether to delete and relocate caret only once michael@0: // Parent and offset are under the mouse cursor michael@0: nsCOMPtr uiEvent = do_QueryInterface(aDropEvent); michael@0: NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr newSelectionParent; michael@0: rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE); michael@0: michael@0: int32_t newSelectionOffset; michael@0: rv = uiEvent->GetRangeOffset(&newSelectionOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr selection; michael@0: rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: michael@0: bool isCollapsed = selection->Collapsed(); michael@0: michael@0: // Only the nsHTMLEditor::FindUserSelectAllNode returns a node. michael@0: nsCOMPtr userSelectNode = FindUserSelectAllNode(newSelectionParent); michael@0: if (userSelectNode) michael@0: { michael@0: // The drop is happening over a "-moz-user-select: all" michael@0: // subtree so make sure the content we insert goes before michael@0: // the root of the subtree. michael@0: // michael@0: // XXX: Note that inserting before the subtree matches the michael@0: // current behavior when dropping on top of an image. michael@0: // The decision for dropping before or after the michael@0: // subtree should really be done based on coordinates. michael@0: michael@0: newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset); michael@0: michael@0: NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: // Check if mouse is in the selection michael@0: // if so, jump through some hoops to determine if mouse is over selection (bail) michael@0: // and whether user wants to copy selection or delete it michael@0: if (!isCollapsed) michael@0: { michael@0: // We never have to delete if selection is already collapsed michael@0: bool cursorIsInSelection = false; michael@0: michael@0: int32_t rangeCount; michael@0: rv = selection->GetRangeCount(&rangeCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (int32_t j = 0; j < rangeCount; j++) michael@0: { michael@0: nsCOMPtr range; michael@0: rv = selection->GetRangeAt(j, getter_AddRefs(range)); michael@0: if (NS_FAILED(rv) || !range) michael@0: continue; // don't bail yet, iterate through them all michael@0: michael@0: rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection); michael@0: if (cursorIsInSelection) michael@0: break; michael@0: } michael@0: michael@0: if (cursorIsInSelection) michael@0: { michael@0: // Dragging within same doc can't drop on itself -- leave! michael@0: if (srcdomdoc == destdomdoc) michael@0: return NS_OK; michael@0: michael@0: // Dragging from another window onto a selection michael@0: // XXX Decision made to NOT do this, michael@0: // note that 4.x does replace if dropped on michael@0: //deleteSelection = true; michael@0: } michael@0: else michael@0: { michael@0: // We are NOT over the selection michael@0: if (srcdomdoc == destdomdoc) michael@0: { michael@0: // Within the same doc: delete if user doesn't want to copy michael@0: uint32_t dropEffect; michael@0: dataTransfer->GetDropEffectInt(&dropEffect); michael@0: deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY); michael@0: } michael@0: else michael@0: { michael@0: // Different source doc: Don't delete michael@0: deleteSelection = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (IsPlaintextEditor()) { michael@0: nsCOMPtr content = do_QueryInterface(newSelectionParent); michael@0: while (content) { michael@0: nsCOMPtr formControl(do_QueryInterface(content)); michael@0: if (formControl && !formControl->AllowDrop()) { michael@0: // Don't allow dropping into a form control that doesn't allow being michael@0: // dropped into. michael@0: return NS_OK; michael@0: } michael@0: content = content->GetParent(); michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < numItems; ++i) { michael@0: InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent, michael@0: newSelectionOffset, deleteSelection); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: ScrollSelectionIntoView(false); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::Paste(int32_t aSelectionType) michael@0: { michael@0: if (!FireClipboardEvent(NS_PASTE, aSelectionType)) michael@0: return NS_OK; michael@0: michael@0: // Get Clipboard Service michael@0: nsresult rv; michael@0: nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); michael@0: if ( NS_FAILED(rv) ) michael@0: return rv; michael@0: michael@0: // Get the nsITransferable interface for getting the data from the clipboard michael@0: nsCOMPtr trans; michael@0: rv = PrepareTransferable(getter_AddRefs(trans)); michael@0: if (NS_SUCCEEDED(rv) && trans) michael@0: { michael@0: // Get the Data from the clipboard michael@0: if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable()) michael@0: { michael@0: // handle transferable hooks michael@0: nsCOMPtr domdoc = GetDOMDocument(); michael@0: if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) michael@0: return NS_OK; michael@0: michael@0: rv = InsertTextFromTransferable(trans, nullptr, 0, true); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable) michael@0: { michael@0: // Use an invalid value for the clipboard type as data comes from aTransferable michael@0: // and we don't currently implement a way to put that in the data transfer yet. michael@0: if (!FireClipboardEvent(NS_PASTE, -1)) michael@0: return NS_OK; michael@0: michael@0: if (!IsModifiable()) michael@0: return NS_OK; michael@0: michael@0: // handle transferable hooks michael@0: nsCOMPtr domdoc = GetDOMDocument(); michael@0: if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) michael@0: return NS_OK; michael@0: michael@0: return InsertTextFromTransferable(aTransferable, nullptr, 0, true); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanPaste); michael@0: *aCanPaste = false; michael@0: michael@0: // can't paste if readonly michael@0: if (!IsModifiable()) michael@0: return NS_OK; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // the flavors that we can deal with michael@0: const char* textEditorFlavors[] = { kUnicodeMime }; michael@0: michael@0: bool haveFlavors; michael@0: rv = clipboard->HasDataMatchingFlavors(textEditorFlavors, michael@0: ArrayLength(textEditorFlavors), michael@0: aSelectionType, &haveFlavors); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aCanPaste = haveFlavors; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsPlaintextEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanPaste); michael@0: michael@0: // can't paste if readonly michael@0: if (!IsModifiable()) { michael@0: *aCanPaste = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If |aTransferable| is null, assume that a paste will succeed. michael@0: if (!aTransferable) { michael@0: *aCanPaste = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr data; michael@0: uint32_t dataLen; michael@0: nsresult rv = aTransferable->GetTransferData(kUnicodeMime, michael@0: getter_AddRefs(data), michael@0: &dataLen); michael@0: if (NS_SUCCEEDED(rv) && data) michael@0: *aCanPaste = true; michael@0: else michael@0: *aCanPaste = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsPlaintextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc) michael@0: { michael@0: // Try to determine whether we should use a sanitizing fragment sink michael@0: bool isSafe = false; michael@0: michael@0: nsCOMPtr destdoc = GetDocument(); michael@0: NS_ASSERTION(destdoc, "Where is our destination doc?"); michael@0: nsCOMPtr dsti = destdoc->GetDocShell(); michael@0: nsCOMPtr root; michael@0: if (dsti) michael@0: dsti->GetRootTreeItem(getter_AddRefs(root)); michael@0: nsCOMPtr docShell = do_QueryInterface(root); michael@0: uint32_t appType; michael@0: if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) michael@0: isSafe = appType == nsIDocShell::APP_TYPE_EDITOR; michael@0: if (!isSafe && aSourceDoc) { michael@0: nsCOMPtr srcdoc = do_QueryInterface(aSourceDoc); michael@0: NS_ASSERTION(srcdoc, "Where is our source doc?"); michael@0: michael@0: nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal(); michael@0: nsIPrincipal* destPrincipal = destdoc->NodePrincipal(); michael@0: NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?"); michael@0: srcPrincipal->Subsumes(destPrincipal, &isSafe); michael@0: } michael@0: michael@0: return isSafe; michael@0: } michael@0: