diff -r 000000000000 -r 6474c204b198 content/base/src/nsContentAreaDragDrop.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/base/src/nsContentAreaDragDrop.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1001 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsReadableUtils.h" + +// Local Includes +#include "nsContentAreaDragDrop.h" + +// Helper Classes +#include "nsString.h" + +// Interfaces needed to be included +#include "nsCopySupport.h" +#include "nsIDOMUIEvent.h" +#include "nsISelection.h" +#include "nsISelectionController.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMEvent.h" +#include "nsIDOMDragEvent.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMRange.h" +#include "nsIFormControl.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsITransferable.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsServiceManagerUtils.h" +#include "nsNetUtil.h" +#include "nsIFile.h" +#include "nsIWebNavigation.h" +#include "nsIDocShell.h" +#include "nsIContent.h" +#include "nsIImageLoadingContent.h" +#include "nsITextControlElement.h" +#include "nsUnicharUtils.h" +#include "nsIURL.h" +#include "nsIDocument.h" +#include "nsIScriptSecurityManager.h" +#include "nsIPrincipal.h" +#include "nsIDocShellTreeItem.h" +#include "nsIWebBrowserPersist.h" +#include "nsEscape.h" +#include "nsContentUtils.h" +#include "nsIMIMEService.h" +#include "imgIContainer.h" +#include "imgIRequest.h" +#include "mozilla/dom/DataTransfer.h" +#include "nsIMIMEInfo.h" +#include "nsRange.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLAreaElement.h" + +using namespace mozilla::dom; + +class MOZ_STACK_CLASS DragDataProducer +{ +public: + DragDataProducer(nsPIDOMWindow* aWindow, + nsIContent* aTarget, + nsIContent* aSelectionTargetNode, + bool aIsAltKeyPressed); + nsresult Produce(DataTransfer* aDataTransfer, + bool* aCanDrag, + nsISelection** aSelection, + nsIContent** aDragNode); + +private: + void AddString(DataTransfer* aDataTransfer, + const nsAString& aFlavor, + const nsAString& aData, + nsIPrincipal* aPrincipal); + nsresult AddStringsToDataTransfer(nsIContent* aDragNode, + DataTransfer* aDataTransfer); + static nsresult GetDraggableSelectionData(nsISelection* inSelection, + nsIContent* inRealTargetNode, + nsIContent **outImageOrLinkNode, + bool* outDragSelectedText); + static already_AddRefed FindParentLinkNode(nsIContent* inNode); + static void GetAnchorURL(nsIContent* inNode, nsAString& outURL); + static void GetNodeString(nsIContent* inNode, nsAString & outNodeString); + static void CreateLinkText(const nsAString& inURL, const nsAString & inText, + nsAString& outLinkText); + static void GetSelectedLink(nsISelection* inSelection, + nsIContent **outLinkNode); + + nsCOMPtr mWindow; + nsCOMPtr mTarget; + nsCOMPtr mSelectionTargetNode; + bool mIsAltKeyPressed; + + nsString mUrlString; + nsString mImageSourceString; + nsString mImageDestFileName; + nsString mTitleString; + // will be filled automatically if you fill urlstring + nsString mHtmlString; + nsString mContextString; + nsString mInfoString; + + bool mIsAnchor; + nsCOMPtr mImage; +}; + + +nsresult +nsContentAreaDragDrop::GetDragData(nsPIDOMWindow* aWindow, + nsIContent* aTarget, + nsIContent* aSelectionTargetNode, + bool aIsAltKeyPressed, + DataTransfer* aDataTransfer, + bool* aCanDrag, + nsISelection** aSelection, + nsIContent** aDragNode) +{ + NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG); + + *aCanDrag = true; + + DragDataProducer + provider(aWindow, aTarget, aSelectionTargetNode, aIsAltKeyPressed); + return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode); +} + + +NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider) + +// SaveURIToFile +// used on platforms where it's possible to drag items (e.g. images) +// into the file system +nsresult +nsContentAreaDragDropDataProvider::SaveURIToFile(nsAString& inSourceURIString, + nsIFile* inDestFile, + bool isPrivate) +{ + nsCOMPtr sourceURI; + nsresult rv = NS_NewURI(getter_AddRefs(sourceURI), inSourceURIString); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr sourceURL = do_QueryInterface(sourceURI); + if (!sourceURL) { + return NS_ERROR_NO_INTERFACE; + } + + rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + // we rely on the fact that the WPB is refcounted by the channel etc, + // so we don't keep a ref to it. It will die when finished. + nsCOMPtr persist = + do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + persist->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION); + + return persist->SavePrivacyAwareURI(sourceURI, nullptr, nullptr, nullptr, nullptr, + inDestFile, isPrivate); +} + +// This is our nsIFlavorDataProvider callback. There are several +// assumptions here that make this work: +// +// 1. Someone put a kFilePromiseURLMime flavor into the transferable +// with the source URI of the file to save (as a string). We did +// that in AddStringsToDataTransfer. +// +// 2. Someone put a kFilePromiseDirectoryMime flavor into the +// transferable with an nsIFile for the directory we are to +// save in. That has to be done by platform-specific code (in +// widget), which gets the destination directory from +// OS-specific drag information. +// +NS_IMETHODIMP +nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable *aTransferable, + const char *aFlavor, + nsISupports **aData, + uint32_t *aDataLen) +{ + NS_ENSURE_ARG_POINTER(aData && aDataLen); + *aData = nullptr; + *aDataLen = 0; + + nsresult rv = NS_ERROR_NOT_IMPLEMENTED; + + if (strcmp(aFlavor, kFilePromiseMime) == 0) { + // get the URI from the kFilePromiseURLMime flavor + NS_ENSURE_ARG(aTransferable); + nsCOMPtr tmp; + uint32_t dataSize = 0; + aTransferable->GetTransferData(kFilePromiseURLMime, + getter_AddRefs(tmp), &dataSize); + nsCOMPtr supportsString = + do_QueryInterface(tmp); + if (!supportsString) + return NS_ERROR_FAILURE; + + nsAutoString sourceURLString; + supportsString->GetData(sourceURLString); + if (sourceURLString.IsEmpty()) + return NS_ERROR_FAILURE; + + aTransferable->GetTransferData(kFilePromiseDestFilename, + getter_AddRefs(tmp), &dataSize); + supportsString = do_QueryInterface(tmp); + if (!supportsString) + return NS_ERROR_FAILURE; + + nsAutoString targetFilename; + supportsString->GetData(targetFilename); + if (targetFilename.IsEmpty()) + return NS_ERROR_FAILURE; + + // get the target directory from the kFilePromiseDirectoryMime + // flavor + nsCOMPtr dirPrimitive; + dataSize = 0; + aTransferable->GetTransferData(kFilePromiseDirectoryMime, + getter_AddRefs(dirPrimitive), &dataSize); + nsCOMPtr destDirectory = do_QueryInterface(dirPrimitive); + if (!destDirectory) + return NS_ERROR_FAILURE; + + nsCOMPtr file; + rv = destDirectory->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + file->Append(targetFilename); + + bool isPrivate; + aTransferable->GetIsPrivateData(&isPrivate); + + rv = SaveURIToFile(sourceURLString, file, isPrivate); + // send back an nsIFile + if (NS_SUCCEEDED(rv)) { + CallQueryInterface(file, aData); + *aDataLen = sizeof(nsIFile*); + } + } + + return rv; +} + +DragDataProducer::DragDataProducer(nsPIDOMWindow* aWindow, + nsIContent* aTarget, + nsIContent* aSelectionTargetNode, + bool aIsAltKeyPressed) + : mWindow(aWindow), + mTarget(aTarget), + mSelectionTargetNode(aSelectionTargetNode), + mIsAltKeyPressed(aIsAltKeyPressed), + mIsAnchor(false) +{ +} + + +// +// FindParentLinkNode +// +// Finds the parent with the given link tag starting at |inNode|. If +// it gets up to the root without finding it, we stop looking and +// return null. +// +already_AddRefed +DragDataProducer::FindParentLinkNode(nsIContent* inNode) +{ + nsIContent* content = inNode; + if (!content) { + // That must have been the document node; nothing else to do here; + return nullptr; + } + + for (; content; content = content->GetParent()) { + if (nsContentUtils::IsDraggableLink(content)) { + nsCOMPtr ret = content; + return ret.forget(); + } + } + + return nullptr; +} + + +// +// GetAnchorURL +// +void +DragDataProducer::GetAnchorURL(nsIContent* inNode, nsAString& outURL) +{ + nsCOMPtr linkURI; + if (!inNode || !inNode->IsLink(getter_AddRefs(linkURI))) { + // Not a link + outURL.Truncate(); + return; + } + + nsAutoCString spec; + linkURI->GetSpec(spec); + CopyUTF8toUTF16(spec, outURL); +} + + +// +// CreateLinkText +// +// Creates the html for an anchor in the form +// inText +// +void +DragDataProducer::CreateLinkText(const nsAString& inURL, + const nsAString & inText, + nsAString& outLinkText) +{ + // use a temp var in case |inText| is the same string as + // |outLinkText| to avoid overwriting it while building up the + // string in pieces. + nsAutoString linkText(NS_LITERAL_STRING("") + + inText + + NS_LITERAL_STRING("") ); + + outLinkText = linkText; +} + + +// +// GetNodeString +// +// Gets the text associated with a node +// +void +DragDataProducer::GetNodeString(nsIContent* inNode, + nsAString & outNodeString) +{ + nsCOMPtr node = inNode; + + outNodeString.Truncate(); + + // use a range to get the text-equivalent of the node + nsCOMPtr doc = node->OwnerDoc(); + mozilla::ErrorResult rv; + nsRefPtr range = doc->CreateRange(rv); + if (range) { + range->SelectNode(*node, rv); + range->ToString(outNodeString); + } +} + +nsresult +DragDataProducer::Produce(DataTransfer* aDataTransfer, + bool* aCanDrag, + nsISelection** aSelection, + nsIContent** aDragNode) +{ + NS_PRECONDITION(aCanDrag && aSelection && aDataTransfer && aDragNode, + "null pointer passed to Produce"); + NS_ASSERTION(mWindow, "window not set"); + NS_ASSERTION(mSelectionTargetNode, "selection target node should have been set"); + + *aDragNode = nullptr; + + nsresult rv; + nsIContent* dragNode = nullptr; + *aSelection = nullptr; + + // Find the selection to see what we could be dragging and if what we're + // dragging is in what is selected. If this is an editable textbox, use + // the textbox's selection, otherwise use the window's selection. + nsCOMPtr selection; + nsIContent* editingElement = mSelectionTargetNode->IsEditable() ? + mSelectionTargetNode->GetEditingHost() : nullptr; + nsCOMPtr textControl = + nsITextControlElement::GetTextControlElementFromEditingHost(editingElement); + if (textControl) { + nsISelectionController* selcon = textControl->GetSelectionController(); + if (selcon) { + selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); + } + + if (!selection) + return NS_OK; + } + else { + mWindow->GetSelection(getter_AddRefs(selection)); + if (!selection) + return NS_OK; + + // Check if the node is inside a form control. Don't set aCanDrag to false + //however, as we still want to allow the drag. + nsCOMPtr findFormNode = mSelectionTargetNode; + nsIContent* findFormParent = findFormNode->GetParent(); + while (findFormParent) { + nsCOMPtr form(do_QueryInterface(findFormParent)); + if (form && !form->AllowDraggableChildren()) { + return NS_OK; + } + findFormParent = findFormParent->GetParent(); + } + } + + // if set, serialize the content under this node + nsCOMPtr nodeToSerialize; + + nsCOMPtr webnav = do_GetInterface(mWindow); + nsCOMPtr dsti = do_QueryInterface(webnav); + const bool isChromeShell = + dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome; + + // In chrome shells, only allow dragging inside editable areas. + if (isChromeShell && !editingElement) + return NS_OK; + + if (isChromeShell && textControl) { + // Only use the selection if the target node is in the selection. + bool selectionContainsTarget = false; + nsCOMPtr targetNode = do_QueryInterface(mSelectionTargetNode); + selection->ContainsNode(targetNode, false, &selectionContainsTarget); + if (!selectionContainsTarget) + return NS_OK; + + selection.swap(*aSelection); + } + else { + // In content shells, a number of checks are made below to determine + // whether an image or a link is being dragged. If so, add additional + // data to the data transfer. This is also done for chrome shells, but + // only when in a non-textbox editor. + + bool haveSelectedContent = false; + + // possible parent link node + nsCOMPtr parentLink; + nsCOMPtr draggedNode; + + { + // only drag form elements by using the alt key, + // otherwise buttons and select widgets are hard to use + + // Note that while elements implement nsIFormControl, we should + // really allow dragging them if they happen to be images. + nsCOMPtr form(do_QueryInterface(mTarget)); + if (form && !mIsAltKeyPressed && form->GetType() != NS_FORM_OBJECT) { + *aCanDrag = false; + return NS_OK; + } + + draggedNode = mTarget; + } + + nsCOMPtr area; // client-side image map + nsCOMPtr image; + nsCOMPtr link; + + nsCOMPtr selectedImageOrLinkNode; + GetDraggableSelectionData(selection, mSelectionTargetNode, + getter_AddRefs(selectedImageOrLinkNode), + &haveSelectedContent); + + // either plain text or anchor text is selected + if (haveSelectedContent) { + link = do_QueryInterface(selectedImageOrLinkNode); + if (link && mIsAltKeyPressed) { + // if alt is pressed, select the link text instead of drag the link + *aCanDrag = false; + return NS_OK; + } + + selection.swap(*aSelection); + } else if (selectedImageOrLinkNode) { + // an image is selected + image = do_QueryInterface(selectedImageOrLinkNode); + } else { + // nothing is selected - + // + // look for draggable elements under the mouse + // + // if the alt key is down, don't start a drag if we're in an + // anchor because we want to do selection. + parentLink = FindParentLinkNode(draggedNode); + if (parentLink && mIsAltKeyPressed) { + *aCanDrag = false; + return NS_OK; + } + + area = do_QueryInterface(draggedNode); + image = do_QueryInterface(draggedNode); + link = do_QueryInterface(draggedNode); + } + + { + // set for linked images, and links + nsCOMPtr linkNode; + + if (area) { + // use the alt text (or, if missing, the href) as the title + HTMLAreaElement* areaElem = static_cast(area.get()); + areaElem->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString); + if (mTitleString.IsEmpty()) { + // this can be a relative link + areaElem->GetAttribute(NS_LITERAL_STRING("href"), mTitleString); + } + + // we'll generate HTML like alt text + mIsAnchor = true; + + // gives an absolute link + GetAnchorURL(draggedNode, mUrlString); + + mHtmlString.AssignLiteral(""); + mHtmlString.Append(mTitleString); + mHtmlString.AppendLiteral(""); + + dragNode = draggedNode; + } else if (image) { + mIsAnchor = true; + // grab the href as the url, use alt text as the title of the + // area if it's there. the drag data is the image tag and src + // attribute. + nsCOMPtr imageURI; + image->GetCurrentURI(getter_AddRefs(imageURI)); + if (imageURI) { + nsAutoCString spec; + imageURI->GetSpec(spec); + CopyUTF8toUTF16(spec, mUrlString); + } + + nsCOMPtr imageElement(do_QueryInterface(image)); + // XXXbz Shouldn't we use the "title" attr for title? Using + // "alt" seems very wrong.... + if (imageElement) { + imageElement->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString); + } + + if (mTitleString.IsEmpty()) { + mTitleString = mUrlString; + } + + nsCOMPtr imgRequest; + + // grab the image data, and its request. + nsCOMPtr img = + nsContentUtils::GetImageFromContent(image, + getter_AddRefs(imgRequest)); + + nsCOMPtr mimeService = + do_GetService("@mozilla.org/mime;1"); + + // Fix the file extension in the URL if necessary + if (imgRequest && mimeService) { + nsCOMPtr imgUri; + imgRequest->GetURI(getter_AddRefs(imgUri)); + + nsCOMPtr imgUrl(do_QueryInterface(imgUri)); + + if (imgUrl) { + nsAutoCString extension; + imgUrl->GetFileExtension(extension); + + nsXPIDLCString mimeType; + imgRequest->GetMimeType(getter_Copies(mimeType)); + + nsCOMPtr mimeInfo; + mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(), + getter_AddRefs(mimeInfo)); + + if (mimeInfo) { + nsAutoCString spec; + imgUrl->GetSpec(spec); + + // pass out the image source string + CopyUTF8toUTF16(spec, mImageSourceString); + + bool validExtension; + if (extension.IsEmpty() || + NS_FAILED(mimeInfo->ExtensionExists(extension, + &validExtension)) || + !validExtension) { + // Fix the file extension in the URL + nsresult rv = imgUrl->Clone(getter_AddRefs(imgUri)); + NS_ENSURE_SUCCESS(rv, rv); + + imgUrl = do_QueryInterface(imgUri); + + nsAutoCString primaryExtension; + mimeInfo->GetPrimaryExtension(primaryExtension); + + imgUrl->SetFileExtension(primaryExtension); + } + + nsAutoCString fileName; + imgUrl->GetFileName(fileName); + + NS_UnescapeURL(fileName); + + // make the filename safe for the filesystem + fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, + '-'); + + CopyUTF8toUTF16(fileName, mImageDestFileName); + + // and the image object + mImage = img; + } + } + } + + if (parentLink) { + // If we are dragging around an image in an anchor, then we + // are dragging the entire anchor + linkNode = parentLink; + nodeToSerialize = linkNode; + } else { + nodeToSerialize = do_QueryInterface(draggedNode); + } + dragNode = nodeToSerialize; + } else if (link) { + // set linkNode. The code below will handle this + linkNode = do_QueryInterface(link); // XXX test this + GetNodeString(draggedNode, mTitleString); + } else if (parentLink) { + // parentLink will always be null if there's selected content + linkNode = parentLink; + nodeToSerialize = linkNode; + } else if (!haveSelectedContent) { + // nothing draggable + return NS_OK; + } + + if (linkNode) { + mIsAnchor = true; + GetAnchorURL(linkNode, mUrlString); + dragNode = linkNode; + } + } + } + + if (nodeToSerialize || *aSelection) { + mHtmlString.Truncate(); + mContextString.Truncate(); + mInfoString.Truncate(); + mTitleString.Truncate(); + + nsCOMPtr doc = mWindow->GetDoc(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + // if we have selected text, use it in preference to the node + nsCOMPtr transferable; + if (*aSelection) { + rv = nsCopySupport::GetTransferableForSelection(*aSelection, doc, + getter_AddRefs(transferable)); + } + else { + rv = nsCopySupport::GetTransferableForNode(nodeToSerialize, doc, + getter_AddRefs(transferable)); + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr supports; + nsCOMPtr data; + uint32_t dataSize; + rv = transferable->GetTransferData(kHTMLMime, getter_AddRefs(supports), + &dataSize); + data = do_QueryInterface(supports); + if (NS_SUCCEEDED(rv)) { + data->GetData(mHtmlString); + } + rv = transferable->GetTransferData(kHTMLContext, getter_AddRefs(supports), + &dataSize); + data = do_QueryInterface(supports); + if (NS_SUCCEEDED(rv)) { + data->GetData(mContextString); + } + rv = transferable->GetTransferData(kHTMLInfo, getter_AddRefs(supports), + &dataSize); + data = do_QueryInterface(supports); + if (NS_SUCCEEDED(rv)) { + data->GetData(mInfoString); + } + rv = transferable->GetTransferData(kUnicodeMime, getter_AddRefs(supports), + &dataSize); + data = do_QueryInterface(supports); + NS_ENSURE_SUCCESS(rv, rv); // require plain text at a minimum + data->GetData(mTitleString); + } + + // default text value is the URL + if (mTitleString.IsEmpty()) { + mTitleString = mUrlString; + } + + // if we haven't constructed a html version, make one now + if (mHtmlString.IsEmpty() && !mUrlString.IsEmpty()) + CreateLinkText(mUrlString, mTitleString, mHtmlString); + + // if there is no drag node, which will be the case for a selection, just + // use the selection target node. + rv = AddStringsToDataTransfer( + dragNode ? dragNode : mSelectionTargetNode.get(), aDataTransfer); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aDragNode = dragNode); + return NS_OK; +} + +void +DragDataProducer::AddString(DataTransfer* aDataTransfer, + const nsAString& aFlavor, + const nsAString& aData, + nsIPrincipal* aPrincipal) +{ + nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); + if (variant) { + variant->SetAsAString(aData); + aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal); + } +} + +nsresult +DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode, + DataTransfer* aDataTransfer) +{ + NS_ASSERTION(aDragNode, "adding strings for null node"); + + // set all of the data to have the principal of the node where the data came from + nsIPrincipal* principal = aDragNode->NodePrincipal(); + + // add a special flavor if we're an anchor to indicate that we have + // a URL in the drag data + if (!mUrlString.IsEmpty() && mIsAnchor) { + nsAutoString dragData(mUrlString); + dragData.AppendLiteral("\n"); + // Remove leading and trailing newlines in the title and replace them with + // space in remaining positions - they confuse PlacesUtils::unwrapNodes + // that expects url\ntitle formatted data for x-moz-url. + nsAutoString title(mTitleString); + title.Trim("\r\n"); + title.ReplaceChar("\r\n", ' '); + dragData += title; + + AddString(aDataTransfer, NS_LITERAL_STRING(kURLMime), dragData, principal); + AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal); + AddString(aDataTransfer, NS_LITERAL_STRING(kURLDescriptionMime), mTitleString, principal); + AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal); + } + + // add a special flavor for the html context data + if (!mContextString.IsEmpty()) + AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), mContextString, principal); + + // add a special flavor if we have html info data + if (!mInfoString.IsEmpty()) + AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), mInfoString, principal); + + // add the full html + if (!mHtmlString.IsEmpty()) + AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLMime), mHtmlString, principal); + + // add the plain text. we use the url for text/plain data if an anchor is + // being dragged, rather than the title text of the link or the alt text for + // an anchor image. + AddString(aDataTransfer, NS_LITERAL_STRING(kTextMime), + mIsAnchor ? mUrlString : mTitleString, principal); + + // add image data, if present. For now, all we're going to do with + // this is turn it into a native data flavor, so indicate that with + // a new flavor so as not to confuse anyone who is really registered + // for image/gif or image/jpg. + if (mImage) { + nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); + if (variant) { + variant->SetAsISupports(mImage); + aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kNativeImageMime), + variant, 0, principal); + } + + // assume the image comes from a file, and add a file promise. We + // register ourselves as a nsIFlavorDataProvider, and will use the + // GetFlavorData callback to save the image to disk. + + nsCOMPtr dataProvider = + new nsContentAreaDragDropDataProvider(); + if (dataProvider) { + nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); + if (variant) { + variant->SetAsISupports(dataProvider); + aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime), + variant, 0, principal); + } + } + + AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime), + mImageSourceString, principal); + AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseDestFilename), + mImageDestFileName, principal); + + // if not an anchor, add the image url + if (!mIsAnchor) { + AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal); + AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal); + } + } + + return NS_OK; +} + +// note that this can return NS_OK, but a null out param (by design) +// static +nsresult +DragDataProducer::GetDraggableSelectionData(nsISelection* inSelection, + nsIContent* inRealTargetNode, + nsIContent **outImageOrLinkNode, + bool* outDragSelectedText) +{ + NS_ENSURE_ARG(inSelection); + NS_ENSURE_ARG(inRealTargetNode); + NS_ENSURE_ARG_POINTER(outImageOrLinkNode); + + *outImageOrLinkNode = nullptr; + *outDragSelectedText = false; + + bool selectionContainsTarget = false; + + bool isCollapsed = false; + inSelection->GetIsCollapsed(&isCollapsed); + if (!isCollapsed) { + nsCOMPtr realTargetNode = do_QueryInterface(inRealTargetNode); + inSelection->ContainsNode(realTargetNode, false, + &selectionContainsTarget); + + if (selectionContainsTarget) { + // track down the anchor node, if any, for the url + nsCOMPtr selectionStart; + inSelection->GetAnchorNode(getter_AddRefs(selectionStart)); + + nsCOMPtr selectionEnd; + inSelection->GetFocusNode(getter_AddRefs(selectionEnd)); + + // look for a selection around a single node, like an image. + // in this case, drag the image, rather than a serialization of the HTML + // XXX generalize this to other draggable element types? + if (selectionStart == selectionEnd) { + bool hasChildren; + selectionStart->HasChildNodes(&hasChildren); + if (hasChildren) { + // see if just one node is selected + int32_t anchorOffset, focusOffset; + inSelection->GetAnchorOffset(&anchorOffset); + inSelection->GetFocusOffset(&focusOffset); + if (abs(anchorOffset - focusOffset) == 1) { + nsCOMPtr selStartContent = + do_QueryInterface(selectionStart); + + if (selStartContent) { + int32_t childOffset = + (anchorOffset < focusOffset) ? anchorOffset : focusOffset; + nsIContent *childContent = + selStartContent->GetChildAt(childOffset); + // if we find an image, we'll fall into the node-dragging code, + // rather the the selection-dragging code + if (nsContentUtils::IsDraggableImage(childContent)) { + NS_ADDREF(*outImageOrLinkNode = childContent); + return NS_OK; + } + } + } + } + } + + // see if the selection is a link; if so, its node will be returned + GetSelectedLink(inSelection, outImageOrLinkNode); + + // indicate that a link or text is selected + *outDragSelectedText = true; + } + } + + return NS_OK; +} + +// static +void +DragDataProducer::GetSelectedLink(nsISelection* inSelection, + nsIContent **outLinkNode) +{ + *outLinkNode = nullptr; + + nsCOMPtr selectionStartNode; + inSelection->GetAnchorNode(getter_AddRefs(selectionStartNode)); + nsCOMPtr selectionEndNode; + inSelection->GetFocusNode(getter_AddRefs(selectionEndNode)); + + // simple case: only one node is selected + // see if it or its parent is an anchor, then exit + + if (selectionStartNode == selectionEndNode) { + nsCOMPtr selectionStart = do_QueryInterface(selectionStartNode); + nsCOMPtr link = FindParentLinkNode(selectionStart); + if (link) { + link.swap(*outLinkNode); + } + + return; + } + + // more complicated case: multiple nodes are selected + + // Unless you use the Alt key while selecting anchor text, it is + // nearly impossible to avoid overlapping into adjacent nodes. + // Deal with this by trimming off the leading and/or trailing + // nodes of the selection if the strings they produce are empty. + + // first, use a range determine if the selection was marked LTR or RTL; + // if the latter, swap endpoints so we trim in the right direction + + int32_t startOffset, endOffset; + { + nsCOMPtr range; + inSelection->GetRangeAt(0, getter_AddRefs(range)); + if (!range) { + return; + } + + nsCOMPtr tempNode; + range->GetStartContainer( getter_AddRefs(tempNode)); + if (tempNode != selectionStartNode) { + selectionEndNode = selectionStartNode; + selectionStartNode = tempNode; + inSelection->GetAnchorOffset(&endOffset); + inSelection->GetFocusOffset(&startOffset); + } else { + inSelection->GetAnchorOffset(&startOffset); + inSelection->GetFocusOffset(&endOffset); + } + } + + // trim leading node if the string is empty or + // the selection starts at the end of the text + + nsAutoString nodeStr; + selectionStartNode->GetNodeValue(nodeStr); + if (nodeStr.IsEmpty() || + startOffset+1 >= static_cast(nodeStr.Length())) { + nsCOMPtr curr = selectionStartNode; + nsIDOMNode* next; + + while (curr) { + curr->GetNextSibling(&next); + + if (next) { + selectionStartNode = dont_AddRef(next); + break; + } + + curr->GetParentNode(&next); + curr = dont_AddRef(next); + } + } + + // trim trailing node if the selection ends before its text begins + + if (endOffset == 0) { + nsCOMPtr curr = selectionEndNode; + nsIDOMNode* next; + + while (curr) { + curr->GetPreviousSibling(&next); + + if (next){ + selectionEndNode = dont_AddRef(next); + break; + } + + curr->GetParentNode(&next); + curr = dont_AddRef(next); + } + } + + // see if the leading & trailing nodes are part of the + // same anchor - if so, return the anchor node + nsCOMPtr selectionStart = do_QueryInterface(selectionStartNode); + nsCOMPtr link = FindParentLinkNode(selectionStart); + if (link) { + nsCOMPtr selectionEnd = do_QueryInterface(selectionEndNode); + nsCOMPtr link2 = FindParentLinkNode(selectionEnd); + + if (link == link2) { + NS_IF_ADDREF(*outLinkNode = link); + } + } + + return; +}