Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsReadableUtils.h"
8 // Local Includes
9 #include "nsContentAreaDragDrop.h"
11 // Helper Classes
12 #include "nsString.h"
14 // Interfaces needed to be included
15 #include "nsCopySupport.h"
16 #include "nsIDOMUIEvent.h"
17 #include "nsISelection.h"
18 #include "nsISelectionController.h"
19 #include "nsIDOMNode.h"
20 #include "nsIDOMNodeList.h"
21 #include "nsIDOMEvent.h"
22 #include "nsIDOMDragEvent.h"
23 #include "nsPIDOMWindow.h"
24 #include "nsIDOMRange.h"
25 #include "nsIFormControl.h"
26 #include "nsIDOMHTMLAreaElement.h"
27 #include "nsIDOMHTMLAnchorElement.h"
28 #include "nsITransferable.h"
29 #include "nsComponentManagerUtils.h"
30 #include "nsXPCOM.h"
31 #include "nsISupportsPrimitives.h"
32 #include "nsServiceManagerUtils.h"
33 #include "nsNetUtil.h"
34 #include "nsIFile.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 "nsIDocument.h"
43 #include "nsIScriptSecurityManager.h"
44 #include "nsIPrincipal.h"
45 #include "nsIDocShellTreeItem.h"
46 #include "nsIWebBrowserPersist.h"
47 #include "nsEscape.h"
48 #include "nsContentUtils.h"
49 #include "nsIMIMEService.h"
50 #include "imgIContainer.h"
51 #include "imgIRequest.h"
52 #include "mozilla/dom/DataTransfer.h"
53 #include "nsIMIMEInfo.h"
54 #include "nsRange.h"
55 #include "mozilla/dom/Element.h"
56 #include "mozilla/dom/HTMLAreaElement.h"
58 using namespace mozilla::dom;
60 class MOZ_STACK_CLASS DragDataProducer
61 {
62 public:
63 DragDataProducer(nsPIDOMWindow* aWindow,
64 nsIContent* aTarget,
65 nsIContent* aSelectionTargetNode,
66 bool aIsAltKeyPressed);
67 nsresult Produce(DataTransfer* aDataTransfer,
68 bool* aCanDrag,
69 nsISelection** aSelection,
70 nsIContent** aDragNode);
72 private:
73 void AddString(DataTransfer* aDataTransfer,
74 const nsAString& aFlavor,
75 const nsAString& aData,
76 nsIPrincipal* aPrincipal);
77 nsresult AddStringsToDataTransfer(nsIContent* aDragNode,
78 DataTransfer* aDataTransfer);
79 static nsresult GetDraggableSelectionData(nsISelection* inSelection,
80 nsIContent* inRealTargetNode,
81 nsIContent **outImageOrLinkNode,
82 bool* outDragSelectedText);
83 static already_AddRefed<nsIContent> FindParentLinkNode(nsIContent* inNode);
84 static void GetAnchorURL(nsIContent* inNode, nsAString& outURL);
85 static void GetNodeString(nsIContent* inNode, nsAString & outNodeString);
86 static void CreateLinkText(const nsAString& inURL, const nsAString & inText,
87 nsAString& outLinkText);
88 static void GetSelectedLink(nsISelection* inSelection,
89 nsIContent **outLinkNode);
91 nsCOMPtr<nsPIDOMWindow> mWindow;
92 nsCOMPtr<nsIContent> mTarget;
93 nsCOMPtr<nsIContent> mSelectionTargetNode;
94 bool mIsAltKeyPressed;
96 nsString mUrlString;
97 nsString mImageSourceString;
98 nsString mImageDestFileName;
99 nsString mTitleString;
100 // will be filled automatically if you fill urlstring
101 nsString mHtmlString;
102 nsString mContextString;
103 nsString mInfoString;
105 bool mIsAnchor;
106 nsCOMPtr<imgIContainer> mImage;
107 };
110 nsresult
111 nsContentAreaDragDrop::GetDragData(nsPIDOMWindow* aWindow,
112 nsIContent* aTarget,
113 nsIContent* aSelectionTargetNode,
114 bool aIsAltKeyPressed,
115 DataTransfer* aDataTransfer,
116 bool* aCanDrag,
117 nsISelection** aSelection,
118 nsIContent** aDragNode)
119 {
120 NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG);
122 *aCanDrag = true;
124 DragDataProducer
125 provider(aWindow, aTarget, aSelectionTargetNode, aIsAltKeyPressed);
126 return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode);
127 }
130 NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider)
132 // SaveURIToFile
133 // used on platforms where it's possible to drag items (e.g. images)
134 // into the file system
135 nsresult
136 nsContentAreaDragDropDataProvider::SaveURIToFile(nsAString& inSourceURIString,
137 nsIFile* inDestFile,
138 bool isPrivate)
139 {
140 nsCOMPtr<nsIURI> sourceURI;
141 nsresult rv = NS_NewURI(getter_AddRefs(sourceURI), inSourceURIString);
142 if (NS_FAILED(rv)) {
143 return NS_ERROR_FAILURE;
144 }
146 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
147 if (!sourceURL) {
148 return NS_ERROR_NO_INTERFACE;
149 }
151 rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
152 NS_ENSURE_SUCCESS(rv, rv);
154 // we rely on the fact that the WPB is refcounted by the channel etc,
155 // so we don't keep a ref to it. It will die when finished.
156 nsCOMPtr<nsIWebBrowserPersist> persist =
157 do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1",
158 &rv);
159 NS_ENSURE_SUCCESS(rv, rv);
161 persist->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
163 return persist->SavePrivacyAwareURI(sourceURI, nullptr, nullptr, nullptr, nullptr,
164 inDestFile, isPrivate);
165 }
167 // This is our nsIFlavorDataProvider callback. There are several
168 // assumptions here that make this work:
169 //
170 // 1. Someone put a kFilePromiseURLMime flavor into the transferable
171 // with the source URI of the file to save (as a string). We did
172 // that in AddStringsToDataTransfer.
173 //
174 // 2. Someone put a kFilePromiseDirectoryMime flavor into the
175 // transferable with an nsIFile for the directory we are to
176 // save in. That has to be done by platform-specific code (in
177 // widget), which gets the destination directory from
178 // OS-specific drag information.
179 //
180 NS_IMETHODIMP
181 nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable *aTransferable,
182 const char *aFlavor,
183 nsISupports **aData,
184 uint32_t *aDataLen)
185 {
186 NS_ENSURE_ARG_POINTER(aData && aDataLen);
187 *aData = nullptr;
188 *aDataLen = 0;
190 nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
192 if (strcmp(aFlavor, kFilePromiseMime) == 0) {
193 // get the URI from the kFilePromiseURLMime flavor
194 NS_ENSURE_ARG(aTransferable);
195 nsCOMPtr<nsISupports> tmp;
196 uint32_t dataSize = 0;
197 aTransferable->GetTransferData(kFilePromiseURLMime,
198 getter_AddRefs(tmp), &dataSize);
199 nsCOMPtr<nsISupportsString> supportsString =
200 do_QueryInterface(tmp);
201 if (!supportsString)
202 return NS_ERROR_FAILURE;
204 nsAutoString sourceURLString;
205 supportsString->GetData(sourceURLString);
206 if (sourceURLString.IsEmpty())
207 return NS_ERROR_FAILURE;
209 aTransferable->GetTransferData(kFilePromiseDestFilename,
210 getter_AddRefs(tmp), &dataSize);
211 supportsString = do_QueryInterface(tmp);
212 if (!supportsString)
213 return NS_ERROR_FAILURE;
215 nsAutoString targetFilename;
216 supportsString->GetData(targetFilename);
217 if (targetFilename.IsEmpty())
218 return NS_ERROR_FAILURE;
220 // get the target directory from the kFilePromiseDirectoryMime
221 // flavor
222 nsCOMPtr<nsISupports> dirPrimitive;
223 dataSize = 0;
224 aTransferable->GetTransferData(kFilePromiseDirectoryMime,
225 getter_AddRefs(dirPrimitive), &dataSize);
226 nsCOMPtr<nsIFile> destDirectory = do_QueryInterface(dirPrimitive);
227 if (!destDirectory)
228 return NS_ERROR_FAILURE;
230 nsCOMPtr<nsIFile> file;
231 rv = destDirectory->Clone(getter_AddRefs(file));
232 NS_ENSURE_SUCCESS(rv, rv);
234 file->Append(targetFilename);
236 bool isPrivate;
237 aTransferable->GetIsPrivateData(&isPrivate);
239 rv = SaveURIToFile(sourceURLString, file, isPrivate);
240 // send back an nsIFile
241 if (NS_SUCCEEDED(rv)) {
242 CallQueryInterface(file, aData);
243 *aDataLen = sizeof(nsIFile*);
244 }
245 }
247 return rv;
248 }
250 DragDataProducer::DragDataProducer(nsPIDOMWindow* aWindow,
251 nsIContent* aTarget,
252 nsIContent* aSelectionTargetNode,
253 bool aIsAltKeyPressed)
254 : mWindow(aWindow),
255 mTarget(aTarget),
256 mSelectionTargetNode(aSelectionTargetNode),
257 mIsAltKeyPressed(aIsAltKeyPressed),
258 mIsAnchor(false)
259 {
260 }
263 //
264 // FindParentLinkNode
265 //
266 // Finds the parent with the given link tag starting at |inNode|. If
267 // it gets up to the root without finding it, we stop looking and
268 // return null.
269 //
270 already_AddRefed<nsIContent>
271 DragDataProducer::FindParentLinkNode(nsIContent* inNode)
272 {
273 nsIContent* content = inNode;
274 if (!content) {
275 // That must have been the document node; nothing else to do here;
276 return nullptr;
277 }
279 for (; content; content = content->GetParent()) {
280 if (nsContentUtils::IsDraggableLink(content)) {
281 nsCOMPtr<nsIContent> ret = content;
282 return ret.forget();
283 }
284 }
286 return nullptr;
287 }
290 //
291 // GetAnchorURL
292 //
293 void
294 DragDataProducer::GetAnchorURL(nsIContent* inNode, nsAString& outURL)
295 {
296 nsCOMPtr<nsIURI> linkURI;
297 if (!inNode || !inNode->IsLink(getter_AddRefs(linkURI))) {
298 // Not a link
299 outURL.Truncate();
300 return;
301 }
303 nsAutoCString spec;
304 linkURI->GetSpec(spec);
305 CopyUTF8toUTF16(spec, outURL);
306 }
309 //
310 // CreateLinkText
311 //
312 // Creates the html for an anchor in the form
313 // <a href="inURL">inText</a>
314 //
315 void
316 DragDataProducer::CreateLinkText(const nsAString& inURL,
317 const nsAString & inText,
318 nsAString& outLinkText)
319 {
320 // use a temp var in case |inText| is the same string as
321 // |outLinkText| to avoid overwriting it while building up the
322 // string in pieces.
323 nsAutoString linkText(NS_LITERAL_STRING("<a href=\"") +
324 inURL +
325 NS_LITERAL_STRING("\">") +
326 inText +
327 NS_LITERAL_STRING("</a>") );
329 outLinkText = linkText;
330 }
333 //
334 // GetNodeString
335 //
336 // Gets the text associated with a node
337 //
338 void
339 DragDataProducer::GetNodeString(nsIContent* inNode,
340 nsAString & outNodeString)
341 {
342 nsCOMPtr<nsINode> node = inNode;
344 outNodeString.Truncate();
346 // use a range to get the text-equivalent of the node
347 nsCOMPtr<nsIDocument> doc = node->OwnerDoc();
348 mozilla::ErrorResult rv;
349 nsRefPtr<nsRange> range = doc->CreateRange(rv);
350 if (range) {
351 range->SelectNode(*node, rv);
352 range->ToString(outNodeString);
353 }
354 }
356 nsresult
357 DragDataProducer::Produce(DataTransfer* aDataTransfer,
358 bool* aCanDrag,
359 nsISelection** aSelection,
360 nsIContent** aDragNode)
361 {
362 NS_PRECONDITION(aCanDrag && aSelection && aDataTransfer && aDragNode,
363 "null pointer passed to Produce");
364 NS_ASSERTION(mWindow, "window not set");
365 NS_ASSERTION(mSelectionTargetNode, "selection target node should have been set");
367 *aDragNode = nullptr;
369 nsresult rv;
370 nsIContent* dragNode = nullptr;
371 *aSelection = nullptr;
373 // Find the selection to see what we could be dragging and if what we're
374 // dragging is in what is selected. If this is an editable textbox, use
375 // the textbox's selection, otherwise use the window's selection.
376 nsCOMPtr<nsISelection> selection;
377 nsIContent* editingElement = mSelectionTargetNode->IsEditable() ?
378 mSelectionTargetNode->GetEditingHost() : nullptr;
379 nsCOMPtr<nsITextControlElement> textControl =
380 nsITextControlElement::GetTextControlElementFromEditingHost(editingElement);
381 if (textControl) {
382 nsISelectionController* selcon = textControl->GetSelectionController();
383 if (selcon) {
384 selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
385 }
387 if (!selection)
388 return NS_OK;
389 }
390 else {
391 mWindow->GetSelection(getter_AddRefs(selection));
392 if (!selection)
393 return NS_OK;
395 // Check if the node is inside a form control. Don't set aCanDrag to false
396 //however, as we still want to allow the drag.
397 nsCOMPtr<nsIContent> findFormNode = mSelectionTargetNode;
398 nsIContent* findFormParent = findFormNode->GetParent();
399 while (findFormParent) {
400 nsCOMPtr<nsIFormControl> form(do_QueryInterface(findFormParent));
401 if (form && !form->AllowDraggableChildren()) {
402 return NS_OK;
403 }
404 findFormParent = findFormParent->GetParent();
405 }
406 }
408 // if set, serialize the content under this node
409 nsCOMPtr<nsIContent> nodeToSerialize;
411 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(mWindow);
412 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
413 const bool isChromeShell =
414 dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome;
416 // In chrome shells, only allow dragging inside editable areas.
417 if (isChromeShell && !editingElement)
418 return NS_OK;
420 if (isChromeShell && textControl) {
421 // Only use the selection if the target node is in the selection.
422 bool selectionContainsTarget = false;
423 nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(mSelectionTargetNode);
424 selection->ContainsNode(targetNode, false, &selectionContainsTarget);
425 if (!selectionContainsTarget)
426 return NS_OK;
428 selection.swap(*aSelection);
429 }
430 else {
431 // In content shells, a number of checks are made below to determine
432 // whether an image or a link is being dragged. If so, add additional
433 // data to the data transfer. This is also done for chrome shells, but
434 // only when in a non-textbox editor.
436 bool haveSelectedContent = false;
438 // possible parent link node
439 nsCOMPtr<nsIContent> parentLink;
440 nsCOMPtr<nsIContent> draggedNode;
442 {
443 // only drag form elements by using the alt key,
444 // otherwise buttons and select widgets are hard to use
446 // Note that while <object> elements implement nsIFormControl, we should
447 // really allow dragging them if they happen to be images.
448 nsCOMPtr<nsIFormControl> form(do_QueryInterface(mTarget));
449 if (form && !mIsAltKeyPressed && form->GetType() != NS_FORM_OBJECT) {
450 *aCanDrag = false;
451 return NS_OK;
452 }
454 draggedNode = mTarget;
455 }
457 nsCOMPtr<nsIDOMHTMLAreaElement> area; // client-side image map
458 nsCOMPtr<nsIImageLoadingContent> image;
459 nsCOMPtr<nsIDOMHTMLAnchorElement> link;
461 nsCOMPtr<nsIContent> selectedImageOrLinkNode;
462 GetDraggableSelectionData(selection, mSelectionTargetNode,
463 getter_AddRefs(selectedImageOrLinkNode),
464 &haveSelectedContent);
466 // either plain text or anchor text is selected
467 if (haveSelectedContent) {
468 link = do_QueryInterface(selectedImageOrLinkNode);
469 if (link && mIsAltKeyPressed) {
470 // if alt is pressed, select the link text instead of drag the link
471 *aCanDrag = false;
472 return NS_OK;
473 }
475 selection.swap(*aSelection);
476 } else if (selectedImageOrLinkNode) {
477 // an image is selected
478 image = do_QueryInterface(selectedImageOrLinkNode);
479 } else {
480 // nothing is selected -
481 //
482 // look for draggable elements under the mouse
483 //
484 // if the alt key is down, don't start a drag if we're in an
485 // anchor because we want to do selection.
486 parentLink = FindParentLinkNode(draggedNode);
487 if (parentLink && mIsAltKeyPressed) {
488 *aCanDrag = false;
489 return NS_OK;
490 }
492 area = do_QueryInterface(draggedNode);
493 image = do_QueryInterface(draggedNode);
494 link = do_QueryInterface(draggedNode);
495 }
497 {
498 // set for linked images, and links
499 nsCOMPtr<nsIContent> linkNode;
501 if (area) {
502 // use the alt text (or, if missing, the href) as the title
503 HTMLAreaElement* areaElem = static_cast<HTMLAreaElement*>(area.get());
504 areaElem->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString);
505 if (mTitleString.IsEmpty()) {
506 // this can be a relative link
507 areaElem->GetAttribute(NS_LITERAL_STRING("href"), mTitleString);
508 }
510 // we'll generate HTML like <a href="absurl">alt text</a>
511 mIsAnchor = true;
513 // gives an absolute link
514 GetAnchorURL(draggedNode, mUrlString);
516 mHtmlString.AssignLiteral("<a href=\"");
517 mHtmlString.Append(mUrlString);
518 mHtmlString.AppendLiteral("\">");
519 mHtmlString.Append(mTitleString);
520 mHtmlString.AppendLiteral("</a>");
522 dragNode = draggedNode;
523 } else if (image) {
524 mIsAnchor = true;
525 // grab the href as the url, use alt text as the title of the
526 // area if it's there. the drag data is the image tag and src
527 // attribute.
528 nsCOMPtr<nsIURI> imageURI;
529 image->GetCurrentURI(getter_AddRefs(imageURI));
530 if (imageURI) {
531 nsAutoCString spec;
532 imageURI->GetSpec(spec);
533 CopyUTF8toUTF16(spec, mUrlString);
534 }
536 nsCOMPtr<nsIDOMElement> imageElement(do_QueryInterface(image));
537 // XXXbz Shouldn't we use the "title" attr for title? Using
538 // "alt" seems very wrong....
539 if (imageElement) {
540 imageElement->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString);
541 }
543 if (mTitleString.IsEmpty()) {
544 mTitleString = mUrlString;
545 }
547 nsCOMPtr<imgIRequest> imgRequest;
549 // grab the image data, and its request.
550 nsCOMPtr<imgIContainer> img =
551 nsContentUtils::GetImageFromContent(image,
552 getter_AddRefs(imgRequest));
554 nsCOMPtr<nsIMIMEService> mimeService =
555 do_GetService("@mozilla.org/mime;1");
557 // Fix the file extension in the URL if necessary
558 if (imgRequest && mimeService) {
559 nsCOMPtr<nsIURI> imgUri;
560 imgRequest->GetURI(getter_AddRefs(imgUri));
562 nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
564 if (imgUrl) {
565 nsAutoCString extension;
566 imgUrl->GetFileExtension(extension);
568 nsXPIDLCString mimeType;
569 imgRequest->GetMimeType(getter_Copies(mimeType));
571 nsCOMPtr<nsIMIMEInfo> mimeInfo;
572 mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
573 getter_AddRefs(mimeInfo));
575 if (mimeInfo) {
576 nsAutoCString spec;
577 imgUrl->GetSpec(spec);
579 // pass out the image source string
580 CopyUTF8toUTF16(spec, mImageSourceString);
582 bool validExtension;
583 if (extension.IsEmpty() ||
584 NS_FAILED(mimeInfo->ExtensionExists(extension,
585 &validExtension)) ||
586 !validExtension) {
587 // Fix the file extension in the URL
588 nsresult rv = imgUrl->Clone(getter_AddRefs(imgUri));
589 NS_ENSURE_SUCCESS(rv, rv);
591 imgUrl = do_QueryInterface(imgUri);
593 nsAutoCString primaryExtension;
594 mimeInfo->GetPrimaryExtension(primaryExtension);
596 imgUrl->SetFileExtension(primaryExtension);
597 }
599 nsAutoCString fileName;
600 imgUrl->GetFileName(fileName);
602 NS_UnescapeURL(fileName);
604 // make the filename safe for the filesystem
605 fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
606 '-');
608 CopyUTF8toUTF16(fileName, mImageDestFileName);
610 // and the image object
611 mImage = img;
612 }
613 }
614 }
616 if (parentLink) {
617 // If we are dragging around an image in an anchor, then we
618 // are dragging the entire anchor
619 linkNode = parentLink;
620 nodeToSerialize = linkNode;
621 } else {
622 nodeToSerialize = do_QueryInterface(draggedNode);
623 }
624 dragNode = nodeToSerialize;
625 } else if (link) {
626 // set linkNode. The code below will handle this
627 linkNode = do_QueryInterface(link); // XXX test this
628 GetNodeString(draggedNode, mTitleString);
629 } else if (parentLink) {
630 // parentLink will always be null if there's selected content
631 linkNode = parentLink;
632 nodeToSerialize = linkNode;
633 } else if (!haveSelectedContent) {
634 // nothing draggable
635 return NS_OK;
636 }
638 if (linkNode) {
639 mIsAnchor = true;
640 GetAnchorURL(linkNode, mUrlString);
641 dragNode = linkNode;
642 }
643 }
644 }
646 if (nodeToSerialize || *aSelection) {
647 mHtmlString.Truncate();
648 mContextString.Truncate();
649 mInfoString.Truncate();
650 mTitleString.Truncate();
652 nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
653 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
655 // if we have selected text, use it in preference to the node
656 nsCOMPtr<nsITransferable> transferable;
657 if (*aSelection) {
658 rv = nsCopySupport::GetTransferableForSelection(*aSelection, doc,
659 getter_AddRefs(transferable));
660 }
661 else {
662 rv = nsCopySupport::GetTransferableForNode(nodeToSerialize, doc,
663 getter_AddRefs(transferable));
664 }
665 NS_ENSURE_SUCCESS(rv, rv);
667 nsCOMPtr<nsISupports> supports;
668 nsCOMPtr<nsISupportsString> data;
669 uint32_t dataSize;
670 rv = transferable->GetTransferData(kHTMLMime, getter_AddRefs(supports),
671 &dataSize);
672 data = do_QueryInterface(supports);
673 if (NS_SUCCEEDED(rv)) {
674 data->GetData(mHtmlString);
675 }
676 rv = transferable->GetTransferData(kHTMLContext, getter_AddRefs(supports),
677 &dataSize);
678 data = do_QueryInterface(supports);
679 if (NS_SUCCEEDED(rv)) {
680 data->GetData(mContextString);
681 }
682 rv = transferable->GetTransferData(kHTMLInfo, getter_AddRefs(supports),
683 &dataSize);
684 data = do_QueryInterface(supports);
685 if (NS_SUCCEEDED(rv)) {
686 data->GetData(mInfoString);
687 }
688 rv = transferable->GetTransferData(kUnicodeMime, getter_AddRefs(supports),
689 &dataSize);
690 data = do_QueryInterface(supports);
691 NS_ENSURE_SUCCESS(rv, rv); // require plain text at a minimum
692 data->GetData(mTitleString);
693 }
695 // default text value is the URL
696 if (mTitleString.IsEmpty()) {
697 mTitleString = mUrlString;
698 }
700 // if we haven't constructed a html version, make one now
701 if (mHtmlString.IsEmpty() && !mUrlString.IsEmpty())
702 CreateLinkText(mUrlString, mTitleString, mHtmlString);
704 // if there is no drag node, which will be the case for a selection, just
705 // use the selection target node.
706 rv = AddStringsToDataTransfer(
707 dragNode ? dragNode : mSelectionTargetNode.get(), aDataTransfer);
708 NS_ENSURE_SUCCESS(rv, rv);
710 NS_IF_ADDREF(*aDragNode = dragNode);
711 return NS_OK;
712 }
714 void
715 DragDataProducer::AddString(DataTransfer* aDataTransfer,
716 const nsAString& aFlavor,
717 const nsAString& aData,
718 nsIPrincipal* aPrincipal)
719 {
720 nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
721 if (variant) {
722 variant->SetAsAString(aData);
723 aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal);
724 }
725 }
727 nsresult
728 DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode,
729 DataTransfer* aDataTransfer)
730 {
731 NS_ASSERTION(aDragNode, "adding strings for null node");
733 // set all of the data to have the principal of the node where the data came from
734 nsIPrincipal* principal = aDragNode->NodePrincipal();
736 // add a special flavor if we're an anchor to indicate that we have
737 // a URL in the drag data
738 if (!mUrlString.IsEmpty() && mIsAnchor) {
739 nsAutoString dragData(mUrlString);
740 dragData.AppendLiteral("\n");
741 // Remove leading and trailing newlines in the title and replace them with
742 // space in remaining positions - they confuse PlacesUtils::unwrapNodes
743 // that expects url\ntitle formatted data for x-moz-url.
744 nsAutoString title(mTitleString);
745 title.Trim("\r\n");
746 title.ReplaceChar("\r\n", ' ');
747 dragData += title;
749 AddString(aDataTransfer, NS_LITERAL_STRING(kURLMime), dragData, principal);
750 AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal);
751 AddString(aDataTransfer, NS_LITERAL_STRING(kURLDescriptionMime), mTitleString, principal);
752 AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal);
753 }
755 // add a special flavor for the html context data
756 if (!mContextString.IsEmpty())
757 AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), mContextString, principal);
759 // add a special flavor if we have html info data
760 if (!mInfoString.IsEmpty())
761 AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), mInfoString, principal);
763 // add the full html
764 if (!mHtmlString.IsEmpty())
765 AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLMime), mHtmlString, principal);
767 // add the plain text. we use the url for text/plain data if an anchor is
768 // being dragged, rather than the title text of the link or the alt text for
769 // an anchor image.
770 AddString(aDataTransfer, NS_LITERAL_STRING(kTextMime),
771 mIsAnchor ? mUrlString : mTitleString, principal);
773 // add image data, if present. For now, all we're going to do with
774 // this is turn it into a native data flavor, so indicate that with
775 // a new flavor so as not to confuse anyone who is really registered
776 // for image/gif or image/jpg.
777 if (mImage) {
778 nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
779 if (variant) {
780 variant->SetAsISupports(mImage);
781 aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kNativeImageMime),
782 variant, 0, principal);
783 }
785 // assume the image comes from a file, and add a file promise. We
786 // register ourselves as a nsIFlavorDataProvider, and will use the
787 // GetFlavorData callback to save the image to disk.
789 nsCOMPtr<nsIFlavorDataProvider> dataProvider =
790 new nsContentAreaDragDropDataProvider();
791 if (dataProvider) {
792 nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
793 if (variant) {
794 variant->SetAsISupports(dataProvider);
795 aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime),
796 variant, 0, principal);
797 }
798 }
800 AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime),
801 mImageSourceString, principal);
802 AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseDestFilename),
803 mImageDestFileName, principal);
805 // if not an anchor, add the image url
806 if (!mIsAnchor) {
807 AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal);
808 AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal);
809 }
810 }
812 return NS_OK;
813 }
815 // note that this can return NS_OK, but a null out param (by design)
816 // static
817 nsresult
818 DragDataProducer::GetDraggableSelectionData(nsISelection* inSelection,
819 nsIContent* inRealTargetNode,
820 nsIContent **outImageOrLinkNode,
821 bool* outDragSelectedText)
822 {
823 NS_ENSURE_ARG(inSelection);
824 NS_ENSURE_ARG(inRealTargetNode);
825 NS_ENSURE_ARG_POINTER(outImageOrLinkNode);
827 *outImageOrLinkNode = nullptr;
828 *outDragSelectedText = false;
830 bool selectionContainsTarget = false;
832 bool isCollapsed = false;
833 inSelection->GetIsCollapsed(&isCollapsed);
834 if (!isCollapsed) {
835 nsCOMPtr<nsIDOMNode> realTargetNode = do_QueryInterface(inRealTargetNode);
836 inSelection->ContainsNode(realTargetNode, false,
837 &selectionContainsTarget);
839 if (selectionContainsTarget) {
840 // track down the anchor node, if any, for the url
841 nsCOMPtr<nsIDOMNode> selectionStart;
842 inSelection->GetAnchorNode(getter_AddRefs(selectionStart));
844 nsCOMPtr<nsIDOMNode> selectionEnd;
845 inSelection->GetFocusNode(getter_AddRefs(selectionEnd));
847 // look for a selection around a single node, like an image.
848 // in this case, drag the image, rather than a serialization of the HTML
849 // XXX generalize this to other draggable element types?
850 if (selectionStart == selectionEnd) {
851 bool hasChildren;
852 selectionStart->HasChildNodes(&hasChildren);
853 if (hasChildren) {
854 // see if just one node is selected
855 int32_t anchorOffset, focusOffset;
856 inSelection->GetAnchorOffset(&anchorOffset);
857 inSelection->GetFocusOffset(&focusOffset);
858 if (abs(anchorOffset - focusOffset) == 1) {
859 nsCOMPtr<nsIContent> selStartContent =
860 do_QueryInterface(selectionStart);
862 if (selStartContent) {
863 int32_t childOffset =
864 (anchorOffset < focusOffset) ? anchorOffset : focusOffset;
865 nsIContent *childContent =
866 selStartContent->GetChildAt(childOffset);
867 // if we find an image, we'll fall into the node-dragging code,
868 // rather the the selection-dragging code
869 if (nsContentUtils::IsDraggableImage(childContent)) {
870 NS_ADDREF(*outImageOrLinkNode = childContent);
871 return NS_OK;
872 }
873 }
874 }
875 }
876 }
878 // see if the selection is a link; if so, its node will be returned
879 GetSelectedLink(inSelection, outImageOrLinkNode);
881 // indicate that a link or text is selected
882 *outDragSelectedText = true;
883 }
884 }
886 return NS_OK;
887 }
889 // static
890 void
891 DragDataProducer::GetSelectedLink(nsISelection* inSelection,
892 nsIContent **outLinkNode)
893 {
894 *outLinkNode = nullptr;
896 nsCOMPtr<nsIDOMNode> selectionStartNode;
897 inSelection->GetAnchorNode(getter_AddRefs(selectionStartNode));
898 nsCOMPtr<nsIDOMNode> selectionEndNode;
899 inSelection->GetFocusNode(getter_AddRefs(selectionEndNode));
901 // simple case: only one node is selected
902 // see if it or its parent is an anchor, then exit
904 if (selectionStartNode == selectionEndNode) {
905 nsCOMPtr<nsIContent> selectionStart = do_QueryInterface(selectionStartNode);
906 nsCOMPtr<nsIContent> link = FindParentLinkNode(selectionStart);
907 if (link) {
908 link.swap(*outLinkNode);
909 }
911 return;
912 }
914 // more complicated case: multiple nodes are selected
916 // Unless you use the Alt key while selecting anchor text, it is
917 // nearly impossible to avoid overlapping into adjacent nodes.
918 // Deal with this by trimming off the leading and/or trailing
919 // nodes of the selection if the strings they produce are empty.
921 // first, use a range determine if the selection was marked LTR or RTL;
922 // if the latter, swap endpoints so we trim in the right direction
924 int32_t startOffset, endOffset;
925 {
926 nsCOMPtr<nsIDOMRange> range;
927 inSelection->GetRangeAt(0, getter_AddRefs(range));
928 if (!range) {
929 return;
930 }
932 nsCOMPtr<nsIDOMNode> tempNode;
933 range->GetStartContainer( getter_AddRefs(tempNode));
934 if (tempNode != selectionStartNode) {
935 selectionEndNode = selectionStartNode;
936 selectionStartNode = tempNode;
937 inSelection->GetAnchorOffset(&endOffset);
938 inSelection->GetFocusOffset(&startOffset);
939 } else {
940 inSelection->GetAnchorOffset(&startOffset);
941 inSelection->GetFocusOffset(&endOffset);
942 }
943 }
945 // trim leading node if the string is empty or
946 // the selection starts at the end of the text
948 nsAutoString nodeStr;
949 selectionStartNode->GetNodeValue(nodeStr);
950 if (nodeStr.IsEmpty() ||
951 startOffset+1 >= static_cast<int32_t>(nodeStr.Length())) {
952 nsCOMPtr<nsIDOMNode> curr = selectionStartNode;
953 nsIDOMNode* next;
955 while (curr) {
956 curr->GetNextSibling(&next);
958 if (next) {
959 selectionStartNode = dont_AddRef(next);
960 break;
961 }
963 curr->GetParentNode(&next);
964 curr = dont_AddRef(next);
965 }
966 }
968 // trim trailing node if the selection ends before its text begins
970 if (endOffset == 0) {
971 nsCOMPtr<nsIDOMNode> curr = selectionEndNode;
972 nsIDOMNode* next;
974 while (curr) {
975 curr->GetPreviousSibling(&next);
977 if (next){
978 selectionEndNode = dont_AddRef(next);
979 break;
980 }
982 curr->GetParentNode(&next);
983 curr = dont_AddRef(next);
984 }
985 }
987 // see if the leading & trailing nodes are part of the
988 // same anchor - if so, return the anchor node
989 nsCOMPtr<nsIContent> selectionStart = do_QueryInterface(selectionStartNode);
990 nsCOMPtr<nsIContent> link = FindParentLinkNode(selectionStart);
991 if (link) {
992 nsCOMPtr<nsIContent> selectionEnd = do_QueryInterface(selectionEndNode);
993 nsCOMPtr<nsIContent> link2 = FindParentLinkNode(selectionEnd);
995 if (link == link2) {
996 NS_IF_ADDREF(*outLinkNode = link);
997 }
998 }
1000 return;
1001 }