nsCopySupport.cpp (34808B)
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 "nsCopySupport.h" 8 9 #include "imgIContainer.h" 10 #include "imgIRequest.h" 11 #include "mozilla/ScopeExit.h" 12 #include "mozilla/dom/BrowsingContext.h" 13 #include "mozilla/dom/DataTransfer.h" 14 #include "mozilla/dom/Document.h" 15 #include "nsComponentManagerUtils.h" 16 #include "nsFocusManager.h" 17 #include "nsFrameSelection.h" 18 #include "nsGkAtoms.h" 19 #include "nsGlobalWindowInner.h" 20 #include "nsHTMLDocument.h" 21 #include "nsIClipboard.h" 22 #include "nsIContent.h" 23 #include "nsIDocShell.h" 24 #include "nsIDocumentEncoder.h" 25 #include "nsIDocumentViewerEdit.h" 26 #include "nsIFormControl.h" 27 #include "nsIFrame.h" 28 #include "nsISelectionController.h" 29 #include "nsISupports.h" 30 #include "nsISupportsPrimitives.h" 31 #include "nsPIDOMWindow.h" 32 #include "nsRange.h" 33 #include "nsServiceManagerUtils.h" 34 #include "nsWidgetsCID.h" 35 #include "nsXPCOM.h" 36 37 // image copy stuff 38 #include "nsContentUtils.h" 39 #include "nsIImageLoadingContent.h" 40 #include "nsIInterfaceRequestorUtils.h" 41 42 #ifdef XP_WIN 43 # include "mozilla/StaticPrefs_clipboard.h" 44 # include "nsCExternalHandlerService.h" 45 # include "nsEscape.h" 46 # include "nsIMIMEInfo.h" 47 # include "nsIMIMEService.h" 48 # include "nsIURIMutator.h" 49 # include "nsIURL.h" 50 # include "nsReadableUtils.h" 51 # include "nsXULAppAPI.h" 52 #endif 53 54 #include "mozilla/ContentEvents.h" 55 #include "mozilla/EventDispatcher.h" 56 #include "mozilla/IntegerRange.h" 57 #include "mozilla/Preferences.h" 58 #include "mozilla/PresShell.h" 59 #include "mozilla/TextEditor.h" 60 #include "mozilla/dom/Element.h" 61 #include "mozilla/dom/HTMLInputElement.h" 62 #include "mozilla/dom/Selection.h" 63 64 using namespace mozilla; 65 using namespace mozilla::dom; 66 67 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID); 68 static NS_DEFINE_CID(kCTransferableCID, NS_TRANSFERABLE_CID); 69 static NS_DEFINE_CID(kHTMLConverterCID, NS_HTMLFORMATCONVERTER_CID); 70 71 // copy string data onto the transferable 72 static nsresult AppendString(nsITransferable* aTransferable, 73 const nsAString& aString, const char* aFlavor); 74 75 // copy HTML node data 76 static nsresult AppendDOMNode(nsITransferable* aTransferable, 77 nsINode* aDOMNode); 78 79 #ifdef XP_WIN 80 // copy image as file promise onto the transferable 81 static nsresult AppendImagePromise(nsITransferable* aTransferable, 82 imgIRequest* aImgRequest, 83 nsINode* aImageNode); 84 #endif 85 86 static nsresult EncodeForTextPlain(nsIDocumentEncoder& aEncoder, 87 Document& aDocument, Selection* aSelection, 88 uint32_t aAdditionalEncoderFlags, 89 bool& aCanBeEncodedAsTextHTML, 90 nsAString& aSerializationResult) { 91 // We assign text/html as the MIME type first, but in fact nsHTMLCopyEncoder 92 // force the use of text/plain depending where the selection is (e.g., a 93 // selection inside an <input> or <textarea> element). See 94 // nsHTMLCopyEncoder::SetSelection. We can then use this behavior to detect 95 // whether the selection can be encoded as text/html by checking the MIME type 96 // after nsHTMLCopyEncoder::SetSelection. 97 nsAutoString mimeType; 98 mimeType.AssignLiteral(kHTMLMime); 99 100 // Do the first and potentially trial encoding as preformatted and raw. 101 uint32_t flags = aAdditionalEncoderFlags | 102 nsIDocumentEncoder::OutputPreformatted | 103 nsIDocumentEncoder::OutputRaw | 104 nsIDocumentEncoder::OutputForPlainTextClipboardCopy | 105 nsIDocumentEncoder::OutputPersistNBSP; 106 107 nsresult rv = aEncoder.Init(&aDocument, mimeType, flags); 108 NS_ENSURE_SUCCESS(rv, rv); 109 110 rv = aEncoder.SetSelection(aSelection); 111 NS_ENSURE_SUCCESS(rv, rv); 112 113 // SetSelection set the mime type to text/plain if the selection is inside a 114 // text widget. 115 rv = aEncoder.GetMimeType(mimeType); 116 NS_ENSURE_SUCCESS(rv, rv); 117 118 // XXX: For XHTML documents, we would like to use pretty-printing encoding for 119 // text/plain, just as we do for HTML documents. This is achieved by 120 // relying on the current nsHTMLCopyEncoder design, where the MIME 121 // type is not updated to text/plain immediately in 122 // nsHTMLCopyEncoder::SetSelection(), but only latter when 123 // nsHTMLCopyEncoder::EncodeToString() is called. As a result, we still see a 124 // text/html MIME type here for XHTML documents. 125 if (mimeType.EqualsLiteral(kTextMime)) { 126 // nsHTMLCopyEncoder force to use text/plain. 127 nsAutoString buf; 128 rv = aEncoder.EncodeToString(buf); 129 if (NS_SUCCEEDED(rv)) { 130 // Nothing to do. buf contains the final, preformatted, raw text/plain. 131 aSerializationResult.Assign(buf); 132 } 133 return rv; 134 } 135 136 MOZ_ASSERT(mimeType.EqualsLiteral(kHTMLMime)); 137 // XXX: We currently only try to encode as text/html for HTML documents. 138 // See bug 857915. 139 if (aDocument.IsHTMLDocument()) { 140 aCanBeEncodedAsTextHTML = true; 141 } 142 143 // Do the text/plain encoding, but this time use pretty printing. 144 flags = nsIDocumentEncoder::OutputSelectionOnly | 145 nsIDocumentEncoder::OutputForPlainTextClipboardCopy | 146 nsIDocumentEncoder::OutputAbsoluteLinks | 147 nsIDocumentEncoder::SkipInvisibleContent | 148 nsIDocumentEncoder::OutputDropInvisibleBreak | 149 (aAdditionalEncoderFlags & 150 (nsIDocumentEncoder::OutputNoScriptContent | 151 nsIDocumentEncoder::OutputRubyAnnotation | 152 nsIDocumentEncoder::AllowCrossShadowBoundary)); 153 154 mimeType.AssignLiteral(kTextMime); 155 rv = aEncoder.Init(&aDocument, mimeType, flags); 156 NS_ENSURE_SUCCESS(rv, rv); 157 158 rv = aEncoder.SetSelection(aSelection); 159 NS_ENSURE_SUCCESS(rv, rv); 160 161 rv = aEncoder.EncodeToString(aSerializationResult); 162 return rv; 163 } 164 165 static nsresult EncodeAsTextHTMLWithContext( 166 nsIDocumentEncoder& aEncoder, Document& aDocument, Selection* aSelection, 167 uint32_t aEncoderFlags, nsAutoString& aTextHTMLEncodingResult, 168 nsAutoString& aHTMLParentsBufResult, nsAutoString& aHTMLInfoBufResult) { 169 nsAutoString mimeType; 170 mimeType.AssignLiteral(kHTMLMime); 171 nsresult rv = aEncoder.Init(&aDocument, mimeType, aEncoderFlags); 172 NS_ENSURE_SUCCESS(rv, rv); 173 174 rv = aEncoder.SetSelection(aSelection); 175 NS_ENSURE_SUCCESS(rv, rv); 176 177 rv = aEncoder.EncodeToStringWithContext( 178 aHTMLParentsBufResult, aHTMLInfoBufResult, aTextHTMLEncodingResult); 179 NS_ENSURE_SUCCESS(rv, rv); 180 return rv; 181 } 182 183 struct EncodedDocumentWithContext { 184 // Whether the document can be encoded as text/html. 185 bool mCanBeEncodedAsTextHTML = false; 186 187 // The serialized document when encoding the document with `text/plain`. 188 nsAutoString mSerializationForTextPlain; 189 190 // When `mCanBeEncodedAsTextHTML` is true, this is the serialized document 191 // using `text/html`. 192 nsAutoString mSerializationForTextHTML; 193 194 // When `mCanBeEncodedAsTextHTML` is true, this contains the serialized 195 // ancestor elements. 196 nsAutoString mHTMLContextBuffer; 197 198 // When `mCanBeEncodedAsTextHTML` is true, this contains numbers 199 // identifying where in the context the serialization came from. 200 nsAutoString mHTMLInfoBuffer; 201 }; 202 203 /** 204 * @param aSelection Can be nullptr. 205 * @param aAdditionalEncoderFlags nsIDocumentEncoder flags. 206 */ 207 static nsresult EncodeDocumentWithContext( 208 Document& aDocument, Selection* aSelection, 209 uint32_t aAdditionalEncoderFlags, 210 EncodedDocumentWithContext& aEncodedDocumentWithContext) { 211 nsCOMPtr<nsIDocumentEncoder> docEncoder = do_createHTMLCopyEncoder(); 212 213 bool canBeEncodedAsTextHTML{false}; 214 nsAutoString serializationForTextPlain; 215 nsresult rv = EncodeForTextPlain( 216 *docEncoder, aDocument, aSelection, aAdditionalEncoderFlags, 217 canBeEncodedAsTextHTML, serializationForTextPlain); 218 NS_ENSURE_SUCCESS(rv, rv); 219 220 nsAutoString serializationForTextHTML; 221 nsAutoString htmlContextBuffer; 222 nsAutoString htmlInfoBuffer; 223 if (canBeEncodedAsTextHTML) { 224 // Redo the encoding, but this time use the passed-in flags. 225 // Don't allow wrapping of CJK strings. 226 rv = EncodeAsTextHTMLWithContext( 227 *docEncoder, aDocument, aSelection, 228 aAdditionalEncoderFlags | 229 nsIDocumentEncoder::OutputDisallowLineBreaking, 230 serializationForTextHTML, htmlContextBuffer, htmlInfoBuffer); 231 NS_ENSURE_SUCCESS(rv, rv); 232 } 233 234 aEncodedDocumentWithContext = { 235 canBeEncodedAsTextHTML, std::move(serializationForTextPlain), 236 std::move(serializationForTextHTML), std::move(htmlContextBuffer), 237 std::move(htmlInfoBuffer)}; 238 239 return rv; 240 } 241 242 static nsresult CreateTransferable( 243 const EncodedDocumentWithContext& aEncodedDocumentWithContext, 244 Document& aDocument, nsCOMPtr<nsITransferable>& aTransferable) { 245 nsresult rv = NS_OK; 246 247 aTransferable = do_CreateInstance(kCTransferableCID); 248 NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER); 249 250 aTransferable->Init(aDocument.GetLoadContext()); 251 aTransferable->SetDataPrincipal(aDocument.NodePrincipal()); 252 if (aEncodedDocumentWithContext.mCanBeEncodedAsTextHTML) { 253 // XXX: Now we provide the text/plain directly, do we still need to always 254 // set a HTML converter? Or perhaps we could set the converter only when the 255 // text/plain is not available. 256 // Set up a format converter so that clipboard flavor queries work. 257 // This converter isn't really used for conversions. 258 nsCOMPtr<nsIFormatConverter> htmlConverter = 259 do_CreateInstance(kHTMLConverterCID); 260 aTransferable->SetConverter(htmlConverter); 261 262 if (!aEncodedDocumentWithContext.mSerializationForTextHTML.IsEmpty()) { 263 // Add the html DataFlavor to the transferable 264 rv = AppendString(aTransferable, 265 aEncodedDocumentWithContext.mSerializationForTextHTML, 266 kHTMLMime); 267 NS_ENSURE_SUCCESS(rv, rv); 268 } 269 270 // Add the htmlcontext DataFlavor to the transferable. Even if the context 271 // buffer is empty, this flavor should be attached to the transferable. 272 rv = AppendString(aTransferable, 273 aEncodedDocumentWithContext.mHTMLContextBuffer, 274 kHTMLContext); 275 NS_ENSURE_SUCCESS(rv, rv); 276 277 if (!aEncodedDocumentWithContext.mHTMLInfoBuffer.IsEmpty()) { 278 // Add the htmlinfo DataFlavor to the transferable 279 rv = AppendString(aTransferable, 280 aEncodedDocumentWithContext.mHTMLInfoBuffer, kHTMLInfo); 281 NS_ENSURE_SUCCESS(rv, rv); 282 } 283 284 if (!aEncodedDocumentWithContext.mSerializationForTextPlain.IsEmpty()) { 285 // Add the plain text DataFlavor to the transferable 286 // If we didn't have this, then nsDataObj::GetData matches 287 // text/plain against the kURLMime flavour which is not desirable 288 // (eg. when pasting into Notepad) 289 rv = AppendString(aTransferable, 290 aEncodedDocumentWithContext.mSerializationForTextPlain, 291 kTextMime); 292 NS_ENSURE_SUCCESS(rv, rv); 293 } 294 295 #if !defined(BASE_BROWSER_VERSION) 296 // Try and get source URI of the items that are being dragged 297 nsIURI* uri = aDocument.GetDocumentURI(); 298 if (uri) { 299 nsAutoCString spec; 300 nsresult rv = uri->GetSpec(spec); 301 NS_ENSURE_SUCCESS(rv, rv); 302 if (!spec.IsEmpty()) { 303 nsAutoString shortcut; 304 AppendUTF8toUTF16(spec, shortcut); 305 306 // Add the URL DataFlavor to the transferable. Don't use kURLMime, 307 // as it will cause an unnecessary UniformResourceLocator to be 308 // added which confuses some apps eg. Outlook 2000 - (See Bug 309 // 315370). Don't use kURLDataMime, as it will cause a bogus 'url ' 310 // flavor to show up on the Mac clipboard, confusing other apps, 311 // like Terminal (see bug 336012). 312 rv = AppendString(aTransferable, shortcut, kURLPrivateMime); 313 NS_ENSURE_SUCCESS(rv, rv); 314 } 315 } 316 #endif 317 } else { 318 if (!aEncodedDocumentWithContext.mSerializationForTextPlain.IsEmpty()) { 319 // Add the unicode DataFlavor to the transferable 320 rv = AppendString(aTransferable, 321 aEncodedDocumentWithContext.mSerializationForTextPlain, 322 kTextMime); 323 NS_ENSURE_SUCCESS(rv, rv); 324 } 325 } 326 327 return rv; 328 } 329 330 static nsresult PutToClipboard( 331 const EncodedDocumentWithContext& aEncodedDocumentWithContext, 332 nsIClipboard::ClipboardType aClipboardID, Document& aDocument) { 333 nsresult rv; 334 nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv); 335 NS_ENSURE_SUCCESS(rv, rv); 336 NS_ENSURE_TRUE(clipboard, NS_ERROR_NULL_POINTER); 337 338 nsCOMPtr<nsITransferable> transferable; 339 rv = CreateTransferable(aEncodedDocumentWithContext, aDocument, transferable); 340 NS_ENSURE_SUCCESS(rv, rv); 341 342 rv = clipboard->SetData(transferable, nullptr, aClipboardID, 343 aDocument.GetWindowContext()); 344 NS_ENSURE_SUCCESS(rv, rv); 345 346 return rv; 347 } 348 349 nsresult nsCopySupport::EncodeDocumentWithContextAndPutToClipboard( 350 Selection* aSel, Document* aDoc, nsIClipboard::ClipboardType aClipboardID, 351 bool aWithRubyAnnotation) { 352 NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); 353 354 uint32_t additionalFlags = nsIDocumentEncoder::SkipInvisibleContent; 355 356 if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) { 357 additionalFlags |= nsIDocumentEncoder::AllowCrossShadowBoundary; 358 } 359 360 if (aWithRubyAnnotation) { 361 additionalFlags |= nsIDocumentEncoder::OutputRubyAnnotation; 362 } 363 364 EncodedDocumentWithContext encodedDocumentWithContext; 365 nsresult rv = EncodeDocumentWithContext(*aDoc, aSel, additionalFlags, 366 encodedDocumentWithContext); 367 NS_ENSURE_SUCCESS(rv, rv); 368 369 rv = PutToClipboard(encodedDocumentWithContext, aClipboardID, *aDoc); 370 NS_ENSURE_SUCCESS(rv, rv); 371 372 return rv; 373 } 374 375 nsresult nsCopySupport::ClearSelectionCache() { 376 nsresult rv; 377 nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv); 378 clipboard->EmptyClipboard(nsIClipboard::kSelectionCache); 379 return rv; 380 } 381 382 /** 383 * @param aAdditionalEncoderFlags flags of `nsIDocumentEncoder`. 384 * @param aTransferable Needs to be not `nullptr`. 385 */ 386 static nsresult EncodeDocumentWithContextAndCreateTransferable( 387 Document& aDocument, Selection* aSelection, 388 uint32_t aAdditionalEncoderFlags, nsITransferable** aTransferable) { 389 NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER); 390 391 // Clear the output parameter for the transferable. 392 *aTransferable = nullptr; 393 394 EncodedDocumentWithContext encodedDocumentWithContext; 395 nsresult rv = 396 EncodeDocumentWithContext(aDocument, aSelection, aAdditionalEncoderFlags, 397 encodedDocumentWithContext); 398 NS_ENSURE_SUCCESS(rv, rv); 399 400 nsCOMPtr<nsITransferable> transferable; 401 rv = CreateTransferable(encodedDocumentWithContext, aDocument, transferable); 402 NS_ENSURE_SUCCESS(rv, rv); 403 404 transferable.swap(*aTransferable); 405 return rv; 406 } 407 408 nsresult nsCopySupport::GetTransferableForSelection( 409 Selection* aSel, Document* aDoc, nsITransferable** aTransferable) { 410 NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); 411 NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER); 412 413 const uint32_t additionalFlags = 414 StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() 415 ? nsIDocumentEncoder::SkipInvisibleContent | 416 nsIDocumentEncoder::AllowCrossShadowBoundary 417 : nsIDocumentEncoder::SkipInvisibleContent; 418 419 return EncodeDocumentWithContextAndCreateTransferable( 420 *aDoc, aSel, additionalFlags, aTransferable); 421 } 422 423 nsresult nsCopySupport::GetTransferableForNode( 424 nsINode* aNode, Document* aDoc, nsITransferable** aTransferable) { 425 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); 426 NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); 427 NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER); 428 429 // Make a temporary selection with aNode in a single range. 430 // XXX We should try to get rid of the Selection object here. 431 // XXX bug 1245883 432 RefPtr<Selection> selection = new Selection(SelectionType::eNormal, nullptr); 433 RefPtr<nsRange> range = nsRange::Create(aNode); 434 ErrorResult result; 435 range->SelectNode(*aNode, result); 436 if (NS_WARN_IF(result.Failed())) { 437 return result.StealNSResult(); 438 } 439 selection->AddRangeAndSelectFramesAndNotifyListenersInternal(*range, aDoc, 440 result); 441 if (NS_WARN_IF(result.Failed())) { 442 return result.StealNSResult(); 443 } 444 // It's not the primary selection - so don't skip invisible content. 445 uint32_t additionalFlags = 0; 446 return EncodeDocumentWithContextAndCreateTransferable( 447 *aDoc, selection, additionalFlags, aTransferable); 448 } 449 450 nsresult nsCopySupport::GetContents(const nsACString& aMimeType, 451 uint32_t aFlags, Selection* aSel, 452 Document* aDoc, nsAString& outdata) { 453 nsCOMPtr<nsIDocumentEncoder> docEncoder = 454 do_createDocumentEncoder(PromiseFlatCString(aMimeType).get()); 455 NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE); 456 457 uint32_t flags = aFlags | nsIDocumentEncoder::SkipInvisibleContent; 458 459 if (aMimeType.EqualsLiteral("text/plain")) 460 flags |= nsIDocumentEncoder::OutputPreformatted; 461 462 NS_ConvertASCIItoUTF16 unicodeMimeType(aMimeType); 463 464 nsresult rv = docEncoder->Init(aDoc, unicodeMimeType, flags); 465 if (NS_FAILED(rv)) return rv; 466 467 if (aSel) { 468 rv = docEncoder->SetSelection(aSel); 469 if (NS_FAILED(rv)) return rv; 470 } 471 472 // encode the selection 473 return docEncoder->EncodeToString(outdata); 474 } 475 476 nsresult nsCopySupport::ImageCopy( 477 nsIImageLoadingContent* aImageElement, nsILoadContext* aLoadContext, 478 int32_t aCopyFlags, mozilla::dom::WindowContext* aSettingWindowContext) { 479 nsresult rv; 480 481 nsCOMPtr<nsINode> imageNode = do_QueryInterface(aImageElement, &rv); 482 NS_ENSURE_SUCCESS(rv, rv); 483 484 // create a transferable for putting data on the Clipboard 485 nsCOMPtr<nsITransferable> trans(do_CreateInstance(kCTransferableCID, &rv)); 486 NS_ENSURE_SUCCESS(rv, rv); 487 trans->Init(aLoadContext); 488 trans->SetDataPrincipal(imageNode->NodePrincipal()); 489 490 if (aCopyFlags & nsIDocumentViewerEdit::COPY_IMAGE_TEXT) { 491 // get the location from the element 492 nsCOMPtr<nsIURI> uri; 493 rv = aImageElement->GetCurrentURI(getter_AddRefs(uri)); 494 NS_ENSURE_SUCCESS(rv, rv); 495 NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); 496 497 nsAutoCString location; 498 rv = uri->GetSpec(location); 499 NS_ENSURE_SUCCESS(rv, rv); 500 501 // append the string to the transferable 502 rv = AppendString(trans, NS_ConvertUTF8toUTF16(location), kTextMime); 503 NS_ENSURE_SUCCESS(rv, rv); 504 } 505 506 if (aCopyFlags & nsIDocumentViewerEdit::COPY_IMAGE_HTML) { 507 // append HTML data to the transferable 508 nsCOMPtr<nsINode> node(do_QueryInterface(aImageElement, &rv)); 509 NS_ENSURE_SUCCESS(rv, rv); 510 511 rv = AppendDOMNode(trans, node); 512 NS_ENSURE_SUCCESS(rv, rv); 513 } 514 515 if (aCopyFlags & nsIDocumentViewerEdit::COPY_IMAGE_DATA) { 516 // get the image data and its request from the element 517 nsCOMPtr<imgIRequest> imgRequest; 518 nsCOMPtr<imgIContainer> image = nsContentUtils::GetImageFromContent( 519 aImageElement, getter_AddRefs(imgRequest)); 520 NS_ENSURE_TRUE(image, NS_ERROR_FAILURE); 521 522 if (imgRequest) { 523 // Remember the referrer used for this image request. 524 nsCOMPtr<nsIReferrerInfo> referrerInfo; 525 imgRequest->GetReferrerInfo(getter_AddRefs(referrerInfo)); 526 trans->SetReferrerInfo(referrerInfo); 527 } 528 529 #ifdef XP_WIN 530 if (StaticPrefs::clipboard_imageAsFile_enabled()) { 531 rv = AppendImagePromise(trans, imgRequest, imageNode); 532 NS_ENSURE_SUCCESS(rv, rv); 533 } 534 #endif 535 536 // copy the image data onto the transferable 537 rv = trans->SetTransferData(kNativeImageMime, image); 538 NS_ENSURE_SUCCESS(rv, rv); 539 } 540 541 // get clipboard 542 nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv)); 543 NS_ENSURE_SUCCESS(rv, rv); 544 545 // check whether the system supports the selection clipboard or not. 546 if (clipboard->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard)) { 547 // put the transferable on the clipboard 548 rv = clipboard->SetData(trans, nullptr, nsIClipboard::kSelectionClipboard, 549 aSettingWindowContext); 550 NS_ENSURE_SUCCESS(rv, rv); 551 } 552 553 return clipboard->SetData(trans, nullptr, nsIClipboard::kGlobalClipboard, 554 aSettingWindowContext); 555 } 556 557 static nsresult AppendString(nsITransferable* aTransferable, 558 const nsAString& aString, const char* aFlavor) { 559 nsresult rv; 560 561 nsCOMPtr<nsISupportsString> data( 562 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); 563 NS_ENSURE_SUCCESS(rv, rv); 564 565 rv = data->SetData(aString); 566 NS_ENSURE_SUCCESS(rv, rv); 567 568 rv = aTransferable->AddDataFlavor(aFlavor); 569 NS_ENSURE_SUCCESS(rv, rv); 570 571 return aTransferable->SetTransferData(aFlavor, data); 572 } 573 574 static nsresult AppendDOMNode(nsITransferable* aTransferable, 575 nsINode* aDOMNode) { 576 nsresult rv; 577 578 // serializer 579 nsCOMPtr<nsIDocumentEncoder> docEncoder = do_createHTMLCopyEncoder(); 580 581 // get document for the encoder 582 nsCOMPtr<Document> document = aDOMNode->OwnerDoc(); 583 584 // Note that XHTML is not counted as HTML here, because we can't copy it 585 // properly (all the copy code for non-plaintext assumes using HTML 586 // serializers and parsers is OK, and those mess up XHTML). 587 NS_ENSURE_TRUE(document->IsHTMLDocument(), NS_OK); 588 589 // init encoder with document and node 590 rv = docEncoder->Init(document, NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime), 591 nsIDocumentEncoder::OutputAbsoluteLinks | 592 nsIDocumentEncoder::OutputEncodeBasicEntities); 593 NS_ENSURE_SUCCESS(rv, rv); 594 595 rv = docEncoder->SetNode(aDOMNode); 596 NS_ENSURE_SUCCESS(rv, rv); 597 598 // serialize to string 599 nsAutoString html, context, info; 600 rv = docEncoder->EncodeToStringWithContext(context, info, html); 601 NS_ENSURE_SUCCESS(rv, rv); 602 603 // copy them to the transferable 604 if (!html.IsEmpty()) { 605 rv = AppendString(aTransferable, html, kHTMLMime); 606 NS_ENSURE_SUCCESS(rv, rv); 607 } 608 609 if (!info.IsEmpty()) { 610 rv = AppendString(aTransferable, info, kHTMLInfo); 611 NS_ENSURE_SUCCESS(rv, rv); 612 } 613 614 // add a special flavor, even if we don't have html context data 615 return AppendString(aTransferable, context, kHTMLContext); 616 } 617 618 #ifdef XP_WIN 619 static nsresult AppendImagePromise(nsITransferable* aTransferable, 620 imgIRequest* aImgRequest, 621 nsINode* aImageNode) { 622 nsresult rv; 623 624 NS_ENSURE_TRUE(aImgRequest && aImageNode, NS_OK); 625 626 bool isMultipart; 627 rv = aImgRequest->GetMultipart(&isMultipart); 628 NS_ENSURE_SUCCESS(rv, rv); 629 if (isMultipart) { 630 return NS_OK; 631 } 632 633 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1"); 634 if (NS_WARN_IF(!mimeService)) { 635 return NS_ERROR_FAILURE; 636 } 637 638 nsCOMPtr<nsIURI> imgUri; 639 rv = aImgRequest->GetFinalURI(getter_AddRefs(imgUri)); 640 NS_ENSURE_SUCCESS(rv, rv); 641 642 nsAutoCString spec; 643 rv = imgUri->GetSpec(spec); 644 NS_ENSURE_SUCCESS(rv, rv); 645 646 // pass out the image source string 647 nsString imageSourceString; 648 CopyUTF8toUTF16(spec, imageSourceString); 649 650 nsCString mimeType; 651 rv = aImgRequest->GetMimeType(getter_Copies(mimeType)); 652 NS_ENSURE_SUCCESS(rv, rv); 653 654 nsAutoCString fileName; 655 rv = aImgRequest->GetFileName(fileName); 656 NS_ENSURE_SUCCESS(rv, rv); 657 658 nsAutoString validFileName = NS_ConvertUTF8toUTF16(fileName); 659 mimeService->ValidateFileNameForSaving( 660 validFileName, mimeType, nsIMIMEService::VALIDATE_DEFAULT, validFileName); 661 662 rv = AppendString(aTransferable, imageSourceString, kFilePromiseURLMime); 663 NS_ENSURE_SUCCESS(rv, rv); 664 665 rv = AppendString(aTransferable, validFileName, kFilePromiseDestFilename); 666 NS_ENSURE_SUCCESS(rv, rv); 667 668 aTransferable->SetCookieJarSettings( 669 aImageNode->OwnerDoc()->CookieJarSettings()); 670 aTransferable->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE); 671 672 // add the dataless file promise flavor 673 return aTransferable->AddDataFlavor(kFilePromiseMime); 674 } 675 #endif // XP_WIN 676 677 already_AddRefed<Selection> nsCopySupport::GetSelectionForCopy( 678 Document* aDocument) { 679 PresShell* presShell = aDocument->GetPresShell(); 680 if (NS_WARN_IF(!presShell)) { 681 return nullptr; 682 } 683 684 RefPtr<nsFrameSelection> frameSel = presShell->GetLastFocusedFrameSelection(); 685 if (NS_WARN_IF(!frameSel)) { 686 return nullptr; 687 } 688 689 RefPtr<Selection> sel = &frameSel->NormalSelection(); 690 return sel.forget(); 691 } 692 693 bool nsCopySupport::CanCopy(Document* aDocument) { 694 if (!aDocument) { 695 return false; 696 } 697 698 RefPtr<Selection> sel = GetSelectionForCopy(aDocument); 699 return sel && !sel->IsCollapsed(); 700 } 701 702 static bool IsInsideRuby(nsINode* aNode) { 703 for (; aNode; aNode = aNode->GetParent()) { 704 if (aNode->IsHTMLElement(nsGkAtoms::ruby)) { 705 return true; 706 } 707 } 708 return false; 709 } 710 711 static bool IsSelectionInsideRuby(Selection* aSelection) { 712 uint32_t rangeCount = aSelection->RangeCount(); 713 for (auto i : IntegerRange(rangeCount)) { 714 MOZ_ASSERT(aSelection->RangeCount() == rangeCount); 715 const nsRange* range = aSelection->GetRangeAt(i); 716 if (!IsInsideRuby(range->GetClosestCommonInclusiveAncestor())) { 717 return false; 718 } 719 } 720 return true; 721 } 722 723 static Element* GetElementOrNearestFlattenedTreeParentElement(nsINode* aNode) { 724 if (!aNode->IsContent()) { 725 return nullptr; 726 } 727 for (nsIContent* content = aNode->AsContent(); content; 728 content = content->GetFlattenedTreeParent()) { 729 if (content->IsElement()) { 730 return content->AsElement(); 731 } 732 } 733 return nullptr; 734 } 735 736 /** 737 * This class is used while processing clipboard paste event. 738 */ 739 class MOZ_RAII AutoHandlingPasteEvent final { 740 public: 741 explicit AutoHandlingPasteEvent( 742 nsGlobalWindowInner* aWindow, DataTransfer* aDataTransfer, 743 const EventMessage& aEventMessage, 744 const mozilla::Maybe<nsIClipboard::ClipboardType> aClipboardType) { 745 MOZ_ASSERT(aDataTransfer); 746 if (aWindow && aEventMessage == ePaste && 747 aClipboardType == Some(nsIClipboard::kGlobalClipboard)) { 748 aWindow->SetCurrentPasteDataTransfer(aDataTransfer); 749 mInnerWindow = aWindow; 750 } 751 } 752 753 ~AutoHandlingPasteEvent() { 754 if (mInnerWindow) { 755 mInnerWindow->SetCurrentPasteDataTransfer(nullptr); 756 } 757 } 758 759 private: 760 RefPtr<nsGlobalWindowInner> mInnerWindow; 761 }; 762 763 bool nsCopySupport::FireClipboardEvent( 764 EventMessage aEventMessage, 765 mozilla::Maybe<nsIClipboard::ClipboardType> aClipboardType, 766 PresShell* aPresShell, Selection* aSelection, DataTransfer* aDataTransfer, 767 bool* aActionTaken) { 768 if (aActionTaken) { 769 *aActionTaken = false; 770 } 771 772 EventMessage originalEventMessage = aEventMessage; 773 if (originalEventMessage == ePasteNoFormatting) { 774 originalEventMessage = ePaste; 775 } 776 777 NS_ASSERTION(originalEventMessage == eCut || originalEventMessage == eCopy || 778 originalEventMessage == ePaste, 779 "Invalid clipboard event type"); 780 781 MOZ_ASSERT_IF(originalEventMessage != ePaste, aClipboardType.isSome()); 782 783 RefPtr<PresShell> presShell = aPresShell; 784 if (!presShell) { 785 return false; 786 } 787 788 nsCOMPtr<Document> doc = presShell->GetDocument(); 789 if (!doc) return false; 790 791 nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow(); 792 if (!piWindow) return false; 793 794 // Event target of clipboard events should be an element node which 795 // contains selection start container. 796 RefPtr<Element> targetElement; 797 798 // If a selection was not supplied, try to find it. 799 RefPtr<Selection> sel = aSelection; 800 if (!sel) { 801 sel = GetSelectionForCopy(doc); 802 } 803 804 // Retrieve the event target node from the start of the selection. 805 if (sel) { 806 const nsRange* range = sel->GetRangeAt(0); 807 if (range) { 808 targetElement = GetElementOrNearestFlattenedTreeParentElement( 809 range->GetStartContainer()); 810 } 811 } 812 813 // If there is no selection ranges, use the <body> or <frameset> element. 814 if (!targetElement) { 815 targetElement = doc->GetBody(); 816 if (!targetElement) { 817 return false; 818 } 819 } 820 821 // It seems to be unsafe to fire an event handler during reflow (bug 393696) 822 if (!nsContentUtils::IsSafeToRunScript()) { 823 nsContentUtils::WarnScriptWasIgnored(doc); 824 return false; 825 } 826 827 BrowsingContext* bc = piWindow->GetBrowsingContext(); 828 const bool chromeShell = bc && bc->IsChrome(); 829 830 // next, fire the cut, copy or paste event 831 bool doDefault = true; 832 RefPtr<DataTransfer> clipboardData; 833 if (chromeShell || StaticPrefs::dom_event_clipboardevents_enabled()) { 834 MOZ_ASSERT_IF(aDataTransfer, 835 aDataTransfer->GetParentObject() == doc->GetScopeObject()); 836 MOZ_ASSERT_IF(aDataTransfer, (aDataTransfer->GetEventMessage() == ePaste) && 837 (aEventMessage == ePaste || 838 aEventMessage == ePasteNoFormatting)); 839 MOZ_ASSERT_IF(aDataTransfer, 840 aDataTransfer->ClipboardType() == aClipboardType); 841 clipboardData = aDataTransfer 842 ? RefPtr<DataTransfer>(aDataTransfer) 843 : MakeRefPtr<DataTransfer>( 844 doc->GetScopeObject(), aEventMessage, 845 originalEventMessage == ePaste, aClipboardType); 846 847 nsEventStatus status = nsEventStatus_eIgnore; 848 InternalClipboardEvent evt(true, originalEventMessage); 849 evt.mClipboardData = clipboardData; 850 851 { 852 AutoHandlingPasteEvent autoHandlingPasteEvent( 853 nsGlobalWindowInner::Cast(doc->GetInnerWindow()), clipboardData, 854 aEventMessage, aClipboardType); 855 856 RefPtr<nsPresContext> presContext = presShell->GetPresContext(); 857 EventDispatcher::Dispatch(targetElement, presContext, &evt, nullptr, 858 &status); 859 } 860 861 // If the event was cancelled, don't do the clipboard operation 862 doDefault = (status != nsEventStatus_eConsumeNoDefault); 863 } 864 865 // When this function exits, the event dispatch is over. We want to disconnect 866 // our DataTransfer, which means setting its mode to `Protected` and clearing 867 // all stored data, before we return. 868 auto clearAfter = MakeScopeExit([&] { 869 if (clipboardData && !aDataTransfer) { 870 clipboardData->Disconnect(); 871 872 // NOTE: Disconnect may not actually clear the DataTransfer if the 873 // dom.events.dataTransfer.protected.enabled pref is not on, so we make 874 // sure we clear here, as not clearing could provide the DataTransfer 875 // access to information from the system clipboard at an arbitrary point 876 // in the future. 877 if (originalEventMessage == ePaste) { 878 clipboardData->ClearAll(); 879 } 880 } 881 }); 882 883 // No need to do anything special during a paste. Either an event listener 884 // took care of it and cancelled the event, or the caller will handle it. 885 // Return true to indicate that the event wasn't cancelled. 886 if (originalEventMessage == ePaste) { 887 if (aActionTaken) { 888 *aActionTaken = true; 889 } 890 return doDefault; 891 } 892 893 // Update the presentation in case the event handler modified the selection, 894 // see bug 602231. 895 presShell->FlushPendingNotifications(FlushType::Frames); 896 if (presShell->IsDestroying()) { 897 return false; 898 } 899 900 // if the event was not cancelled, do the default copy. If the event was 901 // cancelled, use the data added to the data transfer and copy that instead. 902 uint32_t count = 0; 903 if (doDefault) { 904 // find the focused node 905 nsIContent* sourceContent = targetElement.get(); 906 if (targetElement->IsInNativeAnonymousSubtree()) { 907 sourceContent = targetElement->FindFirstNonChromeOnlyAccessContent(); 908 } 909 910 // If it's <input type="password"> and there is no unmasked range or 911 // there is unmasked range but it's collapsed or it'll be masked 912 // automatically, the selected password shouldn't be copied into the 913 // clipboard. 914 if (RefPtr<HTMLInputElement> inputElement = 915 HTMLInputElement::FromNodeOrNull(sourceContent)) { 916 if (TextEditor* textEditor = inputElement->GetTextEditor()) { 917 if (textEditor->IsPasswordEditor() && 918 !textEditor->IsCopyToClipboardAllowed()) { 919 return false; 920 } 921 } 922 } 923 924 // when cutting non-editable content, do nothing 925 // XXX this is probably the wrong editable flag to check 926 if (originalEventMessage != eCut || targetElement->IsEditable()) { 927 // get the data from the selection if any 928 if (sel->AreNormalAndCrossShadowBoundaryRangesCollapsed()) { 929 if (aActionTaken) { 930 *aActionTaken = true; 931 } 932 return false; 933 } 934 // XXX Code which decides whether we should copy text with ruby 935 // annotation is currenct depending on whether each range of the 936 // selection is inside a same ruby container. But we really should 937 // expose the full functionality in browser. See bug 1130891. 938 bool withRubyAnnotation = IsSelectionInsideRuby(sel); 939 nsresult rv = EncodeDocumentWithContextAndPutToClipboard( 940 sel, doc, *aClipboardType, withRubyAnnotation); 941 if (NS_FAILED(rv)) { 942 return false; 943 } 944 } else { 945 return false; 946 } 947 } else if (clipboardData) { 948 // check to see if any data was put on the data transfer. 949 count = clipboardData->MozItemCount(); 950 if (count) { 951 nsCOMPtr<nsIClipboard> clipboard( 952 do_GetService("@mozilla.org/widget/clipboard;1")); 953 NS_ENSURE_TRUE(clipboard, false); 954 955 nsCOMPtr<nsITransferable> transferable = 956 clipboardData->GetTransferable(0, doc->GetLoadContext()); 957 958 NS_ENSURE_TRUE(transferable, false); 959 960 // put the transferable on the clipboard 961 WindowContext* settingWindowContext = nullptr; 962 if (aPresShell && aPresShell->GetDocument()) { 963 settingWindowContext = aPresShell->GetDocument()->GetWindowContext(); 964 } 965 nsresult rv = clipboard->SetData(transferable, nullptr, *aClipboardType, 966 settingWindowContext); 967 if (NS_FAILED(rv)) { 968 return false; 969 } 970 } 971 } 972 973 // Now that we have copied, update the clipboard commands. This should have 974 // the effect of updating the enabled state of the paste menu item. 975 if (doDefault || count) { 976 piWindow->UpdateCommands(u"clipboard"_ns); 977 if (aPresShell && aPresShell->GetDocument()) { 978 // Record that a copy to the clipboard was triggered by JS code 979 aPresShell->GetDocument()->SetClipboardCopyTriggered(); 980 } 981 } 982 983 if (aActionTaken) { 984 *aActionTaken = true; 985 } 986 return doDefault; 987 }