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 NS_ENSURE_SUCCESS(rv
, rv
);
341 nsresult
nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
342 Selection
* aSel
, Document
* aDoc
, int16_t aClipboardID
,
343 bool aWithRubyAnnotation
) {
344 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
346 uint32_t additionalFlags
= nsIDocumentEncoder::SkipInvisibleContent
;
347 if (aWithRubyAnnotation
) {
348 additionalFlags
|= nsIDocumentEncoder::OutputRubyAnnotation
;
351 EncodedDocumentWithContext encodedDocumentWithContext
;
352 nsresult rv
= EncodeDocumentWithContext(*aDoc
, aSel
, additionalFlags
,
353 encodedDocumentWithContext
);
354 NS_ENSURE_SUCCESS(rv
, rv
);
356 rv
= PutToClipboard(encodedDocumentWithContext
, aClipboardID
, *aDoc
);
357 NS_ENSURE_SUCCESS(rv
, rv
);
362 nsresult
nsCopySupport::ClearSelectionCache() {
364 nsCOMPtr
<nsIClipboard
> clipboard
= do_GetService(kCClipboardCID
, &rv
);
365 clipboard
->EmptyClipboard(nsIClipboard::kSelectionCache
);
370 * @param aAdditionalEncoderFlags flags of `nsIDocumentEncoder`.
371 * @param aTransferable Needs to be not `nullptr`.
373 static nsresult
EncodeDocumentWithContextAndCreateTransferable(
374 Document
& aDocument
, Selection
* aSelection
,
375 uint32_t aAdditionalEncoderFlags
, nsITransferable
** aTransferable
) {
376 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
378 // Clear the output parameter for the transferable.
379 *aTransferable
= nullptr;
381 EncodedDocumentWithContext encodedDocumentWithContext
;
383 EncodeDocumentWithContext(aDocument
, aSelection
, aAdditionalEncoderFlags
,
384 encodedDocumentWithContext
);
385 NS_ENSURE_SUCCESS(rv
, rv
);
387 nsCOMPtr
<nsITransferable
> transferable
;
388 rv
= CreateTransferable(encodedDocumentWithContext
, aDocument
, transferable
);
389 NS_ENSURE_SUCCESS(rv
, rv
);
391 transferable
.swap(*aTransferable
);
395 nsresult
nsCopySupport::GetTransferableForSelection(
396 Selection
* aSel
, Document
* aDoc
, nsITransferable
** aTransferable
) {
397 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
398 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
400 const uint32_t additionalFlags
= nsIDocumentEncoder::SkipInvisibleContent
;
401 return EncodeDocumentWithContextAndCreateTransferable(
402 *aDoc
, aSel
, additionalFlags
, aTransferable
);
405 nsresult
nsCopySupport::GetTransferableForNode(
406 nsINode
* aNode
, Document
* aDoc
, nsITransferable
** aTransferable
) {
407 NS_ENSURE_TRUE(aNode
, NS_ERROR_NULL_POINTER
);
408 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
409 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
411 // Make a temporary selection with aNode in a single range.
412 // XXX We should try to get rid of the Selection object here.
414 RefPtr
<Selection
> selection
= new Selection(SelectionType::eNormal
, nullptr);
415 RefPtr
<nsRange
> range
= nsRange::Create(aNode
);
417 range
->SelectNode(*aNode
, result
);
418 if (NS_WARN_IF(result
.Failed())) {
419 return result
.StealNSResult();
421 selection
->AddRangeAndSelectFramesAndNotifyListenersInternal(*range
, aDoc
,
423 if (NS_WARN_IF(result
.Failed())) {
424 return result
.StealNSResult();
426 // It's not the primary selection - so don't skip invisible content.
427 uint32_t additionalFlags
= 0;
428 return EncodeDocumentWithContextAndCreateTransferable(
429 *aDoc
, selection
, additionalFlags
, aTransferable
);
432 nsresult
nsCopySupport::GetContents(const nsACString
& aMimeType
,
433 uint32_t aFlags
, Selection
* aSel
,
434 Document
* aDoc
, nsAString
& outdata
) {
435 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
=
436 do_createDocumentEncoder(PromiseFlatCString(aMimeType
).get());
437 NS_ENSURE_TRUE(docEncoder
, NS_ERROR_FAILURE
);
439 uint32_t flags
= aFlags
| nsIDocumentEncoder::SkipInvisibleContent
;
441 if (aMimeType
.EqualsLiteral("text/plain"))
442 flags
|= nsIDocumentEncoder::OutputPreformatted
;
444 NS_ConvertASCIItoUTF16
unicodeMimeType(aMimeType
);
446 nsresult rv
= docEncoder
->Init(aDoc
, unicodeMimeType
, flags
);
447 if (NS_FAILED(rv
)) return rv
;
450 rv
= docEncoder
->SetSelection(aSel
);
451 if (NS_FAILED(rv
)) return rv
;
454 // encode the selection
455 return docEncoder
->EncodeToString(outdata
);
458 nsresult
nsCopySupport::ImageCopy(nsIImageLoadingContent
* aImageElement
,
459 nsILoadContext
* aLoadContext
,
460 int32_t aCopyFlags
) {
463 nsCOMPtr
<nsINode
> imageNode
= do_QueryInterface(aImageElement
, &rv
);
464 NS_ENSURE_SUCCESS(rv
, rv
);
466 // create a transferable for putting data on the Clipboard
467 nsCOMPtr
<nsITransferable
> trans(do_CreateInstance(kCTransferableCID
, &rv
));
468 NS_ENSURE_SUCCESS(rv
, rv
);
469 trans
->Init(aLoadContext
);
470 trans
->SetRequestingPrincipal(imageNode
->NodePrincipal());
472 if (aCopyFlags
& nsIDocumentViewerEdit::COPY_IMAGE_TEXT
) {
473 // get the location from the element
474 nsCOMPtr
<nsIURI
> uri
;
475 rv
= aImageElement
->GetCurrentURI(getter_AddRefs(uri
));
476 NS_ENSURE_SUCCESS(rv
, rv
);
477 NS_ENSURE_TRUE(uri
, NS_ERROR_FAILURE
);
479 nsAutoCString location
;
480 rv
= uri
->GetSpec(location
);
481 NS_ENSURE_SUCCESS(rv
, rv
);
483 // append the string to the transferable
484 rv
= AppendString(trans
, NS_ConvertUTF8toUTF16(location
), kTextMime
);
485 NS_ENSURE_SUCCESS(rv
, rv
);
488 if (aCopyFlags
& nsIDocumentViewerEdit::COPY_IMAGE_HTML
) {
489 // append HTML data to the transferable
490 nsCOMPtr
<nsINode
> node(do_QueryInterface(aImageElement
, &rv
));
491 NS_ENSURE_SUCCESS(rv
, rv
);
493 rv
= AppendDOMNode(trans
, node
);
494 NS_ENSURE_SUCCESS(rv
, rv
);
497 if (aCopyFlags
& nsIDocumentViewerEdit::COPY_IMAGE_DATA
) {
498 // get the image data and its request from the element
499 nsCOMPtr
<imgIRequest
> imgRequest
;
500 nsCOMPtr
<imgIContainer
> image
= nsContentUtils::GetImageFromContent(
501 aImageElement
, getter_AddRefs(imgRequest
));
502 NS_ENSURE_TRUE(image
, NS_ERROR_FAILURE
);
505 // Remember the referrer used for this image request.
506 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
507 imgRequest
->GetReferrerInfo(getter_AddRefs(referrerInfo
));
508 trans
->SetReferrerInfo(referrerInfo
);
512 if (StaticPrefs::clipboard_imageAsFile_enabled()) {
513 rv
= AppendImagePromise(trans
, imgRequest
, imageNode
);
514 NS_ENSURE_SUCCESS(rv
, rv
);
518 // copy the image data onto the transferable
519 rv
= trans
->SetTransferData(kNativeImageMime
, image
);
520 NS_ENSURE_SUCCESS(rv
, rv
);
524 nsCOMPtr
<nsIClipboard
> clipboard(do_GetService(kCClipboardCID
, &rv
));
525 NS_ENSURE_SUCCESS(rv
, rv
);
527 // check whether the system supports the selection clipboard or not.
528 if (clipboard
->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard
)) {
529 // put the transferable on the clipboard
530 rv
= clipboard
->SetData(trans
, nullptr, nsIClipboard::kSelectionClipboard
);
531 NS_ENSURE_SUCCESS(rv
, rv
);
534 return clipboard
->SetData(trans
, nullptr, nsIClipboard::kGlobalClipboard
);
537 static nsresult
AppendString(nsITransferable
* aTransferable
,
538 const nsAString
& aString
, const char* aFlavor
) {
541 nsCOMPtr
<nsISupportsString
> data(
542 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID
, &rv
));
543 NS_ENSURE_SUCCESS(rv
, rv
);
545 rv
= data
->SetData(aString
);
546 NS_ENSURE_SUCCESS(rv
, rv
);
548 rv
= aTransferable
->AddDataFlavor(aFlavor
);
549 NS_ENSURE_SUCCESS(rv
, rv
);
551 return aTransferable
->SetTransferData(aFlavor
, data
);
554 static nsresult
AppendDOMNode(nsITransferable
* aTransferable
,
559 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
= do_createHTMLCopyEncoder();
561 // get document for the encoder
562 nsCOMPtr
<Document
> document
= aDOMNode
->OwnerDoc();
564 // Note that XHTML is not counted as HTML here, because we can't copy it
565 // properly (all the copy code for non-plaintext assumes using HTML
566 // serializers and parsers is OK, and those mess up XHTML).
567 NS_ENSURE_TRUE(document
->IsHTMLDocument(), NS_OK
);
569 // init encoder with document and node
570 rv
= docEncoder
->NativeInit(
571 document
, NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime
),
572 nsIDocumentEncoder::OutputAbsoluteLinks
|
573 nsIDocumentEncoder::OutputEncodeBasicEntities
);
574 NS_ENSURE_SUCCESS(rv
, rv
);
576 rv
= docEncoder
->SetNode(aDOMNode
);
577 NS_ENSURE_SUCCESS(rv
, rv
);
579 // serialize to string
580 nsAutoString html
, context
, info
;
581 rv
= docEncoder
->EncodeToStringWithContext(context
, info
, html
);
582 NS_ENSURE_SUCCESS(rv
, rv
);
584 // copy them to the transferable
585 if (!html
.IsEmpty()) {
586 rv
= AppendString(aTransferable
, html
, kHTMLMime
);
587 NS_ENSURE_SUCCESS(rv
, rv
);
590 if (!info
.IsEmpty()) {
591 rv
= AppendString(aTransferable
, info
, kHTMLInfo
);
592 NS_ENSURE_SUCCESS(rv
, rv
);
595 // add a special flavor, even if we don't have html context data
596 return AppendString(aTransferable
, context
, kHTMLContext
);
600 static nsresult
AppendImagePromise(nsITransferable
* aTransferable
,
601 imgIRequest
* aImgRequest
,
602 nsINode
* aImageNode
) {
605 NS_ENSURE_TRUE(aImgRequest
&& aImageNode
, NS_OK
);
608 rv
= aImgRequest
->GetMultipart(&isMultipart
);
609 NS_ENSURE_SUCCESS(rv
, rv
);
614 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
615 if (NS_WARN_IF(!mimeService
)) {
616 return NS_ERROR_FAILURE
;
619 nsCOMPtr
<nsIURI
> imgUri
;
620 rv
= aImgRequest
->GetFinalURI(getter_AddRefs(imgUri
));
621 NS_ENSURE_SUCCESS(rv
, rv
);
624 rv
= imgUri
->GetSpec(spec
);
625 NS_ENSURE_SUCCESS(rv
, rv
);
627 // pass out the image source string
628 nsString imageSourceString
;
629 CopyUTF8toUTF16(spec
, imageSourceString
);
632 rv
= aImgRequest
->GetMimeType(getter_Copies(mimeType
));
633 NS_ENSURE_SUCCESS(rv
, rv
);
635 nsAutoCString fileName
;
636 rv
= aImgRequest
->GetFileName(fileName
);
637 NS_ENSURE_SUCCESS(rv
, rv
);
639 nsAutoString validFileName
= NS_ConvertUTF8toUTF16(fileName
);
640 mimeService
->ValidateFileNameForSaving(
641 validFileName
, mimeType
, nsIMIMEService::VALIDATE_DEFAULT
, validFileName
);
643 rv
= AppendString(aTransferable
, imageSourceString
, kFilePromiseURLMime
);
644 NS_ENSURE_SUCCESS(rv
, rv
);
646 rv
= AppendString(aTransferable
, validFileName
, kFilePromiseDestFilename
);
647 NS_ENSURE_SUCCESS(rv
, rv
);
649 aTransferable
->SetCookieJarSettings(
650 aImageNode
->OwnerDoc()->CookieJarSettings());
651 aTransferable
->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE
);
653 // add the dataless file promise flavor
654 return aTransferable
->AddDataFlavor(kFilePromiseMime
);
658 already_AddRefed
<Selection
> nsCopySupport::GetSelectionForCopy(
659 Document
* aDocument
) {
660 PresShell
* presShell
= aDocument
->GetPresShell();
661 if (NS_WARN_IF(!presShell
)) {
665 RefPtr
<nsFrameSelection
> frameSel
= presShell
->GetLastFocusedFrameSelection();
666 if (NS_WARN_IF(!frameSel
)) {
670 RefPtr
<Selection
> sel
= frameSel
->GetSelection(SelectionType::eNormal
);
674 bool nsCopySupport::CanCopy(Document
* aDocument
) {
679 RefPtr
<Selection
> sel
= GetSelectionForCopy(aDocument
);
680 return sel
&& !sel
->IsCollapsed();
683 static bool IsInsideRuby(nsINode
* aNode
) {
684 for (; aNode
; aNode
= aNode
->GetParent()) {
685 if (aNode
->IsHTMLElement(nsGkAtoms::ruby
)) {
692 static bool IsSelectionInsideRuby(Selection
* aSelection
) {
693 uint32_t rangeCount
= aSelection
->RangeCount();
694 for (auto i
: IntegerRange(rangeCount
)) {
695 MOZ_ASSERT(aSelection
->RangeCount() == rangeCount
);
696 const nsRange
* range
= aSelection
->GetRangeAt(i
);
697 if (!IsInsideRuby(range
->GetClosestCommonInclusiveAncestor())) {
704 static Element
* GetElementOrNearestFlattenedTreeParentElement(nsINode
* aNode
) {
705 if (!aNode
->IsContent()) {
708 for (nsIContent
* content
= aNode
->AsContent(); content
;
709 content
= content
->GetFlattenedTreeParent()) {
710 if (content
->IsElement()) {
711 return content
->AsElement();
718 * This class is used while processing clipboard paste event.
720 class MOZ_RAII AutoHandlingPasteEvent final
{
722 explicit AutoHandlingPasteEvent(nsGlobalWindowInner
* aWindow
,
723 DataTransfer
* aDataTransfer
,
724 const EventMessage
& aEventMessage
,
725 const int32_t& aClipboardType
) {
726 MOZ_ASSERT(aDataTransfer
);
727 if (aWindow
&& aEventMessage
== ePaste
&&
728 aClipboardType
== nsIClipboard::kGlobalClipboard
) {
729 aWindow
->SetCurrentPasteDataTransfer(aDataTransfer
);
730 mInnerWindow
= aWindow
;
734 ~AutoHandlingPasteEvent() {
736 mInnerWindow
->SetCurrentPasteDataTransfer(nullptr);
741 RefPtr
<nsGlobalWindowInner
> mInnerWindow
;
744 bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage
,
745 int32_t aClipboardType
,
746 PresShell
* aPresShell
,
747 Selection
* aSelection
,
748 bool* aActionTaken
) {
750 *aActionTaken
= false;
753 EventMessage originalEventMessage
= aEventMessage
;
754 if (originalEventMessage
== ePasteNoFormatting
) {
755 originalEventMessage
= ePaste
;
758 NS_ASSERTION(originalEventMessage
== eCut
|| originalEventMessage
== eCopy
||
759 originalEventMessage
== ePaste
,
760 "Invalid clipboard event type");
762 RefPtr
<PresShell
> presShell
= aPresShell
;
767 nsCOMPtr
<Document
> doc
= presShell
->GetDocument();
768 if (!doc
) return false;
770 nsCOMPtr
<nsPIDOMWindowOuter
> piWindow
= doc
->GetWindow();
771 if (!piWindow
) return false;
773 // Event target of clipboard events should be an element node which
774 // contains selection start container.
775 RefPtr
<Element
> targetElement
;
777 // If a selection was not supplied, try to find it.
778 RefPtr
<Selection
> sel
= aSelection
;
780 sel
= GetSelectionForCopy(doc
);
783 // Retrieve the event target node from the start of the selection.
785 const nsRange
* range
= sel
->GetRangeAt(0);
787 targetElement
= GetElementOrNearestFlattenedTreeParentElement(
788 range
->GetStartContainer());
792 // If there is no selection ranges, use the <body> or <frameset> element.
793 if (!targetElement
) {
794 targetElement
= doc
->GetBody();
795 if (!targetElement
) {
800 // It seems to be unsafe to fire an event handler during reflow (bug 393696)
801 if (!nsContentUtils::IsSafeToRunScript()) {
802 nsContentUtils::WarnScriptWasIgnored(doc
);
806 BrowsingContext
* bc
= piWindow
->GetBrowsingContext();
807 const bool chromeShell
= bc
&& bc
->IsChrome();
809 // next, fire the cut, copy or paste event
810 bool doDefault
= true;
811 RefPtr
<DataTransfer
> clipboardData
;
812 if (chromeShell
|| StaticPrefs::dom_event_clipboardevents_enabled()) {
814 new DataTransfer(doc
->GetScopeObject(), aEventMessage
,
815 originalEventMessage
== ePaste
, aClipboardType
);
817 nsEventStatus status
= nsEventStatus_eIgnore
;
818 InternalClipboardEvent
evt(true, originalEventMessage
);
819 evt
.mClipboardData
= clipboardData
;
822 AutoHandlingPasteEvent
autoHandlingPasteEvent(
823 nsGlobalWindowInner::Cast(doc
->GetInnerWindow()), clipboardData
,
824 aEventMessage
, aClipboardType
);
826 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
827 EventDispatcher::Dispatch(targetElement
, presContext
, &evt
, nullptr,
831 // If the event was cancelled, don't do the clipboard operation
832 doDefault
= (status
!= nsEventStatus_eConsumeNoDefault
);
835 // When this function exits, the event dispatch is over. We want to disconnect
836 // our DataTransfer, which means setting its mode to `Protected` and clearing
837 // all stored data, before we return.
838 auto clearAfter
= MakeScopeExit([&] {
840 clipboardData
->Disconnect();
842 // NOTE: Disconnect may not actually clear the DataTransfer if the
843 // dom.events.dataTransfer.protected.enabled pref is not on, so we make
844 // sure we clear here, as not clearing could provide the DataTransfer
845 // access to information from the system clipboard at an arbitrary point
847 if (originalEventMessage
== ePaste
) {
848 clipboardData
->ClearAll();
853 // No need to do anything special during a paste. Either an event listener
854 // took care of it and cancelled the event, or the caller will handle it.
855 // Return true to indicate that the event wasn't cancelled.
856 if (originalEventMessage
== ePaste
) {
858 *aActionTaken
= true;
863 // Update the presentation in case the event handler modified the selection,
865 presShell
->FlushPendingNotifications(FlushType::Frames
);
866 if (presShell
->IsDestroying()) {
870 // if the event was not cancelled, do the default copy. If the event was
871 // cancelled, use the data added to the data transfer and copy that instead.
874 // find the focused node
875 nsIContent
* sourceContent
= targetElement
.get();
876 if (targetElement
->IsInNativeAnonymousSubtree()) {
877 sourceContent
= targetElement
->FindFirstNonChromeOnlyAccessContent();
880 // If it's <input type="password"> and there is no unmasked range or
881 // there is unmasked range but it's collapsed or it'll be masked
882 // automatically, the selected password shouldn't be copied into the
884 if (RefPtr
<HTMLInputElement
> inputElement
=
885 HTMLInputElement::FromNodeOrNull(sourceContent
)) {
886 if (TextEditor
* textEditor
= inputElement
->GetTextEditor()) {
887 if (textEditor
->IsPasswordEditor() &&
888 !textEditor
->IsCopyToClipboardAllowed()) {
894 // when cutting non-editable content, do nothing
895 // XXX this is probably the wrong editable flag to check
896 if (originalEventMessage
!= eCut
|| targetElement
->IsEditable()) {
897 // get the data from the selection if any
898 if (sel
->IsCollapsed()) {
900 *aActionTaken
= true;
904 // XXX Code which decides whether we should copy text with ruby
905 // annotation is currenct depending on whether each range of the
906 // selection is inside a same ruby container. But we really should
907 // expose the full functionality in browser. See bug 1130891.
908 bool withRubyAnnotation
= IsSelectionInsideRuby(sel
);
909 nsresult rv
= EncodeDocumentWithContextAndPutToClipboard(
910 sel
, doc
, aClipboardType
, withRubyAnnotation
);
917 } else if (clipboardData
) {
918 // check to see if any data was put on the data transfer.
919 count
= clipboardData
->MozItemCount();
921 nsCOMPtr
<nsIClipboard
> clipboard(
922 do_GetService("@mozilla.org/widget/clipboard;1"));
923 NS_ENSURE_TRUE(clipboard
, false);
925 nsCOMPtr
<nsITransferable
> transferable
=
926 clipboardData
->GetTransferable(0, doc
->GetLoadContext());
928 NS_ENSURE_TRUE(transferable
, false);
930 // put the transferable on the clipboard
931 nsresult rv
= clipboard
->SetData(transferable
, nullptr, aClipboardType
);
938 // Now that we have copied, update the clipboard commands. This should have
939 // the effect of updating the enabled state of the paste menu item.
940 if (doDefault
|| count
) {
941 piWindow
->UpdateCommands(u
"clipboard"_ns
);
945 *aActionTaken
= true;