gecko-dev/dom/base/nsContentAreaDragDrop.cpp

930 строки
33 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 "nsIWebNavigation.h"
#include "nsIDocShell.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIImageLoadingContent.h"
#include "nsITextControlElement.h"
#include "nsUnicharUtils.h"
#include "nsIURL.h"
#include "nsIURIMutator.h"
#include "mozilla/dom/Document.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 "BrowserParent.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::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);
private:
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);
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;
#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) {
NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG);
*aCanDrag = true;
DragDataProducer provider(aWindow, aTarget, aSelectionTargetNode,
aIsAltKeyPressed);
return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode,
aPrincipal);
}
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,
nsIFile* inDestFile, 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, mozilla::net::RP_Unset, nullptr,
nullptr, inDestFile, 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, EmptyCString(),
getter_AddRefs(mimeInfo));
NS_ENSURE_SUCCESS(rv, rv);
rv = mimeInfo->GetPrimaryExtension(*aPrimaryExtension);
NS_ENSURE_SUCCESS(rv, rv);
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 imageRequestMime;
supportsString->GetData(imageRequestMime);
// If we have a MIME type, check the extension is compatible
if (!imageRequestMime.IsEmpty()) {
// Build a URL to get the filename extension
nsCOMPtr<nsIURL> imageURL = do_QueryInterface(sourceURI, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString extension;
rv = imageURL->GetFileExtension(extension);
NS_ENSURE_SUCCESS(rv, rv);
NS_ConvertUTF16toUTF8 mimeCString(imageRequestMime);
bool isValidExtension;
nsAutoCString primaryExtension;
rv = CheckAndGetExtensionForMime(extension, mimeCString,
&isValidExtension, &primaryExtension);
NS_ENSURE_SUCCESS(rv, rv);
if (!isValidExtension) {
// The filename extension is missing or incompatible
// with the MIME type, replace it with the primary
// extension.
nsAutoCString newFileName;
rv = imageURL->GetFileBaseName(newFileName);
NS_ENSURE_SUCCESS(rv, rv);
newFileName.Append(".");
newFileName.Append(primaryExtension);
targetFilename = NS_ConvertUTF8toUTF16(newFileName);
}
}
}
// 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();
rv = SaveURIToFile(sourceURI, principal, file, 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) {}
//
// 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<Document> doc = node->OwnerDoc();
RefPtr<nsRange> range = doc->CreateRange(IgnoreErrors());
if (range) {
range->SelectNode(*node, IgnoreErrors());
range->ToString(outNodeString, IgnoreErrors());
}
}
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));
#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);
#else
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
if (NS_WARN_IF(!mimeService)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIMIMEInfo> mimeInfo;
mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
getter_AddRefs(mimeInfo));
if (mimeInfo) {
nsAutoCString extension;
imgUrl->GetFileExtension(extension);
bool validExtension;
if (extension.IsEmpty() ||
NS_FAILED(mimeInfo->ExtensionExists(extension, &validExtension)) ||
!validExtension) {
// Fix the file extension in the URL
nsAutoCString primaryExtension;
mimeInfo->GetPrimaryExtension(primaryExtension);
rv = NS_MutateURI(imgUrl)
.Apply(NS_MutatorMethod(&nsIURLMutator::SetFileExtension,
primaryExtension, nullptr))
.Finalize(imgUrl);
NS_ENSURE_SUCCESS(rv, rv);
}
}
#endif /* defined(XP_MACOSX) */
nsAutoCString fileName;
imgUrl->GetFileName(fileName);
NS_UnescapeURL(fileName);
#if !defined(XP_MACOSX)
// make the filename safe for the filesystem
fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
#endif
CopyUTF8toUTF16(fileName, mImageDestFileName);
// and the image object
mImage = aImage;
}
return NS_OK;
}
nsresult DragDataProducer::Produce(DataTransfer* aDataTransfer, bool* aCanDrag,
Selection** aSelection,
nsIContent** aDragNode,
nsIPrincipal** aPrincipal) {
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;
nsCOMPtr<nsITextControlElement> textControl =
nsITextControlElement::GetTextControlElementFromEditingHost(
editingElement);
if (textControl) {
nsISelectionController* selcon = textControl->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;
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) {
// 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 && textControl) {
// 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() != NS_FORM_OBJECT) {
*aCanDrag = false;
return NS_OK;
}
draggedNode = 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;
RefPtr<HTMLAreaElement> areaElem =
HTMLAreaElement::FromNodeOrNull(draggedNode);
if (areaElem) {
// use the alt text (or, if missing, the href) as the title
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<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->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));
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 (draggedNode && draggedNode->IsHTMLElement(nsGkAtoms::a)) {
// set linkNode. The code below will handle this
linkNode = draggedNode; // 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<Document> 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;
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(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 defined(XP_MACOSX)
AddString(aDataTransfer, NS_LITERAL_STRING(kImageRequestMime),
mImageRequestMime, principal, /* aHidden= */ true);
#endif
// 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(
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;
}