|
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/. */ |
|
5 |
|
6 #include "nsReadableUtils.h" |
|
7 |
|
8 // Local Includes |
|
9 #include "nsContentAreaDragDrop.h" |
|
10 |
|
11 // Helper Classes |
|
12 #include "nsString.h" |
|
13 |
|
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" |
|
57 |
|
58 using namespace mozilla::dom; |
|
59 |
|
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); |
|
71 |
|
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); |
|
90 |
|
91 nsCOMPtr<nsPIDOMWindow> mWindow; |
|
92 nsCOMPtr<nsIContent> mTarget; |
|
93 nsCOMPtr<nsIContent> mSelectionTargetNode; |
|
94 bool mIsAltKeyPressed; |
|
95 |
|
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; |
|
104 |
|
105 bool mIsAnchor; |
|
106 nsCOMPtr<imgIContainer> mImage; |
|
107 }; |
|
108 |
|
109 |
|
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); |
|
121 |
|
122 *aCanDrag = true; |
|
123 |
|
124 DragDataProducer |
|
125 provider(aWindow, aTarget, aSelectionTargetNode, aIsAltKeyPressed); |
|
126 return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode); |
|
127 } |
|
128 |
|
129 |
|
130 NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider) |
|
131 |
|
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 } |
|
145 |
|
146 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI); |
|
147 if (!sourceURL) { |
|
148 return NS_ERROR_NO_INTERFACE; |
|
149 } |
|
150 |
|
151 rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); |
|
152 NS_ENSURE_SUCCESS(rv, rv); |
|
153 |
|
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); |
|
160 |
|
161 persist->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION); |
|
162 |
|
163 return persist->SavePrivacyAwareURI(sourceURI, nullptr, nullptr, nullptr, nullptr, |
|
164 inDestFile, isPrivate); |
|
165 } |
|
166 |
|
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; |
|
189 |
|
190 nsresult rv = NS_ERROR_NOT_IMPLEMENTED; |
|
191 |
|
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; |
|
203 |
|
204 nsAutoString sourceURLString; |
|
205 supportsString->GetData(sourceURLString); |
|
206 if (sourceURLString.IsEmpty()) |
|
207 return NS_ERROR_FAILURE; |
|
208 |
|
209 aTransferable->GetTransferData(kFilePromiseDestFilename, |
|
210 getter_AddRefs(tmp), &dataSize); |
|
211 supportsString = do_QueryInterface(tmp); |
|
212 if (!supportsString) |
|
213 return NS_ERROR_FAILURE; |
|
214 |
|
215 nsAutoString targetFilename; |
|
216 supportsString->GetData(targetFilename); |
|
217 if (targetFilename.IsEmpty()) |
|
218 return NS_ERROR_FAILURE; |
|
219 |
|
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; |
|
229 |
|
230 nsCOMPtr<nsIFile> file; |
|
231 rv = destDirectory->Clone(getter_AddRefs(file)); |
|
232 NS_ENSURE_SUCCESS(rv, rv); |
|
233 |
|
234 file->Append(targetFilename); |
|
235 |
|
236 bool isPrivate; |
|
237 aTransferable->GetIsPrivateData(&isPrivate); |
|
238 |
|
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 } |
|
246 |
|
247 return rv; |
|
248 } |
|
249 |
|
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 } |
|
261 |
|
262 |
|
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 } |
|
278 |
|
279 for (; content; content = content->GetParent()) { |
|
280 if (nsContentUtils::IsDraggableLink(content)) { |
|
281 nsCOMPtr<nsIContent> ret = content; |
|
282 return ret.forget(); |
|
283 } |
|
284 } |
|
285 |
|
286 return nullptr; |
|
287 } |
|
288 |
|
289 |
|
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 } |
|
302 |
|
303 nsAutoCString spec; |
|
304 linkURI->GetSpec(spec); |
|
305 CopyUTF8toUTF16(spec, outURL); |
|
306 } |
|
307 |
|
308 |
|
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>") ); |
|
328 |
|
329 outLinkText = linkText; |
|
330 } |
|
331 |
|
332 |
|
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; |
|
343 |
|
344 outNodeString.Truncate(); |
|
345 |
|
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 } |
|
355 |
|
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"); |
|
366 |
|
367 *aDragNode = nullptr; |
|
368 |
|
369 nsresult rv; |
|
370 nsIContent* dragNode = nullptr; |
|
371 *aSelection = nullptr; |
|
372 |
|
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 } |
|
386 |
|
387 if (!selection) |
|
388 return NS_OK; |
|
389 } |
|
390 else { |
|
391 mWindow->GetSelection(getter_AddRefs(selection)); |
|
392 if (!selection) |
|
393 return NS_OK; |
|
394 |
|
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 } |
|
407 |
|
408 // if set, serialize the content under this node |
|
409 nsCOMPtr<nsIContent> nodeToSerialize; |
|
410 |
|
411 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(mWindow); |
|
412 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav); |
|
413 const bool isChromeShell = |
|
414 dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome; |
|
415 |
|
416 // In chrome shells, only allow dragging inside editable areas. |
|
417 if (isChromeShell && !editingElement) |
|
418 return NS_OK; |
|
419 |
|
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; |
|
427 |
|
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. |
|
435 |
|
436 bool haveSelectedContent = false; |
|
437 |
|
438 // possible parent link node |
|
439 nsCOMPtr<nsIContent> parentLink; |
|
440 nsCOMPtr<nsIContent> draggedNode; |
|
441 |
|
442 { |
|
443 // only drag form elements by using the alt key, |
|
444 // otherwise buttons and select widgets are hard to use |
|
445 |
|
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 } |
|
453 |
|
454 draggedNode = mTarget; |
|
455 } |
|
456 |
|
457 nsCOMPtr<nsIDOMHTMLAreaElement> area; // client-side image map |
|
458 nsCOMPtr<nsIImageLoadingContent> image; |
|
459 nsCOMPtr<nsIDOMHTMLAnchorElement> link; |
|
460 |
|
461 nsCOMPtr<nsIContent> selectedImageOrLinkNode; |
|
462 GetDraggableSelectionData(selection, mSelectionTargetNode, |
|
463 getter_AddRefs(selectedImageOrLinkNode), |
|
464 &haveSelectedContent); |
|
465 |
|
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 } |
|
474 |
|
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 } |
|
491 |
|
492 area = do_QueryInterface(draggedNode); |
|
493 image = do_QueryInterface(draggedNode); |
|
494 link = do_QueryInterface(draggedNode); |
|
495 } |
|
496 |
|
497 { |
|
498 // set for linked images, and links |
|
499 nsCOMPtr<nsIContent> linkNode; |
|
500 |
|
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 } |
|
509 |
|
510 // we'll generate HTML like <a href="absurl">alt text</a> |
|
511 mIsAnchor = true; |
|
512 |
|
513 // gives an absolute link |
|
514 GetAnchorURL(draggedNode, mUrlString); |
|
515 |
|
516 mHtmlString.AssignLiteral("<a href=\""); |
|
517 mHtmlString.Append(mUrlString); |
|
518 mHtmlString.AppendLiteral("\">"); |
|
519 mHtmlString.Append(mTitleString); |
|
520 mHtmlString.AppendLiteral("</a>"); |
|
521 |
|
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 } |
|
535 |
|
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 } |
|
542 |
|
543 if (mTitleString.IsEmpty()) { |
|
544 mTitleString = mUrlString; |
|
545 } |
|
546 |
|
547 nsCOMPtr<imgIRequest> imgRequest; |
|
548 |
|
549 // grab the image data, and its request. |
|
550 nsCOMPtr<imgIContainer> img = |
|
551 nsContentUtils::GetImageFromContent(image, |
|
552 getter_AddRefs(imgRequest)); |
|
553 |
|
554 nsCOMPtr<nsIMIMEService> mimeService = |
|
555 do_GetService("@mozilla.org/mime;1"); |
|
556 |
|
557 // Fix the file extension in the URL if necessary |
|
558 if (imgRequest && mimeService) { |
|
559 nsCOMPtr<nsIURI> imgUri; |
|
560 imgRequest->GetURI(getter_AddRefs(imgUri)); |
|
561 |
|
562 nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri)); |
|
563 |
|
564 if (imgUrl) { |
|
565 nsAutoCString extension; |
|
566 imgUrl->GetFileExtension(extension); |
|
567 |
|
568 nsXPIDLCString mimeType; |
|
569 imgRequest->GetMimeType(getter_Copies(mimeType)); |
|
570 |
|
571 nsCOMPtr<nsIMIMEInfo> mimeInfo; |
|
572 mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(), |
|
573 getter_AddRefs(mimeInfo)); |
|
574 |
|
575 if (mimeInfo) { |
|
576 nsAutoCString spec; |
|
577 imgUrl->GetSpec(spec); |
|
578 |
|
579 // pass out the image source string |
|
580 CopyUTF8toUTF16(spec, mImageSourceString); |
|
581 |
|
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); |
|
590 |
|
591 imgUrl = do_QueryInterface(imgUri); |
|
592 |
|
593 nsAutoCString primaryExtension; |
|
594 mimeInfo->GetPrimaryExtension(primaryExtension); |
|
595 |
|
596 imgUrl->SetFileExtension(primaryExtension); |
|
597 } |
|
598 |
|
599 nsAutoCString fileName; |
|
600 imgUrl->GetFileName(fileName); |
|
601 |
|
602 NS_UnescapeURL(fileName); |
|
603 |
|
604 // make the filename safe for the filesystem |
|
605 fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, |
|
606 '-'); |
|
607 |
|
608 CopyUTF8toUTF16(fileName, mImageDestFileName); |
|
609 |
|
610 // and the image object |
|
611 mImage = img; |
|
612 } |
|
613 } |
|
614 } |
|
615 |
|
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 } |
|
637 |
|
638 if (linkNode) { |
|
639 mIsAnchor = true; |
|
640 GetAnchorURL(linkNode, mUrlString); |
|
641 dragNode = linkNode; |
|
642 } |
|
643 } |
|
644 } |
|
645 |
|
646 if (nodeToSerialize || *aSelection) { |
|
647 mHtmlString.Truncate(); |
|
648 mContextString.Truncate(); |
|
649 mInfoString.Truncate(); |
|
650 mTitleString.Truncate(); |
|
651 |
|
652 nsCOMPtr<nsIDocument> doc = mWindow->GetDoc(); |
|
653 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
|
654 |
|
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); |
|
666 |
|
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 } |
|
694 |
|
695 // default text value is the URL |
|
696 if (mTitleString.IsEmpty()) { |
|
697 mTitleString = mUrlString; |
|
698 } |
|
699 |
|
700 // if we haven't constructed a html version, make one now |
|
701 if (mHtmlString.IsEmpty() && !mUrlString.IsEmpty()) |
|
702 CreateLinkText(mUrlString, mTitleString, mHtmlString); |
|
703 |
|
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); |
|
709 |
|
710 NS_IF_ADDREF(*aDragNode = dragNode); |
|
711 return NS_OK; |
|
712 } |
|
713 |
|
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 } |
|
726 |
|
727 nsresult |
|
728 DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode, |
|
729 DataTransfer* aDataTransfer) |
|
730 { |
|
731 NS_ASSERTION(aDragNode, "adding strings for null node"); |
|
732 |
|
733 // set all of the data to have the principal of the node where the data came from |
|
734 nsIPrincipal* principal = aDragNode->NodePrincipal(); |
|
735 |
|
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; |
|
748 |
|
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 } |
|
754 |
|
755 // add a special flavor for the html context data |
|
756 if (!mContextString.IsEmpty()) |
|
757 AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), mContextString, principal); |
|
758 |
|
759 // add a special flavor if we have html info data |
|
760 if (!mInfoString.IsEmpty()) |
|
761 AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), mInfoString, principal); |
|
762 |
|
763 // add the full html |
|
764 if (!mHtmlString.IsEmpty()) |
|
765 AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLMime), mHtmlString, principal); |
|
766 |
|
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); |
|
772 |
|
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 } |
|
784 |
|
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. |
|
788 |
|
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 } |
|
799 |
|
800 AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime), |
|
801 mImageSourceString, principal); |
|
802 AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseDestFilename), |
|
803 mImageDestFileName, principal); |
|
804 |
|
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 } |
|
811 |
|
812 return NS_OK; |
|
813 } |
|
814 |
|
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); |
|
826 |
|
827 *outImageOrLinkNode = nullptr; |
|
828 *outDragSelectedText = false; |
|
829 |
|
830 bool selectionContainsTarget = false; |
|
831 |
|
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); |
|
838 |
|
839 if (selectionContainsTarget) { |
|
840 // track down the anchor node, if any, for the url |
|
841 nsCOMPtr<nsIDOMNode> selectionStart; |
|
842 inSelection->GetAnchorNode(getter_AddRefs(selectionStart)); |
|
843 |
|
844 nsCOMPtr<nsIDOMNode> selectionEnd; |
|
845 inSelection->GetFocusNode(getter_AddRefs(selectionEnd)); |
|
846 |
|
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); |
|
861 |
|
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 } |
|
877 |
|
878 // see if the selection is a link; if so, its node will be returned |
|
879 GetSelectedLink(inSelection, outImageOrLinkNode); |
|
880 |
|
881 // indicate that a link or text is selected |
|
882 *outDragSelectedText = true; |
|
883 } |
|
884 } |
|
885 |
|
886 return NS_OK; |
|
887 } |
|
888 |
|
889 // static |
|
890 void |
|
891 DragDataProducer::GetSelectedLink(nsISelection* inSelection, |
|
892 nsIContent **outLinkNode) |
|
893 { |
|
894 *outLinkNode = nullptr; |
|
895 |
|
896 nsCOMPtr<nsIDOMNode> selectionStartNode; |
|
897 inSelection->GetAnchorNode(getter_AddRefs(selectionStartNode)); |
|
898 nsCOMPtr<nsIDOMNode> selectionEndNode; |
|
899 inSelection->GetFocusNode(getter_AddRefs(selectionEndNode)); |
|
900 |
|
901 // simple case: only one node is selected |
|
902 // see if it or its parent is an anchor, then exit |
|
903 |
|
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 } |
|
910 |
|
911 return; |
|
912 } |
|
913 |
|
914 // more complicated case: multiple nodes are selected |
|
915 |
|
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. |
|
920 |
|
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 |
|
923 |
|
924 int32_t startOffset, endOffset; |
|
925 { |
|
926 nsCOMPtr<nsIDOMRange> range; |
|
927 inSelection->GetRangeAt(0, getter_AddRefs(range)); |
|
928 if (!range) { |
|
929 return; |
|
930 } |
|
931 |
|
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 } |
|
944 |
|
945 // trim leading node if the string is empty or |
|
946 // the selection starts at the end of the text |
|
947 |
|
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; |
|
954 |
|
955 while (curr) { |
|
956 curr->GetNextSibling(&next); |
|
957 |
|
958 if (next) { |
|
959 selectionStartNode = dont_AddRef(next); |
|
960 break; |
|
961 } |
|
962 |
|
963 curr->GetParentNode(&next); |
|
964 curr = dont_AddRef(next); |
|
965 } |
|
966 } |
|
967 |
|
968 // trim trailing node if the selection ends before its text begins |
|
969 |
|
970 if (endOffset == 0) { |
|
971 nsCOMPtr<nsIDOMNode> curr = selectionEndNode; |
|
972 nsIDOMNode* next; |
|
973 |
|
974 while (curr) { |
|
975 curr->GetPreviousSibling(&next); |
|
976 |
|
977 if (next){ |
|
978 selectionEndNode = dont_AddRef(next); |
|
979 break; |
|
980 } |
|
981 |
|
982 curr->GetParentNode(&next); |
|
983 curr = dont_AddRef(next); |
|
984 } |
|
985 } |
|
986 |
|
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); |
|
994 |
|
995 if (link == link2) { |
|
996 NS_IF_ADDREF(*outLinkNode = link); |
|
997 } |
|
998 } |
|
999 |
|
1000 return; |
|
1001 } |