1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
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"
16 #include "nsGtkUtils.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 ClipboardTargets
nsRetrievalContext::sClipboardTargets
;
63 ClipboardTargets
nsRetrievalContext::sPrimaryTargets
;
65 // Callback when someone asks us for the data
66 void clipboard_get_cb(GtkClipboard
* aGtkClipboard
,
67 GtkSelectionData
* aSelectionData
, guint info
,
70 // Callback when someone asks us to clear a clipboard
71 void clipboard_clear_cb(GtkClipboard
* aGtkClipboard
, gpointer user_data
);
73 static bool ConvertHTMLtoUCS2(Span
<const char> aData
, nsCString
& charset
,
74 char16_t
** unicodeData
, int32_t& outUnicodeLen
);
76 static bool GetHTMLCharset(Span
<const char> aData
, nsCString
& str
);
78 ClipboardTargets
ClipboardTargets::Clone() {
83 reinterpret_cast<GdkAtom
*>(g_malloc(sizeof(GdkAtom
) * mCount
)));
84 memcpy(ret
.mTargets
.get(), mTargets
.get(), sizeof(GdkAtom
) * mCount
);
89 void ClipboardTargets::Set(ClipboardTargets aTargets
) {
90 mCount
= aTargets
.mCount
;
91 mTargets
= std::move(aTargets
.mTargets
);
94 void ClipboardData::SetData(Span
<const uint8_t> aData
) {
96 mLength
= aData
.Length();
98 mData
.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength
)));
99 memcpy(mData
.get(), aData
.data(), sizeof(char) * mLength
);
103 void ClipboardData::SetText(Span
<const char> aData
) {
105 mLength
= aData
.Length();
108 reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength
+ 1))));
109 memcpy(mData
.get(), aData
.data(), sizeof(char) * mLength
);
110 mData
.get()[mLength
] = '\0';
114 void ClipboardData::SetTargets(ClipboardTargets aTargets
) {
115 mLength
= aTargets
.mCount
;
116 mData
.reset(reinterpret_cast<char*>(aTargets
.mTargets
.release()));
119 ClipboardTargets
ClipboardData::ExtractTargets() {
120 GUniquePtr
<GdkAtom
> targets(reinterpret_cast<GdkAtom
*>(mData
.release()));
121 uint32_t length
= std::exchange(mLength
, 0);
122 return ClipboardTargets
{std::move(targets
), length
};
125 GdkAtom
GetSelectionAtom(int32_t aWhichClipboard
) {
126 if (aWhichClipboard
== nsIClipboard::kGlobalClipboard
)
127 return GDK_SELECTION_CLIPBOARD
;
129 return GDK_SELECTION_PRIMARY
;
132 int GetGeckoClipboardType(GtkClipboard
* aGtkClipboard
) {
133 if (aGtkClipboard
== gtk_clipboard_get(GDK_SELECTION_PRIMARY
))
134 return nsClipboard::kSelectionClipboard
;
135 else if (aGtkClipboard
== gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
))
136 return nsClipboard::kGlobalClipboard
;
138 return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
141 void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard
* aClipboard
,
144 LOGCLIP("nsRetrievalContext::ClearCachedTargetsClipboard()");
145 sClipboardTargets
.Clear();
148 void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard
* aClipboard
,
151 LOGCLIP("nsRetrievalContext::ClearCachedTargetsPrimary()");
152 sPrimaryTargets
.Clear();
155 ClipboardTargets
nsRetrievalContext::GetTargets(int32_t aWhichClipboard
) {
156 LOGCLIP("nsRetrievalContext::GetTargets(%s)\n",
157 aWhichClipboard
== nsClipboard::kSelectionClipboard
? "primary"
159 ClipboardTargets
& storedTargets
=
160 (aWhichClipboard
== nsClipboard::kSelectionClipboard
) ? sPrimaryTargets
162 if (!storedTargets
) {
163 LOGCLIP(" getting targets from system");
164 storedTargets
.Set(GetTargetsImpl(aWhichClipboard
));
166 LOGCLIP(" using cached targets");
168 return storedTargets
.Clone();
171 nsRetrievalContext::nsRetrievalContext() {
172 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
), "owner-change",
173 G_CALLBACK(ClearCachedTargetsClipboard
), this);
174 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY
), "owner-change",
175 G_CALLBACK(ClearCachedTargetsPrimary
), this);
178 nsRetrievalContext::~nsRetrievalContext() {
179 g_signal_handlers_disconnect_by_func(
180 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
),
181 FuncToGpointer(ClearCachedTargetsClipboard
), this);
182 g_signal_handlers_disconnect_by_func(
183 gtk_clipboard_get(GDK_SELECTION_PRIMARY
),
184 FuncToGpointer(ClearCachedTargetsPrimary
), this);
185 sClipboardTargets
.Clear();
186 sPrimaryTargets
.Clear();
189 nsClipboard::nsClipboard() = default;
191 nsClipboard::~nsClipboard() {
192 // We have to clear clipboard before gdk_display_close() call.
193 // See bug 531580 for details.
194 if (mGlobalTransferable
) {
195 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
197 if (mSelectionTransferable
) {
198 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY
));
202 NS_IMPL_ISUPPORTS(nsClipboard
, nsIClipboard
, nsIObserver
)
204 nsresult
nsClipboard::Init(void) {
205 if (widget::GdkIsX11Display()) {
206 mContext
= new nsRetrievalContextX11();
207 #if defined(MOZ_WAYLAND)
208 } else if (widget::GdkIsWaylandDisplay()) {
209 mContext
= new nsRetrievalContextWayland();
212 NS_WARNING("Missing nsRetrievalContext for nsClipboard!");
216 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
218 os
->AddObserver(this, "xpcom-shutdown", false);
225 nsClipboard::Observe(nsISupports
* aSubject
, const char* aTopic
,
226 const char16_t
* aData
) {
227 // Save global clipboard content to CLIPBOARD_MANAGER.
228 // gtk_clipboard_store() can run an event loop, so call from a dedicated
230 return SchedulerGroup::Dispatch(
232 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
233 LOGCLIP("nsClipboard storing clipboard content\n");
234 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
239 nsClipboard::SetData(nsITransferable
* aTransferable
, nsIClipboardOwner
* aOwner
,
240 int32_t aWhichClipboard
) {
241 // See if we can short cut
242 if ((aWhichClipboard
== kGlobalClipboard
&&
243 aTransferable
== mGlobalTransferable
.get() &&
244 aOwner
== mGlobalOwner
.get()) ||
245 (aWhichClipboard
== kSelectionClipboard
&&
246 aTransferable
== mSelectionTransferable
.get() &&
247 aOwner
== mSelectionOwner
.get())) {
251 LOGCLIP("nsClipboard::SetData (%s)\n",
252 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
254 // List of suported targets
255 GtkTargetList
* list
= gtk_target_list_new(nullptr, 0);
257 // Get the types of supported flavors
258 nsTArray
<nsCString
> flavors
;
259 nsresult rv
= aTransferable
->FlavorsTransferableCanExport(flavors
);
261 LOGCLIP(" FlavorsTransferableCanExport failed!\n");
262 // Fall through. |gtkTargets| will be null below.
265 // Add all the flavors to this widget's supported type.
266 bool imagesAdded
= false;
267 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
268 nsCString
& flavorStr
= flavors
[i
];
269 LOGCLIP(" processing target %s\n", flavorStr
.get());
271 // Special case text/unicode since we can handle all of the string types.
272 if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
273 LOGCLIP(" adding TEXT targets\n");
274 gtk_target_list_add_text_targets(list
, 0);
278 if (nsContentUtils::IsFlavorImage(flavorStr
)) {
279 // Don't bother adding image targets twice
281 // accept any writable image type
282 LOGCLIP(" adding IMAGE targets\n");
283 gtk_target_list_add_image_targets(list
, 0, TRUE
);
289 // Add this to our list of valid targets
290 LOGCLIP(" adding OTHER target %s\n", flavorStr
.get());
291 GdkAtom atom
= gdk_atom_intern(flavorStr
.get(), FALSE
);
292 gtk_target_list_add(list
, atom
, 0, 0);
295 // Get GTK clipboard (CLIPBOARD or PRIMARY)
296 GtkClipboard
* gtkClipboard
=
297 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard
));
300 GtkTargetEntry
* gtkTargets
=
301 gtk_target_table_new_from_list(list
, &numTargets
);
302 if (!gtkTargets
|| numTargets
== 0) {
304 " gtk_target_table_new_from_list() failed or empty list of "
306 // Clear references to the any old data and let GTK know that it is no
308 EmptyClipboard(aWhichClipboard
);
309 return NS_ERROR_FAILURE
;
312 ClearCachedTargets(aWhichClipboard
);
314 // Set getcallback and request to store data after an application exit
315 if (gtk_clipboard_set_with_data(gtkClipboard
, gtkTargets
, numTargets
,
316 clipboard_get_cb
, clipboard_clear_cb
, this)) {
317 // We managed to set-up the clipboard so update internal state
318 // We have to set it now because gtk_clipboard_set_with_data() calls
319 // clipboard_clear_cb() which reset our internal state
320 if (aWhichClipboard
== kSelectionClipboard
) {
321 mSelectionOwner
= aOwner
;
322 mSelectionTransferable
= aTransferable
;
324 mGlobalOwner
= aOwner
;
325 mGlobalTransferable
= aTransferable
;
326 gtk_clipboard_set_can_store(gtkClipboard
, gtkTargets
, numTargets
);
331 LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
332 EmptyClipboard(aWhichClipboard
);
333 rv
= NS_ERROR_FAILURE
;
336 gtk_target_table_free(gtkTargets
, numTargets
);
337 gtk_target_list_unref(list
);
342 void nsClipboard::SetTransferableData(nsITransferable
* aTransferable
,
344 const char* aClipboardData
,
345 uint32_t aClipboardDataLength
) {
346 LOGCLIP("nsClipboard::SetTransferableData MIME %s\n", aFlavor
.get());
348 nsCOMPtr
<nsISupports
> wrapper
;
349 nsPrimitiveHelpers::CreatePrimitiveForData(
350 aFlavor
, aClipboardData
, aClipboardDataLength
, getter_AddRefs(wrapper
));
351 aTransferable
->SetTransferData(aFlavor
.get(), wrapper
);
354 static bool IsMIMEAtFlavourList(const nsTArray
<nsCString
>& aFlavourList
,
356 for (const auto& flavorStr
: aFlavourList
) {
357 if (flavorStr
.Equals(aMime
)) {
364 // When clipboard contains only images, X11/Gtk tries to convert them
365 // to text when we request text instead of just fail to provide the data.
366 // So if clipboard contains images only remove text MIME offer.
367 bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard
,
368 nsTArray
<nsCString
>& aFlavors
) {
369 LOGCLIP("nsClipboard::FilterImportedFlavors");
371 auto targets
= mContext
->GetTargets(aWhichClipboard
);
373 LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
377 for (const auto& atom
: targets
.AsSpan()) {
378 GUniquePtr
<gchar
> atom_name(gdk_atom_name(atom
));
382 // Filter out system MIME types.
383 if (strcmp(atom_name
.get(), "TARGETS") == 0 ||
384 strcmp(atom_name
.get(), "TIMESTAMP") == 0 ||
385 strcmp(atom_name
.get(), "SAVE_TARGETS") == 0 ||
386 strcmp(atom_name
.get(), "MULTIPLE") == 0) {
389 // Filter out types which can't be converted to text.
390 if (strncmp(atom_name
.get(), "image/", 6) == 0 ||
391 strncmp(atom_name
.get(), "application/", 12) == 0 ||
392 strncmp(atom_name
.get(), "audio/", 6) == 0 ||
393 strncmp(atom_name
.get(), "video/", 6) == 0) {
396 // We have some other MIME type on clipboard which can be hopefully
397 // converted to text without any problem.
398 LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
402 // So make sure we offer only types we have at clipboard.
403 nsTArray
<nsCString
> clipboardFlavors
;
404 for (const auto& atom
: targets
.AsSpan()) {
405 GUniquePtr
<gchar
> atom_name(gdk_atom_name(atom
));
409 if (IsMIMEAtFlavourList(aFlavors
, atom_name
.get())) {
410 clipboardFlavors
.AppendElement(nsCString(atom_name
.get()));
413 aFlavors
.SwapElements(clipboardFlavors
);
415 LOGCLIP(" X11: Flavors which match clipboard content:\n");
416 for (uint32_t i
= 0; i
< aFlavors
.Length(); i
++) {
417 LOGCLIP(" %s\n", aFlavors
[i
].get());
424 nsClipboard::GetData(nsITransferable
* aTransferable
, int32_t aWhichClipboard
) {
425 LOGCLIP("nsClipboard::GetData (%s)\n",
426 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
428 // TODO: Ensure we don't re-enter here.
429 if (!aTransferable
|| !mContext
) {
430 return NS_ERROR_FAILURE
;
433 // Get a list of flavors this transferable can import
434 nsTArray
<nsCString
> flavors
;
435 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(flavors
);
437 LOGCLIP(" FlavorsTransferableCanImport falied!\n");
441 LOGCLIP(" Flavors which can be imported:");
442 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
443 LOGCLIP(" %s\n", flavors
[i
].get());
447 // Filter out MIME types on X11 to prevent unwanted conversions,
449 if (widget::GdkIsX11Display() &&
450 !FilterImportedFlavors(aWhichClipboard
, flavors
)) {
451 LOGCLIP(" Missing suitable clipboard data, quit.");
455 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
456 nsCString
& flavorStr
= flavors
[i
];
458 if (flavorStr
.EqualsLiteral(kJPEGImageMime
) ||
459 flavorStr
.EqualsLiteral(kJPGImageMime
) ||
460 flavorStr
.EqualsLiteral(kPNGImageMime
) ||
461 flavorStr
.EqualsLiteral(kGIFImageMime
)) {
462 // Emulate support for image/jpg
463 if (flavorStr
.EqualsLiteral(kJPGImageMime
)) {
464 flavorStr
.Assign(kJPEGImageMime
);
467 LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr
.get());
470 mContext
->GetClipboardData(flavorStr
.get(), aWhichClipboard
);
471 if (!clipboardData
) {
472 LOGCLIP(" %s type is missing\n", flavorStr
.get());
476 nsCOMPtr
<nsIInputStream
> byteStream
;
477 NS_NewByteInputStream(getter_AddRefs(byteStream
), clipboardData
.AsSpan(),
479 aTransferable
->SetTransferData(flavorStr
.get(), byteStream
);
480 LOGCLIP(" got %s MIME data\n", flavorStr
.get());
484 // Special case text/unicode since we can convert any
485 // string into text/unicode
486 if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
487 LOGCLIP(" Getting unicode %s MIME clipboard data\n", flavorStr
.get());
489 auto clipboardData
= mContext
->GetClipboardText(aWhichClipboard
);
490 if (!clipboardData
) {
491 LOGCLIP(" failed to get unicode data\n");
492 // If the type was text/unicode and we couldn't get
493 // text off the clipboard, run the next loop
498 // Convert utf-8 into our unicode format.
499 NS_ConvertUTF8toUTF16
ucs2string(clipboardData
.get());
500 SetTransferableData(aTransferable
, flavorStr
,
501 (const char*)ucs2string
.BeginReading(),
502 ucs2string
.Length() * 2);
504 LOGCLIP(" got unicode data, length %zd\n", ucs2string
.Length());
508 if (flavorStr
.EqualsLiteral(kFileMime
)) {
509 LOGCLIP(" Getting %s file clipboard data\n", flavorStr
.get());
512 mContext
->GetClipboardData(kURIListMime
, aWhichClipboard
);
513 if (!clipboardData
) {
514 LOGCLIP(" text/uri-list type is missing\n");
518 nsDependentCSubstring
data(clipboardData
.AsSpan());
519 nsTArray
<nsCString
> uris
= mozilla::widget::ParseTextURIList(data
);
520 if (!uris
.IsEmpty()) {
521 nsCOMPtr
<nsIURI
> fileURI
;
522 NS_NewURI(getter_AddRefs(fileURI
), uris
[0]);
523 if (nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(fileURI
, &rv
)) {
524 nsCOMPtr
<nsIFile
> file
;
525 rv
= fileURL
->GetFile(getter_AddRefs(file
));
526 if (NS_SUCCEEDED(rv
)) {
527 aTransferable
->SetTransferData(flavorStr
.get(), file
);
528 LOGCLIP(" successfully set file to clipboard\n");
535 LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr
.get());
538 mContext
->GetClipboardData(flavorStr
.get(), aWhichClipboard
);
541 if (!clipboardData
) {
542 LOGCLIP(" %s type is missing\n", flavorStr
.get());
547 LOGCLIP(" got %s mime type data.\n", flavorStr
.get());
549 // Special case text/html since we can convert into UCS2
550 if (flavorStr
.EqualsLiteral(kHTMLMime
)) {
551 char16_t
* htmlBody
= nullptr;
552 int32_t htmlBodyLen
= 0;
553 // Convert text/html into our unicode format
554 nsAutoCString charset
;
555 if (!GetHTMLCharset(clipboardData
.AsSpan(), charset
)) {
556 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
557 LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
558 charset
.AssignLiteral("utf-8");
560 if (!ConvertHTMLtoUCS2(clipboardData
.AsSpan(), charset
, &htmlBody
,
562 LOGCLIP(" failed to convert text/html to UCS2.\n");
566 SetTransferableData(aTransferable
, flavorStr
, (const char*)htmlBody
,
570 auto span
= clipboardData
.AsSpan();
571 SetTransferableData(aTransferable
, flavorStr
, span
.data(),
578 LOGCLIP(" failed to get clipboard content.\n");
583 nsClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
584 LOGCLIP("nsClipboard::EmptyClipboard (%s)\n",
585 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
586 if (aWhichClipboard
== kSelectionClipboard
) {
587 if (mSelectionTransferable
) {
588 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY
));
589 MOZ_ASSERT(!mSelectionTransferable
);
592 if (mGlobalTransferable
) {
593 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
594 MOZ_ASSERT(!mGlobalTransferable
);
597 ClearCachedTargets(aWhichClipboard
);
601 void nsClipboard::ClearTransferable(int32_t aWhichClipboard
) {
602 if (aWhichClipboard
== kSelectionClipboard
) {
603 if (mSelectionOwner
) {
604 mSelectionOwner
->LosingOwnership(mSelectionTransferable
);
605 mSelectionOwner
= nullptr;
607 mSelectionTransferable
= nullptr;
610 mGlobalOwner
->LosingOwnership(mGlobalTransferable
);
611 mGlobalOwner
= nullptr;
613 mGlobalTransferable
= nullptr;
618 nsClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
619 int32_t aWhichClipboard
, bool* _retval
) {
621 return NS_ERROR_NULL_POINTER
;
624 LOGCLIP("nsClipboard::HasDataMatchingFlavors (%s)\n",
625 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
630 return NS_ERROR_FAILURE
;
633 auto targets
= mContext
->GetTargets(aWhichClipboard
);
635 LOGCLIP(" no targes at clipboard (null)\n");
640 if (LOGCLIP_ENABLED()) {
641 LOGCLIP(" Clipboard content (target nums %zu):\n",
642 targets
.AsSpan().Length());
643 for (const auto& target
: targets
.AsSpan()) {
644 GUniquePtr
<gchar
> atom_name(gdk_atom_name(target
));
646 LOGCLIP(" failed to get MIME\n");
649 LOGCLIP(" MIME %s\n", atom_name
.get());
651 LOGCLIP(" Asking for content:\n");
652 for (auto& flavor
: aFlavorList
) {
653 LOGCLIP(" MIME %s\n", flavor
.get());
658 // Walk through the provided types and try to match it to a
660 for (auto& flavor
: aFlavorList
) {
661 // We special case text/unicode here.
662 if (flavor
.EqualsLiteral(kUnicodeMime
) &&
663 gtk_targets_include_text(targets
.AsSpan().data(),
664 targets
.AsSpan().Length())) {
666 LOGCLIP(" has kUnicodeMime\n");
670 for (const auto& target
: targets
.AsSpan()) {
671 GUniquePtr
<gchar
> atom_name(gdk_atom_name(target
));
676 if (flavor
.Equals(atom_name
.get())) {
677 LOGCLIP(" has %s\n", atom_name
.get());
681 // X clipboard supports image/jpeg, but we want to emulate support
682 // for image/jpg as well
683 if (flavor
.EqualsLiteral(kJPGImageMime
) &&
684 !strcmp(atom_name
.get(), kJPEGImageMime
)) {
685 LOGCLIP(" has image/jpg\n");
689 // application/x-moz-file should be treated like text/uri-list
690 if (flavor
.EqualsLiteral(kFileMime
) &&
691 !strcmp(atom_name
.get(), kURIListMime
)) {
692 LOGCLIP(" has text/uri-list treating as application/x-moz-file");
705 LOGCLIP(" no targes at clipboard (bad match)\n");
713 nsClipboard::SupportsSelectionClipboard(bool* _retval
) {
719 nsClipboard::SupportsFindClipboard(bool* _retval
) {
724 nsITransferable
* nsClipboard::GetTransferable(int32_t aWhichClipboard
) {
725 nsITransferable
* retval
;
727 if (aWhichClipboard
== kSelectionClipboard
)
728 retval
= mSelectionTransferable
.get();
730 retval
= mGlobalTransferable
.get();
735 void nsClipboard::SelectionGetEvent(GtkClipboard
* aClipboard
,
736 GtkSelectionData
* aSelectionData
) {
737 // Someone has asked us to hand them something. The first thing
738 // that we want to do is see if that something includes text. If
739 // it does, try to give it text/unicode after converting it to
742 int32_t whichClipboard
;
745 GdkAtom selection
= gtk_selection_data_get_selection(aSelectionData
);
746 if (selection
== GDK_SELECTION_PRIMARY
)
747 whichClipboard
= kSelectionClipboard
;
748 else if (selection
== GDK_SELECTION_CLIPBOARD
)
749 whichClipboard
= kGlobalClipboard
;
751 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
753 LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
754 whichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
756 nsCOMPtr
<nsITransferable
> trans
= GetTransferable(whichClipboard
);
758 // We have nothing to serve
759 LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
760 whichClipboard
== kSelectionClipboard
? "Primary" : "Clipboard");
765 nsCOMPtr
<nsISupports
> item
;
767 GdkAtom selectionTarget
= gtk_selection_data_get_target(aSelectionData
);
768 LOGCLIP(" selection target %s\n",
769 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
771 // Check to see if the selection data is some text type.
772 if (gtk_targets_include_text(&selectionTarget
, 1)) {
773 LOGCLIP(" providing text/unicode data\n");
774 // Try to convert our internal type into a text string. Get
775 // the transferable for this clipboard and try to get the
776 // text/unicode type for it.
777 rv
= trans
->GetTransferData("text/unicode", getter_AddRefs(item
));
778 if (NS_FAILED(rv
) || !item
) {
779 LOGCLIP(" GetTransferData() failed to get text/unicode!\n");
783 nsCOMPtr
<nsISupportsString
> wideString
;
784 wideString
= do_QueryInterface(item
);
785 if (!wideString
) return;
787 nsAutoString ucs2string
;
788 wideString
->GetData(ucs2string
);
789 NS_ConvertUTF16toUTF8
utf8string(ucs2string
);
791 LOGCLIP(" sent %zd bytes of utf-8 data\n", utf8string
.Length());
792 if (selectionTarget
== gdk_atom_intern("text/plain;charset=utf-8", FALSE
)) {
794 " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
795 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
796 // in some versions of GTK.
797 gtk_selection_data_set(aSelectionData
, selectionTarget
, 8,
798 reinterpret_cast<const guchar
*>(utf8string
.get()),
799 utf8string
.Length());
801 gtk_selection_data_set_text(aSelectionData
, utf8string
.get(),
802 utf8string
.Length());
807 // Check to see if the selection data is an image type
808 if (gtk_targets_include_image(&selectionTarget
, 1, TRUE
)) {
809 LOGCLIP(" providing image data\n");
810 // Look through our transfer data for the image
811 static const char* const imageMimeTypes
[] = {kNativeImageMime
,
812 kPNGImageMime
, kJPEGImageMime
,
813 kJPGImageMime
, kGIFImageMime
};
814 nsCOMPtr
<nsISupports
> imageItem
;
815 nsCOMPtr
<imgIContainer
> image
;
816 for (uint32_t i
= 0; i
< ArrayLength(imageMimeTypes
); i
++) {
817 rv
= trans
->GetTransferData(imageMimeTypes
[i
], getter_AddRefs(imageItem
));
819 LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes
[i
]);
823 image
= do_QueryInterface(imageItem
);
825 LOGCLIP(" %s is available at GetTransferData()\n",
831 if (!image
) { // Not getting an image for an image mime type!?
832 LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
836 RefPtr
<GdkPixbuf
> pixbuf
= nsImageToPixbuf::ImageToPixbuf(image
);
838 LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
842 LOGCLIP(" Setting pixbuf image data as %s\n",
843 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
844 gtk_selection_data_set_pixbuf(aSelectionData
, pixbuf
);
848 if (selectionTarget
== gdk_atom_intern(kHTMLMime
, FALSE
)) {
849 LOGCLIP(" providing %s data\n", kHTMLMime
);
850 rv
= trans
->GetTransferData(kHTMLMime
, getter_AddRefs(item
));
851 if (NS_FAILED(rv
) || !item
) {
852 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime
);
856 nsCOMPtr
<nsISupportsString
> wideString
;
857 wideString
= do_QueryInterface(item
);
859 LOGCLIP(" failed to get wideString interface!");
863 nsAutoString ucs2string
;
864 wideString
->GetData(ucs2string
);
867 // Add the prefix so the encoding is correctly detected.
868 html
.AppendLiteral(kHTMLMarkupPrefix
);
869 AppendUTF16toUTF8(ucs2string
, html
);
871 LOGCLIP(" Setting %zd bytest of %s data\n", html
.Length(),
872 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
873 gtk_selection_data_set(aSelectionData
, selectionTarget
, 8,
874 (const guchar
*)html
.get(), html
.Length());
878 LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
879 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
881 // Try to match up the selection data target to something our
882 // transferable provides.
883 GUniquePtr
<gchar
> target_name(gdk_atom_name(selectionTarget
));
885 LOGCLIP(" Failed to get target name!\n");
889 rv
= trans
->GetTransferData(target_name
.get(), getter_AddRefs(item
));
891 if (NS_FAILED(rv
) || !item
) {
892 LOGCLIP(" Failed to get anything from GetTransferData()!\n");
896 void* primitive_data
= nullptr;
897 uint32_t dataLen
= 0;
898 nsPrimitiveHelpers::CreateDataFromPrimitive(
899 nsDependentCString(target_name
.get()), item
, &primitive_data
, &dataLen
);
900 if (!primitive_data
) {
901 LOGCLIP(" Failed to get primitive data!\n");
905 LOGCLIP(" Setting %s as a primitive data type, %d bytes\n",
906 target_name
.get(), dataLen
);
907 gtk_selection_data_set(aSelectionData
, selectionTarget
,
908 8, /* 8 bits in a unit */
909 (const guchar
*)primitive_data
, dataLen
);
910 free(primitive_data
);
913 void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard
) {
914 if (aWhichClipboard
== kSelectionClipboard
) {
915 nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr);
917 nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
921 void nsClipboard::SelectionClearEvent(GtkClipboard
* aGtkClipboard
) {
922 int32_t whichClipboard
= GetGeckoClipboardType(aGtkClipboard
);
923 if (whichClipboard
< 0) {
926 LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
927 whichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
928 ClearCachedTargets(whichClipboard
);
929 ClearTransferable(whichClipboard
);
932 void clipboard_get_cb(GtkClipboard
* aGtkClipboard
,
933 GtkSelectionData
* aSelectionData
, guint info
,
934 gpointer user_data
) {
935 LOGCLIP("clipboard_get_cb() callback\n");
936 nsClipboard
* aClipboard
= static_cast<nsClipboard
*>(user_data
);
937 aClipboard
->SelectionGetEvent(aGtkClipboard
, aSelectionData
);
940 void clipboard_clear_cb(GtkClipboard
* aGtkClipboard
, gpointer user_data
) {
941 LOGCLIP("clipboard_clear_cb() callback\n");
942 nsClipboard
* aClipboard
= static_cast<nsClipboard
*>(user_data
);
943 aClipboard
->SelectionClearEvent(aGtkClipboard
);
947 * when copy-paste, mozilla wants data encoded using UCS2,
948 * other app such as StarOffice use "text/html"(RFC2854).
949 * This function convert data(got from GTK clipboard)
950 * to data mozilla wanted.
952 * data from GTK clipboard can be 3 forms:
953 * 1. From current mozilla
954 * "text/html", charset = utf-16
955 * 2. From old version mozilla or mozilla-based app
956 * content("body" only), charset = utf-16
957 * 3. From other app who use "text/html" when copy-paste
958 * "text/html", has "charset" info
960 * data : got from GTK clipboard
961 * dataLength: got from GTK clipboard
962 * body : pass to Mozilla
963 * bodyLength: pass to Mozilla
965 bool ConvertHTMLtoUCS2(Span
<const char> aData
, nsCString
& charset
,
966 char16_t
** unicodeData
, int32_t& outUnicodeLen
) {
967 if (charset
.EqualsLiteral("UTF-16")) { // current mozilla
968 outUnicodeLen
= (aData
.Length() / 2) - 1;
969 *unicodeData
= reinterpret_cast<char16_t
*>(
970 moz_xmalloc((outUnicodeLen
+ sizeof('\0')) * sizeof(char16_t
)));
971 memcpy(*unicodeData
, aData
.data() + sizeof(char16_t
),
972 outUnicodeLen
* sizeof(char16_t
));
973 (*unicodeData
)[outUnicodeLen
] = '\0';
976 if (charset
.EqualsLiteral("UNKNOWN")) {
980 // app which use "text/html" to copy&paste
982 auto encoding
= Encoding::ForLabelNoReplacement(charset
);
984 LOGCLIP("ConvertHTMLtoUCS2: get unicode decoder error\n");
989 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
990 // issues, but might confuse other users.
991 const size_t prefixLen
= ArrayLength(kHTMLMarkupPrefix
) - 1;
992 if (aData
.Length() >= prefixLen
&& nsDependentCSubstring(aData
.To(prefixLen
))
993 .EqualsLiteral(kHTMLMarkupPrefix
)) {
994 aData
= aData
.From(prefixLen
);
997 auto decoder
= encoding
->NewDecoder();
998 CheckedInt
<size_t> needed
= decoder
->MaxUTF16BufferLength(aData
.Length());
999 if (!needed
.isValid() || needed
.value() > INT32_MAX
) {
1005 if (needed
.value()) {
1006 *unicodeData
= reinterpret_cast<char16_t
*>(
1007 moz_xmalloc((needed
.value() + 1) * sizeof(char16_t
)));
1011 std::tie(result
, read
, written
, std::ignore
) = decoder
->DecodeToUTF16(
1012 AsBytes(aData
), Span(*unicodeData
, needed
.value()), true);
1013 MOZ_ASSERT(result
== kInputEmpty
);
1014 MOZ_ASSERT(read
== size_t(aData
.Length()));
1015 MOZ_ASSERT(written
<= needed
.value());
1016 outUnicodeLen
= written
;
1018 (*unicodeData
)[outUnicodeLen
] = '\0';
1020 } // if valid length
1025 * get "charset" information from clipboard data
1026 * return value can be:
1027 * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
1028 * 2. "UNKNOWN": mozilla can't detect what encode it use
1029 * 3. other: "text/html" with other charset than utf-16
1031 bool GetHTMLCharset(Span
<const char> aData
, nsCString
& str
) {
1032 // if detect "FFFE" or "FEFF", assume UTF-16
1034 char16_t
* beginChar
= (char16_t
*)aData
.data();
1035 if ((beginChar
[0] == 0xFFFE) || (beginChar
[0] == 0xFEFF)) {
1036 str
.AssignLiteral("UTF-16");
1037 LOGCLIP("GetHTMLCharset: Charset of HTML is UTF-16\n");
1042 // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
1043 const nsDependentCSubstring
htmlStr(aData
);
1044 nsACString::const_iterator start
, end
;
1045 htmlStr
.BeginReading(start
);
1046 htmlStr
.EndReading(end
);
1047 nsACString::const_iterator
valueStart(start
), valueEnd(start
);
1049 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns
, start
, end
)) {
1051 htmlStr
.EndReading(end
);
1053 if (CaseInsensitiveFindInReadable("charset="_ns
, start
, end
)) {
1056 htmlStr
.EndReading(end
);
1058 if (FindCharInReadable('"', start
, end
)) valueEnd
= start
;
1061 // find "charset" in HTML
1062 if (valueStart
!= valueEnd
) {
1063 str
= Substring(valueStart
, valueEnd
);
1065 LOGCLIP("GetHTMLCharset: Charset of HTML = %s\n", str
.get());
1068 str
.AssignLiteral("UNKNOWN");
1069 LOGCLIP("GetHTMLCharset: Failed to get HTML Charset!\n");