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 "nsIDocumentEncoder.h"
9 #include "nsISupports.h"
10 #include "nsIContent.h"
11 #include "nsIClipboard.h"
12 #include "nsIFormControl.h"
13 #include "nsWidgetsCID.h"
15 #include "nsISupportsPrimitives.h"
17 #include "imgIContainer.h"
18 #include "imgIRequest.h"
19 #include "nsComponentManagerUtils.h"
20 #include "nsFocusManager.h"
21 #include "nsFrameSelection.h"
22 #include "nsServiceManagerUtils.h"
23 #include "mozilla/ScopeExit.h"
24 #include "mozilla/dom/DataTransfer.h"
26 #include "nsIDocShell.h"
27 #include "nsIContentViewerEdit.h"
28 #include "nsISelectionController.h"
30 #include "nsPIDOMWindow.h"
31 #include "mozilla/dom/Document.h"
32 #include "nsHTMLDocument.h"
33 #include "nsGkAtoms.h"
37 #include "nsIImageLoadingContent.h"
38 #include "nsIInterfaceRequestorUtils.h"
39 #include "nsContentUtils.h"
40 #include "nsContentCID.h"
43 # include "nsCExternalHandlerService.h"
44 # include "nsEscape.h"
45 # include "nsIMIMEInfo.h"
46 # include "nsIMIMEService.h"
47 # include "nsIURIMutator.h"
49 # include "nsReadableUtils.h"
50 # include "nsXULAppAPI.h"
53 #include "mozilla/ContentEvents.h"
54 #include "mozilla/EventDispatcher.h"
55 #include "mozilla/Preferences.h"
56 #include "mozilla/PresShell.h"
57 #include "mozilla/TextEditor.h"
58 #include "mozilla/IntegerRange.h"
59 #include "mozilla/dom/Element.h"
60 #include "mozilla/dom/HTMLInputElement.h"
61 #include "mozilla/dom/Selection.h"
63 using namespace mozilla
;
64 using namespace mozilla::dom
;
66 static NS_DEFINE_CID(kCClipboardCID
, NS_CLIPBOARD_CID
);
67 static NS_DEFINE_CID(kCTransferableCID
, NS_TRANSFERABLE_CID
);
68 static NS_DEFINE_CID(kHTMLConverterCID
, NS_HTMLFORMATCONVERTER_CID
);
70 // copy string data onto the transferable
71 static nsresult
AppendString(nsITransferable
* aTransferable
,
72 const nsAString
& aString
, const char* aFlavor
);
74 // copy HTML node data
75 static nsresult
AppendDOMNode(nsITransferable
* aTransferable
,
79 // copy image as file promise onto the transferable
80 static nsresult
AppendImagePromise(nsITransferable
* aTransferable
,
81 imgIRequest
* aImgRequest
,
82 nsIImageLoadingContent
* aImageElement
);
85 static nsresult
EncodeForTextUnicode(nsIDocumentEncoder
& aEncoder
,
86 Document
& aDocument
, Selection
* aSelection
,
87 uint32_t aAdditionalEncoderFlags
,
88 bool& aEncodedAsTextHTMLResult
,
89 nsAutoString
& aSerializationResult
) {
90 // note that we assign text/unicode as mime type, but in fact
91 // nsHTMLCopyEncoder ignore it and use text/html or text/plain depending where
92 // the selection is. if it is a selection into input/textarea element or in a
93 // html content with pre-wrap style : text/plain. Otherwise text/html. see
94 // nsHTMLCopyEncoder::SetSelection
95 nsAutoString mimeType
;
96 mimeType
.AssignLiteral("text/unicode");
98 // Do the first and potentially trial encoding as preformatted and raw.
99 uint32_t flags
= aAdditionalEncoderFlags
|
100 nsIDocumentEncoder::OutputPreformatted
|
101 nsIDocumentEncoder::OutputRaw
|
102 nsIDocumentEncoder::OutputForPlainTextClipboardCopy
|
103 nsIDocumentEncoder::OutputPersistNBSP
;
105 nsresult rv
= aEncoder
.Init(&aDocument
, mimeType
, flags
);
106 NS_ENSURE_SUCCESS(rv
, rv
);
108 rv
= aEncoder
.SetSelection(aSelection
);
109 NS_ENSURE_SUCCESS(rv
, rv
);
111 // SetSelection set the mime type to text/plain if the selection is inside a
113 rv
= aEncoder
.GetMimeType(mimeType
);
114 NS_ENSURE_SUCCESS(rv
, rv
);
115 bool selForcedTextPlain
= mimeType
.EqualsLiteral(kTextMime
);
118 rv
= aEncoder
.EncodeToString(buf
);
119 NS_ENSURE_SUCCESS(rv
, rv
);
121 rv
= aEncoder
.GetMimeType(mimeType
);
122 NS_ENSURE_SUCCESS(rv
, rv
);
124 // The mime type is ultimately text/html if the encoder successfully encoded
125 // the selection as text/html.
126 aEncodedAsTextHTMLResult
= mimeType
.EqualsLiteral(kHTMLMime
);
128 if (selForcedTextPlain
) {
129 // Nothing to do. buf contains the final, preformatted, raw text/plain.
130 aSerializationResult
.Assign(buf
);
132 // Redo the encoding, but this time use pretty printing.
134 nsIDocumentEncoder::OutputSelectionOnly
|
135 nsIDocumentEncoder::OutputAbsoluteLinks
|
136 nsIDocumentEncoder::SkipInvisibleContent
|
137 nsIDocumentEncoder::OutputDropInvisibleBreak
|
138 (aAdditionalEncoderFlags
& (nsIDocumentEncoder::OutputNoScriptContent
|
139 nsIDocumentEncoder::OutputRubyAnnotation
));
141 mimeType
.AssignLiteral(kTextMime
);
142 rv
= aEncoder
.Init(&aDocument
, mimeType
, flags
);
143 NS_ENSURE_SUCCESS(rv
, rv
);
145 rv
= aEncoder
.SetSelection(aSelection
);
146 NS_ENSURE_SUCCESS(rv
, rv
);
148 rv
= aEncoder
.EncodeToString(aSerializationResult
);
149 NS_ENSURE_SUCCESS(rv
, rv
);
155 static nsresult
EncodeAsTextHTMLWithContext(
156 nsIDocumentEncoder
& aEncoder
, Document
& aDocument
, Selection
* aSelection
,
157 uint32_t aEncoderFlags
, nsAutoString
& aTextHTMLEncodingResult
,
158 nsAutoString
& aHTMLParentsBufResult
, nsAutoString
& aHTMLInfoBufResult
) {
159 nsAutoString mimeType
;
160 mimeType
.AssignLiteral(kHTMLMime
);
161 nsresult rv
= aEncoder
.Init(&aDocument
, mimeType
, aEncoderFlags
);
162 NS_ENSURE_SUCCESS(rv
, rv
);
164 rv
= aEncoder
.SetSelection(aSelection
);
165 NS_ENSURE_SUCCESS(rv
, rv
);
167 rv
= aEncoder
.EncodeToStringWithContext(
168 aHTMLParentsBufResult
, aHTMLInfoBufResult
, aTextHTMLEncodingResult
);
169 NS_ENSURE_SUCCESS(rv
, rv
);
173 struct EncodedDocumentWithContext
{
174 // When determining `mSerializationForTextUnicode`, `text/unicode` is passed
175 // as mime type to the encoder. It uses this as a switch to decide whether to
176 // encode the document as `text/html` or `text/plain`. It is `true` iff
177 // `text/html` was used.
178 bool mUnicodeEncodingIsTextHTML
= false;
180 // The serialized document when encoding the document with `text/unicode`. See
181 // comment of `mUnicodeEncodingIsTextHTML`.
182 nsAutoString mSerializationForTextUnicode
;
184 // When `mUnicodeEncodingIsTextHTML` is true, this is the serialized document
185 // using `text/html`. Its value may differ from `mSerializationForTextHTML`,
186 // because different flags were passed to the encoder.
187 nsAutoString mSerializationForTextHTML
;
189 // When `mUnicodeEncodingIsTextHTML` is true, this contains the serialized
190 // ancestor elements.
191 nsAutoString mHTMLContextBuffer
;
193 // When `mUnicodeEncodingIsTextHTML` is true, this contains numbers
194 // identifying where in the context the serialization came from.
195 nsAutoString mHTMLInfoBuffer
;
199 * @param aSelection Can be nullptr.
200 * @param aAdditionalEncoderFlags nsIDocumentEncoder flags.
202 static nsresult
EncodeDocumentWithContext(
203 Document
& aDocument
, Selection
* aSelection
,
204 uint32_t aAdditionalEncoderFlags
,
205 EncodedDocumentWithContext
& aEncodedDocumentWithContext
) {
206 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
= do_createHTMLCopyEncoder();
208 bool unicodeEncodingIsTextHTML
{false};
209 nsAutoString serializationForTextUnicode
;
210 nsresult rv
= EncodeForTextUnicode(
211 *docEncoder
, aDocument
, aSelection
, aAdditionalEncoderFlags
,
212 unicodeEncodingIsTextHTML
, serializationForTextUnicode
);
213 NS_ENSURE_SUCCESS(rv
, rv
);
215 nsAutoString serializationForTextHTML
;
216 nsAutoString htmlContextBuffer
;
217 nsAutoString htmlInfoBuffer
;
218 if (unicodeEncodingIsTextHTML
) {
219 // Redo the encoding, but this time use the passed-in flags.
220 // Don't allow wrapping of CJK strings.
221 rv
= EncodeAsTextHTMLWithContext(
222 *docEncoder
, aDocument
, aSelection
,
223 aAdditionalEncoderFlags
|
224 nsIDocumentEncoder::OutputDisallowLineBreaking
,
225 serializationForTextHTML
, htmlContextBuffer
, htmlInfoBuffer
);
226 NS_ENSURE_SUCCESS(rv
, rv
);
229 aEncodedDocumentWithContext
= {
230 unicodeEncodingIsTextHTML
, std::move(serializationForTextUnicode
),
231 std::move(serializationForTextHTML
), std::move(htmlContextBuffer
),
232 std::move(htmlInfoBuffer
)};
237 static nsresult
CreateTransferable(
238 const EncodedDocumentWithContext
& aEncodedDocumentWithContext
,
239 Document
& aDocument
, nsCOMPtr
<nsITransferable
>& aTransferable
) {
242 aTransferable
= do_CreateInstance(kCTransferableCID
);
243 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
245 aTransferable
->Init(aDocument
.GetLoadContext());
246 if (aEncodedDocumentWithContext
.mUnicodeEncodingIsTextHTML
) {
247 // Set up a format converter so that clipboard flavor queries work.
248 // This converter isn't really used for conversions.
249 nsCOMPtr
<nsIFormatConverter
> htmlConverter
=
250 do_CreateInstance(kHTMLConverterCID
);
251 aTransferable
->SetConverter(htmlConverter
);
253 if (!aEncodedDocumentWithContext
.mSerializationForTextHTML
.IsEmpty()) {
254 // Add the html DataFlavor to the transferable
255 rv
= AppendString(aTransferable
,
256 aEncodedDocumentWithContext
.mSerializationForTextHTML
,
258 NS_ENSURE_SUCCESS(rv
, rv
);
261 // Add the htmlcontext DataFlavor to the transferable. Even if the context
262 // buffer is empty, this flavor should be attached to the transferable.
263 rv
= AppendString(aTransferable
,
264 aEncodedDocumentWithContext
.mHTMLContextBuffer
,
266 NS_ENSURE_SUCCESS(rv
, rv
);
268 if (!aEncodedDocumentWithContext
.mHTMLInfoBuffer
.IsEmpty()) {
269 // Add the htmlinfo DataFlavor to the transferable
270 rv
= AppendString(aTransferable
,
271 aEncodedDocumentWithContext
.mHTMLInfoBuffer
, kHTMLInfo
);
272 NS_ENSURE_SUCCESS(rv
, rv
);
275 if (!aEncodedDocumentWithContext
.mSerializationForTextUnicode
.IsEmpty()) {
277 // Add the plain text DataFlavor to the transferable
278 // If we didn't have this, then nsDataObj::GetData matches
279 // text/plain against the kURLMime flavour which is not desirable
280 // (eg. when pasting into Notepad)
283 aEncodedDocumentWithContext
.mSerializationForTextUnicode
, kTextMime
);
284 NS_ENSURE_SUCCESS(rv
, rv
);
287 // Try and get source URI of the items that are being dragged
288 nsIURI
* uri
= aDocument
.GetDocumentURI();
291 nsresult rv
= uri
->GetSpec(spec
);
292 NS_ENSURE_SUCCESS(rv
, rv
);
293 if (!spec
.IsEmpty()) {
294 nsAutoString shortcut
;
295 AppendUTF8toUTF16(spec
, shortcut
);
297 // Add the URL DataFlavor to the transferable. Don't use kURLMime,
298 // as it will cause an unnecessary UniformResourceLocator to be
299 // added which confuses some apps eg. Outlook 2000 - (See Bug
300 // 315370). Don't use kURLDataMime, as it will cause a bogus 'url '
301 // flavor to show up on the Mac clipboard, confusing other apps,
302 // like Terminal (see bug 336012).
303 rv
= AppendString(aTransferable
, shortcut
, kURLPrivateMime
);
304 NS_ENSURE_SUCCESS(rv
, rv
);
308 if (!aEncodedDocumentWithContext
.mSerializationForTextUnicode
.IsEmpty()) {
309 // Add the unicode DataFlavor to the transferable
312 aEncodedDocumentWithContext
.mSerializationForTextUnicode
, kTextMime
);
313 NS_ENSURE_SUCCESS(rv
, rv
);
320 static nsresult
PutToClipboard(
321 const EncodedDocumentWithContext
& aEncodedDocumentWithContext
,
322 int16_t aClipboardID
, Document
& aDocument
) {
324 nsCOMPtr
<nsIClipboard
> clipboard
= do_GetService(kCClipboardCID
, &rv
);
325 NS_ENSURE_SUCCESS(rv
, rv
);
326 NS_ENSURE_TRUE(clipboard
, NS_ERROR_NULL_POINTER
);
328 nsCOMPtr
<nsITransferable
> transferable
;
329 rv
= CreateTransferable(aEncodedDocumentWithContext
, aDocument
, transferable
);
330 NS_ENSURE_SUCCESS(rv
, rv
);
332 rv
= clipboard
->SetData(transferable
, nullptr, aClipboardID
);
333 NS_ENSURE_SUCCESS(rv
, rv
);
338 nsresult
nsCopySupport::EncodeDocumentWithContextAndPutToClipboard(
339 Selection
* aSel
, Document
* aDoc
, int16_t aClipboardID
,
340 bool aWithRubyAnnotation
) {
341 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
343 uint32_t additionalFlags
= nsIDocumentEncoder::SkipInvisibleContent
;
344 if (aWithRubyAnnotation
) {
345 additionalFlags
|= nsIDocumentEncoder::OutputRubyAnnotation
;
348 EncodedDocumentWithContext encodedDocumentWithContext
;
349 nsresult rv
= EncodeDocumentWithContext(*aDoc
, aSel
, additionalFlags
,
350 encodedDocumentWithContext
);
351 NS_ENSURE_SUCCESS(rv
, rv
);
353 rv
= PutToClipboard(encodedDocumentWithContext
, aClipboardID
, *aDoc
);
354 NS_ENSURE_SUCCESS(rv
, rv
);
359 nsresult
nsCopySupport::ClearSelectionCache() {
361 nsCOMPtr
<nsIClipboard
> clipboard
= do_GetService(kCClipboardCID
, &rv
);
362 clipboard
->EmptyClipboard(nsIClipboard::kSelectionCache
);
367 * @param aAdditionalEncoderFlags flags of `nsIDocumentEncoder`.
368 * @param aTransferable Needs to be not `nullptr`.
370 static nsresult
EncodeDocumentWithContextAndCreateTransferable(
371 Document
& aDocument
, Selection
* aSelection
,
372 uint32_t aAdditionalEncoderFlags
, nsITransferable
** aTransferable
) {
373 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
375 // Clear the output parameter for the transferable.
376 *aTransferable
= nullptr;
378 EncodedDocumentWithContext encodedDocumentWithContext
;
380 EncodeDocumentWithContext(aDocument
, aSelection
, aAdditionalEncoderFlags
,
381 encodedDocumentWithContext
);
382 NS_ENSURE_SUCCESS(rv
, rv
);
384 nsCOMPtr
<nsITransferable
> transferable
;
385 rv
= CreateTransferable(encodedDocumentWithContext
, aDocument
, transferable
);
386 NS_ENSURE_SUCCESS(rv
, rv
);
388 transferable
.swap(*aTransferable
);
392 nsresult
nsCopySupport::GetTransferableForSelection(
393 Selection
* aSel
, Document
* aDoc
, nsITransferable
** aTransferable
) {
394 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
395 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
397 const uint32_t additionalFlags
= nsIDocumentEncoder::SkipInvisibleContent
;
398 return EncodeDocumentWithContextAndCreateTransferable(
399 *aDoc
, aSel
, additionalFlags
, aTransferable
);
402 nsresult
nsCopySupport::GetTransferableForNode(
403 nsINode
* aNode
, Document
* aDoc
, nsITransferable
** aTransferable
) {
404 NS_ENSURE_TRUE(aNode
, NS_ERROR_NULL_POINTER
);
405 NS_ENSURE_TRUE(aDoc
, NS_ERROR_NULL_POINTER
);
406 NS_ENSURE_TRUE(aTransferable
, NS_ERROR_NULL_POINTER
);
408 // Make a temporary selection with aNode in a single range.
409 // XXX We should try to get rid of the Selection object here.
411 RefPtr
<Selection
> selection
= new Selection(SelectionType::eNormal
, nullptr);
412 RefPtr
<nsRange
> range
= nsRange::Create(aNode
);
414 range
->SelectNode(*aNode
, result
);
415 if (NS_WARN_IF(result
.Failed())) {
416 return result
.StealNSResult();
418 selection
->AddRangeAndSelectFramesAndNotifyListenersInternal(*range
, aDoc
,
420 if (NS_WARN_IF(result
.Failed())) {
421 return result
.StealNSResult();
423 // It's not the primary selection - so don't skip invisible content.
424 uint32_t additionalFlags
= 0;
425 return EncodeDocumentWithContextAndCreateTransferable(
426 *aDoc
, selection
, additionalFlags
, aTransferable
);
429 nsresult
nsCopySupport::GetContents(const nsACString
& aMimeType
,
430 uint32_t aFlags
, Selection
* aSel
,
431 Document
* aDoc
, nsAString
& outdata
) {
432 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
=
433 do_createDocumentEncoder(PromiseFlatCString(aMimeType
).get());
434 NS_ENSURE_TRUE(docEncoder
, NS_ERROR_FAILURE
);
436 uint32_t flags
= aFlags
| nsIDocumentEncoder::SkipInvisibleContent
;
438 if (aMimeType
.EqualsLiteral("text/plain"))
439 flags
|= nsIDocumentEncoder::OutputPreformatted
;
441 NS_ConvertASCIItoUTF16
unicodeMimeType(aMimeType
);
443 nsresult rv
= docEncoder
->Init(aDoc
, unicodeMimeType
, flags
);
444 if (NS_FAILED(rv
)) return rv
;
447 rv
= docEncoder
->SetSelection(aSel
);
448 if (NS_FAILED(rv
)) return rv
;
451 // encode the selection
452 return docEncoder
->EncodeToString(outdata
);
455 nsresult
nsCopySupport::ImageCopy(nsIImageLoadingContent
* aImageElement
,
456 nsILoadContext
* aLoadContext
,
457 int32_t aCopyFlags
) {
460 // create a transferable for putting data on the Clipboard
461 nsCOMPtr
<nsITransferable
> trans(do_CreateInstance(kCTransferableCID
, &rv
));
462 NS_ENSURE_SUCCESS(rv
, rv
);
463 trans
->Init(aLoadContext
);
465 if (aCopyFlags
& nsIContentViewerEdit::COPY_IMAGE_TEXT
) {
466 // get the location from the element
467 nsCOMPtr
<nsIURI
> uri
;
468 rv
= aImageElement
->GetCurrentURI(getter_AddRefs(uri
));
469 NS_ENSURE_SUCCESS(rv
, rv
);
470 NS_ENSURE_TRUE(uri
, NS_ERROR_FAILURE
);
472 nsAutoCString location
;
473 rv
= uri
->GetSpec(location
);
474 NS_ENSURE_SUCCESS(rv
, rv
);
476 // append the string to the transferable
477 rv
= AppendString(trans
, NS_ConvertUTF8toUTF16(location
), kTextMime
);
478 NS_ENSURE_SUCCESS(rv
, rv
);
481 if (aCopyFlags
& nsIContentViewerEdit::COPY_IMAGE_HTML
) {
482 // append HTML data to the transferable
483 nsCOMPtr
<nsINode
> node(do_QueryInterface(aImageElement
, &rv
));
484 NS_ENSURE_SUCCESS(rv
, rv
);
486 rv
= AppendDOMNode(trans
, node
);
487 NS_ENSURE_SUCCESS(rv
, rv
);
490 if (aCopyFlags
& nsIContentViewerEdit::COPY_IMAGE_DATA
) {
491 // get the image data and its request from the element
492 nsCOMPtr
<imgIRequest
> imgRequest
;
493 nsCOMPtr
<imgIContainer
> image
= nsContentUtils::GetImageFromContent(
494 aImageElement
, getter_AddRefs(imgRequest
));
495 NS_ENSURE_TRUE(image
, NS_ERROR_FAILURE
);
498 // Remember the referrer used for this image request.
499 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
;
500 imgRequest
->GetReferrerInfo(getter_AddRefs(referrerInfo
));
501 trans
->SetReferrerInfo(referrerInfo
);
505 rv
= AppendImagePromise(trans
, imgRequest
, aImageElement
);
506 NS_ENSURE_SUCCESS(rv
, rv
);
509 // copy the image data onto the transferable
510 rv
= trans
->SetTransferData(kNativeImageMime
, image
);
511 NS_ENSURE_SUCCESS(rv
, rv
);
515 nsCOMPtr
<nsIClipboard
> clipboard(do_GetService(kCClipboardCID
, &rv
));
516 NS_ENSURE_SUCCESS(rv
, rv
);
518 // check whether the system supports the selection clipboard or not.
519 if (clipboard
->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard
)) {
520 // put the transferable on the clipboard
521 rv
= clipboard
->SetData(trans
, nullptr, nsIClipboard::kSelectionClipboard
);
522 NS_ENSURE_SUCCESS(rv
, rv
);
525 return clipboard
->SetData(trans
, nullptr, nsIClipboard::kGlobalClipboard
);
528 static nsresult
AppendString(nsITransferable
* aTransferable
,
529 const nsAString
& aString
, const char* aFlavor
) {
532 nsCOMPtr
<nsISupportsString
> data(
533 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID
, &rv
));
534 NS_ENSURE_SUCCESS(rv
, rv
);
536 rv
= data
->SetData(aString
);
537 NS_ENSURE_SUCCESS(rv
, rv
);
539 rv
= aTransferable
->AddDataFlavor(aFlavor
);
540 NS_ENSURE_SUCCESS(rv
, rv
);
542 return aTransferable
->SetTransferData(aFlavor
, data
);
545 static nsresult
AppendDOMNode(nsITransferable
* aTransferable
,
550 nsCOMPtr
<nsIDocumentEncoder
> docEncoder
= do_createHTMLCopyEncoder();
552 // get document for the encoder
553 nsCOMPtr
<Document
> document
= aDOMNode
->OwnerDoc();
555 // Note that XHTML is not counted as HTML here, because we can't copy it
556 // properly (all the copy code for non-plaintext assumes using HTML
557 // serializers and parsers is OK, and those mess up XHTML).
558 NS_ENSURE_TRUE(document
->IsHTMLDocument(), NS_OK
);
560 // init encoder with document and node
561 rv
= docEncoder
->NativeInit(
562 document
, NS_LITERAL_STRING_FROM_CSTRING(kHTMLMime
),
563 nsIDocumentEncoder::OutputAbsoluteLinks
|
564 nsIDocumentEncoder::OutputEncodeBasicEntities
);
565 NS_ENSURE_SUCCESS(rv
, rv
);
567 rv
= docEncoder
->SetNode(aDOMNode
);
568 NS_ENSURE_SUCCESS(rv
, rv
);
570 // serialize to string
571 nsAutoString html
, context
, info
;
572 rv
= docEncoder
->EncodeToStringWithContext(context
, info
, html
);
573 NS_ENSURE_SUCCESS(rv
, rv
);
575 // copy them to the transferable
576 if (!html
.IsEmpty()) {
577 rv
= AppendString(aTransferable
, html
, kHTMLMime
);
578 NS_ENSURE_SUCCESS(rv
, rv
);
581 if (!info
.IsEmpty()) {
582 rv
= AppendString(aTransferable
, info
, kHTMLInfo
);
583 NS_ENSURE_SUCCESS(rv
, rv
);
586 // add a special flavor, even if we don't have html context data
587 return AppendString(aTransferable
, context
, kHTMLContext
);
591 static nsresult
AppendImagePromise(nsITransferable
* aTransferable
,
592 imgIRequest
* aImgRequest
,
593 nsIImageLoadingContent
* aImageElement
) {
596 NS_ENSURE_TRUE(aImgRequest
, NS_OK
);
599 rv
= aImgRequest
->GetMultipart(&isMultipart
);
600 NS_ENSURE_SUCCESS(rv
, rv
);
605 nsCOMPtr
<nsINode
> node
= do_QueryInterface(aImageElement
, &rv
);
606 NS_ENSURE_SUCCESS(rv
, rv
);
608 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
609 if (NS_WARN_IF(!mimeService
)) {
610 return NS_ERROR_FAILURE
;
613 nsCOMPtr
<nsIURI
> imgUri
;
614 rv
= aImgRequest
->GetFinalURI(getter_AddRefs(imgUri
));
615 NS_ENSURE_SUCCESS(rv
, rv
);
618 rv
= imgUri
->GetSpec(spec
);
619 NS_ENSURE_SUCCESS(rv
, rv
);
621 // pass out the image source string
622 nsString imageSourceString
;
623 CopyUTF8toUTF16(spec
, imageSourceString
);
626 rv
= aImgRequest
->GetMimeType(getter_Copies(mimeType
));
627 NS_ENSURE_SUCCESS(rv
, rv
);
629 nsAutoCString fileName
;
630 rv
= aImgRequest
->GetFileName(fileName
);
631 NS_ENSURE_SUCCESS(rv
, rv
);
633 nsAutoString validFileName
= NS_ConvertUTF8toUTF16(fileName
);
634 mimeService
->ValidateFileNameForSaving(
635 validFileName
, mimeType
, nsIMIMEService::VALIDATE_DEFAULT
, validFileName
);
637 rv
= AppendString(aTransferable
, imageSourceString
, kFilePromiseURLMime
);
638 NS_ENSURE_SUCCESS(rv
, rv
);
640 rv
= AppendString(aTransferable
, validFileName
, kFilePromiseDestFilename
);
641 NS_ENSURE_SUCCESS(rv
, rv
);
643 aTransferable
->SetRequestingPrincipal(node
->NodePrincipal());
644 aTransferable
->SetCookieJarSettings(node
->OwnerDoc()->CookieJarSettings());
645 aTransferable
->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE
);
647 // add the dataless file promise flavor
648 return aTransferable
->AddDataFlavor(kFilePromiseMime
);
652 already_AddRefed
<Selection
> nsCopySupport::GetSelectionForCopy(
653 Document
* aDocument
) {
654 PresShell
* presShell
= aDocument
->GetPresShell();
655 if (NS_WARN_IF(!presShell
)) {
659 RefPtr
<nsFrameSelection
> frameSel
= presShell
->GetLastFocusedFrameSelection();
660 if (NS_WARN_IF(!frameSel
)) {
664 RefPtr
<Selection
> sel
= frameSel
->GetSelection(SelectionType::eNormal
);
668 bool nsCopySupport::CanCopy(Document
* aDocument
) {
673 RefPtr
<Selection
> sel
= GetSelectionForCopy(aDocument
);
674 return sel
&& !sel
->IsCollapsed();
677 static bool IsInsideRuby(nsINode
* aNode
) {
678 for (; aNode
; aNode
= aNode
->GetParent()) {
679 if (aNode
->IsHTMLElement(nsGkAtoms::ruby
)) {
686 static bool IsSelectionInsideRuby(Selection
* aSelection
) {
687 uint32_t rangeCount
= aSelection
->RangeCount();
688 for (auto i
: IntegerRange(rangeCount
)) {
689 MOZ_ASSERT(aSelection
->RangeCount() == rangeCount
);
690 const nsRange
* range
= aSelection
->GetRangeAt(i
);
691 if (!IsInsideRuby(range
->GetClosestCommonInclusiveAncestor())) {
698 static Element
* GetElementOrNearestFlattenedTreeParentElement(nsINode
* aNode
) {
699 if (!aNode
->IsContent()) {
702 for (nsIContent
* content
= aNode
->AsContent(); content
;
703 content
= content
->GetFlattenedTreeParent()) {
704 if (content
->IsElement()) {
705 return content
->AsElement();
711 bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage
,
712 int32_t aClipboardType
,
713 PresShell
* aPresShell
,
714 Selection
* aSelection
,
715 bool* aActionTaken
) {
717 *aActionTaken
= false;
720 EventMessage originalEventMessage
= aEventMessage
;
721 if (originalEventMessage
== ePasteNoFormatting
) {
722 originalEventMessage
= ePaste
;
725 NS_ASSERTION(originalEventMessage
== eCut
|| originalEventMessage
== eCopy
||
726 originalEventMessage
== ePaste
,
727 "Invalid clipboard event type");
729 RefPtr
<PresShell
> presShell
= aPresShell
;
734 nsCOMPtr
<Document
> doc
= presShell
->GetDocument();
735 if (!doc
) return false;
737 nsCOMPtr
<nsPIDOMWindowOuter
> piWindow
= doc
->GetWindow();
738 if (!piWindow
) return false;
740 // Event target of clipboard events should be an element node which
741 // contains selection start container.
742 RefPtr
<Element
> targetElement
;
744 // If a selection was not supplied, try to find it.
745 RefPtr
<Selection
> sel
= aSelection
;
747 sel
= GetSelectionForCopy(doc
);
750 // Retrieve the event target node from the start of the selection.
752 const nsRange
* range
= sel
->GetRangeAt(0);
754 targetElement
= GetElementOrNearestFlattenedTreeParentElement(
755 range
->GetStartContainer());
759 // If there is no selection ranges, use the <body> or <frameset> element.
760 if (!targetElement
) {
761 targetElement
= doc
->GetBody();
762 if (!targetElement
) {
767 // It seems to be unsafe to fire an event handler during reflow (bug 393696)
768 if (!nsContentUtils::IsSafeToRunScript()) {
769 nsContentUtils::WarnScriptWasIgnored(doc
);
773 BrowsingContext
* bc
= piWindow
->GetBrowsingContext();
774 const bool chromeShell
= bc
&& bc
->IsChrome();
776 // next, fire the cut, copy or paste event
777 bool doDefault
= true;
778 RefPtr
<DataTransfer
> clipboardData
;
779 if (chromeShell
|| StaticPrefs::dom_event_clipboardevents_enabled()) {
781 new DataTransfer(doc
->GetScopeObject(), aEventMessage
,
782 originalEventMessage
== ePaste
, aClipboardType
);
784 nsEventStatus status
= nsEventStatus_eIgnore
;
785 InternalClipboardEvent
evt(true, originalEventMessage
);
786 evt
.mClipboardData
= clipboardData
;
788 RefPtr
<nsPresContext
> presContext
= presShell
->GetPresContext();
789 EventDispatcher::Dispatch(targetElement
, presContext
, &evt
, nullptr,
791 // If the event was cancelled, don't do the clipboard operation
792 doDefault
= (status
!= nsEventStatus_eConsumeNoDefault
);
795 // When this function exits, the event dispatch is over. We want to disconnect
796 // our DataTransfer, which means setting its mode to `Protected` and clearing
797 // all stored data, before we return.
798 auto clearAfter
= MakeScopeExit([&] {
800 clipboardData
->Disconnect();
802 // NOTE: Disconnect may not actually clear the DataTransfer if the
803 // dom.events.dataTransfer.protected.enabled pref is not on, so we make
804 // sure we clear here, as not clearing could provide the DataTransfer
805 // access to information from the system clipboard at an arbitrary point
807 if (originalEventMessage
== ePaste
) {
808 clipboardData
->ClearAll();
813 // No need to do anything special during a paste. Either an event listener
814 // took care of it and cancelled the event, or the caller will handle it.
815 // Return true to indicate that the event wasn't cancelled.
816 if (originalEventMessage
== ePaste
) {
818 *aActionTaken
= true;
823 // Update the presentation in case the event handler modified the selection,
825 presShell
->FlushPendingNotifications(FlushType::Frames
);
826 if (presShell
->IsDestroying()) {
830 // if the event was not cancelled, do the default copy. If the event was
831 // cancelled, use the data added to the data transfer and copy that instead.
834 // find the focused node
835 nsIContent
* sourceContent
= targetElement
.get();
836 if (targetElement
->IsInNativeAnonymousSubtree()) {
837 sourceContent
= targetElement
->FindFirstNonChromeOnlyAccessContent();
840 // If it's <input type="password"> and there is no unmasked range or
841 // there is unmasked range but it's collapsed or it'll be masked
842 // automatically, the selected password shouldn't be copied into the
844 if (RefPtr
<HTMLInputElement
> inputElement
=
845 HTMLInputElement::FromNodeOrNull(sourceContent
)) {
846 if (TextEditor
* textEditor
= inputElement
->GetTextEditor()) {
847 if (textEditor
->IsPasswordEditor() &&
848 !textEditor
->IsCopyToClipboardAllowed()) {
854 // when cutting non-editable content, do nothing
855 // XXX this is probably the wrong editable flag to check
856 if (originalEventMessage
!= eCut
|| targetElement
->IsEditable()) {
857 // get the data from the selection if any
858 if (sel
->IsCollapsed()) {
860 *aActionTaken
= true;
864 // XXX Code which decides whether we should copy text with ruby
865 // annotation is currenct depending on whether each range of the
866 // selection is inside a same ruby container. But we really should
867 // expose the full functionality in browser. See bug 1130891.
868 bool withRubyAnnotation
= IsSelectionInsideRuby(sel
);
869 nsresult rv
= EncodeDocumentWithContextAndPutToClipboard(
870 sel
, doc
, aClipboardType
, withRubyAnnotation
);
877 } else if (clipboardData
) {
878 // check to see if any data was put on the data transfer.
879 count
= clipboardData
->MozItemCount();
881 nsCOMPtr
<nsIClipboard
> clipboard(
882 do_GetService("@mozilla.org/widget/clipboard;1"));
883 NS_ENSURE_TRUE(clipboard
, false);
885 nsCOMPtr
<nsITransferable
> transferable
=
886 clipboardData
->GetTransferable(0, doc
->GetLoadContext());
888 NS_ENSURE_TRUE(transferable
, false);
890 // put the transferable on the clipboard
891 nsresult rv
= clipboard
->SetData(transferable
, nullptr, aClipboardType
);
898 // Now that we have copied, update the clipboard commands. This should have
899 // the effect of updating the enabled state of the paste menu item.
900 if (doDefault
|| count
) {
901 piWindow
->UpdateCommands(u
"clipboard"_ns
, nullptr, 0);
905 *aActionTaken
= true;