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