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