1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "mozilla/ArrayUtils.h"
10 #include "nsArrayUtils.h"
11 #include "nsClipboard.h"
12 #include "nsClipboardX11.h"
13 #if defined(MOZ_WAYLAND)
14 # include "nsClipboardWayland.h"
15 # include "nsClipboardWaylandAsync.h"
19 #include "nsNetUtil.h"
20 #include "nsContentUtils.h"
21 #include "HeadlessClipboard.h"
22 #include "nsSupportsPrimitives.h"
24 #include "nsReadableUtils.h"
25 #include "nsPrimitiveHelpers.h"
26 #include "nsImageToPixbuf.h"
27 #include "nsStringStream.h"
28 #include "nsIFileURL.h"
29 #include "nsIObserverService.h"
30 #include "mozilla/Services.h"
31 #include "mozilla/RefPtr.h"
32 #include "mozilla/SchedulerGroup.h"
33 #include "mozilla/StaticPrefs_widget.h"
34 #include "mozilla/TimeStamp.h"
35 #include "WidgetUtilsGtk.h"
37 #include "imgIContainer.h"
42 #include "mozilla/Encoding.h"
44 using namespace mozilla
;
46 // Idle timeout for receiving selection and property notify events (microsec)
47 // Right now it's set to 1 sec.
48 const int kClipboardTimeout
= 1000000;
50 // Defines how many event loop iterations will be done without sleep.
51 // We ususally get data in first 2-3 iterations unless some large object
52 // (an image for instance) is transferred through clipboard.
53 const int kClipboardFastIterationNum
= 3;
55 // We add this prefix to HTML markup, so that GetHTMLCharset can correctly
56 // detect the HTML as UTF-8 encoded.
57 static const char kHTMLMarkupPrefix
[] =
58 R
"(<meta http-equiv="content
-type
" content="text
/html
; charset
=utf
-8">)";
60 static const char kURIListMime
[] = "text/uri-list";
62 // Callback when someone asks us for the data
63 void clipboard_get_cb(GtkClipboard
* aGtkClipboard
,
64 GtkSelectionData
* aSelectionData
, guint info
,
67 // Callback when someone asks us to clear a clipboard
68 void clipboard_clear_cb(GtkClipboard
* aGtkClipboard
, gpointer user_data
);
70 static bool ConvertHTMLtoUCS2(const char* data
, int32_t dataLength
,
71 nsCString
& charset
, char16_t
** unicodeData
,
72 int32_t& outUnicodeLen
);
74 static bool GetHTMLCharset(const char* data
, int32_t dataLength
,
77 GdkAtom
GetSelectionAtom(int32_t aWhichClipboard
) {
78 if (aWhichClipboard
== nsIClipboard::kGlobalClipboard
)
79 return GDK_SELECTION_CLIPBOARD
;
81 return GDK_SELECTION_PRIMARY
;
84 int GetGeckoClipboardType(GtkClipboard
* aGtkClipboard
) {
85 if (aGtkClipboard
== gtk_clipboard_get(GDK_SELECTION_PRIMARY
))
86 return nsClipboard::kSelectionClipboard
;
87 else if (aGtkClipboard
== gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
))
88 return nsClipboard::kGlobalClipboard
;
90 return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
93 nsClipboard::nsClipboard() = default;
95 nsClipboard::~nsClipboard() {
96 // We have to clear clipboard before gdk_display_close() call.
97 // See bug 531580 for details.
98 if (mGlobalTransferable
) {
99 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
101 if (mSelectionTransferable
) {
102 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY
));
106 NS_IMPL_ISUPPORTS(nsClipboard
, nsIClipboard
, nsIObserver
)
108 nsresult
nsClipboard::Init(void) {
109 if (widget::GdkIsX11Display()) {
110 mContext
= new nsRetrievalContextX11();
111 #if defined(MOZ_WAYLAND)
112 } else if (widget::GdkIsWaylandDisplay()) {
113 if (StaticPrefs::widget_wayland_async_clipboard_enabled_AtStartup()) {
114 mContext
= new nsRetrievalContextWaylandAsync();
116 mContext
= new nsRetrievalContextWayland();
120 NS_WARNING("Missing nsRetrievalContext for nsClipboard!");
124 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
126 os
->AddObserver(this, "xpcom-shutdown", false);
133 nsClipboard::Observe(nsISupports
* aSubject
, const char* aTopic
,
134 const char16_t
* aData
) {
135 // Save global clipboard content to CLIPBOARD_MANAGER.
136 // gtk_clipboard_store() can run an event loop, so call from a dedicated
138 return SchedulerGroup::Dispatch(
140 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
141 LOGCLIP("nsClipboard storing clipboard content\n");
142 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
147 nsClipboard::SetData(nsITransferable
* aTransferable
, nsIClipboardOwner
* aOwner
,
148 int32_t aWhichClipboard
) {
149 // See if we can short cut
150 if ((aWhichClipboard
== kGlobalClipboard
&&
151 aTransferable
== mGlobalTransferable
.get() &&
152 aOwner
== mGlobalOwner
.get()) ||
153 (aWhichClipboard
== kSelectionClipboard
&&
154 aTransferable
== mSelectionTransferable
.get() &&
155 aOwner
== mSelectionOwner
.get())) {
159 LOGCLIP("nsClipboard::SetData (%s)\n",
160 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
162 // List of suported targets
163 GtkTargetList
* list
= gtk_target_list_new(nullptr, 0);
165 // Get the types of supported flavors
166 nsTArray
<nsCString
> flavors
;
167 nsresult rv
= aTransferable
->FlavorsTransferableCanExport(flavors
);
169 LOGCLIP(" FlavorsTransferableCanExport failed!\n");
170 // Fall through. |gtkTargets| will be null below.
173 // Add all the flavors to this widget's supported type.
174 bool imagesAdded
= false;
175 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
176 nsCString
& flavorStr
= flavors
[i
];
177 LOGCLIP(" processing target %s\n", flavorStr
.get());
179 // Special case text/unicode since we can handle all of the string types.
180 if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
181 LOGCLIP(" adding TEXT targets\n");
182 gtk_target_list_add_text_targets(list
, 0);
186 if (nsContentUtils::IsFlavorImage(flavorStr
)) {
187 // Don't bother adding image targets twice
189 // accept any writable image type
190 LOGCLIP(" adding IMAGE targets\n");
191 gtk_target_list_add_image_targets(list
, 0, TRUE
);
197 // Add this to our list of valid targets
198 LOGCLIP(" adding OTHER target %s\n", flavorStr
.get());
199 GdkAtom atom
= gdk_atom_intern(flavorStr
.get(), FALSE
);
200 gtk_target_list_add(list
, atom
, 0, 0);
203 // Get GTK clipboard (CLIPBOARD or PRIMARY)
204 GtkClipboard
* gtkClipboard
=
205 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard
));
208 GtkTargetEntry
* gtkTargets
=
209 gtk_target_table_new_from_list(list
, &numTargets
);
210 if (!gtkTargets
|| numTargets
== 0) {
212 " gtk_target_table_new_from_list() failed or empty list of "
214 // Clear references to the any old data and let GTK know that it is no
216 EmptyClipboard(aWhichClipboard
);
217 return NS_ERROR_FAILURE
;
220 // Set getcallback and request to store data after an application exit
221 if (gtk_clipboard_set_with_data(gtkClipboard
, gtkTargets
, numTargets
,
222 clipboard_get_cb
, clipboard_clear_cb
, this)) {
223 // We managed to set-up the clipboard so update internal state
224 // We have to set it now because gtk_clipboard_set_with_data() calls
225 // clipboard_clear_cb() which reset our internal state
226 if (aWhichClipboard
== kSelectionClipboard
) {
227 mSelectionOwner
= aOwner
;
228 mSelectionTransferable
= aTransferable
;
230 mGlobalOwner
= aOwner
;
231 mGlobalTransferable
= aTransferable
;
232 gtk_clipboard_set_can_store(gtkClipboard
, gtkTargets
, numTargets
);
237 LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
238 EmptyClipboard(aWhichClipboard
);
239 rv
= NS_ERROR_FAILURE
;
242 gtk_target_table_free(gtkTargets
, numTargets
);
243 gtk_target_list_unref(list
);
248 void nsClipboard::SetTransferableData(nsITransferable
* aTransferable
,
250 const char* aClipboardData
,
251 uint32_t aClipboardDataLength
) {
252 LOGCLIP("nsClipboard::SetTransferableData MIME %s\n", aFlavor
.get());
254 nsCOMPtr
<nsISupports
> wrapper
;
255 nsPrimitiveHelpers::CreatePrimitiveForData(
256 aFlavor
, aClipboardData
, aClipboardDataLength
, getter_AddRefs(wrapper
));
257 aTransferable
->SetTransferData(aFlavor
.get(), wrapper
);
260 static bool IsMIMEAtFlavourList(const nsTArray
<nsCString
>& aFlavourList
,
262 for (const auto& flavorStr
: aFlavourList
) {
263 if (flavorStr
.Equals(aMime
)) {
270 // When clipboard contains only images, X11/Gtk tries to convert them
271 // to text when we request text instead of just fail to provide the data.
272 // So if clipboard contains images only remove text MIME offer.
273 bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard
,
274 nsTArray
<nsCString
>& aFlavors
) {
275 LOGCLIP("nsClipboard::FilterImportedFlavors");
278 GdkAtom
* targets
= mContext
->GetTargets(aWhichClipboard
, &targetNums
);
280 LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
284 for (int i
= 0; i
< targetNums
; i
++) {
285 gchar
* atom_name
= gdk_atom_name(targets
[i
]);
289 // Filter out system MIME types.
290 if (strcmp(atom_name
, "TARGETS") == 0 ||
291 strcmp(atom_name
, "TIMESTAMP") == 0 ||
292 strcmp(atom_name
, "SAVE_TARGETS") == 0 ||
293 strcmp(atom_name
, "MULTIPLE") == 0) {
296 // Filter out types which can't be converted to text.
297 if (strncmp(atom_name
, "image/", 6) == 0 ||
298 strncmp(atom_name
, "application/", 12) == 0 ||
299 strncmp(atom_name
, "audio/", 6) == 0 ||
300 strncmp(atom_name
, "video/", 6) == 0) {
303 // We have some other MIME type on clipboard which can be hopefully
304 // converted to text without any problem.
305 LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
309 // So make sure we offer only types we have at clipboard.
310 nsTArray
<nsCString
> clipboardFlavors
;
311 for (int i
= 0; i
< targetNums
; i
++) {
312 gchar
* atom_name
= gdk_atom_name(targets
[i
]);
316 if (IsMIMEAtFlavourList(aFlavors
, atom_name
)) {
317 clipboardFlavors
.AppendElement(nsCString(atom_name
));
320 aFlavors
.SwapElements(clipboardFlavors
);
322 LOGCLIP(" X11: Flavors which match clipboard content:\n");
323 for (uint32_t i
= 0; i
< aFlavors
.Length(); i
++) {
324 LOGCLIP(" %s\n", aFlavors
[i
].get());
331 nsClipboard::GetData(nsITransferable
* aTransferable
, int32_t aWhichClipboard
) {
332 LOGCLIP("nsClipboard::GetData (%s)\n",
333 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
335 // TODO: Ensure we don't re-enter here.
336 if (!aTransferable
|| !mContext
) {
337 return NS_ERROR_FAILURE
;
340 // Get a list of flavors this transferable can import
341 nsTArray
<nsCString
> flavors
;
342 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(flavors
);
344 LOGCLIP(" FlavorsTransferableCanImport falied!\n");
348 LOGCLIP("Flavors which can be imported:\n");
349 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
350 LOGCLIP(" %s\n", flavors
[i
].get());
354 // Filter out MIME types on X11 to prevent unwanted conversions,
356 if (widget::GdkIsX11Display()) {
357 if (!FilterImportedFlavors(aWhichClipboard
, flavors
)) {
362 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
363 nsCString
& flavorStr
= flavors
[i
];
365 if (flavorStr
.EqualsLiteral(kJPEGImageMime
) ||
366 flavorStr
.EqualsLiteral(kJPGImageMime
) ||
367 flavorStr
.EqualsLiteral(kPNGImageMime
) ||
368 flavorStr
.EqualsLiteral(kGIFImageMime
)) {
369 // Emulate support for image/jpg
370 if (flavorStr
.EqualsLiteral(kJPGImageMime
)) {
371 flavorStr
.Assign(kJPEGImageMime
);
374 LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr
.get());
376 uint32_t clipboardDataLength
;
377 const char* clipboardData
= mContext
->GetClipboardData(
378 flavorStr
.get(), aWhichClipboard
, &clipboardDataLength
);
379 if (!clipboardData
) {
380 LOGCLIP(" %s type is missing\n", flavorStr
.get());
384 nsCOMPtr
<nsIInputStream
> byteStream
;
385 NS_NewByteInputStream(getter_AddRefs(byteStream
),
386 Span(clipboardData
, clipboardDataLength
),
388 aTransferable
->SetTransferData(flavorStr
.get(), byteStream
);
389 LOGCLIP(" got %s MIME data\n", flavorStr
.get());
391 mContext
->ReleaseClipboardData(clipboardData
);
395 // Special case text/unicode since we can convert any
396 // string into text/unicode
397 if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
398 LOGCLIP(" Getting unicode %s MIME clipboard data\n", flavorStr
.get());
400 const char* clipboardData
= mContext
->GetClipboardText(aWhichClipboard
);
401 if (!clipboardData
) {
402 LOGCLIP(" failed to get unicode data\n");
403 // If the type was text/unicode and we couldn't get
404 // text off the clipboard, run the next loop
409 // Convert utf-8 into our unicode format.
410 NS_ConvertUTF8toUTF16
ucs2string(clipboardData
);
411 const char* unicodeData
= (const char*)ToNewUnicode(ucs2string
);
412 uint32_t unicodeDataLength
= ucs2string
.Length() * 2;
413 SetTransferableData(aTransferable
, flavorStr
, unicodeData
,
415 free((void*)unicodeData
);
417 LOGCLIP(" got unicode data, length %d\n", ucs2string
.Length());
419 mContext
->ReleaseClipboardData(clipboardData
);
423 if (flavorStr
.EqualsLiteral(kFileMime
)) {
424 LOGCLIP(" Getting %s file clipboard data\n", flavorStr
.get());
426 uint32_t clipboardDataLength
;
427 const char* clipboardData
= mContext
->GetClipboardData(
428 kURIListMime
, aWhichClipboard
, &clipboardDataLength
);
429 if (!clipboardData
) {
430 LOGCLIP(" text/uri-list type is missing\n");
434 nsDependentCSubstring
data(clipboardData
, clipboardDataLength
);
435 nsTArray
<nsCString
> uris
= mozilla::widget::ParseTextURIList(data
);
436 if (!uris
.IsEmpty()) {
437 nsCOMPtr
<nsIURI
> fileURI
;
438 NS_NewURI(getter_AddRefs(fileURI
), uris
[0]);
439 if (nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(fileURI
, &rv
)) {
440 nsCOMPtr
<nsIFile
> file
;
441 rv
= fileURL
->GetFile(getter_AddRefs(file
));
442 if (NS_SUCCEEDED(rv
)) {
443 aTransferable
->SetTransferData(flavorStr
.get(), file
);
444 LOGCLIP(" successfully set file to clipboard\n");
449 mContext
->ReleaseClipboardData(clipboardData
);
453 LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr
.get());
455 uint32_t clipboardDataLength
;
456 const char* clipboardData
= mContext
->GetClipboardData(
457 flavorStr
.get(), aWhichClipboard
, &clipboardDataLength
);
460 if (!clipboardData
) {
461 LOGCLIP(" %s type is missing\n", flavorStr
.get());
466 LOGCLIP(" got %s mime type data.\n", flavorStr
.get());
468 // Special case text/html since we can convert into UCS2
469 if (flavorStr
.EqualsLiteral(kHTMLMime
)) {
470 char16_t
* htmlBody
= nullptr;
471 int32_t htmlBodyLen
= 0;
472 // Convert text/html into our unicode format
473 nsAutoCString charset
;
474 if (!GetHTMLCharset(clipboardData
, clipboardDataLength
, charset
)) {
475 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
476 LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
477 charset
.AssignLiteral("utf-8");
479 if (!ConvertHTMLtoUCS2(clipboardData
, clipboardDataLength
, charset
,
480 &htmlBody
, htmlBodyLen
)) {
481 LOGCLIP(" failed to convert text/html to UCS2.\n");
482 mContext
->ReleaseClipboardData(clipboardData
);
486 SetTransferableData(aTransferable
, flavorStr
, (const char*)htmlBody
,
490 SetTransferableData(aTransferable
, flavorStr
, clipboardData
,
491 clipboardDataLength
);
494 mContext
->ReleaseClipboardData(clipboardData
);
499 LOGCLIP(" failed to get clipboard content.\n");
504 nsClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
505 LOGCLIP("nsClipboard::EmptyClipboard (%s)\n",
506 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
507 if (aWhichClipboard
== kSelectionClipboard
) {
508 if (mSelectionTransferable
) {
509 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY
));
510 MOZ_ASSERT(!mSelectionTransferable
);
513 if (mGlobalTransferable
) {
514 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
515 MOZ_ASSERT(!mGlobalTransferable
);
522 void nsClipboard::ClearTransferable(int32_t aWhichClipboard
) {
523 if (aWhichClipboard
== kSelectionClipboard
) {
524 if (mSelectionOwner
) {
525 mSelectionOwner
->LosingOwnership(mSelectionTransferable
);
526 mSelectionOwner
= nullptr;
528 mSelectionTransferable
= nullptr;
531 mGlobalOwner
->LosingOwnership(mGlobalTransferable
);
532 mGlobalOwner
= nullptr;
534 mGlobalTransferable
= nullptr;
539 nsClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
540 int32_t aWhichClipboard
, bool* _retval
) {
542 return NS_ERROR_NULL_POINTER
;
545 LOGCLIP("nsClipboard::HasDataMatchingFlavors (%s)\n",
546 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
551 return NS_ERROR_FAILURE
;
555 GdkAtom
* targets
= mContext
->GetTargets(aWhichClipboard
, &targetNums
);
557 LOGCLIP(" no targes at clipboard (null)\n");
562 LOGCLIP(" Clipboard content (target nums %d):\n", targetNums
);
563 for (int32_t j
= 0; j
< targetNums
; j
++) {
564 gchar
* atom_name
= gdk_atom_name(targets
[j
]);
566 LOGCLIP(" failed to get MIME\n");
569 LOGCLIP(" MIME %s\n", atom_name
);
571 LOGCLIP(" Asking for content:\n");
572 for (auto& flavor
: aFlavorList
) {
573 LOGCLIP(" MIME %s\n", flavor
.get());
577 // Walk through the provided types and try to match it to a
579 for (auto& flavor
: aFlavorList
) {
580 // We special case text/unicode here.
581 if (flavor
.EqualsLiteral(kUnicodeMime
) &&
582 gtk_targets_include_text(targets
, targetNums
)) {
584 LOGCLIP(" has kUnicodeMime\n");
588 for (int32_t j
= 0; j
< targetNums
; j
++) {
589 gchar
* atom_name
= gdk_atom_name(targets
[j
]);
590 if (!atom_name
) continue;
592 if (flavor
.Equals(atom_name
)) {
594 LOGCLIP(" has %s\n", atom_name
);
596 // X clipboard supports image/jpeg, but we want to emulate support
597 // for image/jpg as well
598 else if (flavor
.EqualsLiteral(kJPGImageMime
) &&
599 !strcmp(atom_name
, kJPEGImageMime
)) {
601 LOGCLIP(" has image/jpg\n");
603 // application/x-moz-file should be treated like text/uri-list
604 else if (flavor
.EqualsLiteral(kFileMime
) &&
605 !strcmp(atom_name
, kURIListMime
)) {
607 LOGCLIP(" has text/uri-list treating as application/x-moz-file");
618 LOGCLIP(" no targes at clipboard (bad match)\n");
627 nsClipboard::SupportsSelectionClipboard(bool* _retval
) {
628 *_retval
= mContext
? mContext
->HasSelectionSupport() : false;
633 nsClipboard::SupportsFindClipboard(bool* _retval
) {
638 nsITransferable
* nsClipboard::GetTransferable(int32_t aWhichClipboard
) {
639 nsITransferable
* retval
;
641 if (aWhichClipboard
== kSelectionClipboard
)
642 retval
= mSelectionTransferable
.get();
644 retval
= mGlobalTransferable
.get();
649 void nsClipboard::SelectionGetEvent(GtkClipboard
* aClipboard
,
650 GtkSelectionData
* aSelectionData
) {
651 // Someone has asked us to hand them something. The first thing
652 // that we want to do is see if that something includes text. If
653 // it does, try to give it text/unicode after converting it to
656 int32_t whichClipboard
;
659 GdkAtom selection
= gtk_selection_data_get_selection(aSelectionData
);
660 if (selection
== GDK_SELECTION_PRIMARY
)
661 whichClipboard
= kSelectionClipboard
;
662 else if (selection
== GDK_SELECTION_CLIPBOARD
)
663 whichClipboard
= kGlobalClipboard
;
665 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
667 LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
668 whichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
670 nsCOMPtr
<nsITransferable
> trans
= GetTransferable(whichClipboard
);
672 // We have nothing to serve
673 LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
674 whichClipboard
== kSelectionClipboard
? "Primary" : "Clipboard");
679 nsCOMPtr
<nsISupports
> item
;
681 GdkAtom selectionTarget
= gtk_selection_data_get_target(aSelectionData
);
682 LOGCLIP(" selection target %s\n", gdk_atom_name(selectionTarget
));
684 // Check to see if the selection data is some text type.
685 if (gtk_targets_include_text(&selectionTarget
, 1)) {
686 LOGCLIP(" providing text/unicode data\n");
687 // Try to convert our internal type into a text string. Get
688 // the transferable for this clipboard and try to get the
689 // text/unicode type for it.
690 rv
= trans
->GetTransferData("text/unicode", getter_AddRefs(item
));
691 if (NS_FAILED(rv
) || !item
) {
692 LOGCLIP(" GetTransferData() failed to get text/unicode!\n");
696 nsCOMPtr
<nsISupportsString
> wideString
;
697 wideString
= do_QueryInterface(item
);
698 if (!wideString
) return;
700 nsAutoString ucs2string
;
701 wideString
->GetData(ucs2string
);
702 NS_ConvertUTF16toUTF8
utf8string(ucs2string
);
704 LOGCLIP(" sent %d bytes of utf-8 data\n", utf8string
.Length());
705 if (selectionTarget
== gdk_atom_intern("text/plain;charset=utf-8", FALSE
)) {
707 " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
708 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
709 // in some versions of GTK.
710 gtk_selection_data_set(aSelectionData
, selectionTarget
, 8,
711 reinterpret_cast<const guchar
*>(utf8string
.get()),
712 utf8string
.Length());
714 gtk_selection_data_set_text(aSelectionData
, utf8string
.get(),
715 utf8string
.Length());
720 // Check to see if the selection data is an image type
721 if (gtk_targets_include_image(&selectionTarget
, 1, TRUE
)) {
722 LOGCLIP(" providing image data\n");
723 // Look through our transfer data for the image
724 static const char* const imageMimeTypes
[] = {kNativeImageMime
,
725 kPNGImageMime
, kJPEGImageMime
,
726 kJPGImageMime
, kGIFImageMime
};
727 nsCOMPtr
<nsISupports
> imageItem
;
728 nsCOMPtr
<imgIContainer
> image
;
729 for (uint32_t i
= 0; i
< ArrayLength(imageMimeTypes
); i
++) {
730 rv
= trans
->GetTransferData(imageMimeTypes
[i
], getter_AddRefs(imageItem
));
732 LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes
[i
]);
736 image
= do_QueryInterface(imageItem
);
738 LOGCLIP(" %s is available at GetTransferData()\n",
744 if (!image
) { // Not getting an image for an image mime type!?
745 LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
749 GdkPixbuf
* pixbuf
= nsImageToPixbuf::ImageToPixbuf(image
);
751 LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
755 LOGCLIP(" Setting pixbuf image data as %s\n",
756 gdk_atom_name(selectionTarget
));
757 gtk_selection_data_set_pixbuf(aSelectionData
, pixbuf
);
758 g_object_unref(pixbuf
);
762 if (selectionTarget
== gdk_atom_intern(kHTMLMime
, FALSE
)) {
763 LOGCLIP(" providing %s data\n", kHTMLMime
);
764 rv
= trans
->GetTransferData(kHTMLMime
, getter_AddRefs(item
));
765 if (NS_FAILED(rv
) || !item
) {
766 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime
);
770 nsCOMPtr
<nsISupportsString
> wideString
;
771 wideString
= do_QueryInterface(item
);
773 LOGCLIP(" failed to get wideString interface!");
777 nsAutoString ucs2string
;
778 wideString
->GetData(ucs2string
);
781 // Add the prefix so the encoding is correctly detected.
782 html
.AppendLiteral(kHTMLMarkupPrefix
);
783 AppendUTF16toUTF8(ucs2string
, html
);
785 LOGCLIP(" Setting %d bytest of %s data\n", html
.Length(),
786 gdk_atom_name(selectionTarget
));
787 gtk_selection_data_set(aSelectionData
, selectionTarget
, 8,
788 (const guchar
*)html
.get(), html
.Length());
792 LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
793 gdk_atom_name(selectionTarget
));
795 // Try to match up the selection data target to something our
796 // transferable provides.
797 gchar
* target_name
= gdk_atom_name(selectionTarget
);
799 LOGCLIP(" Failed to get target name!\n");
803 rv
= trans
->GetTransferData(target_name
, getter_AddRefs(item
));
805 if (NS_FAILED(rv
) || !item
) {
806 LOGCLIP(" Failed to get anything from GetTransferData()!\n");
811 void* primitive_data
= nullptr;
812 uint32_t dataLen
= 0;
813 nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(target_name
),
814 item
, &primitive_data
, &dataLen
);
816 if (primitive_data
) {
817 LOGCLIP(" Setting %s as a primitive data type, %d bytes\n", target_name
,
819 gtk_selection_data_set(aSelectionData
, selectionTarget
,
820 8, /* 8 bits in a unit */
821 (const guchar
*)primitive_data
, dataLen
);
822 free(primitive_data
);
824 LOGCLIP(" Failed to get primitive data!\n");
830 void nsClipboard::SelectionClearEvent(GtkClipboard
* aGtkClipboard
) {
831 int32_t whichClipboard
= GetGeckoClipboardType(aGtkClipboard
);
832 if (whichClipboard
< 0) {
836 LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
837 whichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
839 ClearTransferable(whichClipboard
);
842 void clipboard_get_cb(GtkClipboard
* aGtkClipboard
,
843 GtkSelectionData
* aSelectionData
, guint info
,
844 gpointer user_data
) {
845 LOGCLIP("clipboard_get_cb() callback\n");
846 nsClipboard
* aClipboard
= static_cast<nsClipboard
*>(user_data
);
847 aClipboard
->SelectionGetEvent(aGtkClipboard
, aSelectionData
);
850 void clipboard_clear_cb(GtkClipboard
* aGtkClipboard
, gpointer user_data
) {
851 LOGCLIP("clipboard_clear_cb() callback\n");
852 nsClipboard
* aClipboard
= static_cast<nsClipboard
*>(user_data
);
853 aClipboard
->SelectionClearEvent(aGtkClipboard
);
857 * when copy-paste, mozilla wants data encoded using UCS2,
858 * other app such as StarOffice use "text/html"(RFC2854).
859 * This function convert data(got from GTK clipboard)
860 * to data mozilla wanted.
862 * data from GTK clipboard can be 3 forms:
863 * 1. From current mozilla
864 * "text/html", charset = utf-16
865 * 2. From old version mozilla or mozilla-based app
866 * content("body" only), charset = utf-16
867 * 3. From other app who use "text/html" when copy-paste
868 * "text/html", has "charset" info
870 * data : got from GTK clipboard
871 * dataLength: got from GTK clipboard
872 * body : pass to Mozilla
873 * bodyLength: pass to Mozilla
875 bool ConvertHTMLtoUCS2(const char* data
, int32_t dataLength
, nsCString
& charset
,
876 char16_t
** unicodeData
, int32_t& outUnicodeLen
) {
877 if (charset
.EqualsLiteral("UTF-16")) { // current mozilla
878 outUnicodeLen
= (dataLength
/ 2) - 1;
879 *unicodeData
= reinterpret_cast<char16_t
*>(
880 moz_xmalloc((outUnicodeLen
+ sizeof('\0')) * sizeof(char16_t
)));
881 memcpy(*unicodeData
, data
+ sizeof(char16_t
),
882 outUnicodeLen
* sizeof(char16_t
));
883 (*unicodeData
)[outUnicodeLen
] = '\0';
886 if (charset
.EqualsLiteral("UNKNOWN")) {
890 // app which use "text/html" to copy&paste
892 auto encoding
= Encoding::ForLabelNoReplacement(charset
);
894 LOGCLIP("ConvertHTMLtoUCS2: get unicode decoder error\n");
899 auto dataSpan
= Span(data
, dataLength
);
900 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
901 // issues, but might confuse other users.
902 const size_t prefixLen
= ArrayLength(kHTMLMarkupPrefix
) - 1;
903 if (dataSpan
.Length() >= prefixLen
&&
904 Substring(data
, prefixLen
).EqualsLiteral(kHTMLMarkupPrefix
)) {
905 dataSpan
= dataSpan
.From(prefixLen
);
908 auto decoder
= encoding
->NewDecoder();
909 CheckedInt
<size_t> needed
= decoder
->MaxUTF16BufferLength(dataSpan
.Length());
910 if (!needed
.isValid() || needed
.value() > INT32_MAX
) {
916 if (needed
.value()) {
917 *unicodeData
= reinterpret_cast<char16_t
*>(
918 moz_xmalloc((needed
.value() + 1) * sizeof(char16_t
)));
923 Tie(result
, read
, written
, hadErrors
) = decoder
->DecodeToUTF16(
924 AsBytes(dataSpan
), Span(*unicodeData
, needed
.value()), true);
925 MOZ_ASSERT(result
== kInputEmpty
);
926 MOZ_ASSERT(read
== size_t(dataSpan
.Length()));
927 MOZ_ASSERT(written
<= needed
.value());
929 outUnicodeLen
= written
;
931 (*unicodeData
)[outUnicodeLen
] = '\0';
938 * get "charset" information from clipboard data
939 * return value can be:
940 * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
941 * 2. "UNKNOWN": mozilla can't detect what encode it use
942 * 3. other: "text/html" with other charset than utf-16
944 bool GetHTMLCharset(const char* data
, int32_t dataLength
, nsCString
& str
) {
945 // if detect "FFFE" or "FEFF", assume UTF-16
946 char16_t
* beginChar
= (char16_t
*)data
;
947 if ((beginChar
[0] == 0xFFFE) || (beginChar
[0] == 0xFEFF)) {
948 str
.AssignLiteral("UTF-16");
949 LOGCLIP("GetHTMLCharset: Charset of HTML is UTF-16\n");
952 // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
953 const nsDependentCSubstring
htmlStr(data
, dataLength
);
954 nsACString::const_iterator start
, end
;
955 htmlStr
.BeginReading(start
);
956 htmlStr
.EndReading(end
);
957 nsACString::const_iterator
valueStart(start
), valueEnd(start
);
959 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns
, start
, end
)) {
961 htmlStr
.EndReading(end
);
963 if (CaseInsensitiveFindInReadable("charset="_ns
, start
, end
)) {
966 htmlStr
.EndReading(end
);
968 if (FindCharInReadable('"', start
, end
)) valueEnd
= start
;
971 // find "charset" in HTML
972 if (valueStart
!= valueEnd
) {
973 str
= Substring(valueStart
, valueEnd
);
975 LOGCLIP("GetHTMLCharset: Charset of HTML = %s\n", str
.get());
978 str
.AssignLiteral("UNKNOWN");
979 LOGCLIP("GetHTMLCharset: Failed to get HTML Charset!\n");