Bug 1890793: Assert CallArgs::newTarget is not gray. r=spidermonkey-reviewers,sfink...
[gecko.git] / dom / base / nsCopySupport.cpp
blob15c0cf4cf0dba384f4775932d2461d2d758ca4fe
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"
15 #include "nsXPCOM.h"
16 #include "nsISupportsPrimitives.h"
17 #include "nsRange.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"
36 #include "nsIFrame.h"
38 // image copy stuff
39 #include "nsIImageLoadingContent.h"
40 #include "nsIInterfaceRequestorUtils.h"
41 #include "nsContentUtils.h"
43 #ifdef XP_WIN
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"
50 # include "nsIURL.h"
51 # include "nsReadableUtils.h"
52 # include "nsXULAppAPI.h"
53 #endif
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,
78 nsINode* aDOMNode);
80 #ifdef XP_WIN
81 // copy image as file promise onto the transferable
82 static nsresult AppendImagePromise(nsITransferable* aTransferable,
83 imgIRequest* aImgRequest,
84 nsINode* aImageNode);
85 #endif
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
114 // text widget.
115 rv = aEncoder.GetMimeType(mimeType);
116 NS_ENSURE_SUCCESS(rv, rv);
117 bool selForcedTextPlain = mimeType.EqualsLiteral(kTextMime);
119 nsAutoString buf;
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);
133 } else {
134 // Redo the encoding, but this time use pretty printing.
135 flags =
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);
154 return 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);
172 return 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)};
236 return rv;
239 static nsresult CreateTransferable(
240 const EncodedDocumentWithContext& aEncodedDocumentWithContext,
241 Document& aDocument, nsCOMPtr<nsITransferable>& aTransferable) {
242 nsresult rv = NS_OK;
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,
260 kHTMLMime);
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,
268 kHTMLContext);
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()) {
279 // unicode text
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)
284 rv = AppendString(
285 aTransferable,
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();
292 if (uri) {
293 nsAutoCString spec;
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);
310 } else {
311 if (!aEncodedDocumentWithContext.mSerializationForTextUnicode.IsEmpty()) {
312 // Add the unicode DataFlavor to the transferable
313 rv = AppendString(
314 aTransferable,
315 aEncodedDocumentWithContext.mSerializationForTextUnicode, kTextMime);
316 NS_ENSURE_SUCCESS(rv, rv);
320 return rv;
323 static nsresult PutToClipboard(
324 const EncodedDocumentWithContext& aEncodedDocumentWithContext,
325 int16_t aClipboardID, Document& aDocument) {
326 nsresult rv;
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);
338 return 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);
359 return rv;
362 nsresult nsCopySupport::ClearSelectionCache() {
363 nsresult rv;
364 nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv);
365 clipboard->EmptyClipboard(nsIClipboard::kSelectionCache);
366 return rv;
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;
382 nsresult rv =
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);
392 return rv;
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.
413 // XXX bug 1245883
414 RefPtr<Selection> selection = new Selection(SelectionType::eNormal, nullptr);
415 RefPtr<nsRange> range = nsRange::Create(aNode);
416 ErrorResult result;
417 range->SelectNode(*aNode, result);
418 if (NS_WARN_IF(result.Failed())) {
419 return result.StealNSResult();
421 selection->AddRangeAndSelectFramesAndNotifyListenersInternal(*range, aDoc,
422 result);
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;
449 if (aSel) {
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) {
461 nsresult rv;
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);
504 if (imgRequest) {
505 // Remember the referrer used for this image request.
506 nsCOMPtr<nsIReferrerInfo> referrerInfo;
507 imgRequest->GetReferrerInfo(getter_AddRefs(referrerInfo));
508 trans->SetReferrerInfo(referrerInfo);
511 #ifdef XP_WIN
512 if (StaticPrefs::clipboard_imageAsFile_enabled()) {
513 rv = AppendImagePromise(trans, imgRequest, imageNode);
514 NS_ENSURE_SUCCESS(rv, rv);
516 #endif
518 // copy the image data onto the transferable
519 rv = trans->SetTransferData(kNativeImageMime, image);
520 NS_ENSURE_SUCCESS(rv, rv);
523 // get clipboard
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) {
539 nsresult rv;
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,
555 nsINode* aDOMNode) {
556 nsresult rv;
558 // serializer
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);
599 #ifdef XP_WIN
600 static nsresult AppendImagePromise(nsITransferable* aTransferable,
601 imgIRequest* aImgRequest,
602 nsINode* aImageNode) {
603 nsresult rv;
605 NS_ENSURE_TRUE(aImgRequest && aImageNode, NS_OK);
607 bool isMultipart;
608 rv = aImgRequest->GetMultipart(&isMultipart);
609 NS_ENSURE_SUCCESS(rv, rv);
610 if (isMultipart) {
611 return NS_OK;
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);
623 nsAutoCString spec;
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);
631 nsCString mimeType;
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);
656 #endif // XP_WIN
658 already_AddRefed<Selection> nsCopySupport::GetSelectionForCopy(
659 Document* aDocument) {
660 PresShell* presShell = aDocument->GetPresShell();
661 if (NS_WARN_IF(!presShell)) {
662 return nullptr;
665 RefPtr<nsFrameSelection> frameSel = presShell->GetLastFocusedFrameSelection();
666 if (NS_WARN_IF(!frameSel)) {
667 return nullptr;
670 RefPtr<Selection> sel = frameSel->GetSelection(SelectionType::eNormal);
671 return sel.forget();
674 bool nsCopySupport::CanCopy(Document* aDocument) {
675 if (!aDocument) {
676 return false;
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)) {
686 return true;
689 return false;
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())) {
698 return false;
701 return true;
704 static Element* GetElementOrNearestFlattenedTreeParentElement(nsINode* aNode) {
705 if (!aNode->IsContent()) {
706 return nullptr;
708 for (nsIContent* content = aNode->AsContent(); content;
709 content = content->GetFlattenedTreeParent()) {
710 if (content->IsElement()) {
711 return content->AsElement();
714 return nullptr;
718 * This class is used while processing clipboard paste event.
720 class MOZ_RAII AutoHandlingPasteEvent final {
721 public:
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() {
735 if (mInnerWindow) {
736 mInnerWindow->SetCurrentPasteDataTransfer(nullptr);
740 private:
741 RefPtr<nsGlobalWindowInner> mInnerWindow;
744 bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
745 int32_t aClipboardType,
746 PresShell* aPresShell,
747 Selection* aSelection,
748 bool* aActionTaken) {
749 if (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;
763 if (!presShell) {
764 return false;
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;
779 if (!sel) {
780 sel = GetSelectionForCopy(doc);
783 // Retrieve the event target node from the start of the selection.
784 if (sel) {
785 const nsRange* range = sel->GetRangeAt(0);
786 if (range) {
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) {
796 return false;
800 // It seems to be unsafe to fire an event handler during reflow (bug 393696)
801 if (!nsContentUtils::IsSafeToRunScript()) {
802 nsContentUtils::WarnScriptWasIgnored(doc);
803 return false;
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()) {
813 clipboardData =
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,
828 &status);
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([&] {
839 if (clipboardData) {
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
846 // in the future.
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) {
857 if (aActionTaken) {
858 *aActionTaken = true;
860 return doDefault;
863 // Update the presentation in case the event handler modified the selection,
864 // see bug 602231.
865 presShell->FlushPendingNotifications(FlushType::Frames);
866 if (presShell->IsDestroying()) {
867 return false;
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.
872 uint32_t count = 0;
873 if (doDefault) {
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
883 // clipboard.
884 if (RefPtr<HTMLInputElement> inputElement =
885 HTMLInputElement::FromNodeOrNull(sourceContent)) {
886 if (TextEditor* textEditor = inputElement->GetTextEditor()) {
887 if (textEditor->IsPasswordEditor() &&
888 !textEditor->IsCopyToClipboardAllowed()) {
889 return false;
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()) {
899 if (aActionTaken) {
900 *aActionTaken = true;
902 return false;
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);
911 if (NS_FAILED(rv)) {
912 return false;
914 } else {
915 return false;
917 } else if (clipboardData) {
918 // check to see if any data was put on the data transfer.
919 count = clipboardData->MozItemCount();
920 if (count) {
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);
932 if (NS_FAILED(rv)) {
933 return false;
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);
944 if (aActionTaken) {
945 *aActionTaken = true;
947 return doDefault;