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/. */
7 #include "nsCopySupport.h"
8 #include "nsGlobalWindowInner.h"
9 #include "nsIDocumentEncoder.h"
10 #include "nsISupports.h"
11 #include "nsIContent.h"
12 #include "nsIClipboard.h"
13 #include "nsIFormControl.h"
14 #include "nsWidgetsCID.h"
16 #include "nsISupportsPrimitives.h"
18 #include "imgIContainer.h"
19 #include "imgIRequest.h"
20 #include "nsComponentManagerUtils.h"
21 #include "nsFocusManager.h"
22 #include "nsFrameSelection.h"
23 #include "nsServiceManagerUtils.h"
24 #include "mozilla/ScopeExit.h"
25 #include "mozilla/dom/DataTransfer.h"
26 #include "mozilla/dom/BrowsingContext.h"
28 #include "nsIDocShell.h"
29 #include "nsIDocumentViewerEdit.h"
30 #include "nsISelectionController.h"
32 #include "nsPIDOMWindow.h"
33 #include "mozilla/dom/Document.h"
34 #include "nsHTMLDocument.h"
35 #include "nsGkAtoms.h"
39 #include "nsIImageLoadingContent.h"
40 #include "nsIInterfaceRequestorUtils.h"
41 #include "nsContentUtils.h"
44 # include "mozilla/StaticPrefs_clipboard.h"
45 # include "nsCExternalHandlerService.h"
46 # include "nsEscape.h"
47 # include "nsIMIMEInfo.h"
48 # include "nsIMIMEService.h"
49 # include "nsIURIMutator.h"
51 # include "nsReadableUtils.h"
52 # include "nsXULAppAPI.h"
55 #include "mozilla/ContentEvents.h"
56 #include "mozilla/EventDispatcher.h"
57 #include "mozilla/Preferences.h"
58 #include "mozilla/PresShell.h"
59 #include "mozilla/TextEditor.h"
60 #include "mozilla/IntegerRange.h"
61 #include "mozilla/dom/Element.h"
62 #include "mozilla/dom/HTMLInputElement.h"
63 #include "mozilla/dom/Selection.h"
65 using namespace mozilla
;
66 using namespace mozilla::dom
;
68 static NS_DEFINE_CID(kCClipboardCID
, NS_CLIPBOARD_CID
);
69 static NS_DEFINE_CID(kCTransferableCID
, NS_TRANSFERABLE_CID
);
70 static NS_DEFINE_CID(kHTMLConverterCID
, NS_HTMLFORMATCONVERTER_CID
);
72 // copy string data onto the transferable
73 static nsresult
AppendString(nsITransferable
* aTransferable
,
74 const nsAString
& aString
, const char* aFlavor
);
76 // copy HTML node data
77 static nsresult
AppendDOMNode(nsITransferable
* aTransferable
,
81 // copy image as file promise onto the transferable
82 static nsresult
AppendImagePromise(nsITransferable
* aTransferable
,
83 imgIRequest
* aImgRequest
,
87 static nsresult
EncodeForTextUnicode(nsIDocumentEncoder
& aEncoder
,
88 Document
& aDocument
, Selection
* aSelection
,
89 uint32_t aAdditionalEncoderFlags
,
90 bool& aEncodedAsTextHTMLResult
,
91 nsAutoString
& aSerializationResult
) {
92 // note that we assign text/unicode as mime type, but in fact
93 // nsHTMLCopyEncoder ignore it and use text/html or text/plain depending where
94 // the selection is. if it is a selection into input/textarea element or in a
95 // html content with pre-wrap style : text/plain. Otherwise text/html. see
96 // nsHTMLCopyEncoder::SetSelection
97 nsAutoString mimeType
;
98 mimeType
.AssignLiteral("text/unicode");
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
;
107 nsresult rv
= aEncoder
.Init(&aDocument
, mimeType
, flags
);
108 NS_ENSURE_SUCCESS(rv
, rv
);
110 rv
= aEncoder
.SetSelection(aSelection
);
111 NS_ENSURE_SUCCESS(rv
, rv
);
113 // SetSelection set the mime type to text/plain if the selection is inside a
115 rv
= aEncoder
.GetMimeType(mimeType
);
116 NS_ENSURE_SUCCESS(rv
, rv
);
117 bool selForcedTextPlain
= mimeType
.EqualsLiteral(kTextMime
);
120 rv
= aEncoder
.EncodeToString(buf
);
121 NS_ENSURE_SUCCESS(rv
, rv
);
123 rv
= aEncoder
.GetMimeType(mimeType
);
124 NS_ENSURE_SUCCESS(rv
, rv
);
126 // The mime type is ultimately text/html if the encoder successfully encoded
127 // the selection as text/html.
128 aEncodedAsTextHTMLResult
= mimeType
.EqualsLiteral(kHTMLMime
);
130 if (selForcedTextPlain
) {
131 // Nothing to do. buf contains the final, preformatted, raw text/plain.
132 aSerializationResult
.Assign(buf
);
134 // Redo the encoding, but this time use pretty printing.
136 nsIDocumentEncoder::OutputSelectionOnly
|
137 nsIDocumentEncoder::OutputAbsoluteLinks
|
138 nsIDocumentEncoder::SkipInvisibleContent
|
139 nsIDocumentEncoder::OutputDropInvisibleBreak
|
140 (aAdditionalEncoderFlags
& (nsIDocumentEncoder::OutputNoScriptContent
|
141 nsIDocumentEncoder::OutputRubyAnnotation
));
143 mimeType
.AssignLiteral(kTextMime
);
144 rv
= aEncoder
.Init(&aDocument
, mimeType
, flags
);
145 NS_ENSURE_SUCCESS(rv
, rv
);
147 rv
= aEncoder
.SetSelection(aSelection
);
148 NS_ENSURE_SUCCESS(rv
, rv
);
150 rv
= aEncoder
.EncodeToString(aSerializationResult
);
151 NS_ENSURE_SUCCESS(rv
, rv
);
157 static nsresult
EncodeAsTextHTMLWithContext(
158 nsIDocumentEncoder
& aEncoder
, Document
& aDocument
, Selection
* aSelection
,
159 uint32_t aEncoderFlags
, nsAutoString
& aTextHTMLEncodingResult
,
160 nsAutoString
& aHTMLParentsBufResult
, nsAutoString
& aHTMLInfoBufResult
) {
161 nsAutoString mimeType
;
162 mimeType
.AssignLiteral(kHTMLMime
);
163 nsresult rv
= aEncoder
.Init(&aDocument
, mimeType
, aEncoderFlags
);
164 NS_ENSURE_SUCCESS(rv
, rv
);
166 rv
= aEncoder
.SetSelection(aSelection
);
167 NS_ENSURE_SUCCESS(rv
, rv
);
169 rv
= aEncoder
.EncodeToStringWithContext(
170 aHTMLParentsBufResult
, aHTMLInfoBufResult
, aTextHTMLEncodingResult
);
171 NS_ENSURE_SUCCESS(rv
, rv
);
175 struct EncodedDocumentWithContext
{
176 // When determining `mSerializationForTextUnicode`, `text/unicode` is passed
177 // as mime type to the encoder. It uses this as a switch to decide whether to
178 // encode the document as `text/html` or `text/plain`. It is `true` iff
179 // `text/html` was used.
180 bool mUnicodeEncodingIsTextHTML
= false;
182 // The serialized document when encoding the document with `text/unicode`. See
183 // comment of `mUnicodeEncodingIsTextHTML`.
184 nsAutoString mSerializationForTextUnicode
;
186 // When `mUnicodeEncodingIsTextHTML` is true, this is the serialized document
187 // using `text/html`. Its value may differ from `mSerializationForTextHTML`,
188 // because different flags were passed to the encoder.
189 nsAutoString mSerializationForTextHTML
;
191 // When `mUnicodeEncodingIsTextHTML` is true, this contains the serialized
192 // ancestor elements.
193 nsAutoString mHTMLContextBuffer
;
195 // When `mUnicodeEncodingIsTextHTML` is true, this contains numbers
196 // identifying where in the context the serialization came from.
197 nsAutoString mHTMLInfoBuffer
;
201 * @param aSelection Can be nullptr.
202 * @param aAdditionalEncoderFlags nsIDocumentEncoder flags.
204 static nsresult
EncodeDocumentWithContext(
205 Document
& aDocument
, Selection
* aSelection
,
206 uint32_t aAdditionalEncoderFlags
,
207 EncodedDocumentWithContext
& aEncodedDocumentWithContext
) {
208 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
= do_createHTMLCopyEncoder();
210 bool unicodeEncodingIsTextHTML
{false};
211 nsAutoString serializationForTextUnicode
;
212 nsresult rv
= EncodeForTextUnicode(
213 *docEncoder
, aDocument
, aSelection
, aAdditionalEncoderFlags
,
214 unicodeEncodingIsTextHTML
, serializationForTextUnicode
);
215 NS_ENSURE_SUCCESS(rv
, rv
);
217 nsAutoString serializationForTextHTML
;
218 nsAutoString htmlContextBuffer
;
219 nsAutoString htmlInfoBuffer
;
220 if (unicodeEncodingIsTextHTML
) {
221 // Redo the encoding, but this time use the passed-in flags.
222 // Don't allow wrapping of CJK strings.
223 rv
= EncodeAsTextHTMLWithContext(
224 *docEncoder
, aDocument
, aSelection
,
225 aAdditionalEncoderFlags
|
226 nsIDocumentEncoder::OutputDisallowLineBreaking
,
227 serializationForTextHTML
, htmlContextBuffer
, htmlInfoBuffer
);
228 NS_ENSURE_SUCCESS(rv
, rv
);
231 aEncodedDocumentWithContext
= {
232 unicodeEncodingIsTextHTML
, std::move(serializationForTextUnicode
),
233 std::move(serializationForTextHTML
), std::move(htmlContextBuffer
),
234 std::move(htmlInfoBuffer
)};
239 static nsresult
CreateTransferable(
240 const EncodedDocumentWithContext
& aEncodedDocumentWithContext
,
241 Document
& aDocument
, nsCOMPtr
<nsITransferable
>& aTransferable
) {
244 aTransferable
= do_CreateInstance(kCTransferableCID
);
245 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
247 aTransferable
->Init(aDocument
.GetLoadContext());
248 aTransferable
->SetRequestingPrincipal(aDocument
.NodePrincipal());
249 if (aEncodedDocumentWithContext
.mUnicodeEncodingIsTextHTML
) {
250 // Set up a format converter so that clipboard flavor queries work.
251 // This converter isn't really used for conversions.
252 nsCOMPtr
<nsIFormatConverter
> htmlConverter
=
253 do_CreateInstance(kHTMLConverterCID
);
254 aTransferable
->SetConverter(htmlConverter
);
256 if (!aEncodedDocumentWithContext
.mSerializationForTextHTML
.IsEmpty()) {
257 // Add the html DataFlavor to the transferable
258 rv
= AppendString(aTransferable
,
259 aEncodedDocumentWithContext
.mSerializationForTextHTML
,
261 NS_ENSURE_SUCCESS(rv
, rv
);
264 // Add the htmlcontext DataFlavor to the transferable. Even if the context
265 // buffer is empty, this flavor should be attached to the transferable.
266 rv
= AppendString(aTransferable
,
267 aEncodedDocumentWithContext
.mHTMLContextBuffer
,
269 NS_ENSURE_SUCCESS(rv
, rv
);
271 if (!aEncodedDocumentWithContext
.mHTMLInfoBuffer
.IsEmpty()) {
272 // Add the htmlinfo DataFlavor to the transferable
273 rv
= AppendString(aTransferable
,
274 aEncodedDocumentWithContext
.mHTMLInfoBuffer
, kHTMLInfo
);
275 NS_ENSURE_SUCCESS(rv
, rv
);
278 if (!aEncodedDocumentWithContext
.mSerializationForTextUnicode
.IsEmpty()) {
280 // Add the plain text DataFlavor to the transferable
281 // If we didn't have this, then nsDataObj::GetData matches
282 // text/plain against the kURLMime flavour which is not desirable
283 // (eg. when pasting into Notepad)
286 aEncodedDocumentWithContext
.mSerializationForTextUnicode
, kTextMime
);
287 NS_ENSURE_SUCCESS(rv
, rv
);
290 // Try and get source URI of the items that are being dragged
291 nsIURI
* uri
= aDocument
.GetDocumentURI();
294 nsresult rv
= uri
->GetSpec(spec
);
295 NS_ENSURE_SUCCESS(rv
, rv
);
296 if (!spec
.IsEmpty()) {
297 nsAutoString shortcut
;
298 AppendUTF8toUTF16(spec
, shortcut
);
300 // Add the URL DataFlavor to the transferable. Don't use kURLMime,
301 // as it will cause an unnecessary UniformResourceLocator to be
302 // added which confuses some apps eg. Outlook 2000 - (See Bug
303 // 315370). Don't use kURLDataMime, as it will cause a bogus 'url '
304 // flavor to show up on the Mac clipboard, confusing other apps,
305 // like Terminal (see bug 336012).
306 rv
= AppendString(aTransferable
, shortcut
, kURLPrivateMime
);
307 NS_ENSURE_SUCCESS(rv
, rv
);
311 if (!aEncodedDocumentWithContext
.mSerializationForTextUnicode
.IsEmpty()) {
312 // Add the unicode DataFlavor to the transferable
315 aEncodedDocumentWithContext
.mSerializationForTextUnicode
, kTextMime
);
316 NS_ENSURE_SUCCESS(rv
, rv
);
323 static nsresult
PutToClipboard(
324 const EncodedDocumentWithContext
& aEncodedDocumentWithContext
,
325 int16_t aClipboardID
, Document
& aDocument
) {
327 nsCOMPtr
<nsIClipboard
> clipboard
= do_GetService(kCClipboardCID
, &rv
);
328 NS_ENSURE_SUCCESS(rv
, rv
);
329 NS_ENSURE_TRUE(clipboard
, NS_ERROR_NULL_POINTER
);
331 nsCOMPtr
<nsITransferable
> transferable
;
332 rv
= CreateTransferable(aEncodedDocumentWithContext
, aDocument
, transferable
);
333 NS_ENSURE_SUCCESS(rv
, rv
);
335 rv
= clipboard
->SetData(transferable
, nullptr, aClipboardID
,
336 aDocument
.GetWindowContext());
337 NS_ENSURE_SUCCESS(rv
, rv
);
342 nsresult
nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
343 Selection
* aSel
, Document
* aDoc
, int16_t aClipboardID
,
344 bool aWithRubyAnnotation
) {
345 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
347 uint32_t additionalFlags
= nsIDocumentEncoder::SkipInvisibleContent
;
348 if (aWithRubyAnnotation
) {
349 additionalFlags
|= nsIDocumentEncoder::OutputRubyAnnotation
;
352 EncodedDocumentWithContext encodedDocumentWithContext
;
353 nsresult rv
= EncodeDocumentWithContext(*aDoc
, aSel
, additionalFlags
,
354 encodedDocumentWithContext
);
355 NS_ENSURE_SUCCESS(rv
, rv
);
357 rv
= PutToClipboard(encodedDocumentWithContext
, aClipboardID
, *aDoc
);
358 NS_ENSURE_SUCCESS(rv
, rv
);
363 nsresult
nsCopySupport::ClearSelectionCache() {
365 nsCOMPtr
<nsIClipboard
> clipboard
= do_GetService(kCClipboardCID
, &rv
);
366 clipboard
->EmptyClipboard(nsIClipboard::kSelectionCache
);
371 * @param aAdditionalEncoderFlags flags of `nsIDocumentEncoder`.
372 * @param aTransferable Needs to be not `nullptr`.
374 static nsresult
EncodeDocumentWithContextAndCreateTransferable(
375 Document
& aDocument
, Selection
* aSelection
,
376 uint32_t aAdditionalEncoderFlags
, nsITransferable
** aTransferable
) {
377 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
379 // Clear the output parameter for the transferable.
380 *aTransferable
= nullptr;
382 EncodedDocumentWithContext encodedDocumentWithContext
;
384 EncodeDocumentWithContext(aDocument
, aSelection
, aAdditionalEncoderFlags
,
385 encodedDocumentWithContext
);
386 NS_ENSURE_SUCCESS(rv
, rv
);
388 nsCOMPtr
<nsITransferable
> transferable
;
389 rv
= CreateTransferable(encodedDocumentWithContext
, aDocument
, transferable
);
390 NS_ENSURE_SUCCESS(rv
, rv
);
392 transferable
.swap(*aTransferable
);
396 nsresult
nsCopySupport::GetTransferableForSelection(
397 Selection
* aSel
, Document
* aDoc
, nsITransferable
** aTransferable
) {
398 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
399 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
401 const uint32_t additionalFlags
= nsIDocumentEncoder::SkipInvisibleContent
;
402 return EncodeDocumentWithContextAndCreateTransferable(
403 *aDoc
, aSel
, additionalFlags
, aTransferable
);
406 nsresult
nsCopySupport::GetTransferableForNode(
407 nsINode
* aNode
, Document
* aDoc
, nsITransferable
** aTransferable
) {
408 NS_ENSURE_TRUE(aNode
, NS_ERROR_NULL_POINTER
);
409 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
410 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
412 // Make a temporary selection with aNode in a single range.
413 // XXX We should try to get rid of the Selection object here.
415 RefPtr
<Selection
> selection
= new Selection(SelectionType::eNormal
, nullptr);
416 RefPtr
<nsRange
> range
= nsRange::Create(aNode
);
418 range
->SelectNode(*aNode
, result
);
419 if (NS_WARN_IF(result
.Failed())) {
420 return result
.StealNSResult();
422 selection
->AddRangeAndSelectFramesAndNotifyListenersInternal(*range
, aDoc
,
424 if (NS_WARN_IF(result
.Failed())) {
425 return result
.StealNSResult();
427 // It's not the primary selection - so don't skip invisible content.
428 uint32_t additionalFlags
= 0;
429 return EncodeDocumentWithContextAndCreateTransferable(
430 *aDoc
, selection
, additionalFlags
, aTransferable
);
433 nsresult
nsCopySupport::GetContents(const nsACString
& aMimeType
,
434 uint32_t aFlags
, Selection
* aSel
,
435 Document
* aDoc
, nsAString
& outdata
) {
436 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
=
437 do_createDocumentEncoder(PromiseFlatCString(aMimeType
).get());
438 NS_ENSURE_TRUE(docEncoder
, NS_ERROR_FAILURE
);
440 uint32_t flags
= aFlags
| nsIDocumentEncoder::SkipInvisibleContent
;
442 if (aMimeType
.EqualsLiteral("text/plain"))
443 flags
|= nsIDocumentEncoder::OutputPreformatted
;
445 NS_ConvertASCIItoUTF16
unicodeMimeType(aMimeType
);
447 nsresult rv
= docEncoder
->Init(aDoc
, unicodeMimeType
, flags
);
448 if (NS_FAILED(rv
)) return rv
;
451 rv
= docEncoder
->SetSelection(aSel
);
452 if (NS_FAILED(rv
)) return rv
;
455 // encode the selection
456 return docEncoder
->EncodeToString(outdata
);
459 nsresult
nsCopySupport::ImageCopy(
460 nsIImageLoadingContent
* aImageElement
, nsILoadContext
* aLoadContext
,
461 int32_t aCopyFlags
, mozilla::dom::WindowContext
* aSettingWindowContext
) {
464 nsCOMPtr
<nsINode
> imageNode
= do_QueryInterface(aImageElement
, &rv
);
465 NS_ENSURE_SUCCESS(rv
, rv
);
467 // create a transferable for putting data on the Clipboard
468 nsCOMPtr
<nsITransferable
> trans(do_CreateInstance(kCTransferableCID
, &rv
));
469 NS_ENSURE_SUCCESS(rv
, rv
);
470 trans
->Init(aLoadContext
);
471 trans
->SetRequestingPrincipal(imageNode
->NodePrincipal());
473 if (aCopyFlags
& nsIDocumentViewerEdit::COPY_IMAGE_TEXT
) {
474 // get the location from the element
475 nsCOMPtr
<nsIURI
> uri
;
476 rv
= aImageElement
->GetCurrentURI(getter_AddRefs(uri
));
477 NS_ENSURE_SUCCESS(rv
, rv
);
478 NS_ENSURE_TRUE(uri
, NS_ERROR_FAILURE
);
480 nsAutoCString location
;
481 rv
= uri
->GetSpec(location
);
482 NS_ENSURE_SUCCESS(rv
, rv
);
484 // append the string to the transferable
485 rv
= AppendString(trans
, NS_ConvertUTF8toUTF16(location
), kTextMime
);
486 NS_ENSURE_SUCCESS(rv
, rv
);
489 if (aCopyFlags
& nsIDocumentViewerEdit::COPY_IMAGE_HTML
) {
490 // append HTML data to the transferable
491 nsCOMPtr
<nsINode
> node(do_QueryInterface(aImageElement
, &rv
));
492 NS_ENSURE_SUCCESS(rv
, rv
);
494 rv
= AppendDOMNode(trans
, node
);
495 NS_ENSURE_SUCCESS(rv
, rv
);
498 if (aCopyFlags
& nsIDocumentViewerEdit::COPY_IMAGE_DATA
) {
499 // get the image data and its request from the element
500 nsCOMPtr
<imgIRequest
> imgRequest
;
501 nsCOMPtr
<imgIContainer
> image
= nsContentUtils::GetImageFromContent(
502 aImageElement
, getter_AddRefs(imgRequest
));
503 NS_ENSURE_TRUE(image
, NS_ERROR_FAILURE
);
506 // Remember the referrer used for this image request.
507 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
508 imgRequest
->GetReferrerInfo(getter_AddRefs(referrerInfo
));
509 trans
->SetReferrerInfo(referrerInfo
);
513 if (StaticPrefs::clipboard_imageAsFile_enabled()) {
514 rv
= AppendImagePromise(trans
, imgRequest
, imageNode
);
515 NS_ENSURE_SUCCESS(rv
, rv
);
519 // copy the image data onto the transferable
520 rv
= trans
->SetTransferData(kNativeImageMime
, image
);
521 NS_ENSURE_SUCCESS(rv
, rv
);
525 nsCOMPtr
<nsIClipboard
> clipboard(do_GetService(kCClipboardCID
, &rv
));
526 NS_ENSURE_SUCCESS(rv
, rv
);
528 // check whether the system supports the selection clipboard or not.
529 if (clipboard
->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard
)) {
530 // put the transferable on the clipboard
531 rv
= clipboard
->SetData(trans
, nullptr, nsIClipboard::kSelectionClipboard
,
532 aSettingWindowContext
);
533 NS_ENSURE_SUCCESS(rv
, rv
);
536 return clipboard
->SetData(trans
, nullptr, nsIClipboard::kGlobalClipboard
,
537 aSettingWindowContext
);
540 static nsresult
AppendString(nsITransferable
* aTransferable
,
541 const nsAString
& aString
, const char* aFlavor
) {
544 nsCOMPtr
<nsISupportsString
> data(
545 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID
, &rv
));
546 NS_ENSURE_SUCCESS(rv
, rv
);
548 rv
= data
->SetData(aString
);
549 NS_ENSURE_SUCCESS(rv
, rv
);
551 rv
= aTransferable
->AddDataFlavor(aFlavor
);
552 NS_ENSURE_SUCCESS(rv
, rv
);
554 return aTransferable
->SetTransferData(aFlavor
, data
);
557 static nsresult
AppendDOMNode(nsITransferable
* aTransferable
,
562 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
= do_createHTMLCopyEncoder();
564 // get document for the encoder
565 nsCOMPtr
<Document
> document
= aDOMNode
->OwnerDoc();
567 // Note that XHTML is not counted as HTML here, because we can't copy it
568 // properly (all the copy code for non-plaintext assumes using HTML
569 // serializers and parsers is OK, and those mess up XHTML).
570 NS_ENSURE_TRUE(document
->IsHTMLDocument(), NS_OK
);
572 // init encoder with document and node
573 rv
= docEncoder
->NativeInit(
574 document
, NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime
),
575 nsIDocumentEncoder::OutputAbsoluteLinks
|
576 nsIDocumentEncoder::OutputEncodeBasicEntities
);
577 NS_ENSURE_SUCCESS(rv
, rv
);
579 rv
= docEncoder
->SetNode(aDOMNode
);
580 NS_ENSURE_SUCCESS(rv
, rv
);
582 // serialize to string
583 nsAutoString html
, context
, info
;
584 rv
= docEncoder
->EncodeToStringWithContext(context
, info
, html
);
585 NS_ENSURE_SUCCESS(rv
, rv
);
587 // copy them to the transferable
588 if (!html
.IsEmpty()) {
589 rv
= AppendString(aTransferable
, html
, kHTMLMime
);
590 NS_ENSURE_SUCCESS(rv
, rv
);
593 if (!info
.IsEmpty()) {
594 rv
= AppendString(aTransferable
, info
, kHTMLInfo
);
595 NS_ENSURE_SUCCESS(rv
, rv
);
598 // add a special flavor, even if we don't have html context data
599 return AppendString(aTransferable
, context
, kHTMLContext
);
603 static nsresult
AppendImagePromise(nsITransferable
* aTransferable
,
604 imgIRequest
* aImgRequest
,
605 nsINode
* aImageNode
) {
608 NS_ENSURE_TRUE(aImgRequest
&& aImageNode
, NS_OK
);
611 rv
= aImgRequest
->GetMultipart(&isMultipart
);
612 NS_ENSURE_SUCCESS(rv
, rv
);
617 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
618 if (NS_WARN_IF(!mimeService
)) {
619 return NS_ERROR_FAILURE
;
622 nsCOMPtr
<nsIURI
> imgUri
;
623 rv
= aImgRequest
->GetFinalURI(getter_AddRefs(imgUri
));
624 NS_ENSURE_SUCCESS(rv
, rv
);
627 rv
= imgUri
->GetSpec(spec
);
628 NS_ENSURE_SUCCESS(rv
, rv
);
630 // pass out the image source string
631 nsString imageSourceString
;
632 CopyUTF8toUTF16(spec
, imageSourceString
);
635 rv
= aImgRequest
->GetMimeType(getter_Copies(mimeType
));
636 NS_ENSURE_SUCCESS(rv
, rv
);
638 nsAutoCString fileName
;
639 rv
= aImgRequest
->GetFileName(fileName
);
640 NS_ENSURE_SUCCESS(rv
, rv
);
642 nsAutoString validFileName
= NS_ConvertUTF8toUTF16(fileName
);
643 mimeService
->ValidateFileNameForSaving(
644 validFileName
, mimeType
, nsIMIMEService::VALIDATE_DEFAULT
, validFileName
);
646 rv
= AppendString(aTransferable
, imageSourceString
, kFilePromiseURLMime
);
647 NS_ENSURE_SUCCESS(rv
, rv
);
649 rv
= AppendString(aTransferable
, validFileName
, kFilePromiseDestFilename
);
650 NS_ENSURE_SUCCESS(rv
, rv
);
652 aTransferable
->SetCookieJarSettings(
653 aImageNode
->OwnerDoc()->CookieJarSettings());
654 aTransferable
->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE
);
656 // add the dataless file promise flavor
657 return aTransferable
->AddDataFlavor(kFilePromiseMime
);
661 already_AddRefed
<Selection
> nsCopySupport::GetSelectionForCopy(
662 Document
* aDocument
) {
663 PresShell
* presShell
= aDocument
->GetPresShell();
664 if (NS_WARN_IF(!presShell
)) {
668 RefPtr
<nsFrameSelection
> frameSel
= presShell
->GetLastFocusedFrameSelection();
669 if (NS_WARN_IF(!frameSel
)) {
673 RefPtr
<Selection
> sel
= frameSel
->GetSelection(SelectionType::eNormal
);
677 bool nsCopySupport::CanCopy(Document
* aDocument
) {
682 RefPtr
<Selection
> sel
= GetSelectionForCopy(aDocument
);
683 return sel
&& !sel
->IsCollapsed();
686 static bool IsInsideRuby(nsINode
* aNode
) {
687 for (; aNode
; aNode
= aNode
->GetParent()) {
688 if (aNode
->IsHTMLElement(nsGkAtoms::ruby
)) {
695 static bool IsSelectionInsideRuby(Selection
* aSelection
) {
696 uint32_t rangeCount
= aSelection
->RangeCount();
697 for (auto i
: IntegerRange(rangeCount
)) {
698 MOZ_ASSERT(aSelection
->RangeCount() == rangeCount
);
699 const nsRange
* range
= aSelection
->GetRangeAt(i
);
700 if (!IsInsideRuby(range
->GetClosestCommonInclusiveAncestor())) {
707 static Element
* GetElementOrNearestFlattenedTreeParentElement(nsINode
* aNode
) {
708 if (!aNode
->IsContent()) {
711 for (nsIContent
* content
= aNode
->AsContent(); content
;
712 content
= content
->GetFlattenedTreeParent()) {
713 if (content
->IsElement()) {
714 return content
->AsElement();
721 * This class is used while processing clipboard paste event.
723 class MOZ_RAII AutoHandlingPasteEvent final
{
725 explicit AutoHandlingPasteEvent(nsGlobalWindowInner
* aWindow
,
726 DataTransfer
* aDataTransfer
,
727 const EventMessage
& aEventMessage
,
728 const int32_t& aClipboardType
) {
729 MOZ_ASSERT(aDataTransfer
);
730 if (aWindow
&& aEventMessage
== ePaste
&&
731 aClipboardType
== nsIClipboard::kGlobalClipboard
) {
732 aWindow
->SetCurrentPasteDataTransfer(aDataTransfer
);
733 mInnerWindow
= aWindow
;
737 ~AutoHandlingPasteEvent() {
739 mInnerWindow
->SetCurrentPasteDataTransfer(nullptr);
744 RefPtr
<nsGlobalWindowInner
> mInnerWindow
;
747 bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage
,
748 int32_t aClipboardType
,
749 PresShell
* aPresShell
,
750 Selection
* aSelection
,
751 bool* aActionTaken
) {
753 *aActionTaken
= false;
756 EventMessage originalEventMessage
= aEventMessage
;
757 if (originalEventMessage
== ePasteNoFormatting
) {
758 originalEventMessage
= ePaste
;
761 NS_ASSERTION(originalEventMessage
== eCut
|| originalEventMessage
== eCopy
||
762 originalEventMessage
== ePaste
,
763 "Invalid clipboard event type");
765 RefPtr
<PresShell
> presShell
= aPresShell
;
770 nsCOMPtr
<Document
> doc
= presShell
->GetDocument();
771 if (!doc
) return false;
773 nsCOMPtr
<nsPIDOMWindowOuter
> piWindow
= doc
->GetWindow();
774 if (!piWindow
) return false;
776 // Event target of clipboard events should be an element node which
777 // contains selection start container.
778 RefPtr
<Element
> targetElement
;
780 // If a selection was not supplied, try to find it.
781 RefPtr
<Selection
> sel
= aSelection
;
783 sel
= GetSelectionForCopy(doc
);
786 // Retrieve the event target node from the start of the selection.
788 const nsRange
* range
= sel
->GetRangeAt(0);
790 targetElement
= GetElementOrNearestFlattenedTreeParentElement(
791 range
->GetStartContainer());
795 // If there is no selection ranges, use the <body> or <frameset> element.
796 if (!targetElement
) {
797 targetElement
= doc
->GetBody();
798 if (!targetElement
) {
803 // It seems to be unsafe to fire an event handler during reflow (bug 393696)
804 if (!nsContentUtils::IsSafeToRunScript()) {
805 nsContentUtils::WarnScriptWasIgnored(doc
);
809 BrowsingContext
* bc
= piWindow
->GetBrowsingContext();
810 const bool chromeShell
= bc
&& bc
->IsChrome();
812 // next, fire the cut, copy or paste event
813 bool doDefault
= true;
814 RefPtr
<DataTransfer
> clipboardData
;
815 if (chromeShell
|| StaticPrefs::dom_event_clipboardevents_enabled()) {
817 new DataTransfer(doc
->GetScopeObject(), aEventMessage
,
818 originalEventMessage
== ePaste
, aClipboardType
);
820 nsEventStatus status
= nsEventStatus_eIgnore
;
821 InternalClipboardEvent
evt(true, originalEventMessage
);
822 evt
.mClipboardData
= clipboardData
;
825 AutoHandlingPasteEvent
autoHandlingPasteEvent(
826 nsGlobalWindowInner::Cast(doc
->GetInnerWindow()), clipboardData
,
827 aEventMessage
, aClipboardType
);
829 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
830 EventDispatcher::Dispatch(targetElement
, presContext
, &evt
, nullptr,
834 // If the event was cancelled, don't do the clipboard operation
835 doDefault
= (status
!= nsEventStatus_eConsumeNoDefault
);
838 // When this function exits, the event dispatch is over. We want to disconnect
839 // our DataTransfer, which means setting its mode to `Protected` and clearing
840 // all stored data, before we return.
841 auto clearAfter
= MakeScopeExit([&] {
843 clipboardData
->Disconnect();
845 // NOTE: Disconnect may not actually clear the DataTransfer if the
846 // dom.events.dataTransfer.protected.enabled pref is not on, so we make
847 // sure we clear here, as not clearing could provide the DataTransfer
848 // access to information from the system clipboard at an arbitrary point
850 if (originalEventMessage
== ePaste
) {
851 clipboardData
->ClearAll();
856 // No need to do anything special during a paste. Either an event listener
857 // took care of it and cancelled the event, or the caller will handle it.
858 // Return true to indicate that the event wasn't cancelled.
859 if (originalEventMessage
== ePaste
) {
861 *aActionTaken
= true;
866 // Update the presentation in case the event handler modified the selection,
868 presShell
->FlushPendingNotifications(FlushType::Frames
);
869 if (presShell
->IsDestroying()) {
873 // if the event was not cancelled, do the default copy. If the event was
874 // cancelled, use the data added to the data transfer and copy that instead.
877 // find the focused node
878 nsIContent
* sourceContent
= targetElement
.get();
879 if (targetElement
->IsInNativeAnonymousSubtree()) {
880 sourceContent
= targetElement
->FindFirstNonChromeOnlyAccessContent();
883 // If it's <input type="password"> and there is no unmasked range or
884 // there is unmasked range but it's collapsed or it'll be masked
885 // automatically, the selected password shouldn't be copied into the
887 if (RefPtr
<HTMLInputElement
> inputElement
=
888 HTMLInputElement::FromNodeOrNull(sourceContent
)) {
889 if (TextEditor
* textEditor
= inputElement
->GetTextEditor()) {
890 if (textEditor
->IsPasswordEditor() &&
891 !textEditor
->IsCopyToClipboardAllowed()) {
897 // when cutting non-editable content, do nothing
898 // XXX this is probably the wrong editable flag to check
899 if (originalEventMessage
!= eCut
|| targetElement
->IsEditable()) {
900 // get the data from the selection if any
901 if (sel
->IsCollapsed()) {
903 *aActionTaken
= true;
907 // XXX Code which decides whether we should copy text with ruby
908 // annotation is currenct depending on whether each range of the
909 // selection is inside a same ruby container. But we really should
910 // expose the full functionality in browser. See bug 1130891.
911 bool withRubyAnnotation
= IsSelectionInsideRuby(sel
);
912 nsresult rv
= EncodeDocumentWithContextAndPutToClipboard(
913 sel
, doc
, aClipboardType
, withRubyAnnotation
);
920 } else if (clipboardData
) {
921 // check to see if any data was put on the data transfer.
922 count
= clipboardData
->MozItemCount();
924 nsCOMPtr
<nsIClipboard
> clipboard(
925 do_GetService("@mozilla.org/widget/clipboard;1"));
926 NS_ENSURE_TRUE(clipboard
, false);
928 nsCOMPtr
<nsITransferable
> transferable
=
929 clipboardData
->GetTransferable(0, doc
->GetLoadContext());
931 NS_ENSURE_TRUE(transferable
, false);
933 // put the transferable on the clipboard
934 WindowContext
* settingWindowContext
= nullptr;
935 if (aPresShell
&& aPresShell
->GetDocument()) {
936 settingWindowContext
= aPresShell
->GetDocument()->GetWindowContext();
938 nsresult rv
= clipboard
->SetData(transferable
, nullptr, aClipboardType
,
939 settingWindowContext
);
946 // Now that we have copied, update the clipboard commands. This should have
947 // the effect of updating the enabled state of the paste menu item.
948 if (doDefault
|| count
) {
949 piWindow
->UpdateCommands(u
"clipboard"_ns
);
953 *aActionTaken
= true;