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"
13 # include "nsClipboardX11.h"
15 #if defined(MOZ_WAYLAND)
16 # include "nsClipboardWayland.h"
18 #include "nsGtkUtils.h"
21 #include "nsNetUtil.h"
22 #include "nsContentUtils.h"
23 #include "HeadlessClipboard.h"
24 #include "nsSupportsPrimitives.h"
26 #include "nsReadableUtils.h"
27 #include "nsPrimitiveHelpers.h"
28 #include "nsImageToPixbuf.h"
29 #include "nsStringStream.h"
30 #include "nsIFileURL.h"
31 #include "nsIObserverService.h"
32 #include "mozilla/Services.h"
33 #include "mozilla/RefPtr.h"
34 #include "mozilla/GRefPtr.h"
35 #include "mozilla/SchedulerGroup.h"
36 #include "mozilla/StaticPrefs_widget.h"
37 #include "mozilla/TimeStamp.h"
39 #include "WidgetUtilsGtk.h"
41 #include "imgIContainer.h"
45 # include <gtk/gtkx.h>
48 #include "mozilla/Encoding.h"
50 using namespace mozilla
;
52 // Idle timeout for receiving selection and property notify events (microsec)
53 // Right now it's set to 1 sec.
54 const int kClipboardTimeout
= 1000000;
56 // Defines how many event loop iterations will be done without sleep.
57 // We ususally get data in first 2-3 iterations unless some large object
58 // (an image for instance) is transferred through clipboard.
59 const int kClipboardFastIterationNum
= 3;
61 // We add this prefix to HTML markup, so that GetHTMLCharset can correctly
62 // detect the HTML as UTF-8 encoded.
63 static const char kHTMLMarkupPrefix
[] =
64 R
"(<meta http-equiv="content
-type
" content="text
/html
; charset
=utf
-8">)";
66 static const char kURIListMime
[] = "text/uri-list";
68 ClipboardTargets
nsRetrievalContext::sClipboardTargets
;
69 ClipboardTargets
nsRetrievalContext::sPrimaryTargets
;
71 // Callback when someone asks us for the data
72 static void clipboard_get_cb(GtkClipboard
* aGtkClipboard
,
73 GtkSelectionData
* aSelectionData
, guint info
,
76 // Callback when someone asks us to clear a clipboard
77 static void clipboard_clear_cb(GtkClipboard
* aGtkClipboard
, gpointer user_data
);
79 // Callback when owner of clipboard data is changed
80 static void clipboard_owner_change_cb(GtkClipboard
* aGtkClipboard
,
81 GdkEventOwnerChange
* aEvent
,
84 static bool GetHTMLCharset(Span
<const char> aData
, nsCString
& str
);
86 static void SetTransferableData(nsITransferable
* aTransferable
,
87 const nsACString
& aFlavor
,
88 const char* aClipboardData
,
89 uint32_t aClipboardDataLength
) {
90 LOGCLIP("SetTransferableData MIME %s\n", PromiseFlatCString(aFlavor
).get());
91 nsCOMPtr
<nsISupports
> wrapper
;
92 nsPrimitiveHelpers::CreatePrimitiveForData(
93 aFlavor
, aClipboardData
, aClipboardDataLength
, getter_AddRefs(wrapper
));
94 aTransferable
->SetTransferData(PromiseFlatCString(aFlavor
).get(), wrapper
);
97 ClipboardTargets
ClipboardTargets::Clone() {
102 reinterpret_cast<GdkAtom
*>(g_malloc(sizeof(GdkAtom
) * mCount
)));
103 memcpy(ret
.mTargets
.get(), mTargets
.get(), sizeof(GdkAtom
) * mCount
);
108 void ClipboardTargets::Set(ClipboardTargets aTargets
) {
109 mCount
= aTargets
.mCount
;
110 mTargets
= std::move(aTargets
.mTargets
);
113 void ClipboardData::SetData(Span
<const uint8_t> aData
) {
115 mLength
= aData
.Length();
117 mData
.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength
)));
118 memcpy(mData
.get(), aData
.data(), sizeof(char) * mLength
);
122 void ClipboardData::SetText(Span
<const char> aData
) {
124 mLength
= aData
.Length();
127 reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength
+ 1))));
128 memcpy(mData
.get(), aData
.data(), sizeof(char) * mLength
);
129 mData
.get()[mLength
] = '\0';
133 void ClipboardData::SetTargets(ClipboardTargets aTargets
) {
134 mLength
= aTargets
.mCount
;
135 mData
.reset(reinterpret_cast<char*>(aTargets
.mTargets
.release()));
138 ClipboardTargets
ClipboardData::ExtractTargets() {
139 GUniquePtr
<GdkAtom
> targets(reinterpret_cast<GdkAtom
*>(mData
.release()));
140 uint32_t length
= std::exchange(mLength
, 0);
141 return ClipboardTargets
{std::move(targets
), length
};
144 GdkAtom
GetSelectionAtom(int32_t aWhichClipboard
) {
145 if (aWhichClipboard
== nsIClipboard::kGlobalClipboard
)
146 return GDK_SELECTION_CLIPBOARD
;
148 return GDK_SELECTION_PRIMARY
;
151 int GetGeckoClipboardType(GtkClipboard
* aGtkClipboard
) {
152 if (aGtkClipboard
== gtk_clipboard_get(GDK_SELECTION_PRIMARY
))
153 return nsClipboard::kSelectionClipboard
;
154 else if (aGtkClipboard
== gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
))
155 return nsClipboard::kGlobalClipboard
;
157 return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
160 void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard
* aClipboard
,
163 LOGCLIP("nsRetrievalContext::ClearCachedTargetsClipboard()");
164 sClipboardTargets
.Clear();
167 void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard
* aClipboard
,
170 LOGCLIP("nsRetrievalContext::ClearCachedTargetsPrimary()");
171 sPrimaryTargets
.Clear();
174 ClipboardTargets
nsRetrievalContext::GetTargets(int32_t aWhichClipboard
) {
175 LOGCLIP("nsRetrievalContext::GetTargets(%s)\n",
176 aWhichClipboard
== nsClipboard::kSelectionClipboard
? "primary"
178 ClipboardTargets
& storedTargets
=
179 (aWhichClipboard
== nsClipboard::kSelectionClipboard
) ? sPrimaryTargets
181 if (!storedTargets
) {
182 LOGCLIP(" getting targets from system");
183 storedTargets
.Set(GetTargetsImpl(aWhichClipboard
));
185 LOGCLIP(" using cached targets");
187 return storedTargets
.Clone();
190 nsRetrievalContext::~nsRetrievalContext() {
191 sClipboardTargets
.Clear();
192 sPrimaryTargets
.Clear();
195 nsClipboard::nsClipboard()
196 : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
197 true /* supportsSelectionClipboard */,
198 false /* supportsFindClipboard */,
199 false /* supportsSelectionCache */)) {
200 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
), "owner-change",
201 G_CALLBACK(clipboard_owner_change_cb
), this);
202 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY
), "owner-change",
203 G_CALLBACK(clipboard_owner_change_cb
), this);
206 nsClipboard::~nsClipboard() {
207 g_signal_handlers_disconnect_by_func(
208 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
),
209 FuncToGpointer(clipboard_owner_change_cb
), this);
210 g_signal_handlers_disconnect_by_func(
211 gtk_clipboard_get(GDK_SELECTION_PRIMARY
),
212 FuncToGpointer(clipboard_owner_change_cb
), this);
215 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard
, nsBaseClipboard
, nsIObserver
)
217 nsresult
nsClipboard::Init(void) {
219 if (widget::GdkIsX11Display()) {
220 mContext
= new nsRetrievalContextX11();
223 #if defined(MOZ_WAYLAND)
224 if (widget::GdkIsWaylandDisplay()) {
225 mContext
= new nsRetrievalContextWayland();
229 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
231 os
->AddObserver(this, "xpcom-shutdown", false);
238 nsClipboard::Observe(nsISupports
* aSubject
, const char* aTopic
,
239 const char16_t
* aData
) {
240 // Save global clipboard content to CLIPBOARD_MANAGER.
241 // gtk_clipboard_store() can run an event loop, so call from a dedicated
243 return SchedulerGroup::Dispatch(
244 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
245 LOGCLIP("nsClipboard storing clipboard content\n");
246 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
251 nsClipboard::SetNativeClipboardData(nsITransferable
* aTransferable
,
252 int32_t aWhichClipboard
) {
253 MOZ_DIAGNOSTIC_ASSERT(aTransferable
);
254 MOZ_DIAGNOSTIC_ASSERT(
255 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
257 // See if we can short cut
258 if ((aWhichClipboard
== kGlobalClipboard
&&
259 aTransferable
== mGlobalTransferable
.get()) ||
260 (aWhichClipboard
== kSelectionClipboard
&&
261 aTransferable
== mSelectionTransferable
.get())) {
265 LOGCLIP("nsClipboard::SetNativeClipboardData (%s)\n",
266 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
268 // List of suported targets
269 GtkTargetList
* list
= gtk_target_list_new(nullptr, 0);
271 // Get the types of supported flavors
272 nsTArray
<nsCString
> flavors
;
273 nsresult rv
= aTransferable
->FlavorsTransferableCanExport(flavors
);
275 LOGCLIP(" FlavorsTransferableCanExport failed!\n");
276 // Fall through. |gtkTargets| will be null below.
279 // Add all the flavors to this widget's supported type.
280 bool imagesAdded
= false;
281 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
282 nsCString
& flavorStr
= flavors
[i
];
283 LOGCLIP(" processing target %s\n", flavorStr
.get());
285 // Special case text/plain since we can handle all of the string types.
286 if (flavorStr
.EqualsLiteral(kTextMime
)) {
287 LOGCLIP(" adding TEXT targets\n");
288 gtk_target_list_add_text_targets(list
, 0);
292 if (nsContentUtils::IsFlavorImage(flavorStr
)) {
293 // Don't bother adding image targets twice
295 // accept any writable image type
296 LOGCLIP(" adding IMAGE targets\n");
297 gtk_target_list_add_image_targets(list
, 0, TRUE
);
303 if (flavorStr
.EqualsLiteral(kFileMime
)) {
304 LOGCLIP(" adding text/uri-list target\n");
305 GdkAtom atom
= gdk_atom_intern(kURIListMime
, FALSE
);
306 gtk_target_list_add(list
, atom
, 0, 0);
310 // Add this to our list of valid targets
311 LOGCLIP(" adding OTHER target %s\n", flavorStr
.get());
312 GdkAtom atom
= gdk_atom_intern(flavorStr
.get(), FALSE
);
313 gtk_target_list_add(list
, atom
, 0, 0);
316 // Get GTK clipboard (CLIPBOARD or PRIMARY)
317 GtkClipboard
* gtkClipboard
=
318 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard
));
321 GtkTargetEntry
* gtkTargets
=
322 gtk_target_table_new_from_list(list
, &numTargets
);
323 if (!gtkTargets
|| numTargets
== 0) {
325 " gtk_target_table_new_from_list() failed or empty list of "
327 // Clear references to the any old data and let GTK know that it is no
329 EmptyNativeClipboardData(aWhichClipboard
);
330 return NS_ERROR_FAILURE
;
333 ClearCachedTargets(aWhichClipboard
);
335 // Set getcallback and request to store data after an application exit
336 if (gtk_clipboard_set_with_data(gtkClipboard
, gtkTargets
, numTargets
,
337 clipboard_get_cb
, clipboard_clear_cb
, this)) {
338 // We managed to set-up the clipboard so update internal state
339 // We have to set it now because gtk_clipboard_set_with_data() calls
340 // clipboard_clear_cb() which reset our internal state
341 if (aWhichClipboard
== kSelectionClipboard
) {
342 mSelectionSequenceNumber
++;
343 mSelectionTransferable
= aTransferable
;
345 mGlobalSequenceNumber
++;
346 mGlobalTransferable
= aTransferable
;
347 gtk_clipboard_set_can_store(gtkClipboard
, gtkTargets
, numTargets
);
352 LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
353 EmptyNativeClipboardData(aWhichClipboard
);
354 rv
= NS_ERROR_FAILURE
;
357 gtk_target_table_free(gtkTargets
, numTargets
);
358 gtk_target_list_unref(list
);
363 mozilla::Result
<int32_t, nsresult
>
364 nsClipboard::GetNativeClipboardSequenceNumber(int32_t aWhichClipboard
) {
365 MOZ_DIAGNOSTIC_ASSERT(
366 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
367 return aWhichClipboard
== kSelectionClipboard
? mSelectionSequenceNumber
368 : mGlobalSequenceNumber
;
371 static bool IsMIMEAtFlavourList(const nsTArray
<nsCString
>& aFlavourList
,
373 for (const auto& flavorStr
: aFlavourList
) {
374 if (flavorStr
.Equals(aMime
)) {
381 // When clipboard contains only images, X11/Gtk tries to convert them
382 // to text when we request text instead of just fail to provide the data.
383 // So if clipboard contains images only remove text MIME offer.
384 bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard
,
385 nsTArray
<nsCString
>& aFlavors
) {
386 LOGCLIP("nsClipboard::FilterImportedFlavors");
388 auto targets
= mContext
->GetTargets(aWhichClipboard
);
390 LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
394 for (const auto& atom
: targets
.AsSpan()) {
395 GUniquePtr
<gchar
> atom_name(gdk_atom_name(atom
));
399 // Filter out system MIME types.
400 if (strcmp(atom_name
.get(), "TARGETS") == 0 ||
401 strcmp(atom_name
.get(), "TIMESTAMP") == 0 ||
402 strcmp(atom_name
.get(), "SAVE_TARGETS") == 0 ||
403 strcmp(atom_name
.get(), "MULTIPLE") == 0) {
406 // Filter out types which can't be converted to text.
407 if (strncmp(atom_name
.get(), "image/", 6) == 0 ||
408 strncmp(atom_name
.get(), "application/", 12) == 0 ||
409 strncmp(atom_name
.get(), "audio/", 6) == 0 ||
410 strncmp(atom_name
.get(), "video/", 6) == 0) {
413 // We have some other MIME type on clipboard which can be hopefully
414 // converted to text without any problem.
415 LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
419 // So make sure we offer only types we have at clipboard.
420 nsTArray
<nsCString
> clipboardFlavors
;
421 for (const auto& atom
: targets
.AsSpan()) {
422 GUniquePtr
<gchar
> atom_name(gdk_atom_name(atom
));
426 if (IsMIMEAtFlavourList(aFlavors
, atom_name
.get())) {
427 clipboardFlavors
.AppendElement(nsCString(atom_name
.get()));
430 aFlavors
.SwapElements(clipboardFlavors
);
432 LOGCLIP(" X11: Flavors which match clipboard content:\n");
433 for (uint32_t i
= 0; i
< aFlavors
.Length(); i
++) {
434 LOGCLIP(" %s\n", aFlavors
[i
].get());
440 static nsresult
GetTransferableFlavors(nsITransferable
* aTransferable
,
441 nsTArray
<nsCString
>& aFlavors
) {
442 if (!aTransferable
) {
443 return NS_ERROR_FAILURE
;
445 // Get a list of flavors this transferable can import
446 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(aFlavors
);
448 LOGCLIP(" FlavorsTransferableCanImport falied!\n");
452 LOGCLIP(" Flavors which can be imported:");
453 for (const auto& flavor
: aFlavors
) {
454 LOGCLIP(" %s", flavor
.get());
460 static bool TransferableSetFile(nsITransferable
* aTransferable
,
461 const nsACString
& aURIList
) {
463 nsTArray
<nsCString
> uris
= mozilla::widget::ParseTextURIList(aURIList
);
464 if (!uris
.IsEmpty()) {
465 nsCOMPtr
<nsIURI
> fileURI
;
466 NS_NewURI(getter_AddRefs(fileURI
), uris
[0]);
467 if (nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(fileURI
, &rv
)) {
468 nsCOMPtr
<nsIFile
> file
;
469 rv
= fileURL
->GetFile(getter_AddRefs(file
));
470 if (NS_SUCCEEDED(rv
)) {
471 aTransferable
->SetTransferData(kFileMime
, file
);
472 LOGCLIP(" successfully set file to clipboard\n");
480 static bool TransferableSetHTML(nsITransferable
* aTransferable
,
481 Span
<const char> aData
) {
482 nsLiteralCString
mimeType(kHTMLMime
);
484 // Convert text/html into our text format
485 nsAutoCString charset
;
486 if (!GetHTMLCharset(aData
, charset
)) {
487 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
488 LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
489 charset
.AssignLiteral("utf-8");
492 LOGCLIP("TransferableSetHTML: HTML detected charset %s", charset
.get());
493 // app which use "text/html" to copy&paste
495 auto encoding
= Encoding::ForLabelNoReplacement(charset
);
497 LOGCLIP("TransferableSetHTML: get unicode decoder error (charset: %s)",
502 // According to spec html UTF-16BE/LE should be switched to UTF-8
503 // https://html.spec.whatwg.org/#determining-the-character-encoding:utf-16-encoding-2
504 if (encoding
== UTF_16LE_ENCODING
|| encoding
== UTF_16BE_ENCODING
) {
505 encoding
= UTF_8_ENCODING
;
508 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
509 // issues, but might confuse other users.
510 const size_t prefixLen
= ArrayLength(kHTMLMarkupPrefix
) - 1;
511 if (aData
.Length() >= prefixLen
&& nsDependentCSubstring(aData
.To(prefixLen
))
512 .EqualsLiteral(kHTMLMarkupPrefix
)) {
513 aData
= aData
.From(prefixLen
);
516 nsAutoString unicodeData
;
517 auto [rv
, enc
] = encoding
->Decode(AsBytes(aData
), unicodeData
);
519 if (enc
!= UTF_8_ENCODING
&&
520 MOZ_LOG_TEST(gClipboardLog
, mozilla::LogLevel::Debug
)) {
521 nsCString decoderName
;
522 enc
->Name(decoderName
);
523 LOGCLIP("TransferableSetHTML: expected UTF-8 decoder but got %s",
528 LOGCLIP("TransferableSetHTML: failed to decode HTML");
531 SetTransferableData(aTransferable
, mimeType
,
532 (const char*)unicodeData
.BeginReading(),
533 unicodeData
.Length() * sizeof(char16_t
));
538 nsClipboard::GetNativeClipboardData(nsITransferable
* aTransferable
,
539 int32_t aWhichClipboard
) {
540 MOZ_DIAGNOSTIC_ASSERT(aTransferable
);
542 LOGCLIP("nsClipboard::GetNativeClipboardData (%s)\n",
543 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
545 if (NS_WARN_IF(!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
))) {
546 return NS_ERROR_FAILURE
;
549 // TODO: Ensure we don't re-enter here.
551 return NS_ERROR_FAILURE
;
554 nsTArray
<nsCString
> flavors
;
555 nsresult rv
= GetTransferableFlavors(aTransferable
, flavors
);
556 NS_ENSURE_SUCCESS(rv
, rv
);
558 // Filter out MIME types on X11 to prevent unwanted conversions,
560 if (widget::GdkIsX11Display() &&
561 !FilterImportedFlavors(aWhichClipboard
, flavors
)) {
562 LOGCLIP(" Missing suitable clipboard data, quit.");
566 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
567 nsCString
& flavorStr
= flavors
[i
];
569 if (flavorStr
.EqualsLiteral(kJPEGImageMime
) ||
570 flavorStr
.EqualsLiteral(kJPGImageMime
) ||
571 flavorStr
.EqualsLiteral(kPNGImageMime
) ||
572 flavorStr
.EqualsLiteral(kGIFImageMime
)) {
573 // Emulate support for image/jpg
574 if (flavorStr
.EqualsLiteral(kJPGImageMime
)) {
575 flavorStr
.Assign(kJPEGImageMime
);
578 LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr
.get());
581 mContext
->GetClipboardData(flavorStr
.get(), aWhichClipboard
);
582 if (!clipboardData
) {
583 LOGCLIP(" %s type is missing\n", flavorStr
.get());
587 nsCOMPtr
<nsIInputStream
> byteStream
;
588 NS_NewByteInputStream(getter_AddRefs(byteStream
), clipboardData
.AsSpan(),
590 aTransferable
->SetTransferData(flavorStr
.get(), byteStream
);
591 LOGCLIP(" got %s MIME data\n", flavorStr
.get());
595 // Special case text/plain since we can convert any
596 // string into text/plain
597 if (flavorStr
.EqualsLiteral(kTextMime
)) {
598 LOGCLIP(" Getting text %s MIME clipboard data\n", flavorStr
.get());
600 auto clipboardData
= mContext
->GetClipboardText(aWhichClipboard
);
601 if (!clipboardData
) {
602 LOGCLIP(" failed to get text data\n");
603 // If the type was text/plain and we couldn't get
604 // text off the clipboard, run the next loop
609 // Convert utf-8 into our text format.
610 NS_ConvertUTF8toUTF16
ucs2string(clipboardData
.get());
611 SetTransferableData(aTransferable
, flavorStr
,
612 (const char*)ucs2string
.BeginReading(),
613 ucs2string
.Length() * 2);
615 LOGCLIP(" got text data, length %zd\n", ucs2string
.Length());
619 if (flavorStr
.EqualsLiteral(kFileMime
)) {
620 LOGCLIP(" Getting %s file clipboard data\n", flavorStr
.get());
623 mContext
->GetClipboardData(kURIListMime
, aWhichClipboard
);
624 if (!clipboardData
) {
625 LOGCLIP(" text/uri-list type is missing\n");
629 nsDependentCSubstring
fileName(clipboardData
.AsSpan());
630 if (!TransferableSetFile(aTransferable
, fileName
)) {
636 LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr
.get());
639 mContext
->GetClipboardData(flavorStr
.get(), aWhichClipboard
);
642 if (!clipboardData
) {
643 LOGCLIP(" %s type is missing\n", flavorStr
.get());
648 LOGCLIP(" got %s mime type data.\n", flavorStr
.get());
650 // Special case text/html since we can convert into UCS2
651 if (flavorStr
.EqualsLiteral(kHTMLMime
)) {
652 if (!TransferableSetHTML(aTransferable
, clipboardData
.AsSpan())) {
656 auto span
= clipboardData
.AsSpan();
657 SetTransferableData(aTransferable
, flavorStr
, span
.data(),
664 LOGCLIP(" failed to get clipboard content.\n");
675 struct DataCallbackHandler
{
676 RefPtr
<nsITransferable
> mTransferable
;
677 nsBaseClipboard::GetDataCallback mDataCallback
;
681 explicit DataCallbackHandler(RefPtr
<nsITransferable
> aTransferable
,
682 nsBaseClipboard::GetDataCallback
&& aDataCallback
,
683 const char* aMimeType
,
684 DataType aDataType
= DATATYPE_RAW
)
685 : mTransferable(std::move(aTransferable
)),
686 mDataCallback(std::move(aDataCallback
)),
687 mMimeType(aMimeType
),
688 mDataType(aDataType
) {
689 MOZ_COUNT_CTOR(DataCallbackHandler
);
690 LOGCLIP("DataCallbackHandler created [%p] MIME %s type %d", this,
691 mMimeType
.get(), mDataType
);
693 ~DataCallbackHandler() {
694 LOGCLIP("DataCallbackHandler deleted [%p]", this);
695 MOZ_COUNT_DTOR(DataCallbackHandler
);
699 static void AsyncGetTextImpl(nsITransferable
* aTransferable
,
700 int32_t aWhichClipboard
,
701 nsBaseClipboard::GetDataCallback
&& aCallback
) {
702 LOGCLIP("AsyncGetText() type '%s'",
703 aWhichClipboard
== nsClipboard::kSelectionClipboard
? "primary"
706 gtk_clipboard_request_text(
707 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard
)),
708 [](GtkClipboard
* aClipboard
, const gchar
* aText
, gpointer aData
) -> void {
709 UniquePtr
<DataCallbackHandler
> ref(
710 static_cast<DataCallbackHandler
*>(aData
));
711 LOGCLIP("AsyncGetText async handler of [%p]", aData
);
713 size_t dataLength
= aText
? strlen(aText
) : 0;
714 if (dataLength
<= 0) {
715 LOGCLIP(" quit, text is not available");
716 ref
->mDataCallback(NS_OK
);
720 // Convert utf-8 into our unicode format.
721 NS_ConvertUTF8toUTF16
utf16string(aText
, dataLength
);
722 nsLiteralCString
flavor(kTextMime
);
723 SetTransferableData(ref
->mTransferable
, flavor
,
724 (const char*)utf16string
.BeginReading(),
725 utf16string
.Length() * 2);
726 LOGCLIP(" text is set, length = %d", (int)dataLength
);
727 ref
->mDataCallback(NS_OK
);
729 new DataCallbackHandler(aTransferable
, std::move(aCallback
), kTextMime
));
732 static void AsyncGetDataImpl(nsITransferable
* aTransferable
,
733 int32_t aWhichClipboard
, const char* aMimeType
,
735 nsBaseClipboard::GetDataCallback
&& aCallback
) {
736 LOGCLIP("AsyncGetData() type '%s'",
737 aWhichClipboard
== nsClipboard::kSelectionClipboard
? "primary"
740 const char* gtkMIMEType
= nullptr;
743 // Don't ask Gtk for application/x-moz-file
744 gtkMIMEType
= kURIListMime
;
749 gtkMIMEType
= aMimeType
;
753 gtk_clipboard_request_contents(
754 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard
)),
755 gdk_atom_intern(gtkMIMEType
, FALSE
),
756 [](GtkClipboard
* aClipboard
, GtkSelectionData
* aSelection
,
757 gpointer aData
) -> void {
758 UniquePtr
<DataCallbackHandler
> ref(
759 static_cast<DataCallbackHandler
*>(aData
));
760 LOGCLIP("AsyncGetData async handler [%p] MIME %s type %d", aData
,
761 ref
->mMimeType
.get(), ref
->mDataType
);
763 int dataLength
= gtk_selection_data_get_length(aSelection
);
764 if (dataLength
<= 0) {
765 ref
->mDataCallback(NS_OK
);
768 const char* data
= (const char*)gtk_selection_data_get_data(aSelection
);
770 ref
->mDataCallback(NS_OK
);
773 switch (ref
->mDataType
) {
774 case DATATYPE_IMAGE
: {
775 LOGCLIP(" set image clipboard data");
776 nsCOMPtr
<nsIInputStream
> byteStream
;
777 NS_NewByteInputStream(getter_AddRefs(byteStream
),
778 Span(data
, dataLength
), NS_ASSIGNMENT_COPY
);
779 ref
->mTransferable
->SetTransferData(ref
->mMimeType
.get(),
783 case DATATYPE_FILE
: {
784 LOGCLIP(" set file clipboard data");
785 nsDependentCSubstring
file(data
, dataLength
);
786 TransferableSetFile(ref
->mTransferable
, file
);
789 case DATATYPE_HTML
: {
790 LOGCLIP(" html clipboard data");
791 Span
dataSpan(data
, dataLength
);
792 TransferableSetHTML(ref
->mTransferable
, dataSpan
);
796 LOGCLIP(" raw clipboard data %s", ref
->mMimeType
.get());
797 SetTransferableData(ref
->mTransferable
, ref
->mMimeType
, data
,
802 ref
->mDataCallback(NS_OK
);
804 new DataCallbackHandler(aTransferable
, std::move(aCallback
), aMimeType
,
808 static void AsyncGetDataFlavor(nsITransferable
* aTransferable
,
809 int32_t aWhichClipboard
, nsCString
& aFlavorStr
,
810 nsBaseClipboard::GetDataCallback
&& aCallback
) {
811 if (aFlavorStr
.EqualsLiteral(kJPEGImageMime
) ||
812 aFlavorStr
.EqualsLiteral(kJPGImageMime
) ||
813 aFlavorStr
.EqualsLiteral(kPNGImageMime
) ||
814 aFlavorStr
.EqualsLiteral(kGIFImageMime
)) {
815 // Emulate support for image/jpg
816 if (aFlavorStr
.EqualsLiteral(kJPGImageMime
)) {
817 aFlavorStr
.Assign(kJPEGImageMime
);
819 LOGCLIP(" Getting image %s MIME clipboard data", aFlavorStr
.get());
820 AsyncGetDataImpl(aTransferable
, aWhichClipboard
, aFlavorStr
.get(),
821 DATATYPE_IMAGE
, std::move(aCallback
));
824 // Special case text/plain since we can convert any
825 // string into text/plain
826 if (aFlavorStr
.EqualsLiteral(kTextMime
)) {
827 LOGCLIP(" Getting unicode clipboard data");
828 AsyncGetTextImpl(aTransferable
, aWhichClipboard
, std::move(aCallback
));
831 if (aFlavorStr
.EqualsLiteral(kFileMime
)) {
832 LOGCLIP(" Getting file clipboard data\n");
833 AsyncGetDataImpl(aTransferable
, aWhichClipboard
, aFlavorStr
.get(),
834 DATATYPE_FILE
, std::move(aCallback
));
837 if (aFlavorStr
.EqualsLiteral(kHTMLMime
)) {
838 LOGCLIP(" Getting HTML clipboard data");
839 AsyncGetDataImpl(aTransferable
, aWhichClipboard
, aFlavorStr
.get(),
840 DATATYPE_HTML
, std::move(aCallback
));
843 LOGCLIP(" Getting raw %s MIME clipboard data\n", aFlavorStr
.get());
844 AsyncGetDataImpl(aTransferable
, aWhichClipboard
, aFlavorStr
.get(),
845 DATATYPE_RAW
, std::move(aCallback
));
848 void nsClipboard::AsyncGetNativeClipboardData(nsITransferable
* aTransferable
,
849 int32_t aWhichClipboard
,
850 GetDataCallback
&& aCallback
) {
851 MOZ_DIAGNOSTIC_ASSERT(aTransferable
);
852 MOZ_DIAGNOSTIC_ASSERT(
853 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
855 LOGCLIP("nsClipboard::AsyncGetNativeClipboardData (%s)",
856 aWhichClipboard
== nsClipboard::kSelectionClipboard
? "primary"
858 nsTArray
<nsCString
> importedFlavors
;
859 nsresult rv
= GetTransferableFlavors(aTransferable
, importedFlavors
);
865 auto flavorsNum
= importedFlavors
.Length();
871 if (flavorsNum
> 1) {
872 LOGCLIP(" Only first MIME type (%s) will be imported from clipboard!",
873 importedFlavors
[0].get());
877 // Filter out MIME types on X11 to prevent unwanted conversions,
879 if (widget::GdkIsX11Display()) {
880 AsyncHasNativeClipboardDataMatchingFlavors(
881 importedFlavors
, aWhichClipboard
,
882 [aWhichClipboard
, transferable
= nsCOMPtr
{aTransferable
},
883 callback
= std::move(aCallback
)](auto aResultOrError
) mutable {
884 if (aResultOrError
.isErr()) {
885 callback(aResultOrError
.unwrapErr());
889 nsTArray
<nsCString
> clipboardFlavors
=
890 std::move(aResultOrError
.unwrap());
891 if (!clipboardFlavors
.Length()) {
892 LOGCLIP(" no flavors in clipboard, quit.");
897 AsyncGetDataFlavor(transferable
, aWhichClipboard
, clipboardFlavors
[0],
898 std::move(callback
));
903 // Read clipboard directly on Wayland
904 AsyncGetDataFlavor(aTransferable
, aWhichClipboard
, importedFlavors
[0],
905 std::move(aCallback
));
908 nsresult
nsClipboard::EmptyNativeClipboardData(int32_t aWhichClipboard
) {
909 MOZ_DIAGNOSTIC_ASSERT(
910 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
912 LOGCLIP("nsClipboard::EmptyNativeClipboardData (%s)\n",
913 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
914 if (aWhichClipboard
== kSelectionClipboard
) {
915 if (mSelectionTransferable
) {
916 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY
));
917 MOZ_ASSERT(!mSelectionTransferable
);
920 if (mGlobalTransferable
) {
921 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
922 MOZ_ASSERT(!mGlobalTransferable
);
925 ClearCachedTargets(aWhichClipboard
);
929 void nsClipboard::ClearTransferable(int32_t aWhichClipboard
) {
930 if (aWhichClipboard
== kSelectionClipboard
) {
931 mSelectionSequenceNumber
++;
932 mSelectionTransferable
= nullptr;
934 mGlobalSequenceNumber
++;
935 mGlobalTransferable
= nullptr;
939 static bool FlavorMatchesTarget(const nsACString
& aFlavor
, GdkAtom aTarget
) {
940 GUniquePtr
<gchar
> atom_name(gdk_atom_name(aTarget
));
944 if (aFlavor
.Equals(atom_name
.get())) {
945 LOGCLIP(" has %s\n", atom_name
.get());
948 // X clipboard supports image/jpeg, but we want to emulate support
949 // for image/jpg as well
950 if (aFlavor
.EqualsLiteral(kJPGImageMime
) &&
951 !strcmp(atom_name
.get(), kJPEGImageMime
)) {
952 LOGCLIP(" has image/jpg\n");
955 // application/x-moz-file should be treated like text/uri-list
956 if (aFlavor
.EqualsLiteral(kFileMime
) &&
957 !strcmp(atom_name
.get(), kURIListMime
)) {
958 LOGCLIP(" has text/uri-list treating as application/x-moz-file");
964 mozilla::Result
<bool, nsresult
>
965 nsClipboard::HasNativeClipboardDataMatchingFlavors(
966 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
) {
967 MOZ_DIAGNOSTIC_ASSERT(
968 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
970 LOGCLIP("nsClipboard::HasNativeClipboardDataMatchingFlavors (%s)\n",
971 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
974 return Err(NS_ERROR_FAILURE
);
977 auto targets
= mContext
->GetTargets(aWhichClipboard
);
979 LOGCLIP(" no targes at clipboard (null)\n");
984 if (LOGCLIP_ENABLED()) {
985 LOGCLIP(" Asking for content:\n");
986 for (auto& flavor
: aFlavorList
) {
987 LOGCLIP(" MIME %s\n", flavor
.get());
989 LOGCLIP(" Clipboard content (target nums %zu):\n",
990 targets
.AsSpan().Length());
991 for (const auto& target
: targets
.AsSpan()) {
992 GUniquePtr
<gchar
> atom_name(gdk_atom_name(target
));
994 LOGCLIP(" failed to get MIME\n");
997 LOGCLIP(" MIME %s\n", atom_name
.get());
1002 // Walk through the provided types and try to match it to a
1004 for (auto& flavor
: aFlavorList
) {
1005 // We special case text/plain here.
1006 if (flavor
.EqualsLiteral(kTextMime
) &&
1007 gtk_targets_include_text(targets
.AsSpan().data(),
1008 targets
.AsSpan().Length())) {
1009 LOGCLIP(" has kTextMime\n");
1012 for (const auto& target
: targets
.AsSpan()) {
1013 if (FlavorMatchesTarget(flavor
, target
)) {
1019 LOGCLIP(" no targes at clipboard (bad match)\n");
1023 struct TragetCallbackHandler
{
1024 TragetCallbackHandler(const nsTArray
<nsCString
>& aAcceptedFlavorList
,
1025 nsBaseClipboard::HasMatchingFlavorsCallback
&& aCallback
)
1026 : mAcceptedFlavorList(aAcceptedFlavorList
.Clone()),
1027 mCallback(std::move(aCallback
)) {
1028 LOGCLIP("TragetCallbackHandler(%p) created", this);
1030 ~TragetCallbackHandler() {
1031 LOGCLIP("TragetCallbackHandler(%p) deleted", this);
1033 nsTArray
<nsCString
> mAcceptedFlavorList
;
1034 nsBaseClipboard::HasMatchingFlavorsCallback mCallback
;
1037 void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1038 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
1039 nsBaseClipboard::HasMatchingFlavorsCallback
&& aCallback
) {
1040 MOZ_DIAGNOSTIC_ASSERT(
1041 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
1043 LOGCLIP("nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors (%s)",
1044 aWhichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
1046 gtk_clipboard_request_contents(
1047 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard
)),
1048 gdk_atom_intern("TARGETS", FALSE
),
1049 [](GtkClipboard
* aClipboard
, GtkSelectionData
* aSelection
,
1050 gpointer aData
) -> void {
1051 LOGCLIP("gtk_clipboard_request_contents async handler (%p)", aData
);
1052 UniquePtr
<TragetCallbackHandler
> handler(
1053 static_cast<TragetCallbackHandler
*>(aData
));
1055 GdkAtom
* targets
= nullptr;
1056 gint targetsNum
= 0;
1057 if (gtk_selection_data_get_length(aSelection
) > 0) {
1058 gtk_selection_data_get_targets(aSelection
, &targets
, &targetsNum
);
1060 nsTArray
<nsCString
> results
;
1062 for (auto& flavor
: handler
->mAcceptedFlavorList
) {
1063 LOGCLIP(" looking for %s", flavor
.get());
1064 if (flavor
.EqualsLiteral(kTextMime
) &&
1065 gtk_targets_include_text(targets
, targetsNum
)) {
1066 results
.AppendElement(flavor
);
1067 LOGCLIP(" has kTextMime\n");
1070 for (int i
= 0; i
< targetsNum
; i
++) {
1071 if (FlavorMatchesTarget(flavor
, targets
[i
])) {
1072 results
.AppendElement(flavor
);
1077 handler
->mCallback(std::move(results
));
1079 new TragetCallbackHandler(aFlavorList
, std::move(aCallback
)));
1082 nsITransferable
* nsClipboard::GetTransferable(int32_t aWhichClipboard
) {
1083 nsITransferable
* retval
;
1085 if (aWhichClipboard
== kSelectionClipboard
)
1086 retval
= mSelectionTransferable
.get();
1088 retval
= mGlobalTransferable
.get();
1093 void nsClipboard::SelectionGetEvent(GtkClipboard
* aClipboard
,
1094 GtkSelectionData
* aSelectionData
) {
1095 // Someone has asked us to hand them something. The first thing
1096 // that we want to do is see if that something includes text. If
1097 // it does, try to give it text/plain after converting it to
1100 int32_t whichClipboard
;
1103 GdkAtom selection
= gtk_selection_data_get_selection(aSelectionData
);
1104 if (selection
== GDK_SELECTION_PRIMARY
)
1105 whichClipboard
= kSelectionClipboard
;
1106 else if (selection
== GDK_SELECTION_CLIPBOARD
)
1107 whichClipboard
= kGlobalClipboard
;
1109 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
1111 LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
1112 whichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
1114 nsCOMPtr
<nsITransferable
> trans
= GetTransferable(whichClipboard
);
1116 // We have nothing to serve
1117 LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
1118 whichClipboard
== kSelectionClipboard
? "Primary" : "Clipboard");
1123 nsCOMPtr
<nsISupports
> item
;
1125 GdkAtom selectionTarget
= gtk_selection_data_get_target(aSelectionData
);
1126 LOGCLIP(" selection target %s\n",
1127 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
1129 // Check to see if the selection data is some text type.
1130 if (gtk_targets_include_text(&selectionTarget
, 1)) {
1131 LOGCLIP(" providing text/plain data\n");
1132 // Try to convert our internal type into a text string. Get
1133 // the transferable for this clipboard and try to get the
1134 // text/plain type for it.
1135 rv
= trans
->GetTransferData("text/plain", getter_AddRefs(item
));
1136 if (NS_FAILED(rv
) || !item
) {
1137 LOGCLIP(" GetTransferData() failed to get text/plain!\n");
1141 nsCOMPtr
<nsISupportsString
> wideString
;
1142 wideString
= do_QueryInterface(item
);
1143 if (!wideString
) return;
1145 nsAutoString ucs2string
;
1146 wideString
->GetData(ucs2string
);
1147 NS_ConvertUTF16toUTF8
utf8string(ucs2string
);
1149 LOGCLIP(" sent %zd bytes of utf-8 data\n", utf8string
.Length());
1150 if (selectionTarget
== gdk_atom_intern("text/plain;charset=utf-8", FALSE
)) {
1152 " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
1153 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
1154 // in some versions of GTK.
1155 gtk_selection_data_set(aSelectionData
, selectionTarget
, 8,
1156 reinterpret_cast<const guchar
*>(utf8string
.get()),
1157 utf8string
.Length());
1159 gtk_selection_data_set_text(aSelectionData
, utf8string
.get(),
1160 utf8string
.Length());
1165 // Check to see if the selection data is an image type
1166 if (gtk_targets_include_image(&selectionTarget
, 1, TRUE
)) {
1167 LOGCLIP(" providing image data\n");
1168 // Look through our transfer data for the image
1169 static const char* const imageMimeTypes
[] = {kNativeImageMime
,
1170 kPNGImageMime
, kJPEGImageMime
,
1171 kJPGImageMime
, kGIFImageMime
};
1172 nsCOMPtr
<nsISupports
> imageItem
;
1173 nsCOMPtr
<imgIContainer
> image
;
1174 for (uint32_t i
= 0; i
< ArrayLength(imageMimeTypes
); i
++) {
1175 rv
= trans
->GetTransferData(imageMimeTypes
[i
], getter_AddRefs(imageItem
));
1176 if (NS_FAILED(rv
)) {
1177 LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes
[i
]);
1181 image
= do_QueryInterface(imageItem
);
1183 LOGCLIP(" %s is available at GetTransferData()\n",
1189 if (!image
) { // Not getting an image for an image mime type!?
1190 LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
1194 RefPtr
<GdkPixbuf
> pixbuf
= nsImageToPixbuf::ImageToPixbuf(image
);
1196 LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
1200 LOGCLIP(" Setting pixbuf image data as %s\n",
1201 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
1202 gtk_selection_data_set_pixbuf(aSelectionData
, pixbuf
);
1206 if (selectionTarget
== gdk_atom_intern(kHTMLMime
, FALSE
)) {
1207 LOGCLIP(" providing %s data\n", kHTMLMime
);
1208 rv
= trans
->GetTransferData(kHTMLMime
, getter_AddRefs(item
));
1209 if (NS_FAILED(rv
) || !item
) {
1210 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime
);
1214 nsCOMPtr
<nsISupportsString
> wideString
;
1215 wideString
= do_QueryInterface(item
);
1217 LOGCLIP(" failed to get wideString interface!");
1221 nsAutoString ucs2string
;
1222 wideString
->GetData(ucs2string
);
1225 // Add the prefix so the encoding is correctly detected.
1226 html
.AppendLiteral(kHTMLMarkupPrefix
);
1227 AppendUTF16toUTF8(ucs2string
, html
);
1229 LOGCLIP(" Setting %zd bytes of %s data\n", html
.Length(),
1230 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
1231 gtk_selection_data_set(aSelectionData
, selectionTarget
, 8,
1232 (const guchar
*)html
.get(), html
.Length());
1236 // We put kFileMime onto the clipboard as kURIListMime.
1237 if (selectionTarget
== gdk_atom_intern(kURIListMime
, FALSE
)) {
1238 LOGCLIP(" providing %s data\n", kURIListMime
);
1239 rv
= trans
->GetTransferData(kFileMime
, getter_AddRefs(item
));
1240 if (NS_FAILED(rv
) || !item
) {
1241 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kFileMime
);
1245 nsCOMPtr
<nsIFile
> file
= do_QueryInterface(item
);
1247 LOGCLIP(" failed to get nsIFile interface!");
1251 nsCOMPtr
<nsIURI
> fileURI
;
1252 rv
= NS_NewFileURI(getter_AddRefs(fileURI
), file
);
1253 if (NS_FAILED(rv
)) {
1254 LOGCLIP(" failed to get fileURI\n");
1259 if (NS_FAILED(fileURI
->GetSpec(uri
))) {
1260 LOGCLIP(" failed to get fileURI spec\n");
1264 LOGCLIP(" Setting %zd bytes of data\n", uri
.Length());
1265 gtk_selection_data_set(aSelectionData
, selectionTarget
, 8,
1266 (const guchar
*)uri
.get(), uri
.Length());
1270 LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
1271 GUniquePtr
<gchar
>(gdk_atom_name(selectionTarget
)).get());
1273 // Try to match up the selection data target to something our
1274 // transferable provides.
1275 GUniquePtr
<gchar
> target_name(gdk_atom_name(selectionTarget
));
1277 LOGCLIP(" Failed to get target name!\n");
1281 rv
= trans
->GetTransferData(target_name
.get(), getter_AddRefs(item
));
1283 if (NS_FAILED(rv
) || !item
) {
1284 LOGCLIP(" Failed to get anything from GetTransferData()!\n");
1288 void* primitive_data
= nullptr;
1289 uint32_t dataLen
= 0;
1290 nsPrimitiveHelpers::CreateDataFromPrimitive(
1291 nsDependentCString(target_name
.get()), item
, &primitive_data
, &dataLen
);
1292 if (!primitive_data
) {
1293 LOGCLIP(" Failed to get primitive data!\n");
1297 LOGCLIP(" Setting %s as a primitive data type, %d bytes\n",
1298 target_name
.get(), dataLen
);
1299 gtk_selection_data_set(aSelectionData
, selectionTarget
,
1300 8, /* 8 bits in a unit */
1301 (const guchar
*)primitive_data
, dataLen
);
1302 free(primitive_data
);
1305 void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard
) {
1306 if (aWhichClipboard
== kSelectionClipboard
) {
1307 nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr);
1309 nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
1313 void nsClipboard::SelectionClearEvent(GtkClipboard
* aGtkClipboard
) {
1314 int32_t whichClipboard
= GetGeckoClipboardType(aGtkClipboard
);
1315 if (whichClipboard
< 0) {
1318 LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
1319 whichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
1320 ClearCachedTargets(whichClipboard
);
1321 ClearTransferable(whichClipboard
);
1322 ClearClipboardCache(whichClipboard
);
1325 void nsClipboard::OwnerChangedEvent(GtkClipboard
* aGtkClipboard
,
1326 GdkEventOwnerChange
* aEvent
) {
1327 int32_t whichClipboard
= GetGeckoClipboardType(aGtkClipboard
);
1328 if (whichClipboard
< 0) {
1331 LOGCLIP("nsClipboard::OwnerChangedEvent (%s)\n",
1332 whichClipboard
== kSelectionClipboard
? "primary" : "clipboard");
1333 GtkWidget
* gtkWidget
= [aEvent
]() -> GtkWidget
* {
1334 if (!aEvent
->owner
) {
1337 gpointer user_data
= nullptr;
1338 gdk_window_get_user_data(aEvent
->owner
, &user_data
);
1339 return GTK_WIDGET(user_data
);
1341 // If we can get GtkWidget from the current clipboard owner, this
1342 // owner-changed event must be triggered by ourself via calling
1343 // gtk_clipboard_set_with_data, the sequence number should already be handled.
1345 if (whichClipboard
== kSelectionClipboard
) {
1346 mSelectionSequenceNumber
++;
1348 mGlobalSequenceNumber
++;
1352 ClearCachedTargets(whichClipboard
);
1355 void clipboard_get_cb(GtkClipboard
* aGtkClipboard
,
1356 GtkSelectionData
* aSelectionData
, guint info
,
1357 gpointer user_data
) {
1358 LOGCLIP("clipboard_get_cb() callback\n");
1359 nsClipboard
* clipboard
= static_cast<nsClipboard
*>(user_data
);
1360 clipboard
->SelectionGetEvent(aGtkClipboard
, aSelectionData
);
1363 void clipboard_clear_cb(GtkClipboard
* aGtkClipboard
, gpointer user_data
) {
1364 LOGCLIP("clipboard_clear_cb() callback\n");
1365 nsClipboard
* clipboard
= static_cast<nsClipboard
*>(user_data
);
1366 clipboard
->SelectionClearEvent(aGtkClipboard
);
1369 void clipboard_owner_change_cb(GtkClipboard
* aGtkClipboard
,
1370 GdkEventOwnerChange
* aEvent
,
1371 gpointer aUserData
) {
1372 LOGCLIP("clipboard_owner_change_cb() callback\n");
1373 nsClipboard
* clipboard
= static_cast<nsClipboard
*>(aUserData
);
1374 clipboard
->OwnerChangedEvent(aGtkClipboard
, aEvent
);
1378 * This function extracts the encoding label from the subset of HTML internal
1379 * encoding declaration syntax that uses the old long form with double quotes
1380 * and without spaces around the equals sign between the "content" attribute
1381 * name and the attribute value.
1383 * This was added for the sake of an ancient version of StarOffice
1384 * in the pre-UTF-8 era in bug 123389. It is unclear if supporting
1385 * non-UTF-8 encodings is still necessary and if this function
1386 * still needs to exist.
1388 * As of December 2022, both Gecko and LibreOffice emit an UTF-8
1389 * declaration that this function successfully extracts "UTF-8" from,
1390 * but that's also the default that we fall back on if this function
1391 * fails to extract a label.
1393 bool GetHTMLCharset(Span
<const char> aData
, nsCString
& aFoundCharset
) {
1394 // Assume ASCII first to find "charset" info
1395 const nsDependentCSubstring
htmlStr(aData
);
1396 nsACString::const_iterator start
, end
;
1397 htmlStr
.BeginReading(start
);
1398 htmlStr
.EndReading(end
);
1399 nsACString::const_iterator
valueStart(start
), valueEnd(start
);
1401 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns
, start
, end
)) {
1403 htmlStr
.EndReading(end
);
1405 if (CaseInsensitiveFindInReadable("charset="_ns
, start
, end
)) {
1408 htmlStr
.EndReading(end
);
1410 if (FindCharInReadable('"', start
, end
)) valueEnd
= start
;
1413 // find "charset" in HTML
1414 if (valueStart
!= valueEnd
) {
1415 aFoundCharset
= Substring(valueStart
, valueEnd
);
1416 ToUpperCase(aFoundCharset
);