diff options
Diffstat (limited to 'dom/base/nsContentAreaDragDrop.cpp')
-rw-r--r-- | dom/base/nsContentAreaDragDrop.cpp | 877 |
1 files changed, 877 insertions, 0 deletions
diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp new file mode 100644 index 0000000000..7bf42580ec --- /dev/null +++ b/dom/base/nsContentAreaDragDrop.cpp @@ -0,0 +1,877 @@ +/* -*- 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 "nsISelectionController.h" +#include "nsPIDOMWindow.h" +#include "nsIFormControl.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 "nsFrameLoaderOwner.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIContentPolicy.h" +#include "nsIImageLoadingContent.h" +#include "nsUnicharUtils.h" +#include "nsIURL.h" +#include "nsIURIMutator.h" +#include "mozilla/dom/Document.h" +#include "nsICookieJarSettings.h" +#include "nsIPrincipal.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 "BrowserParent.h" +#include "mozilla/TextControlElement.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLAreaElement.h" +#include "mozilla/dom/HTMLAnchorElement.h" +#include "mozilla/dom/Selection.h" +#include "nsVariant.h" +#include "nsQueryObject.h" + +using namespace mozilla; +using namespace mozilla::dom; +using mozilla::IgnoreErrors; + +class MOZ_STACK_CLASS DragDataProducer { + public: + DragDataProducer(nsPIDOMWindowOuter* aWindow, nsIContent* aTarget, + nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed); + nsresult Produce(DataTransfer* aDataTransfer, bool* aCanDrag, + Selection** aSelection, nsIContent** aDragNode, + nsIPrincipal** aPrincipal, nsIContentSecurityPolicy** aCsp, + nsICookieJarSettings** aCookieJarSettings); + + private: + // @param aHidden true, iff the data should be hidden from non-chrome code. + void AddString(DataTransfer* aDataTransfer, const nsAString& aFlavor, + const nsAString& aData, nsIPrincipal* aPrincipal, + bool aHidden = false); + nsresult AddStringsToDataTransfer(nsIContent* aDragNode, + DataTransfer* aDataTransfer); + nsresult GetImageData(imgIContainer* aImage, imgIRequest* aRequest); + static nsresult GetDraggableSelectionData(Selection* inSelection, + nsIContent* inRealTargetNode, + nsIContent** outImageOrLinkNode, + bool* outDragSelectedText); + [[nodiscard]] static nsresult GetAnchorURL(nsIContent* inNode, + nsAString& outURL); + 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; +#if defined(XP_MACOSX) + nsString mImageRequestMime; +#endif + 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, Selection** aSelection, + nsIContent** aDragNode, nsIPrincipal** aPrincipal, + nsIContentSecurityPolicy** aCsp, + nsICookieJarSettings** aCookieJarSettings) { + NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG); + + *aCanDrag = true; + + DragDataProducer provider(aWindow, aTarget, aSelectionTargetNode, + aIsAltKeyPressed); + return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode, + aPrincipal, aCsp, aCookieJarSettings); +} + +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( + nsIURI* inSourceURI, nsIPrincipal* inTriggeringPrincipal, + nsICookieJarSettings* inCookieJarSettings, nsIFile* inDestFile, + nsContentPolicyType inContentPolicyType, bool isPrivate) { + nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(inSourceURI); + if (!sourceURL) { + return NS_ERROR_NO_INTERFACE; + } + + nsresult 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( + inSourceURI, inTriggeringPrincipal, 0, nullptr, inCookieJarSettings, + nullptr, nullptr, inDestFile, inContentPolicyType, isPrivate); +} + +/* + * Check if the provided filename extension is valid for the MIME type and + * return the MIME type's primary extension. + * + * @param aExtension [in] the extension to check + * @param aMimeType [in] the MIME type to check the extension with + * @param aIsValidExtension [out] true if |aExtension| is valid for + * |aMimeType| + * @param aPrimaryExtension [out] the primary extension for the MIME type + * to potentially be used as a replacement + * for |aExtension| + */ +nsresult CheckAndGetExtensionForMime(const nsCString& aExtension, + const nsCString& aMimeType, + bool* aIsValidExtension, + nsACString* aPrimaryExtension) { + nsresult rv; + + nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1"); + if (NS_WARN_IF(!mimeService)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIMIMEInfo> mimeInfo; + rv = mimeService->GetFromTypeAndExtension(aMimeType, ""_ns, + getter_AddRefs(mimeInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + mimeInfo->GetPrimaryExtension(*aPrimaryExtension); + + if (aExtension.IsEmpty()) { + *aIsValidExtension = false; + return NS_OK; + } + + rv = mimeInfo->ExtensionExists(aExtension, aIsValidExtension); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// 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) { + NS_ENSURE_ARG_POINTER(aData); + *aData = nullptr; + + 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; + rv = aTransferable->GetTransferData(kFilePromiseURLMime, + getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(tmp); + if (!supportsString) return NS_ERROR_FAILURE; + + nsAutoString sourceURLString; + supportsString->GetData(sourceURLString); + if (sourceURLString.IsEmpty()) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURI> sourceURI; + rv = NS_NewURI(getter_AddRefs(sourceURI), sourceURLString); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aTransferable->GetTransferData(kFilePromiseDestFilename, + getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(rv, rv); + supportsString = do_QueryInterface(tmp); + if (!supportsString) return NS_ERROR_FAILURE; + + nsAutoString targetFilename; + supportsString->GetData(targetFilename); + if (targetFilename.IsEmpty()) return NS_ERROR_FAILURE; + +#if defined(XP_MACOSX) + // Use the image request's MIME type to ensure the filename's + // extension is compatible with the OS's handler for this type. + // If it isn't, or is missing, replace the extension with the + // primary extension. On Mac, do this in the parent process + // because sandboxing blocks access to MIME-handler info from + // content processes. + if (XRE_IsParentProcess()) { + rv = aTransferable->GetTransferData(kImageRequestMime, + getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(rv, rv); + supportsString = do_QueryInterface(tmp); + if (!supportsString) return NS_ERROR_FAILURE; + + nsAutoString contentType; + supportsString->GetData(contentType); + + nsCOMPtr<nsIMIMEService> mimeService = + do_GetService("@mozilla.org/mime;1"); + if (NS_WARN_IF(!mimeService)) { + return NS_ERROR_FAILURE; + } + + mimeService->ValidateFileNameForSaving( + targetFilename, NS_ConvertUTF16toUTF8(contentType), + nsIMIMEService::VALIDATE_DEFAULT, targetFilename); + } else { + // make the filename safe for the filesystem + targetFilename.ReplaceChar( + u"" FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, u'-'); + } +#endif /* defined(XP_MACOSX) */ + + // get the target directory from the kFilePromiseDirectoryMime + // flavor + nsCOMPtr<nsISupports> dirPrimitive; + rv = aTransferable->GetTransferData(kFilePromiseDirectoryMime, + getter_AddRefs(dirPrimitive)); + NS_ENSURE_SUCCESS(rv, rv); + 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(); + + nsCOMPtr<nsIPrincipal> principal = aTransferable->GetRequestingPrincipal(); + nsContentPolicyType contentPolicyType = + aTransferable->GetContentPolicyType(); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = + aTransferable->GetCookieJarSettings(); + rv = SaveURIToFile(sourceURI, principal, cookieJarSettings, file, + contentPolicyType, isPrivate); + // send back an nsIFile + if (NS_SUCCEEDED(rv)) { + CallQueryInterface(file, aData); + } + } + + return rv; +} + +DragDataProducer::DragDataProducer(nsPIDOMWindowOuter* aWindow, + nsIContent* aTarget, + nsIContent* aSelectionTargetNode, + bool aIsAltKeyPressed) + : mWindow(aWindow), + mTarget(aTarget), + mSelectionTargetNode(aSelectionTargetNode), + mIsAltKeyPressed(aIsAltKeyPressed), + mIsAnchor(false) {} + +static nsIContent* FindDragTarget(nsIContent* aContent) { + for (nsIContent* content = aContent; content; + content = content->GetFlattenedTreeParent()) { + if (nsContentUtils::ContentIsDraggable(content)) { + return content; + } + } + return nullptr; +} + +static nsIContent* FindParentLinkNode(nsIContent* aContent) { + for (nsIContent* content = aContent; content; + content = content->GetFlattenedTreeParent()) { + if (nsContentUtils::IsDraggableLink(content)) { + return content; + } + } + return nullptr; +} + +// +// GetAnchorURL +// +nsresult DragDataProducer::GetAnchorURL(nsIContent* aContent, nsAString& aURL) { + aURL.Truncate(); + auto* element = Element::FromNodeOrNull(aContent); + if (!element || !element->IsLink()) { + return NS_OK; + } + + nsCOMPtr<nsIURI> linkURI = element->GetHrefURI(); + if (!linkURI) { + return NS_OK; + } + + nsAutoCString spec; + nsresult rv = linkURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + CopyUTF8toUTF16(spec, aURL); + 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(u"<a href=\""_ns + inURL + u"\">"_ns + inText + + u"</a>"_ns); + + outLinkText = linkText; +} + +nsresult DragDataProducer::GetImageData(imgIContainer* aImage, + imgIRequest* aRequest) { + nsCOMPtr<nsIURI> imgUri = aRequest->GetURI(); + + nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri)); + if (imgUrl) { + nsAutoCString spec; + nsresult rv = imgUrl->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // pass out the image source string + CopyUTF8toUTF16(spec, mImageSourceString); + + nsCString mimeType; + aRequest->GetMimeType(getter_Copies(mimeType)); + + nsAutoCString fileName; + aRequest->GetFileName(fileName); + +#if defined(XP_MACOSX) + // Save the MIME type so we can make sure the extension + // is compatible (and replace it if it isn't) when the + // image is dropped. On Mac, we need to get the OS MIME + // handler information in the parent due to sandboxing. + CopyUTF8toUTF16(mimeType, mImageRequestMime); + CopyUTF8toUTF16(fileName, mImageDestFileName); +#else + nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1"); + if (NS_WARN_IF(!mimeService)) { + return NS_ERROR_FAILURE; + } + + CopyUTF8toUTF16(fileName, mImageDestFileName); + mimeService->ValidateFileNameForSaving(mImageDestFileName, mimeType, + nsIMIMEService::VALIDATE_DEFAULT, + mImageDestFileName); +#endif + + // and the image object + mImage = aImage; + } + + return NS_OK; +} + +nsresult DragDataProducer::Produce(DataTransfer* aDataTransfer, bool* aCanDrag, + Selection** aSelection, + nsIContent** aDragNode, + nsIPrincipal** aPrincipal, + nsIContentSecurityPolicy** aCsp, + nsICookieJarSettings** aCookieJarSettings) { + MOZ_ASSERT(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. + RefPtr<Selection> selection; + nsIContent* editingElement = mSelectionTargetNode->IsEditable() + ? mSelectionTargetNode->GetEditingHost() + : nullptr; + RefPtr<TextControlElement> textControlElement = + TextControlElement::GetTextControlElementFromEditingHost(editingElement); + if (textControlElement) { + nsISelectionController* selcon = + textControlElement->GetSelectionController(); + if (selcon) { + selection = + selcon->GetSelection(nsISelectionController::SELECTION_NORMAL); + } + + 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; + + BrowsingContext* bc = mWindow->GetBrowsingContext(); + const bool isChromeShell = bc && bc->IsChrome(); + + // In chrome shells, only allow dragging inside editable areas. + if (isChromeShell && !editingElement) { + // This path should already be filtered out in + // EventStateManager::DetermineDragTargetAndDefaultData. + MOZ_ASSERT_UNREACHABLE("Shouldn't be generating drag data for chrome"); + return NS_OK; + } + + if (isChromeShell && textControlElement) { + // Only use the selection if the target node is in the selection. + if (!selection->ContainsNode(*mSelectionTargetNode, false, IgnoreErrors())) + 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->ControlType() != FormControlType::Object) { + *aCanDrag = false; + return NS_OK; + } + + draggedNode = FindDragTarget(mTarget); + } + + nsCOMPtr<nsIImageLoadingContent> image; + + 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; + } + image = do_QueryInterface(draggedNode); + } + + { + // set for linked images, and links + nsCOMPtr<nsIContent> linkNode; + if (const auto* areaElem = HTMLAreaElement::FromNodeOrNull(draggedNode)) { + // use the alt text (or, if missing, the href) as the title + areaElem->GetAttr(nsGkAtoms::alt, mTitleString); + if (mTitleString.IsEmpty()) { + // this can be a relative link + areaElem->GetAttr(nsGkAtoms::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<Element> imageElement(do_QueryInterface(image)); + // XXXbz Shouldn't we use the "title" attr for title? Using + // "alt" seems very wrong.... + // XXXbz Also, what if this is an nsIImageLoadingContent + // that's not an <html:img>? + if (imageElement) { + imageElement->GetAttr(nsGkAtoms::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)); + if (imgRequest) { + rv = GetImageData(img, imgRequest); + NS_ENSURE_SUCCESS(rv, rv); + } + + 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 = draggedNode; + } + dragNode = nodeToSerialize; + } 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<Document> doc = mWindow->GetDoc(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp(); + if (csp) { + NS_IF_ADDREF(*aCsp = csp); + } + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = doc->CookieJarSettings(); + if (cookieJarSettings) { + NS_IF_ADDREF(*aCookieJarSettings = cookieJarSettings); + } + + // 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; + rv = transferable->GetTransferData(kHTMLMime, getter_AddRefs(supports)); + data = do_QueryInterface(supports); + if (NS_SUCCEEDED(rv)) { + data->GetData(mHtmlString); + } + rv = transferable->GetTransferData(kHTMLContext, getter_AddRefs(supports)); + data = do_QueryInterface(supports); + if (NS_SUCCEEDED(rv)) { + data->GetData(mContextString); + } + rv = transferable->GetTransferData(kHTMLInfo, getter_AddRefs(supports)); + data = do_QueryInterface(supports); + if (NS_SUCCEEDED(rv)) { + data->GetData(mInfoString); + } + rv = transferable->GetTransferData(kUnicodeMime, getter_AddRefs(supports)); + 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, bool aHidden) { + RefPtr<nsVariantCC> variant = new nsVariantCC(); + variant->SetAsAString(aData); + aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal, aHidden); +} + +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(u"\r\n", ' '); + dragData += title; + + AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLMime), dragData, + principal); + AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLDataMime), + mUrlString, principal); + AddString(aDataTransfer, + NS_LITERAL_STRING_FROM_CSTRING(kURLDescriptionMime), mTitleString, + principal); + AddString(aDataTransfer, u"text/uri-list"_ns, mUrlString, principal); + } + + // add a special flavor for the html context data + if (!mContextString.IsEmpty()) + AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext), + mContextString, principal); + + // add a special flavor if we have html info data + if (!mInfoString.IsEmpty()) + AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo), + mInfoString, principal); + + // add the full html + if (!mHtmlString.IsEmpty()) + AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(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_FROM_CSTRING(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_FROM_CSTRING(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_FROM_CSTRING(kFilePromiseMime), variant, 0, + principal); + } + + AddString(aDataTransfer, + NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseURLMime), + mImageSourceString, principal); + AddString(aDataTransfer, + NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseDestFilename), + mImageDestFileName, principal); +#if defined(XP_MACOSX) + AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kImageRequestMime), + mImageRequestMime, principal, /* aHidden= */ true); +#endif + + // if not an anchor, add the image url + if (!mIsAnchor) { + AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLDataMime), + mUrlString, principal); + AddString(aDataTransfer, u"text/uri-list"_ns, mUrlString, principal); + } + } + + return NS_OK; +} + +// note that this can return NS_OK, but a null out param (by design) +// static +nsresult DragDataProducer::GetDraggableSelectionData( + Selection* inSelection, nsIContent* inRealTargetNode, + nsIContent** outImageOrLinkNode, bool* outDragSelectedText) { + NS_ENSURE_ARG(inSelection); + NS_ENSURE_ARG(inRealTargetNode); + NS_ENSURE_ARG_POINTER(outImageOrLinkNode); + + *outImageOrLinkNode = nullptr; + *outDragSelectedText = false; + + if (!inSelection->IsCollapsed()) { + if (inSelection->ContainsNode(*inRealTargetNode, false, IgnoreErrors())) { + // track down the anchor node, if any, for the url + nsINode* selectionStart = inSelection->GetAnchorNode(); + nsINode* selectionEnd = inSelection->GetFocusNode(); + + // 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) { + nsCOMPtr<nsIContent> selStartContent = + nsIContent::FromNodeOrNull(selectionStart); + if (selStartContent && selStartContent->HasChildNodes()) { + // see if just one node is selected + uint32_t anchorOffset = inSelection->AnchorOffset(); + uint32_t focusOffset = inSelection->FocusOffset(); + if (anchorOffset == focusOffset + 1 || + focusOffset == anchorOffset + 1) { + uint32_t childOffset = std::min(anchorOffset, focusOffset); + nsIContent* childContent = + selStartContent->GetChildAt_Deprecated(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; +} |