michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsReadableUtils.h" michael@0: michael@0: // Local Includes michael@0: #include "nsContentAreaDragDrop.h" michael@0: michael@0: // Helper Classes michael@0: #include "nsString.h" michael@0: michael@0: // Interfaces needed to be included michael@0: #include "nsCopySupport.h" michael@0: #include "nsIDOMUIEvent.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISelectionController.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMDragEvent.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIFormControl.h" michael@0: #include "nsIDOMHTMLAreaElement.h" michael@0: #include "nsIDOMHTMLAnchorElement.h" michael@0: #include "nsITransferable.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIImageLoadingContent.h" michael@0: #include "nsITextControlElement.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIWebBrowserPersist.h" michael@0: #include "nsEscape.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIMIMEService.h" michael@0: #include "imgIContainer.h" michael@0: #include "imgIRequest.h" michael@0: #include "mozilla/dom/DataTransfer.h" michael@0: #include "nsIMIMEInfo.h" michael@0: #include "nsRange.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/HTMLAreaElement.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: class MOZ_STACK_CLASS DragDataProducer michael@0: { michael@0: public: michael@0: DragDataProducer(nsPIDOMWindow* aWindow, michael@0: nsIContent* aTarget, michael@0: nsIContent* aSelectionTargetNode, michael@0: bool aIsAltKeyPressed); michael@0: nsresult Produce(DataTransfer* aDataTransfer, michael@0: bool* aCanDrag, michael@0: nsISelection** aSelection, michael@0: nsIContent** aDragNode); michael@0: michael@0: private: michael@0: void AddString(DataTransfer* aDataTransfer, michael@0: const nsAString& aFlavor, michael@0: const nsAString& aData, michael@0: nsIPrincipal* aPrincipal); michael@0: nsresult AddStringsToDataTransfer(nsIContent* aDragNode, michael@0: DataTransfer* aDataTransfer); michael@0: static nsresult GetDraggableSelectionData(nsISelection* inSelection, michael@0: nsIContent* inRealTargetNode, michael@0: nsIContent **outImageOrLinkNode, michael@0: bool* outDragSelectedText); michael@0: static already_AddRefed FindParentLinkNode(nsIContent* inNode); michael@0: static void GetAnchorURL(nsIContent* inNode, nsAString& outURL); michael@0: static void GetNodeString(nsIContent* inNode, nsAString & outNodeString); michael@0: static void CreateLinkText(const nsAString& inURL, const nsAString & inText, michael@0: nsAString& outLinkText); michael@0: static void GetSelectedLink(nsISelection* inSelection, michael@0: nsIContent **outLinkNode); michael@0: michael@0: nsCOMPtr mWindow; michael@0: nsCOMPtr mTarget; michael@0: nsCOMPtr mSelectionTargetNode; michael@0: bool mIsAltKeyPressed; michael@0: michael@0: nsString mUrlString; michael@0: nsString mImageSourceString; michael@0: nsString mImageDestFileName; michael@0: nsString mTitleString; michael@0: // will be filled automatically if you fill urlstring michael@0: nsString mHtmlString; michael@0: nsString mContextString; michael@0: nsString mInfoString; michael@0: michael@0: bool mIsAnchor; michael@0: nsCOMPtr mImage; michael@0: }; michael@0: michael@0: michael@0: nsresult michael@0: nsContentAreaDragDrop::GetDragData(nsPIDOMWindow* aWindow, michael@0: nsIContent* aTarget, michael@0: nsIContent* aSelectionTargetNode, michael@0: bool aIsAltKeyPressed, michael@0: DataTransfer* aDataTransfer, michael@0: bool* aCanDrag, michael@0: nsISelection** aSelection, michael@0: nsIContent** aDragNode) michael@0: { michael@0: NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG); michael@0: michael@0: *aCanDrag = true; michael@0: michael@0: DragDataProducer michael@0: provider(aWindow, aTarget, aSelectionTargetNode, aIsAltKeyPressed); michael@0: return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode); michael@0: } michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider) michael@0: michael@0: // SaveURIToFile michael@0: // used on platforms where it's possible to drag items (e.g. images) michael@0: // into the file system michael@0: nsresult michael@0: nsContentAreaDragDropDataProvider::SaveURIToFile(nsAString& inSourceURIString, michael@0: nsIFile* inDestFile, michael@0: bool isPrivate) michael@0: { michael@0: nsCOMPtr sourceURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(sourceURI), inSourceURIString); michael@0: if (NS_FAILED(rv)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr sourceURL = do_QueryInterface(sourceURI); michael@0: if (!sourceURL) { michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // we rely on the fact that the WPB is refcounted by the channel etc, michael@0: // so we don't keep a ref to it. It will die when finished. michael@0: nsCOMPtr persist = michael@0: do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", michael@0: &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: persist->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION); michael@0: michael@0: return persist->SavePrivacyAwareURI(sourceURI, nullptr, nullptr, nullptr, nullptr, michael@0: inDestFile, isPrivate); michael@0: } michael@0: michael@0: // This is our nsIFlavorDataProvider callback. There are several michael@0: // assumptions here that make this work: michael@0: // michael@0: // 1. Someone put a kFilePromiseURLMime flavor into the transferable michael@0: // with the source URI of the file to save (as a string). We did michael@0: // that in AddStringsToDataTransfer. michael@0: // michael@0: // 2. Someone put a kFilePromiseDirectoryMime flavor into the michael@0: // transferable with an nsIFile for the directory we are to michael@0: // save in. That has to be done by platform-specific code (in michael@0: // widget), which gets the destination directory from michael@0: // OS-specific drag information. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable *aTransferable, michael@0: const char *aFlavor, michael@0: nsISupports **aData, michael@0: uint32_t *aDataLen) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aData && aDataLen); michael@0: *aData = nullptr; michael@0: *aDataLen = 0; michael@0: michael@0: nsresult rv = NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: if (strcmp(aFlavor, kFilePromiseMime) == 0) { michael@0: // get the URI from the kFilePromiseURLMime flavor michael@0: NS_ENSURE_ARG(aTransferable); michael@0: nsCOMPtr tmp; michael@0: uint32_t dataSize = 0; michael@0: aTransferable->GetTransferData(kFilePromiseURLMime, michael@0: getter_AddRefs(tmp), &dataSize); michael@0: nsCOMPtr supportsString = michael@0: do_QueryInterface(tmp); michael@0: if (!supportsString) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsAutoString sourceURLString; michael@0: supportsString->GetData(sourceURLString); michael@0: if (sourceURLString.IsEmpty()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: aTransferable->GetTransferData(kFilePromiseDestFilename, michael@0: getter_AddRefs(tmp), &dataSize); michael@0: supportsString = do_QueryInterface(tmp); michael@0: if (!supportsString) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsAutoString targetFilename; michael@0: supportsString->GetData(targetFilename); michael@0: if (targetFilename.IsEmpty()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // get the target directory from the kFilePromiseDirectoryMime michael@0: // flavor michael@0: nsCOMPtr dirPrimitive; michael@0: dataSize = 0; michael@0: aTransferable->GetTransferData(kFilePromiseDirectoryMime, michael@0: getter_AddRefs(dirPrimitive), &dataSize); michael@0: nsCOMPtr destDirectory = do_QueryInterface(dirPrimitive); michael@0: if (!destDirectory) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr file; michael@0: rv = destDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: file->Append(targetFilename); michael@0: michael@0: bool isPrivate; michael@0: aTransferable->GetIsPrivateData(&isPrivate); michael@0: michael@0: rv = SaveURIToFile(sourceURLString, file, isPrivate); michael@0: // send back an nsIFile michael@0: if (NS_SUCCEEDED(rv)) { michael@0: CallQueryInterface(file, aData); michael@0: *aDataLen = sizeof(nsIFile*); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: DragDataProducer::DragDataProducer(nsPIDOMWindow* aWindow, michael@0: nsIContent* aTarget, michael@0: nsIContent* aSelectionTargetNode, michael@0: bool aIsAltKeyPressed) michael@0: : mWindow(aWindow), michael@0: mTarget(aTarget), michael@0: mSelectionTargetNode(aSelectionTargetNode), michael@0: mIsAltKeyPressed(aIsAltKeyPressed), michael@0: mIsAnchor(false) michael@0: { michael@0: } michael@0: michael@0: michael@0: // michael@0: // FindParentLinkNode michael@0: // michael@0: // Finds the parent with the given link tag starting at |inNode|. If michael@0: // it gets up to the root without finding it, we stop looking and michael@0: // return null. michael@0: // michael@0: already_AddRefed michael@0: DragDataProducer::FindParentLinkNode(nsIContent* inNode) michael@0: { michael@0: nsIContent* content = inNode; michael@0: if (!content) { michael@0: // That must have been the document node; nothing else to do here; michael@0: return nullptr; michael@0: } michael@0: michael@0: for (; content; content = content->GetParent()) { michael@0: if (nsContentUtils::IsDraggableLink(content)) { michael@0: nsCOMPtr ret = content; michael@0: return ret.forget(); michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: // michael@0: // GetAnchorURL michael@0: // michael@0: void michael@0: DragDataProducer::GetAnchorURL(nsIContent* inNode, nsAString& outURL) michael@0: { michael@0: nsCOMPtr linkURI; michael@0: if (!inNode || !inNode->IsLink(getter_AddRefs(linkURI))) { michael@0: // Not a link michael@0: outURL.Truncate(); michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString spec; michael@0: linkURI->GetSpec(spec); michael@0: CopyUTF8toUTF16(spec, outURL); michael@0: } michael@0: michael@0: michael@0: // michael@0: // CreateLinkText michael@0: // michael@0: // Creates the html for an anchor in the form michael@0: // inText michael@0: // michael@0: void michael@0: DragDataProducer::CreateLinkText(const nsAString& inURL, michael@0: const nsAString & inText, michael@0: nsAString& outLinkText) michael@0: { michael@0: // use a temp var in case |inText| is the same string as michael@0: // |outLinkText| to avoid overwriting it while building up the michael@0: // string in pieces. michael@0: nsAutoString linkText(NS_LITERAL_STRING("") + michael@0: inText + michael@0: NS_LITERAL_STRING("") ); michael@0: michael@0: outLinkText = linkText; michael@0: } michael@0: michael@0: michael@0: // michael@0: // GetNodeString michael@0: // michael@0: // Gets the text associated with a node michael@0: // michael@0: void michael@0: DragDataProducer::GetNodeString(nsIContent* inNode, michael@0: nsAString & outNodeString) michael@0: { michael@0: nsCOMPtr node = inNode; michael@0: michael@0: outNodeString.Truncate(); michael@0: michael@0: // use a range to get the text-equivalent of the node michael@0: nsCOMPtr doc = node->OwnerDoc(); michael@0: mozilla::ErrorResult rv; michael@0: nsRefPtr range = doc->CreateRange(rv); michael@0: if (range) { michael@0: range->SelectNode(*node, rv); michael@0: range->ToString(outNodeString); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: DragDataProducer::Produce(DataTransfer* aDataTransfer, michael@0: bool* aCanDrag, michael@0: nsISelection** aSelection, michael@0: nsIContent** aDragNode) michael@0: { michael@0: NS_PRECONDITION(aCanDrag && aSelection && aDataTransfer && aDragNode, michael@0: "null pointer passed to Produce"); michael@0: NS_ASSERTION(mWindow, "window not set"); michael@0: NS_ASSERTION(mSelectionTargetNode, "selection target node should have been set"); michael@0: michael@0: *aDragNode = nullptr; michael@0: michael@0: nsresult rv; michael@0: nsIContent* dragNode = nullptr; michael@0: *aSelection = nullptr; michael@0: michael@0: // Find the selection to see what we could be dragging and if what we're michael@0: // dragging is in what is selected. If this is an editable textbox, use michael@0: // the textbox's selection, otherwise use the window's selection. michael@0: nsCOMPtr selection; michael@0: nsIContent* editingElement = mSelectionTargetNode->IsEditable() ? michael@0: mSelectionTargetNode->GetEditingHost() : nullptr; michael@0: nsCOMPtr textControl = michael@0: nsITextControlElement::GetTextControlElementFromEditingHost(editingElement); michael@0: if (textControl) { michael@0: nsISelectionController* selcon = textControl->GetSelectionController(); michael@0: if (selcon) { michael@0: selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: } michael@0: michael@0: if (!selection) michael@0: return NS_OK; michael@0: } michael@0: else { michael@0: mWindow->GetSelection(getter_AddRefs(selection)); michael@0: if (!selection) michael@0: return NS_OK; michael@0: michael@0: // Check if the node is inside a form control. Don't set aCanDrag to false michael@0: //however, as we still want to allow the drag. michael@0: nsCOMPtr findFormNode = mSelectionTargetNode; michael@0: nsIContent* findFormParent = findFormNode->GetParent(); michael@0: while (findFormParent) { michael@0: nsCOMPtr form(do_QueryInterface(findFormParent)); michael@0: if (form && !form->AllowDraggableChildren()) { michael@0: return NS_OK; michael@0: } michael@0: findFormParent = findFormParent->GetParent(); michael@0: } michael@0: } michael@0: michael@0: // if set, serialize the content under this node michael@0: nsCOMPtr nodeToSerialize; michael@0: michael@0: nsCOMPtr webnav = do_GetInterface(mWindow); michael@0: nsCOMPtr dsti = do_QueryInterface(webnav); michael@0: const bool isChromeShell = michael@0: dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome; michael@0: michael@0: // In chrome shells, only allow dragging inside editable areas. michael@0: if (isChromeShell && !editingElement) michael@0: return NS_OK; michael@0: michael@0: if (isChromeShell && textControl) { michael@0: // Only use the selection if the target node is in the selection. michael@0: bool selectionContainsTarget = false; michael@0: nsCOMPtr targetNode = do_QueryInterface(mSelectionTargetNode); michael@0: selection->ContainsNode(targetNode, false, &selectionContainsTarget); michael@0: if (!selectionContainsTarget) michael@0: return NS_OK; michael@0: michael@0: selection.swap(*aSelection); michael@0: } michael@0: else { michael@0: // In content shells, a number of checks are made below to determine michael@0: // whether an image or a link is being dragged. If so, add additional michael@0: // data to the data transfer. This is also done for chrome shells, but michael@0: // only when in a non-textbox editor. michael@0: michael@0: bool haveSelectedContent = false; michael@0: michael@0: // possible parent link node michael@0: nsCOMPtr parentLink; michael@0: nsCOMPtr draggedNode; michael@0: michael@0: { michael@0: // only drag form elements by using the alt key, michael@0: // otherwise buttons and select widgets are hard to use michael@0: michael@0: // Note that while elements implement nsIFormControl, we should michael@0: // really allow dragging them if they happen to be images. michael@0: nsCOMPtr form(do_QueryInterface(mTarget)); michael@0: if (form && !mIsAltKeyPressed && form->GetType() != NS_FORM_OBJECT) { michael@0: *aCanDrag = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: draggedNode = mTarget; michael@0: } michael@0: michael@0: nsCOMPtr area; // client-side image map michael@0: nsCOMPtr image; michael@0: nsCOMPtr link; michael@0: michael@0: nsCOMPtr selectedImageOrLinkNode; michael@0: GetDraggableSelectionData(selection, mSelectionTargetNode, michael@0: getter_AddRefs(selectedImageOrLinkNode), michael@0: &haveSelectedContent); michael@0: michael@0: // either plain text or anchor text is selected michael@0: if (haveSelectedContent) { michael@0: link = do_QueryInterface(selectedImageOrLinkNode); michael@0: if (link && mIsAltKeyPressed) { michael@0: // if alt is pressed, select the link text instead of drag the link michael@0: *aCanDrag = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: selection.swap(*aSelection); michael@0: } else if (selectedImageOrLinkNode) { michael@0: // an image is selected michael@0: image = do_QueryInterface(selectedImageOrLinkNode); michael@0: } else { michael@0: // nothing is selected - michael@0: // michael@0: // look for draggable elements under the mouse michael@0: // michael@0: // if the alt key is down, don't start a drag if we're in an michael@0: // anchor because we want to do selection. michael@0: parentLink = FindParentLinkNode(draggedNode); michael@0: if (parentLink && mIsAltKeyPressed) { michael@0: *aCanDrag = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: area = do_QueryInterface(draggedNode); michael@0: image = do_QueryInterface(draggedNode); michael@0: link = do_QueryInterface(draggedNode); michael@0: } michael@0: michael@0: { michael@0: // set for linked images, and links michael@0: nsCOMPtr linkNode; michael@0: michael@0: if (area) { michael@0: // use the alt text (or, if missing, the href) as the title michael@0: HTMLAreaElement* areaElem = static_cast(area.get()); michael@0: areaElem->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString); michael@0: if (mTitleString.IsEmpty()) { michael@0: // this can be a relative link michael@0: areaElem->GetAttribute(NS_LITERAL_STRING("href"), mTitleString); michael@0: } michael@0: michael@0: // we'll generate HTML like alt text michael@0: mIsAnchor = true; michael@0: michael@0: // gives an absolute link michael@0: GetAnchorURL(draggedNode, mUrlString); michael@0: michael@0: mHtmlString.AssignLiteral(""); michael@0: mHtmlString.Append(mTitleString); michael@0: mHtmlString.AppendLiteral(""); michael@0: michael@0: dragNode = draggedNode; michael@0: } else if (image) { michael@0: mIsAnchor = true; michael@0: // grab the href as the url, use alt text as the title of the michael@0: // area if it's there. the drag data is the image tag and src michael@0: // attribute. michael@0: nsCOMPtr imageURI; michael@0: image->GetCurrentURI(getter_AddRefs(imageURI)); michael@0: if (imageURI) { michael@0: nsAutoCString spec; michael@0: imageURI->GetSpec(spec); michael@0: CopyUTF8toUTF16(spec, mUrlString); michael@0: } michael@0: michael@0: nsCOMPtr imageElement(do_QueryInterface(image)); michael@0: // XXXbz Shouldn't we use the "title" attr for title? Using michael@0: // "alt" seems very wrong.... michael@0: if (imageElement) { michael@0: imageElement->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString); michael@0: } michael@0: michael@0: if (mTitleString.IsEmpty()) { michael@0: mTitleString = mUrlString; michael@0: } michael@0: michael@0: nsCOMPtr imgRequest; michael@0: michael@0: // grab the image data, and its request. michael@0: nsCOMPtr img = michael@0: nsContentUtils::GetImageFromContent(image, michael@0: getter_AddRefs(imgRequest)); michael@0: michael@0: nsCOMPtr mimeService = michael@0: do_GetService("@mozilla.org/mime;1"); michael@0: michael@0: // Fix the file extension in the URL if necessary michael@0: if (imgRequest && mimeService) { michael@0: nsCOMPtr imgUri; michael@0: imgRequest->GetURI(getter_AddRefs(imgUri)); michael@0: michael@0: nsCOMPtr imgUrl(do_QueryInterface(imgUri)); michael@0: michael@0: if (imgUrl) { michael@0: nsAutoCString extension; michael@0: imgUrl->GetFileExtension(extension); michael@0: michael@0: nsXPIDLCString mimeType; michael@0: imgRequest->GetMimeType(getter_Copies(mimeType)); michael@0: michael@0: nsCOMPtr mimeInfo; michael@0: mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(), michael@0: getter_AddRefs(mimeInfo)); michael@0: michael@0: if (mimeInfo) { michael@0: nsAutoCString spec; michael@0: imgUrl->GetSpec(spec); michael@0: michael@0: // pass out the image source string michael@0: CopyUTF8toUTF16(spec, mImageSourceString); michael@0: michael@0: bool validExtension; michael@0: if (extension.IsEmpty() || michael@0: NS_FAILED(mimeInfo->ExtensionExists(extension, michael@0: &validExtension)) || michael@0: !validExtension) { michael@0: // Fix the file extension in the URL michael@0: nsresult rv = imgUrl->Clone(getter_AddRefs(imgUri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: imgUrl = do_QueryInterface(imgUri); michael@0: michael@0: nsAutoCString primaryExtension; michael@0: mimeInfo->GetPrimaryExtension(primaryExtension); michael@0: michael@0: imgUrl->SetFileExtension(primaryExtension); michael@0: } michael@0: michael@0: nsAutoCString fileName; michael@0: imgUrl->GetFileName(fileName); michael@0: michael@0: NS_UnescapeURL(fileName); michael@0: michael@0: // make the filename safe for the filesystem michael@0: fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, michael@0: '-'); michael@0: michael@0: CopyUTF8toUTF16(fileName, mImageDestFileName); michael@0: michael@0: // and the image object michael@0: mImage = img; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (parentLink) { michael@0: // If we are dragging around an image in an anchor, then we michael@0: // are dragging the entire anchor michael@0: linkNode = parentLink; michael@0: nodeToSerialize = linkNode; michael@0: } else { michael@0: nodeToSerialize = do_QueryInterface(draggedNode); michael@0: } michael@0: dragNode = nodeToSerialize; michael@0: } else if (link) { michael@0: // set linkNode. The code below will handle this michael@0: linkNode = do_QueryInterface(link); // XXX test this michael@0: GetNodeString(draggedNode, mTitleString); michael@0: } else if (parentLink) { michael@0: // parentLink will always be null if there's selected content michael@0: linkNode = parentLink; michael@0: nodeToSerialize = linkNode; michael@0: } else if (!haveSelectedContent) { michael@0: // nothing draggable michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (linkNode) { michael@0: mIsAnchor = true; michael@0: GetAnchorURL(linkNode, mUrlString); michael@0: dragNode = linkNode; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (nodeToSerialize || *aSelection) { michael@0: mHtmlString.Truncate(); michael@0: mContextString.Truncate(); michael@0: mInfoString.Truncate(); michael@0: mTitleString.Truncate(); michael@0: michael@0: nsCOMPtr doc = mWindow->GetDoc(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: michael@0: // if we have selected text, use it in preference to the node michael@0: nsCOMPtr transferable; michael@0: if (*aSelection) { michael@0: rv = nsCopySupport::GetTransferableForSelection(*aSelection, doc, michael@0: getter_AddRefs(transferable)); michael@0: } michael@0: else { michael@0: rv = nsCopySupport::GetTransferableForNode(nodeToSerialize, doc, michael@0: getter_AddRefs(transferable)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr supports; michael@0: nsCOMPtr data; michael@0: uint32_t dataSize; michael@0: rv = transferable->GetTransferData(kHTMLMime, getter_AddRefs(supports), michael@0: &dataSize); michael@0: data = do_QueryInterface(supports); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: data->GetData(mHtmlString); michael@0: } michael@0: rv = transferable->GetTransferData(kHTMLContext, getter_AddRefs(supports), michael@0: &dataSize); michael@0: data = do_QueryInterface(supports); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: data->GetData(mContextString); michael@0: } michael@0: rv = transferable->GetTransferData(kHTMLInfo, getter_AddRefs(supports), michael@0: &dataSize); michael@0: data = do_QueryInterface(supports); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: data->GetData(mInfoString); michael@0: } michael@0: rv = transferable->GetTransferData(kUnicodeMime, getter_AddRefs(supports), michael@0: &dataSize); michael@0: data = do_QueryInterface(supports); michael@0: NS_ENSURE_SUCCESS(rv, rv); // require plain text at a minimum michael@0: data->GetData(mTitleString); michael@0: } michael@0: michael@0: // default text value is the URL michael@0: if (mTitleString.IsEmpty()) { michael@0: mTitleString = mUrlString; michael@0: } michael@0: michael@0: // if we haven't constructed a html version, make one now michael@0: if (mHtmlString.IsEmpty() && !mUrlString.IsEmpty()) michael@0: CreateLinkText(mUrlString, mTitleString, mHtmlString); michael@0: michael@0: // if there is no drag node, which will be the case for a selection, just michael@0: // use the selection target node. michael@0: rv = AddStringsToDataTransfer( michael@0: dragNode ? dragNode : mSelectionTargetNode.get(), aDataTransfer); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_IF_ADDREF(*aDragNode = dragNode); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: DragDataProducer::AddString(DataTransfer* aDataTransfer, michael@0: const nsAString& aFlavor, michael@0: const nsAString& aData, michael@0: nsIPrincipal* aPrincipal) michael@0: { michael@0: nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); michael@0: if (variant) { michael@0: variant->SetAsAString(aData); michael@0: aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode, michael@0: DataTransfer* aDataTransfer) michael@0: { michael@0: NS_ASSERTION(aDragNode, "adding strings for null node"); michael@0: michael@0: // set all of the data to have the principal of the node where the data came from michael@0: nsIPrincipal* principal = aDragNode->NodePrincipal(); michael@0: michael@0: // add a special flavor if we're an anchor to indicate that we have michael@0: // a URL in the drag data michael@0: if (!mUrlString.IsEmpty() && mIsAnchor) { michael@0: nsAutoString dragData(mUrlString); michael@0: dragData.AppendLiteral("\n"); michael@0: // Remove leading and trailing newlines in the title and replace them with michael@0: // space in remaining positions - they confuse PlacesUtils::unwrapNodes michael@0: // that expects url\ntitle formatted data for x-moz-url. michael@0: nsAutoString title(mTitleString); michael@0: title.Trim("\r\n"); michael@0: title.ReplaceChar("\r\n", ' '); michael@0: dragData += title; michael@0: michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kURLMime), dragData, principal); michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal); michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kURLDescriptionMime), mTitleString, principal); michael@0: AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal); michael@0: } michael@0: michael@0: // add a special flavor for the html context data michael@0: if (!mContextString.IsEmpty()) michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), mContextString, principal); michael@0: michael@0: // add a special flavor if we have html info data michael@0: if (!mInfoString.IsEmpty()) michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), mInfoString, principal); michael@0: michael@0: // add the full html michael@0: if (!mHtmlString.IsEmpty()) michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLMime), mHtmlString, principal); michael@0: michael@0: // add the plain text. we use the url for text/plain data if an anchor is michael@0: // being dragged, rather than the title text of the link or the alt text for michael@0: // an anchor image. michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kTextMime), michael@0: mIsAnchor ? mUrlString : mTitleString, principal); michael@0: michael@0: // add image data, if present. For now, all we're going to do with michael@0: // this is turn it into a native data flavor, so indicate that with michael@0: // a new flavor so as not to confuse anyone who is really registered michael@0: // for image/gif or image/jpg. michael@0: if (mImage) { michael@0: nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); michael@0: if (variant) { michael@0: variant->SetAsISupports(mImage); michael@0: aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kNativeImageMime), michael@0: variant, 0, principal); michael@0: } michael@0: michael@0: // assume the image comes from a file, and add a file promise. We michael@0: // register ourselves as a nsIFlavorDataProvider, and will use the michael@0: // GetFlavorData callback to save the image to disk. michael@0: michael@0: nsCOMPtr dataProvider = michael@0: new nsContentAreaDragDropDataProvider(); michael@0: if (dataProvider) { michael@0: nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); michael@0: if (variant) { michael@0: variant->SetAsISupports(dataProvider); michael@0: aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime), michael@0: variant, 0, principal); michael@0: } michael@0: } michael@0: michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime), michael@0: mImageSourceString, principal); michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseDestFilename), michael@0: mImageDestFileName, principal); michael@0: michael@0: // if not an anchor, add the image url michael@0: if (!mIsAnchor) { michael@0: AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal); michael@0: AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // note that this can return NS_OK, but a null out param (by design) michael@0: // static michael@0: nsresult michael@0: DragDataProducer::GetDraggableSelectionData(nsISelection* inSelection, michael@0: nsIContent* inRealTargetNode, michael@0: nsIContent **outImageOrLinkNode, michael@0: bool* outDragSelectedText) michael@0: { michael@0: NS_ENSURE_ARG(inSelection); michael@0: NS_ENSURE_ARG(inRealTargetNode); michael@0: NS_ENSURE_ARG_POINTER(outImageOrLinkNode); michael@0: michael@0: *outImageOrLinkNode = nullptr; michael@0: *outDragSelectedText = false; michael@0: michael@0: bool selectionContainsTarget = false; michael@0: michael@0: bool isCollapsed = false; michael@0: inSelection->GetIsCollapsed(&isCollapsed); michael@0: if (!isCollapsed) { michael@0: nsCOMPtr realTargetNode = do_QueryInterface(inRealTargetNode); michael@0: inSelection->ContainsNode(realTargetNode, false, michael@0: &selectionContainsTarget); michael@0: michael@0: if (selectionContainsTarget) { michael@0: // track down the anchor node, if any, for the url michael@0: nsCOMPtr selectionStart; michael@0: inSelection->GetAnchorNode(getter_AddRefs(selectionStart)); michael@0: michael@0: nsCOMPtr selectionEnd; michael@0: inSelection->GetFocusNode(getter_AddRefs(selectionEnd)); michael@0: michael@0: // look for a selection around a single node, like an image. michael@0: // in this case, drag the image, rather than a serialization of the HTML michael@0: // XXX generalize this to other draggable element types? michael@0: if (selectionStart == selectionEnd) { michael@0: bool hasChildren; michael@0: selectionStart->HasChildNodes(&hasChildren); michael@0: if (hasChildren) { michael@0: // see if just one node is selected michael@0: int32_t anchorOffset, focusOffset; michael@0: inSelection->GetAnchorOffset(&anchorOffset); michael@0: inSelection->GetFocusOffset(&focusOffset); michael@0: if (abs(anchorOffset - focusOffset) == 1) { michael@0: nsCOMPtr selStartContent = michael@0: do_QueryInterface(selectionStart); michael@0: michael@0: if (selStartContent) { michael@0: int32_t childOffset = michael@0: (anchorOffset < focusOffset) ? anchorOffset : focusOffset; michael@0: nsIContent *childContent = michael@0: selStartContent->GetChildAt(childOffset); michael@0: // if we find an image, we'll fall into the node-dragging code, michael@0: // rather the the selection-dragging code michael@0: if (nsContentUtils::IsDraggableImage(childContent)) { michael@0: NS_ADDREF(*outImageOrLinkNode = childContent); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // see if the selection is a link; if so, its node will be returned michael@0: GetSelectedLink(inSelection, outImageOrLinkNode); michael@0: michael@0: // indicate that a link or text is selected michael@0: *outDragSelectedText = true; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: DragDataProducer::GetSelectedLink(nsISelection* inSelection, michael@0: nsIContent **outLinkNode) michael@0: { michael@0: *outLinkNode = nullptr; michael@0: michael@0: nsCOMPtr selectionStartNode; michael@0: inSelection->GetAnchorNode(getter_AddRefs(selectionStartNode)); michael@0: nsCOMPtr selectionEndNode; michael@0: inSelection->GetFocusNode(getter_AddRefs(selectionEndNode)); michael@0: michael@0: // simple case: only one node is selected michael@0: // see if it or its parent is an anchor, then exit michael@0: michael@0: if (selectionStartNode == selectionEndNode) { michael@0: nsCOMPtr selectionStart = do_QueryInterface(selectionStartNode); michael@0: nsCOMPtr link = FindParentLinkNode(selectionStart); michael@0: if (link) { michael@0: link.swap(*outLinkNode); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: // more complicated case: multiple nodes are selected michael@0: michael@0: // Unless you use the Alt key while selecting anchor text, it is michael@0: // nearly impossible to avoid overlapping into adjacent nodes. michael@0: // Deal with this by trimming off the leading and/or trailing michael@0: // nodes of the selection if the strings they produce are empty. michael@0: michael@0: // first, use a range determine if the selection was marked LTR or RTL; michael@0: // if the latter, swap endpoints so we trim in the right direction michael@0: michael@0: int32_t startOffset, endOffset; michael@0: { michael@0: nsCOMPtr range; michael@0: inSelection->GetRangeAt(0, getter_AddRefs(range)); michael@0: if (!range) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr tempNode; michael@0: range->GetStartContainer( getter_AddRefs(tempNode)); michael@0: if (tempNode != selectionStartNode) { michael@0: selectionEndNode = selectionStartNode; michael@0: selectionStartNode = tempNode; michael@0: inSelection->GetAnchorOffset(&endOffset); michael@0: inSelection->GetFocusOffset(&startOffset); michael@0: } else { michael@0: inSelection->GetAnchorOffset(&startOffset); michael@0: inSelection->GetFocusOffset(&endOffset); michael@0: } michael@0: } michael@0: michael@0: // trim leading node if the string is empty or michael@0: // the selection starts at the end of the text michael@0: michael@0: nsAutoString nodeStr; michael@0: selectionStartNode->GetNodeValue(nodeStr); michael@0: if (nodeStr.IsEmpty() || michael@0: startOffset+1 >= static_cast(nodeStr.Length())) { michael@0: nsCOMPtr curr = selectionStartNode; michael@0: nsIDOMNode* next; michael@0: michael@0: while (curr) { michael@0: curr->GetNextSibling(&next); michael@0: michael@0: if (next) { michael@0: selectionStartNode = dont_AddRef(next); michael@0: break; michael@0: } michael@0: michael@0: curr->GetParentNode(&next); michael@0: curr = dont_AddRef(next); michael@0: } michael@0: } michael@0: michael@0: // trim trailing node if the selection ends before its text begins michael@0: michael@0: if (endOffset == 0) { michael@0: nsCOMPtr curr = selectionEndNode; michael@0: nsIDOMNode* next; michael@0: michael@0: while (curr) { michael@0: curr->GetPreviousSibling(&next); michael@0: michael@0: if (next){ michael@0: selectionEndNode = dont_AddRef(next); michael@0: break; michael@0: } michael@0: michael@0: curr->GetParentNode(&next); michael@0: curr = dont_AddRef(next); michael@0: } michael@0: } michael@0: michael@0: // see if the leading & trailing nodes are part of the michael@0: // same anchor - if so, return the anchor node michael@0: nsCOMPtr selectionStart = do_QueryInterface(selectionStartNode); michael@0: nsCOMPtr link = FindParentLinkNode(selectionStart); michael@0: if (link) { michael@0: nsCOMPtr selectionEnd = do_QueryInterface(selectionEndNode); michael@0: nsCOMPtr link2 = FindParentLinkNode(selectionEnd); michael@0: michael@0: if (link == link2) { michael@0: NS_IF_ADDREF(*outLinkNode = link); michael@0: } michael@0: } michael@0: michael@0: return; michael@0: }