summaryrefslogtreecommitdiffstats
path: root/dom/base/nsContentAreaDragDrop.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/base/nsContentAreaDragDrop.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/base/nsContentAreaDragDrop.cpp')
-rw-r--r--dom/base/nsContentAreaDragDrop.cpp873
1 files changed, 873 insertions, 0 deletions
diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp
new file mode 100644
index 0000000000..3ca21e725a
--- /dev/null
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -0,0 +1,873 @@
+/* -*- 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,
+ 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, 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, 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->SaveURI(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;
+}
+
+//
+// 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);
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ rv = secMan->CheckLoadURIStrWithPrincipal(aContent->NodePrincipal(), spec, 0);
+ 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,
+ 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 = nsContentUtils::GetClosestLinkInFlatTree(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);
+ }
+
+ // gives an absolute link
+ nsresult rv = GetAnchorURL(draggedNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we'll generate HTML like <a href="absurl">alt text</a>
+ mIsAnchor = true;
+
+ mHtmlString.AssignLiteral("<a href=\"");
+ mHtmlString.Append(mUrlString);
+ mHtmlString.AppendLiteral("\">");
+ mHtmlString.Append(mTitleString);
+ mHtmlString.AppendLiteral("</a>");
+
+ dragNode = draggedNode;
+ } else if (image) {
+ // 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));
+ nsCOMPtr<Element> imageElement(do_QueryInterface(image));
+ if (imageURI) {
+ nsAutoCString spec;
+ rv = imageURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIScriptSecurityManager* secMan =
+ nsContentUtils::GetSecurityManager();
+ rv = secMan->CheckLoadURIStrWithPrincipal(
+ imageElement->NodePrincipal(), spec, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mIsAnchor = true;
+ CopyUTF8toUTF16(spec, mUrlString);
+ }
+
+ // 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) {
+ rv = GetAnchorURL(linkNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mIsAnchor = true;
+ 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(kTextMime, 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;
+}