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