From 8acdaab321ee759f92228349c33146a8677e35ea Mon Sep 17 00:00:00 2001 From: Neil Deakin Date: Fri, 17 Feb 2012 09:16:59 -0500 Subject: [PATCH] Bug 499008, part 4, convert editor drop handling to use datatransfers instead of the drag service, r=ehsan --- .../libeditor/base/nsEditorEventListener.cpp | 8 +- editor/libeditor/base/nsEditorUtils.h | 1 - editor/libeditor/html/nsHTMLDataTransfer.cpp | 501 ++++++++++-------- editor/libeditor/html/nsHTMLEditor.h | 18 + .../text/nsPlaintextDataTransfer.cpp | 69 ++- 5 files changed, 332 insertions(+), 265 deletions(-) diff --git a/editor/libeditor/base/nsEditorEventListener.cpp b/editor/libeditor/base/nsEditorEventListener.cpp index b2c702d22d0a..50aa05206c5c 100644 --- a/editor/libeditor/base/nsEditorEventListener.cpp +++ b/editor/libeditor/base/nsEditorEventListener.cpp @@ -61,8 +61,6 @@ // Drag & Drop, Clipboard #include "nsIServiceManager.h" #include "nsIClipboard.h" -#include "nsIDragService.h" -#include "nsIDragSession.h" #include "nsIContent.h" #include "nsISupportsPrimitives.h" #include "nsIDOMRange.h" @@ -700,6 +698,10 @@ nsEditorEventListener::DragOver(nsIDOMDragEvent* aDragEvent) } else { + // This is needed when dropping on an input, to prevent the editor for + // the editable parent from receiving the event. + aDragEvent->StopPropagation(); + if (mCaret) { mCaret->EraseCaret(); @@ -771,8 +773,6 @@ nsEditorEventListener::Drop(nsIDOMDragEvent* aMouseEvent) aMouseEvent->StopPropagation(); aMouseEvent->PreventDefault(); - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. return mEditor->InsertFromDrop(aMouseEvent); } diff --git a/editor/libeditor/base/nsEditorUtils.h b/editor/libeditor/base/nsEditorUtils.h index 1bdab22e5b7f..debe2dc05fa3 100644 --- a/editor/libeditor/base/nsEditorUtils.h +++ b/editor/libeditor/base/nsEditorUtils.h @@ -257,7 +257,6 @@ class nsEditorUtils }; -class nsIDragSession; class nsITransferable; class nsIDOMEvent; class nsISimpleEnumerator; diff --git a/editor/libeditor/html/nsHTMLDataTransfer.cpp b/editor/libeditor/html/nsHTMLDataTransfer.cpp index 406ccef63ccc..c1f86455a3ff 100644 --- a/editor/libeditor/html/nsHTMLDataTransfer.cpp +++ b/editor/libeditor/html/nsHTMLDataTransfer.cpp @@ -64,6 +64,8 @@ #include "nsIContent.h" #include "nsIContentIterator.h" #include "nsIDOMRange.h" +#include "nsIDOMDOMStringList.h" +#include "nsIDOMDragEvent.h" #include "nsCOMArray.h" #include "nsIFile.h" #include "nsIURL.h" @@ -1204,6 +1206,116 @@ nsHTMLEditor::ParseCFHTML(nsCString & aCfhtml, PRUnichar **aStuffToPaste, PRUnic return NS_OK; } +bool nsHTMLEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc) +{ + // Try to determine whether we should use a sanitizing fragment sink + bool isSafe = false; + nsCOMPtr destdomdoc; + GetDocument(getter_AddRefs(destdomdoc)); + + nsCOMPtr destdoc = do_QueryInterface(destdomdoc); + NS_ASSERTION(destdoc, "Where is our destination doc?"); + nsCOMPtr container = destdoc->GetContainer(); + nsCOMPtr dsti(do_QueryInterface(container)); + nsCOMPtr root; + if (dsti) + dsti->GetRootTreeItem(getter_AddRefs(root)); + nsCOMPtr docShell(do_QueryInterface(root)); + PRUint32 appType; + if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) + isSafe = appType == nsIDocShell::APP_TYPE_EDITOR; + if (!isSafe && aSourceDoc) { + nsCOMPtr srcdoc = do_QueryInterface(aSourceDoc); + NS_ASSERTION(srcdoc, "Where is our source doc?"); + + nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal(); + nsIPrincipal* destPrincipal = destdoc->NodePrincipal(); + NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?"); + srcPrincipal->Subsumes(destPrincipal, &isSafe); + } + + return isSafe; +} + +nsresult nsHTMLEditor::InsertObject(const char* aType, nsISupports* aObject, bool aIsSafe, + nsIDOMDocument *aSourceDoc, + nsIDOMNode *aDestinationNode, + PRInt32 aDestOffset, + bool aDoDeleteSelection) +{ + nsresult rv; + + const char* type = aType; + + // Check to see if we can insert an image file + bool insertAsImage = false; + nsCOMPtr fileURI; + if (0 == nsCRT::strcmp(type, kFileMime)) + { + nsCOMPtr fileObj(do_QueryInterface(aObject)); + if (fileObj) + { + rv = NS_NewFileURI(getter_AddRefs(fileURI), fileObj); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr mime = do_GetService("@mozilla.org/mime;1"); + NS_ENSURE_TRUE(mime, NS_ERROR_FAILURE); + nsCAutoString contentType; + rv = mime->GetTypeFromFile(fileObj, contentType); + NS_ENSURE_SUCCESS(rv, rv); + + // Accept any image type fed to us + if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) { + insertAsImage = true; + type = contentType.get(); + } + } + } + + if (0 == nsCRT::strcmp(type, kJPEGImageMime) || + 0 == nsCRT::strcmp(type, kPNGImageMime) || + 0 == nsCRT::strcmp(type, kGIFImageMime) || + insertAsImage) + { + nsCOMPtr imageStream; + if (insertAsImage) { + NS_ASSERTION(fileURI, "The file URI should be retrieved earlier"); + rv = NS_OpenURI(getter_AddRefs(imageStream), fileURI); + NS_ENSURE_SUCCESS(rv, rv); + } else { + imageStream = do_QueryInterface(aObject); + NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE); + } + + nsCString imageData; + rv = NS_ConsumeStream(imageStream, PR_UINT32_MAX, imageData); + NS_ENSURE_SUCCESS(rv, rv); + + rv = imageStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + char * base64 = PL_Base64Encode(imageData.get(), imageData.Length(), nsnull); + NS_ENSURE_TRUE(base64, NS_ERROR_OUT_OF_MEMORY); + + nsAutoString stuffToPaste; + stuffToPaste.AssignLiteral("\"\""); + nsAutoEditBatch beginBatching(this); + rv = DoInsertHTMLWithContext(stuffToPaste, EmptyString(), EmptyString(), + NS_LITERAL_STRING(kFileMime), + aSourceDoc, + aDestinationNode, aDestOffset, + aDoDeleteSelection, + aIsSafe); + PR_Free(base64); + } + + return NS_OK; +} + NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable, nsIDOMDocument *aSourceDoc, const nsAString & aContextStr, @@ -1226,59 +1338,16 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable printf("Got flavor [%s]\n", bestFlavor.get()); #endif - // Try to determine whether we should use a sanitizing fragment sink - bool isSafe = false; - nsCOMPtr destdomdoc; - rv = GetDocument(getter_AddRefs(destdomdoc)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr destdoc = do_QueryInterface(destdomdoc); - NS_ASSERTION(destdoc, "Where is our destination doc?"); - nsCOMPtr container = destdoc->GetContainer(); - nsCOMPtr dsti(do_QueryInterface(container)); - nsCOMPtr root; - if (dsti) - dsti->GetRootTreeItem(getter_AddRefs(root)); - nsCOMPtr docShell(do_QueryInterface(root)); - PRUint32 appType; - if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) - isSafe = appType == nsIDocShell::APP_TYPE_EDITOR; - if (!isSafe && aSourceDoc) { - nsCOMPtr srcdoc = do_QueryInterface(aSourceDoc); - NS_ASSERTION(srcdoc, "Where is our source doc?"); + bool isSafe = IsSafeToInsertData(aSourceDoc); - nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal(); - nsIPrincipal* destPrincipal = destdoc->NodePrincipal(); - NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?"); - rv = srcPrincipal->Subsumes(destPrincipal, &isSafe); - NS_ENSURE_SUCCESS(rv, rv); + if (0 == nsCRT::strcmp(bestFlavor, kFileMime) || + 0 == nsCRT::strcmp(bestFlavor, kJPEGImageMime) || + 0 == nsCRT::strcmp(bestFlavor, kPNGImageMime) || + 0 == nsCRT::strcmp(bestFlavor, kGIFImageMime)) { + rv = InsertObject(bestFlavor, genericDataObj, isSafe, + aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection); } - - // Check to see if we can insert an image file - bool insertAsImage = false; - nsCOMPtr fileURI; - if (0 == nsCRT::strcmp(bestFlavor, kFileMime)) - { - nsCOMPtr fileObj(do_QueryInterface(genericDataObj)); - if (fileObj && len > 0) - { - rv = NS_NewFileURI(getter_AddRefs(fileURI), fileObj); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr mime = do_GetService("@mozilla.org/mime;1"); - NS_ENSURE_TRUE(mime, NS_ERROR_FAILURE); - nsCAutoString contentType; - rv = mime->GetTypeFromFile(fileObj, contentType); - NS_ENSURE_SUCCESS(rv, rv); - - // Accept any image type fed to us - if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) { - insertAsImage = true; - bestFlavor = contentType; - } - } - } - - if (0 == nsCRT::strcmp(bestFlavor, kNativeHTMLMime)) + else if (0 == nsCRT::strcmp(bestFlavor, kNativeHTMLMime)) { // note cf_html uses utf8, hence use length = len, not len/2 as in flavors below nsCOMPtr textDataObj(do_QueryInterface(genericDataObj)); @@ -1302,8 +1371,9 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable } } } - else if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime)) - { + else if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime) || + 0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) || + 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)) { nsCOMPtr textDataObj(do_QueryInterface(genericDataObj)); if (textDataObj && len > 0) { @@ -1311,113 +1381,148 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable textDataObj->GetData(text); NS_ASSERTION(text.Length() <= (len/2), "Invalid length!"); stuffToPaste.Assign(text.get(), len / 2); + nsAutoEditBatch beginBatching(this); - rv = DoInsertHTMLWithContext(stuffToPaste, - aContextStr, aInfoStr, flavor, - aSourceDoc, - aDestinationNode, aDestOffset, - aDoDeleteSelection, - isSafe); + if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime)) { + rv = DoInsertHTMLWithContext(stuffToPaste, + aContextStr, aInfoStr, flavor, + aSourceDoc, + aDestinationNode, aDestOffset, + aDoDeleteSelection, + isSafe); + } else { + rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection); + } } } - else if (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) || - 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)) - { - nsCOMPtr textDataObj(do_QueryInterface(genericDataObj)); - if (textDataObj && len > 0) - { - nsAutoString text; - textDataObj->GetData(text); - NS_ASSERTION(text.Length() <= (len/2), "Invalid length!"); - stuffToPaste.Assign(text.get(), len / 2); - nsAutoEditBatch beginBatching(this); - // need to provide a hook from this point - rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection); - } - } - else if (0 == nsCRT::strcmp(bestFlavor, kJPEGImageMime) || - 0 == nsCRT::strcmp(bestFlavor, kPNGImageMime) || - 0 == nsCRT::strcmp(bestFlavor, kGIFImageMime) || - insertAsImage) - { - nsCOMPtr imageStream; - if (insertAsImage) { - NS_ASSERTION(fileURI, "The file URI should be retrieved earlier"); - rv = NS_OpenURI(getter_AddRefs(imageStream), fileURI); - NS_ENSURE_SUCCESS(rv, rv); - } else { - imageStream = do_QueryInterface(genericDataObj); - NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE); - } - - nsCString imageData; - rv = NS_ConsumeStream(imageStream, PR_UINT32_MAX, imageData); - NS_ENSURE_SUCCESS(rv, rv); - - rv = imageStream->Close(); - NS_ENSURE_SUCCESS(rv, rv); - - char * base64 = PL_Base64Encode(imageData.get(), imageData.Length(), nsnull); - NS_ENSURE_TRUE(base64, NS_ERROR_OUT_OF_MEMORY); - - stuffToPaste.AssignLiteral("\"\""); - nsAutoEditBatch beginBatching(this); - rv = DoInsertHTMLWithContext(stuffToPaste, EmptyString(), EmptyString(), - NS_LITERAL_STRING(kFileMime), - aSourceDoc, - aDestinationNode, aDestOffset, - aDoDeleteSelection, - isSafe); - PR_Free(base64); - } } - - // Try to scroll the selection into view if the paste/drop succeeded - // After ScrollSelectionIntoView(), the pending notifications might be - // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + // Try to scroll the selection into view if the paste succeeded if (NS_SUCCEEDED(rv)) ScrollSelectionIntoView(false); return rv; } +static void +GetStringFromDataTransfer(nsIDOMDataTransfer *aDataTransfer, const nsAString& aType, + PRInt32 aIndex, nsAString& aOutputString) +{ + nsCOMPtr variant; + aDataTransfer->MozGetDataAt(aType, aIndex, getter_AddRefs(variant)); + if (variant) + variant->GetAsAString(aOutputString); +} + +nsresult nsHTMLEditor::InsertFromDataTransfer(nsIDOMDataTransfer *aDataTransfer, + PRInt32 aIndex, + nsIDOMDocument *aSourceDoc, + const nsAString & aContextStr, + const nsAString & aInfoStr, + nsIDOMNode *aDestinationNode, + PRInt32 aDestOffset, + bool aDoDeleteSelection) +{ + nsresult rv = NS_OK; + + nsCOMPtr types; + aDataTransfer->MozTypesAt(aIndex, getter_AddRefs(types)); + + bool hasPrivateHTMLFlavor; + types->Contains(NS_LITERAL_STRING(kHTMLContext), &hasPrivateHTMLFlavor); + + bool isText = IsPlaintextEditor(); + bool isSafe = IsSafeToInsertData(aSourceDoc); + + PRUint32 length; + types->GetLength(&length); + for (PRUint32 t = 0; t < length; t++) { + nsAutoString type; + types->Item(t, type); + + if (!isText) { + if (type.EqualsLiteral(kFileMime) || + type.EqualsLiteral(kJPEGImageMime) || + type.EqualsLiteral(kPNGImageMime) || + type.EqualsLiteral(kGIFImageMime)) { + nsCOMPtr variant; + aDataTransfer->MozGetDataAt(type, aIndex, getter_AddRefs(variant)); + if (variant) { + nsCOMPtr object; + variant->GetAsISupports(getter_AddRefs(object)); + rv = InsertObject(NS_ConvertUTF16toUTF8(type).get(), object, isSafe, + aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection); + if (NS_SUCCEEDED(rv)) + return NS_OK; + } + } + else if (!hasPrivateHTMLFlavor && type.EqualsLiteral(kNativeHTMLMime)) { + nsAutoString text; + GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kNativeHTMLMime), aIndex, text); + NS_ConvertUTF16toUTF8 cfhtml(text); + + nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now + + rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext)); + if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) + { + nsAutoEditBatch beginBatching(this); + rv = DoInsertHTMLWithContext(cffragment, + cfcontext, cfselection, type, + aSourceDoc, + aDestinationNode, aDestOffset, + aDoDeleteSelection, + isSafe); + if (NS_SUCCEEDED(rv)) + return NS_OK; + } + } + else if (type.EqualsLiteral(kHTMLMime)) { + nsAutoString text; + GetStringFromDataTransfer(aDataTransfer, type, aIndex, text); + + nsAutoEditBatch beginBatching(this); + if (type.EqualsLiteral(kHTMLMime)) { + rv = DoInsertHTMLWithContext(text, + aContextStr, aInfoStr, type, + aSourceDoc, + aDestinationNode, aDestOffset, + aDoDeleteSelection, + isSafe); + if (NS_SUCCEEDED(rv)) + return NS_OK; + } + } + } + + if (type.EqualsLiteral(kTextMime) || + type.EqualsLiteral(kMozTextInternal)) { + nsAutoString text; + GetStringFromDataTransfer(aDataTransfer, type, aIndex, text); + + nsAutoEditBatch beginBatching(this); + rv = InsertTextAt(text, aDestinationNode, aDestOffset, aDoDeleteSelection); + if (NS_SUCCEEDED(rv)) + return NS_OK; + } + } + + return rv; +} + NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) { ForceCompositionEnd(); - - nsresult rv; - nsCOMPtr dragService = - do_GetService("@mozilla.org/widget/dragservice;1", &rv); + + nsCOMPtr dragEvent(do_QueryInterface(aDropEvent)); + NS_ENSURE_TRUE(dragEvent, NS_OK); + + nsCOMPtr dataTransfer; + nsresult rv = dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer)); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr dragSession; - dragService->GetCurrentSession(getter_AddRefs(dragSession)); - NS_ENSURE_TRUE(dragSession, NS_OK); - - // transferable hooks here - nsCOMPtr domdoc; - GetDocument(getter_AddRefs(domdoc)); - - // find out if we have our internal html flavor on the clipboard. We don't want to mess - // around with cfhtml if we do. - bool bHavePrivateHTMLFlavor = false; - rv = dragSession->IsDataFlavorSupported(kHTMLContext, &bHavePrivateHTMLFlavor); - NS_ENSURE_SUCCESS(rv, rv); - - // Get the nsITransferable interface for getting the data from the drop - nsCOMPtr trans; - rv = PrepareHTMLTransferable(getter_AddRefs(trans), bHavePrivateHTMLFlavor); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(trans, NS_OK); // NS_ERROR_FAILURE; SHOULD WE FAIL? - - PRUint32 numItems = 0; - rv = dragSession->GetNumDropItems(&numItems); - NS_ENSURE_SUCCESS(rv, rv); + PRUint32 numItems = 0; + dataTransfer->GetMozItemCount(&numItems); // Combine any deletion and drop insertion into one transaction nsAutoEditBatch beginBatching(this); @@ -1427,80 +1532,25 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) nsCOMPtr newSelectionParent; PRInt32 newSelectionOffset = 0; - // Source doc is null if source is *not* the current editor document + nsCOMPtr sourceNode; + dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode)); + nsCOMPtr srcdomdoc; - rv = dragSession->GetSourceDocument(getter_AddRefs(srcdomdoc)); - NS_ENSURE_SUCCESS(rv, rv); + if (sourceNode) { + sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc)); + NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE); + } PRUint32 i; bool doPlaceCaret = true; for (i = 0; i < numItems; ++i) { - rv = dragSession->GetData(trans, i); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(trans, NS_OK); // NS_ERROR_FAILURE; Should we fail? - - // get additional html copy hints, if present - nsAutoString contextStr, infoStr; - nsCOMPtr contextDataObj, infoDataObj; - PRUint32 contextLen, infoLen; - nsCOMPtr textDataObj; - - nsCOMPtr contextTrans = - do_CreateInstance("@mozilla.org/widget/transferable;1"); - NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER); - contextTrans->AddDataFlavor(kHTMLContext); - dragSession->GetData(contextTrans, i); - contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen); - - nsCOMPtr infoTrans = - do_CreateInstance("@mozilla.org/widget/transferable;1"); - NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER); - infoTrans->AddDataFlavor(kHTMLInfo); - dragSession->GetData(infoTrans, i); - infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen); - - if (contextDataObj) - { - nsAutoString text; - textDataObj = do_QueryInterface(contextDataObj); - textDataObj->GetData(text); - NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!"); - contextStr.Assign(text.get(), contextLen / 2); - } - - if (infoDataObj) - { - nsAutoString text; - textDataObj = do_QueryInterface(infoDataObj); - textDataObj->GetData(text); - NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!"); - infoStr.Assign(text.get(), infoLen / 2); - } + nsAutoString contextString, infoString; + GetStringFromDataTransfer(dataTransfer, NS_LITERAL_STRING(kHTMLContext), i, contextString); + GetStringFromDataTransfer(dataTransfer, NS_LITERAL_STRING(kHTMLInfo), i, infoString); if (doPlaceCaret) { - // check if the user pressed the key to force a copy rather than a move - // if we run into problems here, we'll just assume the user doesn't want a copy - bool userWantsCopy = false; - - nsCOMPtr uiEvent = do_QueryInterface(aDropEvent); - NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE); - - nsCOMPtr mouseEvent = do_QueryInterface(aDropEvent); - if (mouseEvent) { -#if defined(XP_MACOSX) - mouseEvent->GetAltKey(&userWantsCopy); -#else - mouseEvent->GetCtrlKey(&userWantsCopy); -#endif - } - - // Current doc is destination - nsCOMPtr destdomdoc; - rv = GetDocument(getter_AddRefs(destdomdoc)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr selection; rv = GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(rv, rv); @@ -1509,7 +1559,10 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) bool isCollapsed; rv = selection->GetIsCollapsed(&isCollapsed); NS_ENSURE_SUCCESS(rv, rv); - + + nsCOMPtr uiEvent = do_QueryInterface(aDropEvent); + NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE); + // Parent and offset under the mouse cursor rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent)); NS_ENSURE_SUCCESS(rv, rv); @@ -1565,6 +1618,11 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) if(cursorIsInSelection) break; } + + nsCOMPtr destdomdoc; + GetDocument(getter_AddRefs(destdomdoc)); + NS_ENSURE_TRUE(destdomdoc, NS_ERROR_FAILURE); + if (cursorIsInSelection) { // Dragging within same doc can't drop on itself -- leave! @@ -1583,7 +1641,9 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) if (srcdomdoc == destdomdoc) { // Within the same doc: delete if user doesn't want to copy - deleteSelection = !userWantsCopy; + PRUint32 dropEffect; + dataTransfer->GetDropEffectInt(&dropEffect); + deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY); } else { @@ -1597,17 +1657,14 @@ NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) doPlaceCaret = false; } - // handle transferable hooks - if (!nsEditorHookUtils::DoInsertionHook(domdoc, aDropEvent, trans)) - return NS_OK; - - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. - rv = InsertFromTransferable(trans, srcdomdoc, contextStr, infoStr, - newSelectionParent, + rv = InsertFromDataTransfer(dataTransfer, i, srcdomdoc, + contextString, infoString, newSelectionParent, newSelectionOffset, deleteSelection); } + if (NS_SUCCEEDED(rv)) + ScrollSelectionIntoView(false); + return rv; } @@ -1701,8 +1758,6 @@ NS_IMETHODIMP nsHTMLEditor::Paste(PRInt32 aSelectionType) if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, trans)) return NS_OK; - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. rv = InsertFromTransferable(trans, nsnull, contextStr, infoStr, nsnull, 0, true); } @@ -1722,8 +1777,6 @@ NS_IMETHODIMP nsHTMLEditor::PasteTransferable(nsITransferable *aTransferable) if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, aTransferable)) return NS_OK; - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. nsAutoString contextStr, infoStr; return InsertFromTransferable(aTransferable, nsnull, contextStr, infoStr, nsnull, 0, true); @@ -1751,8 +1804,6 @@ NS_IMETHODIMP nsHTMLEditor::PasteNoFormatting(PRInt32 aSelectionType) if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable()) { const nsAFlatString& empty = EmptyString(); - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. rv = InsertFromTransferable(trans, nsnull, empty, empty, nsnull, 0, true); } diff --git a/editor/libeditor/html/nsHTMLEditor.h b/editor/libeditor/html/nsHTMLEditor.h index a48e303aeeec..cf7a75e73e28 100644 --- a/editor/libeditor/html/nsHTMLEditor.h +++ b/editor/libeditor/html/nsHTMLEditor.h @@ -572,6 +572,16 @@ protected: NS_IMETHOD InsertAsPlaintextQuotation(const nsAString & aQuotedText, bool aAddCites, nsIDOMNode **aNodeInserted); + // Return true if the data is safe to insert as the source and destination + // principals match, or we are in a editor context where this doesn't matter. + // Otherwise, the data must be sanitized first. + bool IsSafeToInsertData(nsIDOMDocument* aSourceDoc); + + nsresult InsertObject(const char* aType, nsISupports* aObject, bool aIsSafe, + nsIDOMDocument *aSourceDoc, + nsIDOMNode *aDestinationNode, + PRInt32 aDestOffset, + bool aDoDeleteSelection); // factored methods for handling insertion of data from transferables (drag&drop or clipboard) NS_IMETHOD PrepareTransferable(nsITransferable **transferable); @@ -583,6 +593,14 @@ protected: nsIDOMNode *aDestinationNode, PRInt32 aDestinationOffset, bool aDoDeleteSelection); + nsresult InsertFromDataTransfer(nsIDOMDataTransfer *aDataTransfer, + PRInt32 aIndex, + nsIDOMDocument *aSourceDoc, + const nsAString & aContextStr, + const nsAString & aInfoStr, + nsIDOMNode *aDestinationNode, + PRInt32 aDestOffset, + bool aDoDeleteSelection); bool HavePrivateHTMLFlavor( nsIClipboard *clipboard ); nsresult ParseCFHTML(nsCString & aCfhtml, PRUnichar **aStuffToPaste, PRUnichar **aCfcontext); nsresult DoContentFilterCallback(const nsAString &aFlavor, diff --git a/editor/libeditor/text/nsPlaintextDataTransfer.cpp b/editor/libeditor/text/nsPlaintextDataTransfer.cpp index 1982f73cef72..20d8803e4dc1 100644 --- a/editor/libeditor/text/nsPlaintextDataTransfer.cpp +++ b/editor/libeditor/text/nsPlaintextDataTransfer.cpp @@ -52,6 +52,7 @@ #include "nsServiceManagerUtils.h" #include "nsIDOMRange.h" +#include "nsIDOMDOMStringList.h" #include "nsIDocumentEncoder.h" #include "nsISupportsPrimitives.h" @@ -61,6 +62,7 @@ #include "nsIDragService.h" #include "nsIDOMUIEvent.h" #include "nsCopySupport.h" +#include "nsITransferable.h" // Misc #include "nsEditorUtils.h" @@ -70,6 +72,10 @@ #include "nsEventDispatcher.h" #include "nsContentUtils.h" +// private clipboard data flavors for html copy/paste +#define kHTMLContext "text/_moz_htmlcontext" +#define kHTMLInfo "text/_moz_htmlinfo" + using namespace mozilla; NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable) @@ -151,8 +157,6 @@ NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTr // Try to scroll the selection into view if the paste/drop succeeded - // After ScrollSelectionIntoView(), the pending notifications might be flushed - // and PresShell/PresContext/Frames may be dead. See bug 418470. if (NS_SUCCEEDED(rv)) ScrollSelectionIntoView(false); @@ -162,29 +166,21 @@ NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTr NS_IMETHODIMP nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) { ForceCompositionEnd(); - - nsresult rv; - nsCOMPtr dragService = - do_GetService("@mozilla.org/widget/dragservice;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr dragSession; - dragService->GetCurrentSession(getter_AddRefs(dragSession)); - NS_ENSURE_TRUE(dragSession, NS_OK); + nsCOMPtr dragEvent(do_QueryInterface(aDropEvent)); + NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE); + + nsCOMPtr dataTransfer; + nsresult rv = dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer)); + NS_ENSURE_SUCCESS(rv, rv); // Current doc is destination nsCOMPtr destdomdoc; rv = GetDocument(getter_AddRefs(destdomdoc)); NS_ENSURE_SUCCESS(rv, rv); - // Get the nsITransferable interface for getting the data from the drop - nsCOMPtr trans; - rv = PrepareTransferable(getter_AddRefs(trans)); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(trans, NS_OK); // NS_ERROR_FAILURE; SHOULD WE FAIL? - PRUint32 numItems = 0; - rv = dragSession->GetNumDropItems(&numItems); + rv = dataTransfer->GetMozItemCount(&numItems); NS_ENSURE_SUCCESS(rv, rv); if (numItems < 1) return NS_ERROR_FAILURE; // nothing to drop? @@ -240,11 +236,14 @@ NS_IMETHODIMP nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) break; } - // Source doc is null if source is *not* the current editor document - // Current doc is destination (set earlier) + nsCOMPtr sourceNode; + dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode)); + nsCOMPtr srcdomdoc; - rv = dragSession->GetSourceDocument(getter_AddRefs(srcdomdoc)); - NS_ENSURE_SUCCESS(rv, rv); + if (sourceNode) { + sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc)); + NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE); + } if (cursorIsInSelection) { @@ -263,9 +262,9 @@ NS_IMETHODIMP nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) if (srcdomdoc == destdomdoc) { // Within the same doc: delete if user doesn't want to copy - PRUint32 action; - dragSession->GetDragAction(&action); - deleteSelection = !(action & nsIDragService::DRAGDROP_ACTION_COPY); + PRUint32 dropEffect; + dataTransfer->GetDropEffectInt(&dropEffect); + deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY); } else { @@ -285,7 +284,6 @@ NS_IMETHODIMP nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) if (formControl && !formControl->AllowDrop()) { // Don't allow dropping into a form control that doesn't allow being // dropped into. - return NS_OK; } @@ -295,15 +293,20 @@ NS_IMETHODIMP nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) PRUint32 i; for (i = 0; i < numItems; ++i) { - rv = dragSession->GetData(trans, i); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(trans, NS_OK); // NS_ERROR_FAILURE; Should we fail? + nsCOMPtr data; + dataTransfer->MozGetDataAt(NS_LITERAL_STRING("text/plain"), i, + getter_AddRefs(data)); + nsAutoString insertText; + data->GetAsAString(insertText); + nsContentUtils::PlatformToDOMLineBreaks(insertText); - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. - rv = InsertTextFromTransferable(trans, newSelectionParent, newSelectionOffset, deleteSelection); + nsAutoEditBatch beginBatching(this); + rv = InsertTextAt(insertText, newSelectionParent, newSelectionOffset, deleteSelection); } + if (NS_SUCCEEDED(rv)) + ScrollSelectionIntoView(false); + return rv; } @@ -332,8 +335,6 @@ NS_IMETHODIMP nsPlaintextEditor::Paste(PRInt32 aSelectionType) if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, trans)) return NS_OK; - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. rv = InsertTextFromTransferable(trans, nsnull, nsnull, true); } } @@ -355,8 +356,6 @@ NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferabl if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, aTransferable)) return NS_OK; - // Beware! This may flush notifications via synchronous - // ScrollSelectionIntoView. return InsertTextFromTransferable(aTransferable, nsnull, nsnull, true); }