Bug 1750871 - run mochitest-remote on fission everywhere. r=releng-reviewers,aki
[gecko.git] / dom / base / nsContentAreaDragDrop.cpp
blob451ef12ecc29b2c954e273a20fef5f18761cb54f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsReadableUtils.h"
9 // Local Includes
10 #include "nsContentAreaDragDrop.h"
12 // Helper Classes
13 #include "nsString.h"
15 // Interfaces needed to be included
16 #include "nsCopySupport.h"
17 #include "nsISelectionController.h"
18 #include "nsPIDOMWindow.h"
19 #include "nsIFormControl.h"
20 #include "nsITransferable.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsXPCOM.h"
23 #include "nsISupportsPrimitives.h"
24 #include "nsServiceManagerUtils.h"
25 #include "nsNetUtil.h"
26 #include "nsIFile.h"
27 #include "nsFrameLoader.h"
28 #include "nsFrameLoaderOwner.h"
29 #include "nsIContent.h"
30 #include "nsIContentInlines.h"
31 #include "nsIContentPolicy.h"
32 #include "nsIImageLoadingContent.h"
33 #include "nsUnicharUtils.h"
34 #include "nsIURL.h"
35 #include "nsIURIMutator.h"
36 #include "mozilla/dom/Document.h"
37 #include "nsICookieJarSettings.h"
38 #include "nsIPrincipal.h"
39 #include "nsIWebBrowserPersist.h"
40 #include "nsEscape.h"
41 #include "nsContentUtils.h"
42 #include "nsIMIMEService.h"
43 #include "imgIContainer.h"
44 #include "imgIRequest.h"
45 #include "mozilla/dom/DataTransfer.h"
46 #include "nsIMIMEInfo.h"
47 #include "nsRange.h"
48 #include "BrowserParent.h"
49 #include "mozilla/TextControlElement.h"
50 #include "mozilla/dom/BrowsingContext.h"
51 #include "mozilla/dom/Element.h"
52 #include "mozilla/dom/HTMLAreaElement.h"
53 #include "mozilla/dom/HTMLAnchorElement.h"
54 #include "mozilla/dom/Selection.h"
55 #include "nsVariant.h"
56 #include "nsQueryObject.h"
58 using namespace mozilla;
59 using namespace mozilla::dom;
60 using mozilla::IgnoreErrors;
62 class MOZ_STACK_CLASS DragDataProducer {
63 public:
64 DragDataProducer(nsPIDOMWindowOuter* aWindow, nsIContent* aTarget,
65 nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed);
66 nsresult Produce(DataTransfer* aDataTransfer, bool* aCanDrag,
67 Selection** aSelection, nsIContent** aDragNode,
68 nsIPrincipal** aPrincipal, nsIContentSecurityPolicy** aCsp,
69 nsICookieJarSettings** aCookieJarSettings);
71 private:
72 void AddString(DataTransfer* aDataTransfer, const nsAString& aFlavor,
73 const nsAString& aData, nsIPrincipal* aPrincipal,
74 bool aHidden = false);
75 nsresult AddStringsToDataTransfer(nsIContent* aDragNode,
76 DataTransfer* aDataTransfer);
77 nsresult GetImageData(imgIContainer* aImage, imgIRequest* aRequest);
78 static nsresult GetDraggableSelectionData(Selection* inSelection,
79 nsIContent* inRealTargetNode,
80 nsIContent** outImageOrLinkNode,
81 bool* outDragSelectedText);
82 static already_AddRefed<nsIContent> FindParentLinkNode(nsIContent* inNode);
83 [[nodiscard]] static nsresult GetAnchorURL(nsIContent* inNode,
84 nsAString& outURL);
85 static void GetNodeString(nsIContent* inNode, nsAString& outNodeString);
86 static void CreateLinkText(const nsAString& inURL, const nsAString& inText,
87 nsAString& outLinkText);
89 nsCOMPtr<nsPIDOMWindowOuter> mWindow;
90 nsCOMPtr<nsIContent> mTarget;
91 nsCOMPtr<nsIContent> mSelectionTargetNode;
92 bool mIsAltKeyPressed;
94 nsString mUrlString;
95 nsString mImageSourceString;
96 nsString mImageDestFileName;
97 #if defined(XP_MACOSX)
98 nsString mImageRequestMime;
99 #endif
100 nsString mTitleString;
101 // will be filled automatically if you fill urlstring
102 nsString mHtmlString;
103 nsString mContextString;
104 nsString mInfoString;
106 bool mIsAnchor;
107 nsCOMPtr<imgIContainer> mImage;
110 nsresult nsContentAreaDragDrop::GetDragData(
111 nsPIDOMWindowOuter* aWindow, nsIContent* aTarget,
112 nsIContent* aSelectionTargetNode, bool aIsAltKeyPressed,
113 DataTransfer* aDataTransfer, bool* aCanDrag, Selection** aSelection,
114 nsIContent** aDragNode, nsIPrincipal** aPrincipal,
115 nsIContentSecurityPolicy** aCsp,
116 nsICookieJarSettings** aCookieJarSettings) {
117 NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG);
119 *aCanDrag = true;
121 DragDataProducer provider(aWindow, aTarget, aSelectionTargetNode,
122 aIsAltKeyPressed);
123 return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode,
124 aPrincipal, aCsp, aCookieJarSettings);
127 NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider)
129 // SaveURIToFile
130 // used on platforms where it's possible to drag items (e.g. images)
131 // into the file system
132 nsresult nsContentAreaDragDropDataProvider::SaveURIToFile(
133 nsIURI* inSourceURI, nsIPrincipal* inTriggeringPrincipal,
134 nsICookieJarSettings* inCookieJarSettings, nsIFile* inDestFile,
135 nsContentPolicyType inContentPolicyType, bool isPrivate) {
136 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(inSourceURI);
137 if (!sourceURL) {
138 return NS_ERROR_NO_INTERFACE;
141 nsresult rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
142 NS_ENSURE_SUCCESS(rv, rv);
144 // we rely on the fact that the WPB is refcounted by the channel etc,
145 // so we don't keep a ref to it. It will die when finished.
146 nsCOMPtr<nsIWebBrowserPersist> persist = do_CreateInstance(
147 "@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
148 NS_ENSURE_SUCCESS(rv, rv);
150 persist->SetPersistFlags(
151 nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
153 // referrer policy can be anything since the referrer is nullptr
154 return persist->SavePrivacyAwareURI(
155 inSourceURI, inTriggeringPrincipal, 0, nullptr, inCookieJarSettings,
156 nullptr, nullptr, inDestFile, inContentPolicyType, isPrivate);
160 * Check if the provided filename extension is valid for the MIME type and
161 * return the MIME type's primary extension.
163 * @param aExtension [in] the extension to check
164 * @param aMimeType [in] the MIME type to check the extension with
165 * @param aIsValidExtension [out] true if |aExtension| is valid for
166 * |aMimeType|
167 * @param aPrimaryExtension [out] the primary extension for the MIME type
168 * to potentially be used as a replacement
169 * for |aExtension|
171 nsresult CheckAndGetExtensionForMime(const nsCString& aExtension,
172 const nsCString& aMimeType,
173 bool* aIsValidExtension,
174 nsACString* aPrimaryExtension) {
175 nsresult rv;
177 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
178 if (NS_WARN_IF(!mimeService)) {
179 return NS_ERROR_FAILURE;
182 nsCOMPtr<nsIMIMEInfo> mimeInfo;
183 rv = mimeService->GetFromTypeAndExtension(aMimeType, ""_ns,
184 getter_AddRefs(mimeInfo));
185 NS_ENSURE_SUCCESS(rv, rv);
187 mimeInfo->GetPrimaryExtension(*aPrimaryExtension);
189 if (aExtension.IsEmpty()) {
190 *aIsValidExtension = false;
191 return NS_OK;
194 rv = mimeInfo->ExtensionExists(aExtension, aIsValidExtension);
195 NS_ENSURE_SUCCESS(rv, rv);
197 return NS_OK;
200 // This is our nsIFlavorDataProvider callback. There are several
201 // assumptions here that make this work:
203 // 1. Someone put a kFilePromiseURLMime flavor into the transferable
204 // with the source URI of the file to save (as a string). We did
205 // that in AddStringsToDataTransfer.
207 // 2. Someone put a kFilePromiseDirectoryMime flavor into the
208 // transferable with an nsIFile for the directory we are to
209 // save in. That has to be done by platform-specific code (in
210 // widget), which gets the destination directory from
211 // OS-specific drag information.
213 NS_IMETHODIMP
214 nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable* aTransferable,
215 const char* aFlavor,
216 nsISupports** aData) {
217 NS_ENSURE_ARG_POINTER(aData);
218 *aData = nullptr;
220 nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
222 if (strcmp(aFlavor, kFilePromiseMime) == 0) {
223 // get the URI from the kFilePromiseURLMime flavor
224 NS_ENSURE_ARG(aTransferable);
225 nsCOMPtr<nsISupports> tmp;
226 rv = aTransferable->GetTransferData(kFilePromiseURLMime,
227 getter_AddRefs(tmp));
228 NS_ENSURE_SUCCESS(rv, rv);
229 nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(tmp);
230 if (!supportsString) return NS_ERROR_FAILURE;
232 nsAutoString sourceURLString;
233 supportsString->GetData(sourceURLString);
234 if (sourceURLString.IsEmpty()) return NS_ERROR_FAILURE;
236 nsCOMPtr<nsIURI> sourceURI;
237 rv = NS_NewURI(getter_AddRefs(sourceURI), sourceURLString);
238 NS_ENSURE_SUCCESS(rv, rv);
240 rv = aTransferable->GetTransferData(kFilePromiseDestFilename,
241 getter_AddRefs(tmp));
242 NS_ENSURE_SUCCESS(rv, rv);
243 supportsString = do_QueryInterface(tmp);
244 if (!supportsString) return NS_ERROR_FAILURE;
246 nsAutoString targetFilename;
247 supportsString->GetData(targetFilename);
248 if (targetFilename.IsEmpty()) return NS_ERROR_FAILURE;
250 #if defined(XP_MACOSX)
251 // Use the image request's MIME type to ensure the filename's
252 // extension is compatible with the OS's handler for this type.
253 // If it isn't, or is missing, replace the extension with the
254 // primary extension. On Mac, do this in the parent process
255 // because sandboxing blocks access to MIME-handler info from
256 // content processes.
257 if (XRE_IsParentProcess()) {
258 rv = aTransferable->GetTransferData(kImageRequestMime,
259 getter_AddRefs(tmp));
260 NS_ENSURE_SUCCESS(rv, rv);
261 supportsString = do_QueryInterface(tmp);
262 if (!supportsString) return NS_ERROR_FAILURE;
264 nsAutoString imageRequestMime;
265 supportsString->GetData(imageRequestMime);
267 // If we have a MIME type, check the extension is compatible
268 if (!imageRequestMime.IsEmpty()) {
269 // Build a URL to get the filename extension
270 nsCOMPtr<nsIURL> imageURL = do_QueryInterface(sourceURI, &rv);
271 NS_ENSURE_SUCCESS(rv, rv);
273 nsAutoCString extension;
274 rv = imageURL->GetFileExtension(extension);
275 NS_ENSURE_SUCCESS(rv, rv);
277 NS_ConvertUTF16toUTF8 mimeCString(imageRequestMime);
278 bool isValidExtension;
279 nsAutoCString primaryExtension;
280 rv = CheckAndGetExtensionForMime(extension, mimeCString,
281 &isValidExtension, &primaryExtension);
282 NS_ENSURE_SUCCESS(rv, rv);
284 if (!isValidExtension && !primaryExtension.IsEmpty()) {
285 // The filename extension is missing or incompatible
286 // with the MIME type, replace it with the primary
287 // extension.
288 nsAutoCString newFileName;
289 rv = imageURL->GetFileBaseName(newFileName);
290 NS_ENSURE_SUCCESS(rv, rv);
291 newFileName.Append(".");
292 newFileName.Append(primaryExtension);
293 CopyUTF8toUTF16(newFileName, targetFilename);
297 // make the filename safe for the filesystem
298 targetFilename.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
299 '-');
300 #endif /* defined(XP_MACOSX) */
302 // get the target directory from the kFilePromiseDirectoryMime
303 // flavor
304 nsCOMPtr<nsISupports> dirPrimitive;
305 rv = aTransferable->GetTransferData(kFilePromiseDirectoryMime,
306 getter_AddRefs(dirPrimitive));
307 NS_ENSURE_SUCCESS(rv, rv);
308 nsCOMPtr<nsIFile> destDirectory = do_QueryInterface(dirPrimitive);
309 if (!destDirectory) return NS_ERROR_FAILURE;
311 nsCOMPtr<nsIFile> file;
312 rv = destDirectory->Clone(getter_AddRefs(file));
313 NS_ENSURE_SUCCESS(rv, rv);
315 file->Append(targetFilename);
317 bool isPrivate = aTransferable->GetIsPrivateData();
319 nsCOMPtr<nsIPrincipal> principal = aTransferable->GetRequestingPrincipal();
320 nsContentPolicyType contentPolicyType =
321 aTransferable->GetContentPolicyType();
322 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
323 aTransferable->GetCookieJarSettings();
324 rv = SaveURIToFile(sourceURI, principal, cookieJarSettings, file,
325 contentPolicyType, isPrivate);
326 // send back an nsIFile
327 if (NS_SUCCEEDED(rv)) {
328 CallQueryInterface(file, aData);
332 return rv;
335 DragDataProducer::DragDataProducer(nsPIDOMWindowOuter* aWindow,
336 nsIContent* aTarget,
337 nsIContent* aSelectionTargetNode,
338 bool aIsAltKeyPressed)
339 : mWindow(aWindow),
340 mTarget(aTarget),
341 mSelectionTargetNode(aSelectionTargetNode),
342 mIsAltKeyPressed(aIsAltKeyPressed),
343 mIsAnchor(false) {}
346 // FindParentLinkNode
348 // Finds the parent with the given link tag starting at |aContent|. If
349 // it gets up to the root without finding it, we stop looking and
350 // return null.
352 already_AddRefed<nsIContent> DragDataProducer::FindParentLinkNode(
353 nsIContent* aContent) {
354 for (nsIContent* content = aContent; content;
355 content = content->GetFlattenedTreeParent()) {
356 if (nsContentUtils::IsDraggableLink(content)) {
357 return do_AddRef(content);
360 return nullptr;
364 // GetAnchorURL
366 nsresult DragDataProducer::GetAnchorURL(nsIContent* inNode, nsAString& outURL) {
367 nsCOMPtr<nsIURI> linkURI;
368 if (!inNode || !inNode->IsLink(getter_AddRefs(linkURI))) {
369 // Not a link
370 outURL.Truncate();
371 return NS_OK;
374 nsAutoCString spec;
375 nsresult rv = linkURI->GetSpec(spec);
376 NS_ENSURE_SUCCESS(rv, rv);
377 CopyUTF8toUTF16(spec, outURL);
378 return NS_OK;
382 // CreateLinkText
384 // Creates the html for an anchor in the form
385 // <a href="inURL">inText</a>
387 void DragDataProducer::CreateLinkText(const nsAString& inURL,
388 const nsAString& inText,
389 nsAString& outLinkText) {
390 // use a temp var in case |inText| is the same string as
391 // |outLinkText| to avoid overwriting it while building up the
392 // string in pieces.
393 nsAutoString linkText(u"<a href=\""_ns + inURL + u"\">"_ns + inText +
394 u"</a>"_ns);
396 outLinkText = linkText;
400 // GetNodeString
402 // Gets the text associated with a node
404 void DragDataProducer::GetNodeString(nsIContent* inNode,
405 nsAString& outNodeString) {
406 nsCOMPtr<nsINode> node = inNode;
408 outNodeString.Truncate();
410 // use a range to get the text-equivalent of the node
411 nsCOMPtr<Document> doc = node->OwnerDoc();
412 RefPtr<nsRange> range = doc->CreateRange(IgnoreErrors());
413 if (range) {
414 range->SelectNode(*node, IgnoreErrors());
415 range->ToString(outNodeString, IgnoreErrors());
419 nsresult DragDataProducer::GetImageData(imgIContainer* aImage,
420 imgIRequest* aRequest) {
421 nsCOMPtr<nsIURI> imgUri;
422 aRequest->GetURI(getter_AddRefs(imgUri));
424 nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
425 if (imgUrl) {
426 nsAutoCString spec;
427 nsresult rv = imgUrl->GetSpec(spec);
428 NS_ENSURE_SUCCESS(rv, rv);
430 // pass out the image source string
431 CopyUTF8toUTF16(spec, mImageSourceString);
433 nsCString mimeType;
434 aRequest->GetMimeType(getter_Copies(mimeType));
436 #if defined(XP_MACOSX)
437 // Save the MIME type so we can make sure the extension
438 // is compatible (and replace it if it isn't) when the
439 // image is dropped. On Mac, we need to get the OS MIME
440 // handler information in the parent due to sandboxing.
441 CopyUTF8toUTF16(mimeType, mImageRequestMime);
442 #else
443 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
444 if (NS_WARN_IF(!mimeService)) {
445 return NS_ERROR_FAILURE;
448 nsCOMPtr<nsIMIMEInfo> mimeInfo;
449 mimeService->GetFromTypeAndExtension(mimeType, ""_ns,
450 getter_AddRefs(mimeInfo));
451 if (mimeInfo) {
452 nsAutoCString extension;
453 imgUrl->GetFileExtension(extension);
455 bool validExtension;
456 if (extension.IsEmpty() ||
457 NS_FAILED(mimeInfo->ExtensionExists(extension, &validExtension)) ||
458 !validExtension) {
459 // Fix the file extension in the URL
460 nsAutoCString primaryExtension;
461 mimeInfo->GetPrimaryExtension(primaryExtension);
462 if (!primaryExtension.IsEmpty()) {
463 rv = NS_MutateURI(imgUrl)
464 .Apply(&nsIURLMutator::SetFileExtension, primaryExtension,
465 nullptr)
466 .Finalize(imgUrl);
467 NS_ENSURE_SUCCESS(rv, rv);
471 #endif /* defined(XP_MACOSX) */
473 nsAutoCString fileName;
474 imgUrl->GetFileName(fileName);
476 NS_UnescapeURL(fileName);
478 #if !defined(XP_MACOSX)
479 // make the filename safe for the filesystem
480 fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
481 #endif
483 CopyUTF8toUTF16(fileName, mImageDestFileName);
485 // and the image object
486 mImage = aImage;
489 return NS_OK;
492 nsresult DragDataProducer::Produce(DataTransfer* aDataTransfer, bool* aCanDrag,
493 Selection** aSelection,
494 nsIContent** aDragNode,
495 nsIPrincipal** aPrincipal,
496 nsIContentSecurityPolicy** aCsp,
497 nsICookieJarSettings** aCookieJarSettings) {
498 MOZ_ASSERT(aCanDrag && aSelection && aDataTransfer && aDragNode,
499 "null pointer passed to Produce");
500 NS_ASSERTION(mWindow, "window not set");
501 NS_ASSERTION(mSelectionTargetNode,
502 "selection target node should have been set");
504 *aDragNode = nullptr;
506 nsresult rv;
507 nsIContent* dragNode = nullptr;
508 *aSelection = nullptr;
510 // Find the selection to see what we could be dragging and if what we're
511 // dragging is in what is selected. If this is an editable textbox, use
512 // the textbox's selection, otherwise use the window's selection.
513 RefPtr<Selection> selection;
514 nsIContent* editingElement = mSelectionTargetNode->IsEditable()
515 ? mSelectionTargetNode->GetEditingHost()
516 : nullptr;
517 RefPtr<TextControlElement> textControlElement =
518 TextControlElement::GetTextControlElementFromEditingHost(editingElement);
519 if (textControlElement) {
520 nsISelectionController* selcon =
521 textControlElement->GetSelectionController();
522 if (selcon) {
523 selection =
524 selcon->GetSelection(nsISelectionController::SELECTION_NORMAL);
527 if (!selection) return NS_OK;
528 } else {
529 selection = mWindow->GetSelection();
530 if (!selection) return NS_OK;
532 // Check if the node is inside a form control. Don't set aCanDrag to false
533 // however, as we still want to allow the drag.
534 nsCOMPtr<nsIContent> findFormNode = mSelectionTargetNode;
535 nsIContent* findFormParent = findFormNode->GetParent();
536 while (findFormParent) {
537 nsCOMPtr<nsIFormControl> form(do_QueryInterface(findFormParent));
538 if (form && !form->AllowDraggableChildren()) {
539 return NS_OK;
541 findFormParent = findFormParent->GetParent();
545 // if set, serialize the content under this node
546 nsCOMPtr<nsIContent> nodeToSerialize;
548 BrowsingContext* bc = mWindow->GetBrowsingContext();
549 const bool isChromeShell = bc && bc->IsChrome();
551 // In chrome shells, only allow dragging inside editable areas.
552 if (isChromeShell && !editingElement) {
553 // This path should already be filtered out in
554 // EventStateManager::DetermineDragTargetAndDefaultData.
555 MOZ_ASSERT_UNREACHABLE("Shouldn't be generating drag data for chrome");
556 return NS_OK;
559 if (isChromeShell && textControlElement) {
560 // Only use the selection if the target node is in the selection.
561 if (!selection->ContainsNode(*mSelectionTargetNode, false, IgnoreErrors()))
562 return NS_OK;
564 selection.swap(*aSelection);
565 } else {
566 // In content shells, a number of checks are made below to determine
567 // whether an image or a link is being dragged. If so, add additional
568 // data to the data transfer. This is also done for chrome shells, but
569 // only when in a non-textbox editor.
571 bool haveSelectedContent = false;
573 // possible parent link node
574 nsCOMPtr<nsIContent> parentLink;
575 nsCOMPtr<nsIContent> draggedNode;
578 // only drag form elements by using the alt key,
579 // otherwise buttons and select widgets are hard to use
581 // Note that while <object> elements implement nsIFormControl, we should
582 // really allow dragging them if they happen to be images.
583 nsCOMPtr<nsIFormControl> form(do_QueryInterface(mTarget));
584 if (form && !mIsAltKeyPressed &&
585 form->ControlType() != FormControlType::Object) {
586 *aCanDrag = false;
587 return NS_OK;
590 draggedNode = mTarget;
593 nsCOMPtr<nsIImageLoadingContent> image;
595 nsCOMPtr<nsIContent> selectedImageOrLinkNode;
596 GetDraggableSelectionData(selection, mSelectionTargetNode,
597 getter_AddRefs(selectedImageOrLinkNode),
598 &haveSelectedContent);
600 // either plain text or anchor text is selected
601 if (haveSelectedContent) {
602 selection.swap(*aSelection);
603 } else if (selectedImageOrLinkNode) {
604 // an image is selected
605 image = do_QueryInterface(selectedImageOrLinkNode);
606 } else {
607 // nothing is selected -
609 // look for draggable elements under the mouse
611 // if the alt key is down, don't start a drag if we're in an
612 // anchor because we want to do selection.
613 parentLink = FindParentLinkNode(draggedNode);
614 if (parentLink && mIsAltKeyPressed) {
615 *aCanDrag = false;
616 return NS_OK;
618 image = do_QueryInterface(draggedNode);
622 // set for linked images, and links
623 nsCOMPtr<nsIContent> linkNode;
625 RefPtr<HTMLAreaElement> areaElem =
626 HTMLAreaElement::FromNodeOrNull(draggedNode);
627 if (areaElem) {
628 // use the alt text (or, if missing, the href) as the title
629 areaElem->GetAttr(nsGkAtoms::alt, mTitleString);
630 if (mTitleString.IsEmpty()) {
631 // this can be a relative link
632 areaElem->GetAttr(nsGkAtoms::href, mTitleString);
635 // we'll generate HTML like <a href="absurl">alt text</a>
636 mIsAnchor = true;
638 // gives an absolute link
639 nsresult rv = GetAnchorURL(draggedNode, mUrlString);
640 NS_ENSURE_SUCCESS(rv, rv);
642 mHtmlString.AssignLiteral("<a href=\"");
643 mHtmlString.Append(mUrlString);
644 mHtmlString.AppendLiteral("\">");
645 mHtmlString.Append(mTitleString);
646 mHtmlString.AppendLiteral("</a>");
648 dragNode = draggedNode;
649 } else if (image) {
650 mIsAnchor = true;
651 // grab the href as the url, use alt text as the title of the
652 // area if it's there. the drag data is the image tag and src
653 // attribute.
654 nsCOMPtr<nsIURI> imageURI;
655 image->GetCurrentURI(getter_AddRefs(imageURI));
656 if (imageURI) {
657 nsAutoCString spec;
658 rv = imageURI->GetSpec(spec);
659 NS_ENSURE_SUCCESS(rv, rv);
660 CopyUTF8toUTF16(spec, mUrlString);
663 nsCOMPtr<Element> imageElement(do_QueryInterface(image));
664 // XXXbz Shouldn't we use the "title" attr for title? Using
665 // "alt" seems very wrong....
666 // XXXbz Also, what if this is an nsIImageLoadingContent
667 // that's not an <html:img>?
668 if (imageElement) {
669 imageElement->GetAttr(nsGkAtoms::alt, mTitleString);
672 if (mTitleString.IsEmpty()) {
673 mTitleString = mUrlString;
676 nsCOMPtr<imgIRequest> imgRequest;
678 // grab the image data, and its request.
679 nsCOMPtr<imgIContainer> img = nsContentUtils::GetImageFromContent(
680 image, getter_AddRefs(imgRequest));
681 if (imgRequest) {
682 rv = GetImageData(img, imgRequest);
683 NS_ENSURE_SUCCESS(rv, rv);
686 if (parentLink) {
687 // If we are dragging around an image in an anchor, then we
688 // are dragging the entire anchor
689 linkNode = parentLink;
690 nodeToSerialize = linkNode;
691 } else {
692 nodeToSerialize = draggedNode;
694 dragNode = nodeToSerialize;
695 } else if (draggedNode && draggedNode->IsHTMLElement(nsGkAtoms::a)) {
696 // set linkNode. The code below will handle this
697 linkNode = draggedNode; // XXX test this
698 GetNodeString(draggedNode, mTitleString);
699 } else if (parentLink) {
700 // parentLink will always be null if there's selected content
701 linkNode = parentLink;
702 nodeToSerialize = linkNode;
703 } else if (!haveSelectedContent) {
704 // nothing draggable
705 return NS_OK;
708 if (linkNode) {
709 mIsAnchor = true;
710 rv = GetAnchorURL(linkNode, mUrlString);
711 NS_ENSURE_SUCCESS(rv, rv);
712 dragNode = linkNode;
717 if (nodeToSerialize || *aSelection) {
718 mHtmlString.Truncate();
719 mContextString.Truncate();
720 mInfoString.Truncate();
721 mTitleString.Truncate();
723 nsCOMPtr<Document> doc = mWindow->GetDoc();
724 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
726 nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp();
727 if (csp) {
728 NS_IF_ADDREF(*aCsp = csp);
731 nsCOMPtr<nsICookieJarSettings> cookieJarSettings = doc->CookieJarSettings();
732 if (cookieJarSettings) {
733 NS_IF_ADDREF(*aCookieJarSettings = cookieJarSettings);
736 // if we have selected text, use it in preference to the node
737 nsCOMPtr<nsITransferable> transferable;
738 if (*aSelection) {
739 rv = nsCopySupport::GetTransferableForSelection(
740 *aSelection, doc, getter_AddRefs(transferable));
741 } else {
742 rv = nsCopySupport::GetTransferableForNode(nodeToSerialize, doc,
743 getter_AddRefs(transferable));
745 NS_ENSURE_SUCCESS(rv, rv);
747 nsCOMPtr<nsISupports> supports;
748 nsCOMPtr<nsISupportsString> data;
749 rv = transferable->GetTransferData(kHTMLMime, getter_AddRefs(supports));
750 data = do_QueryInterface(supports);
751 if (NS_SUCCEEDED(rv)) {
752 data->GetData(mHtmlString);
754 rv = transferable->GetTransferData(kHTMLContext, getter_AddRefs(supports));
755 data = do_QueryInterface(supports);
756 if (NS_SUCCEEDED(rv)) {
757 data->GetData(mContextString);
759 rv = transferable->GetTransferData(kHTMLInfo, getter_AddRefs(supports));
760 data = do_QueryInterface(supports);
761 if (NS_SUCCEEDED(rv)) {
762 data->GetData(mInfoString);
764 rv = transferable->GetTransferData(kUnicodeMime, getter_AddRefs(supports));
765 data = do_QueryInterface(supports);
766 NS_ENSURE_SUCCESS(rv, rv); // require plain text at a minimum
767 data->GetData(mTitleString);
770 // default text value is the URL
771 if (mTitleString.IsEmpty()) {
772 mTitleString = mUrlString;
775 // if we haven't constructed a html version, make one now
776 if (mHtmlString.IsEmpty() && !mUrlString.IsEmpty())
777 CreateLinkText(mUrlString, mTitleString, mHtmlString);
779 // if there is no drag node, which will be the case for a selection, just
780 // use the selection target node.
781 rv = AddStringsToDataTransfer(
782 dragNode ? dragNode : mSelectionTargetNode.get(), aDataTransfer);
783 NS_ENSURE_SUCCESS(rv, rv);
785 NS_IF_ADDREF(*aDragNode = dragNode);
786 return NS_OK;
789 void DragDataProducer::AddString(DataTransfer* aDataTransfer,
790 const nsAString& aFlavor,
791 const nsAString& aData,
792 nsIPrincipal* aPrincipal, bool aHidden) {
793 RefPtr<nsVariantCC> variant = new nsVariantCC();
794 variant->SetAsAString(aData);
795 aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal, aHidden);
798 nsresult DragDataProducer::AddStringsToDataTransfer(
799 nsIContent* aDragNode, DataTransfer* aDataTransfer) {
800 NS_ASSERTION(aDragNode, "adding strings for null node");
802 // set all of the data to have the principal of the node where the data came
803 // from
804 nsIPrincipal* principal = aDragNode->NodePrincipal();
806 // add a special flavor if we're an anchor to indicate that we have
807 // a URL in the drag data
808 if (!mUrlString.IsEmpty() && mIsAnchor) {
809 nsAutoString dragData(mUrlString);
810 dragData.Append('\n');
811 // Remove leading and trailing newlines in the title and replace them with
812 // space in remaining positions - they confuse PlacesUtils::unwrapNodes
813 // that expects url\ntitle formatted data for x-moz-url.
814 nsAutoString title(mTitleString);
815 title.Trim("\r\n");
816 title.ReplaceChar("\r\n", ' ');
817 dragData += title;
819 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLMime), dragData,
820 principal);
821 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLDataMime),
822 mUrlString, principal);
823 AddString(aDataTransfer,
824 NS_LITERAL_STRING_FROM_CSTRING(kURLDescriptionMime), mTitleString,
825 principal);
826 AddString(aDataTransfer, u"text/uri-list"_ns, mUrlString, principal);
829 // add a special flavor for the html context data
830 if (!mContextString.IsEmpty())
831 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
832 mContextString, principal);
834 // add a special flavor if we have html info data
835 if (!mInfoString.IsEmpty())
836 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
837 mInfoString, principal);
839 // add the full html
840 if (!mHtmlString.IsEmpty())
841 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime),
842 mHtmlString, principal);
844 // add the plain text. we use the url for text/plain data if an anchor is
845 // being dragged, rather than the title text of the link or the alt text for
846 // an anchor image.
847 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kTextMime),
848 mIsAnchor ? mUrlString : mTitleString, principal);
850 // add image data, if present. For now, all we're going to do with
851 // this is turn it into a native data flavor, so indicate that with
852 // a new flavor so as not to confuse anyone who is really registered
853 // for image/gif or image/jpg.
854 if (mImage) {
855 RefPtr<nsVariantCC> variant = new nsVariantCC();
856 variant->SetAsISupports(mImage);
857 aDataTransfer->SetDataWithPrincipal(
858 NS_LITERAL_STRING_FROM_CSTRING(kNativeImageMime), variant, 0,
859 principal);
861 // assume the image comes from a file, and add a file promise. We
862 // register ourselves as a nsIFlavorDataProvider, and will use the
863 // GetFlavorData callback to save the image to disk.
865 nsCOMPtr<nsIFlavorDataProvider> dataProvider =
866 new nsContentAreaDragDropDataProvider();
867 if (dataProvider) {
868 RefPtr<nsVariantCC> variant = new nsVariantCC();
869 variant->SetAsISupports(dataProvider);
870 aDataTransfer->SetDataWithPrincipal(
871 NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseMime), variant, 0,
872 principal);
875 AddString(aDataTransfer,
876 NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseURLMime),
877 mImageSourceString, principal);
878 AddString(aDataTransfer,
879 NS_LITERAL_STRING_FROM_CSTRING(kFilePromiseDestFilename),
880 mImageDestFileName, principal);
881 #if defined(XP_MACOSX)
882 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kImageRequestMime),
883 mImageRequestMime, principal, /* aHidden= */ true);
884 #endif
886 // if not an anchor, add the image url
887 if (!mIsAnchor) {
888 AddString(aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kURLDataMime),
889 mUrlString, principal);
890 AddString(aDataTransfer, u"text/uri-list"_ns, mUrlString, principal);
894 return NS_OK;
897 // note that this can return NS_OK, but a null out param (by design)
898 // static
899 nsresult DragDataProducer::GetDraggableSelectionData(
900 Selection* inSelection, nsIContent* inRealTargetNode,
901 nsIContent** outImageOrLinkNode, bool* outDragSelectedText) {
902 NS_ENSURE_ARG(inSelection);
903 NS_ENSURE_ARG(inRealTargetNode);
904 NS_ENSURE_ARG_POINTER(outImageOrLinkNode);
906 *outImageOrLinkNode = nullptr;
907 *outDragSelectedText = false;
909 if (!inSelection->IsCollapsed()) {
910 if (inSelection->ContainsNode(*inRealTargetNode, false, IgnoreErrors())) {
911 // track down the anchor node, if any, for the url
912 nsINode* selectionStart = inSelection->GetAnchorNode();
913 nsINode* selectionEnd = inSelection->GetFocusNode();
915 // look for a selection around a single node, like an image.
916 // in this case, drag the image, rather than a serialization of the HTML
917 // XXX generalize this to other draggable element types?
918 if (selectionStart == selectionEnd) {
919 nsCOMPtr<nsIContent> selStartContent =
920 nsIContent::FromNodeOrNull(selectionStart);
921 if (selStartContent && selStartContent->HasChildNodes()) {
922 // see if just one node is selected
923 uint32_t anchorOffset = inSelection->AnchorOffset();
924 uint32_t focusOffset = inSelection->FocusOffset();
925 if (anchorOffset == focusOffset + 1 ||
926 focusOffset == anchorOffset + 1) {
927 uint32_t childOffset = std::min(anchorOffset, focusOffset);
928 nsIContent* childContent =
929 selStartContent->GetChildAt_Deprecated(childOffset);
930 // if we find an image, we'll fall into the node-dragging code,
931 // rather the the selection-dragging code
932 if (nsContentUtils::IsDraggableImage(childContent)) {
933 NS_ADDREF(*outImageOrLinkNode = childContent);
934 return NS_OK;
940 // indicate that a link or text is selected
941 *outDragSelectedText = true;
945 return NS_OK;