Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / base / nsCopySupport.cpp
blobde58a735b2a613a7fcaa8330dd8de1ca518cde85
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"
14 #include "nsXPCOM.h"
15 #include "nsISupportsPrimitives.h"
16 #include "nsRange.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"
34 #include "nsIFrame.h"
36 // image copy stuff
37 #include "nsIImageLoadingContent.h"
38 #include "nsIInterfaceRequestorUtils.h"
39 #include "nsContentUtils.h"
40 #include "nsContentCID.h"
42 #ifdef XP_WIN
43 # include "nsCExternalHandlerService.h"
44 # include "nsEscape.h"
45 # include "nsIMIMEInfo.h"
46 # include "nsIMIMEService.h"
47 # include "nsIURIMutator.h"
48 # include "nsIURL.h"
49 # include "nsReadableUtils.h"
50 # include "nsXULAppAPI.h"
51 #endif
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,
76 nsINode* aDOMNode);
78 #ifdef XP_WIN
79 // copy image as file promise onto the transferable
80 static nsresult AppendImagePromise(nsITransferable* aTransferable,
81 imgIRequest* aImgRequest,
82 nsIImageLoadingContent* aImageElement);
83 #endif
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
112 // text widget.
113 rv = aEncoder.GetMimeType(mimeType);
114 NS_ENSURE_SUCCESS(rv, rv);
115 bool selForcedTextPlain = mimeType.EqualsLiteral(kTextMime);
117 nsAutoString buf;
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);
131 } else {
132 // Redo the encoding, but this time use pretty printing.
133 flags =
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);
152 return 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);
170 return 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)};
234 return rv;
237 static nsresult CreateTransferable(
238 const EncodedDocumentWithContext& aEncodedDocumentWithContext,
239 Document& aDocument, nsCOMPtr<nsITransferable>& aTransferable) {
240 nsresult rv = NS_OK;
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,
257 kHTMLMime);
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,
265 kHTMLContext);
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()) {
276 // unicode text
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)
281 rv = AppendString(
282 aTransferable,
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();
289 if (uri) {
290 nsAutoCString spec;
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);
307 } else {
308 if (!aEncodedDocumentWithContext.mSerializationForTextUnicode.IsEmpty()) {
309 // Add the unicode DataFlavor to the transferable
310 rv = AppendString(
311 aTransferable,
312 aEncodedDocumentWithContext.mSerializationForTextUnicode, kTextMime);
313 NS_ENSURE_SUCCESS(rv, rv);
317 return rv;
320 static nsresult PutToClipboard(
321 const EncodedDocumentWithContext& aEncodedDocumentWithContext,
322 int16_t aClipboardID, Document& aDocument) {
323 nsresult rv;
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);
335 return 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);
356 return rv;
359 nsresult nsCopySupport::ClearSelectionCache() {
360 nsresult rv;
361 nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv);
362 clipboard->EmptyClipboard(nsIClipboard::kSelectionCache);
363 return rv;
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;
379 nsresult rv =
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);
389 return rv;
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.
410 // XXX bug 1245883
411 RefPtr<Selection> selection = new Selection(SelectionType::eNormal, nullptr);
412 RefPtr<nsRange> range = nsRange::Create(aNode);
413 ErrorResult result;
414 range->SelectNode(*aNode, result);
415 if (NS_WARN_IF(result.Failed())) {
416 return result.StealNSResult();
418 selection->AddRangeAndSelectFramesAndNotifyListenersInternal(*range, aDoc,
419 result);
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;
446 if (aSel) {
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) {
458 nsresult rv;
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);
497 if (imgRequest) {
498 // Remember the referrer used for this image request.
499 nsCOMPtr<nsIReferrerInfo> referrerInfo;
500 imgRequest->GetReferrerInfo(getter_AddRefs(referrerInfo));
501 trans->SetReferrerInfo(referrerInfo);
504 #ifdef XP_WIN
505 rv = AppendImagePromise(trans, imgRequest, aImageElement);
506 NS_ENSURE_SUCCESS(rv, rv);
507 #endif
509 // copy the image data onto the transferable
510 rv = trans->SetTransferData(kNativeImageMime, image);
511 NS_ENSURE_SUCCESS(rv, rv);
514 // get clipboard
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) {
530 nsresult rv;
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,
546 nsINode* aDOMNode) {
547 nsresult rv;
549 // serializer
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);
590 #ifdef XP_WIN
591 static nsresult AppendImagePromise(nsITransferable* aTransferable,
592 imgIRequest* aImgRequest,
593 nsIImageLoadingContent* aImageElement) {
594 nsresult rv;
596 NS_ENSURE_TRUE(aImgRequest, NS_OK);
598 bool isMultipart;
599 rv = aImgRequest->GetMultipart(&isMultipart);
600 NS_ENSURE_SUCCESS(rv, rv);
601 if (isMultipart) {
602 return NS_OK;
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);
617 nsAutoCString spec;
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);
625 nsCString mimeType;
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);
650 #endif // XP_WIN
652 already_AddRefed<Selection> nsCopySupport::GetSelectionForCopy(
653 Document* aDocument) {
654 PresShell* presShell = aDocument->GetPresShell();
655 if (NS_WARN_IF(!presShell)) {
656 return nullptr;
659 RefPtr<nsFrameSelection> frameSel = presShell->GetLastFocusedFrameSelection();
660 if (NS_WARN_IF(!frameSel)) {
661 return nullptr;
664 RefPtr<Selection> sel = frameSel->GetSelection(SelectionType::eNormal);
665 return sel.forget();
668 bool nsCopySupport::CanCopy(Document* aDocument) {
669 if (!aDocument) {
670 return false;
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)) {
680 return true;
683 return false;
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())) {
692 return false;
695 return true;
698 static Element* GetElementOrNearestFlattenedTreeParentElement(nsINode* aNode) {
699 if (!aNode->IsContent()) {
700 return nullptr;
702 for (nsIContent* content = aNode->AsContent(); content;
703 content = content->GetFlattenedTreeParent()) {
704 if (content->IsElement()) {
705 return content->AsElement();
708 return nullptr;
711 bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
712 int32_t aClipboardType,
713 PresShell* aPresShell,
714 Selection* aSelection,
715 bool* aActionTaken) {
716 if (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;
730 if (!presShell) {
731 return false;
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;
746 if (!sel) {
747 sel = GetSelectionForCopy(doc);
750 // Retrieve the event target node from the start of the selection.
751 if (sel) {
752 const nsRange* range = sel->GetRangeAt(0);
753 if (range) {
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) {
763 return false;
767 // It seems to be unsafe to fire an event handler during reflow (bug 393696)
768 if (!nsContentUtils::IsSafeToRunScript()) {
769 nsContentUtils::WarnScriptWasIgnored(doc);
770 return false;
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()) {
780 clipboardData =
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,
790 &status);
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([&] {
799 if (clipboardData) {
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
806 // in the future.
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) {
817 if (aActionTaken) {
818 *aActionTaken = true;
820 return doDefault;
823 // Update the presentation in case the event handler modified the selection,
824 // see bug 602231.
825 presShell->FlushPendingNotifications(FlushType::Frames);
826 if (presShell->IsDestroying()) {
827 return false;
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.
832 uint32_t count = 0;
833 if (doDefault) {
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
843 // clipboard.
844 if (RefPtr<HTMLInputElement> inputElement =
845 HTMLInputElement::FromNodeOrNull(sourceContent)) {
846 if (TextEditor* textEditor = inputElement->GetTextEditor()) {
847 if (textEditor->IsPasswordEditor() &&
848 !textEditor->IsCopyToClipboardAllowed()) {
849 return false;
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()) {
859 if (aActionTaken) {
860 *aActionTaken = true;
862 return false;
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);
871 if (NS_FAILED(rv)) {
872 return false;
874 } else {
875 return false;
877 } else if (clipboardData) {
878 // check to see if any data was put on the data transfer.
879 count = clipboardData->MozItemCount();
880 if (count) {
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);
892 if (NS_FAILED(rv)) {
893 return false;
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);
904 if (aActionTaken) {
905 *aActionTaken = true;
907 return doDefault;