forked from mirrors/gecko-dev
Backed out changeset f17c7565707b (bug 1772006) Backed out changeset c725fe1f5882 (bug 1772006) Backed out changeset d19663161261 (bug 1772006) Backed out changeset b6611ab002d9 (bug 1772006) Backed out changeset 790f42b64af9 (bug 1772006) Backed out changeset 79a734b4e4d9 (bug 1772006) Backed out changeset 42730aae16ea (bug 1772006) Backed out changeset b2542aef3054 (bug 1772006) Backed out changeset 962bfea4a309 (bug 1772006)
878 lines
31 KiB
C++
878 lines
31 KiB
C++
/* -*- 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(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
|
|
'-');
|
|
}
|
|
#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(getter_AddRefs(imgUri));
|
|
|
|
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("\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;
|
|
}
|