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