summaryrefslogtreecommitdiff
path: root/dom/base/nsContentAreaDragDrop.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsContentAreaDragDrop.cpp')
-rw-r--r--dom/base/nsContentAreaDragDrop.cpp895
1 files changed, 895 insertions, 0 deletions
diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp
new file mode 100644
index 0000000000..6a2fb10ed3
--- /dev/null
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -0,0 +1,895 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsFrameLoader.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 "TabParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "nsVariant.h"
+
+using namespace mozilla::dom;
+
+class MOZ_STACK_CLASS DragDataProducer
+{
+public:
+ DragDataProducer(nsPIDOMWindowOuter* 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<nsIContent> FindParentLinkNode(nsIContent* inNode);
+ static MOZ_MUST_USE nsresult
+ GetAnchorURL(nsIContent* inNode, nsAString& outURL);
+ static void GetNodeString(nsIContent* inNode, nsAString & outNodeString);
+ static void CreateLinkText(const nsAString& inURL, const nsAString & inText,
+ nsAString& outLinkText);
+
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ nsCOMPtr<nsIContent> mTarget;
+ nsCOMPtr<nsIContent> 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<imgIContainer> mImage;
+};
+
+
+nsresult
+nsContentAreaDragDrop::GetDragData(nsPIDOMWindowOuter* 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<nsIURI> sourceURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(sourceURI), inSourceURIString);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURL> 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<nsIWebBrowserPersist> persist =
+ do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ persist->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
+
+ // referrer policy can be anything since the referrer is nullptr
+ return persist->SavePrivacyAwareURI(sourceURI, nullptr, nullptr,
+ mozilla::net::RP_Default,
+ 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<nsISupports> tmp;
+ uint32_t dataSize = 0;
+ aTransferable->GetTransferData(kFilePromiseURLMime,
+ getter_AddRefs(tmp), &dataSize);
+ nsCOMPtr<nsISupportsString> 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<nsISupports> dirPrimitive;
+ dataSize = 0;
+ aTransferable->GetTransferData(kFilePromiseDirectoryMime,
+ getter_AddRefs(dirPrimitive), &dataSize);
+ nsCOMPtr<nsIFile> destDirectory = do_QueryInterface(dirPrimitive);
+ if (!destDirectory)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> 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(nsPIDOMWindowOuter* 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<nsIContent>
+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<nsIContent> ret = content;
+ return ret.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+
+//
+// GetAnchorURL
+//
+nsresult
+DragDataProducer::GetAnchorURL(nsIContent* inNode, nsAString& outURL)
+{
+ nsCOMPtr<nsIURI> linkURI;
+ if (!inNode || !inNode->IsLink(getter_AddRefs(linkURI))) {
+ // Not a link
+ outURL.Truncate();
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsresult rv = linkURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, outURL);
+ return NS_OK;
+}
+
+
+//
+// CreateLinkText
+//
+// Creates the html for an anchor in the form
+// <a href="inURL">inText</a>
+//
+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("<a href=\"") +
+ inURL +
+ NS_LITERAL_STRING("\">") +
+ inText +
+ NS_LITERAL_STRING("</a>") );
+
+ outLinkText = linkText;
+}
+
+
+//
+// GetNodeString
+//
+// Gets the text associated with a node
+//
+void
+DragDataProducer::GetNodeString(nsIContent* inNode,
+ nsAString & outNodeString)
+{
+ nsCOMPtr<nsINode> node = inNode;
+
+ outNodeString.Truncate();
+
+ // use a range to get the text-equivalent of the node
+ nsCOMPtr<nsIDocument> doc = node->OwnerDoc();
+ mozilla::ErrorResult rv;
+ RefPtr<nsRange> 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<nsISelection> selection;
+ nsIContent* editingElement = mSelectionTargetNode->IsEditable() ?
+ mSelectionTargetNode->GetEditingHost() : nullptr;
+ nsCOMPtr<nsITextControlElement> 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 {
+ selection = mWindow->GetSelection();
+ 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<nsIContent> findFormNode = mSelectionTargetNode;
+ nsIContent* findFormParent = findFormNode->GetParent();
+ while (findFormParent) {
+ nsCOMPtr<nsIFormControl> form(do_QueryInterface(findFormParent));
+ if (form && !form->AllowDraggableChildren()) {
+ return NS_OK;
+ }
+ findFormParent = findFormParent->GetParent();
+ }
+ }
+
+ // if set, serialize the content under this node
+ nsCOMPtr<nsIContent> nodeToSerialize;
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = mWindow->GetDocShell();
+ const bool isChromeShell =
+ dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome;
+
+ // In chrome shells, only allow dragging inside editable areas.
+ if (isChromeShell && !editingElement) {
+ nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(mTarget);
+ if (flo) {
+ RefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
+ if (fl) {
+ TabParent* tp = static_cast<TabParent*>(fl->GetRemoteBrowser());
+ if (tp) {
+ // We have a TabParent, so it may have data for dnd in case the child
+ // process started a dnd session.
+ tp->AddInitialDnDDataTo(aDataTransfer);
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ if (isChromeShell && textControl) {
+ // Only use the selection if the target node is in the selection.
+ bool selectionContainsTarget = false;
+ nsCOMPtr<nsIDOMNode> 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<nsIContent> parentLink;
+ nsCOMPtr<nsIContent> draggedNode;
+
+ {
+ // only drag form elements by using the alt key,
+ // otherwise buttons and select widgets are hard to use
+
+ // Note that while <object> elements implement nsIFormControl, we should
+ // really allow dragging them if they happen to be images.
+ nsCOMPtr<nsIFormControl> form(do_QueryInterface(mTarget));
+ if (form && !mIsAltKeyPressed && form->GetType() != NS_FORM_OBJECT) {
+ *aCanDrag = false;
+ return NS_OK;
+ }
+
+ draggedNode = mTarget;
+ }
+
+ nsCOMPtr<nsIDOMHTMLAreaElement> area; // client-side image map
+ nsCOMPtr<nsIImageLoadingContent> image;
+ nsCOMPtr<nsIDOMHTMLAnchorElement> link;
+
+ nsCOMPtr<nsIContent> selectedImageOrLinkNode;
+ GetDraggableSelectionData(selection, mSelectionTargetNode,
+ getter_AddRefs(selectedImageOrLinkNode),
+ &haveSelectedContent);
+
+ // either plain text or anchor text is selected
+ if (haveSelectedContent) {
+ 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<nsIContent> linkNode;
+
+ if (area) {
+ // use the alt text (or, if missing, the href) as the title
+ HTMLAreaElement* areaElem = static_cast<HTMLAreaElement*>(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 <a href="absurl">alt text</a>
+ mIsAnchor = true;
+
+ // gives an absolute link
+ nsresult rv = GetAnchorURL(draggedNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHtmlString.AssignLiteral("<a href=\"");
+ mHtmlString.Append(mUrlString);
+ mHtmlString.AppendLiteral("\">");
+ mHtmlString.Append(mTitleString);
+ mHtmlString.AppendLiteral("</a>");
+
+ 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<nsIURI> imageURI;
+ image->GetCurrentURI(getter_AddRefs(imageURI));
+ if (imageURI) {
+ nsAutoCString spec;
+ rv = imageURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, mUrlString);
+ }
+
+ nsCOMPtr<nsIDOMElement> 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<imgIRequest> imgRequest;
+
+ // grab the image data, and its request.
+ nsCOMPtr<imgIContainer> img =
+ nsContentUtils::GetImageFromContent(image,
+ getter_AddRefs(imgRequest));
+
+ nsCOMPtr<nsIMIMEService> mimeService =
+ do_GetService("@mozilla.org/mime;1");
+
+ // Fix the file extension in the URL if necessary
+ if (imgRequest && mimeService) {
+ nsCOMPtr<nsIURI> imgUri;
+ imgRequest->GetURI(getter_AddRefs(imgUri));
+
+ nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
+
+ if (imgUrl) {
+ nsAutoCString extension;
+ imgUrl->GetFileExtension(extension);
+
+ nsXPIDLCString mimeType;
+ imgRequest->GetMimeType(getter_Copies(mimeType));
+
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
+ getter_AddRefs(mimeInfo));
+
+ if (mimeInfo) {
+ nsAutoCString spec;
+ rv = imgUrl->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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;
+ rv = GetAnchorURL(linkNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dragNode = linkNode;
+ }
+ }
+ }
+
+ if (nodeToSerialize || *aSelection) {
+ mHtmlString.Truncate();
+ mContextString.Truncate();
+ mInfoString.Truncate();
+ mTitleString.Truncate();
+
+ nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ // if we have selected text, use it in preference to the node
+ nsCOMPtr<nsITransferable> 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<nsISupports> supports;
+ nsCOMPtr<nsISupportsString> 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)
+{
+ RefPtr<nsVariantCC> variant = new nsVariantCC();
+ 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.Append('\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) {
+ RefPtr<nsVariantCC> variant = new nsVariantCC();
+ 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<nsIFlavorDataProvider> dataProvider =
+ new nsContentAreaDragDropDataProvider();
+ if (dataProvider) {
+ RefPtr<nsVariantCC> variant = new nsVariantCC();
+ 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<nsIDOMNode> realTargetNode = do_QueryInterface(inRealTargetNode);
+ inSelection->ContainsNode(realTargetNode, false,
+ &selectionContainsTarget);
+
+ if (selectionContainsTarget) {
+ // track down the anchor node, if any, for the url
+ nsCOMPtr<nsIDOMNode> selectionStart;
+ inSelection->GetAnchorNode(getter_AddRefs(selectionStart));
+
+ nsCOMPtr<nsIDOMNode> 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<nsIContent> 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;
+ }
+ }
+ }
+ }
+ }
+
+ // indicate that a link or text is selected
+ *outDragSelectedText = true;
+ }
+ }
+
+ return NS_OK;
+}