Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / widget / gtk / nsClipboard.cpp
blob034514bd3f4f8b0582cfb919a831667bf23a7a44
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
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 #if defined(MOZ_X11)
13 # include "nsClipboardX11.h"
14 #endif
15 #if defined(MOZ_WAYLAND)
16 # include "nsClipboardWayland.h"
17 #endif
18 #include "nsGtkUtils.h"
19 #include "nsIURI.h"
20 #include "nsIFile.h"
21 #include "nsNetUtil.h"
22 #include "nsContentUtils.h"
23 #include "HeadlessClipboard.h"
24 #include "nsSupportsPrimitives.h"
25 #include "nsString.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"
38 #include "GRefPtr.h"
39 #include "WidgetUtilsGtk.h"
41 #include "imgIContainer.h"
43 #include <gtk/gtk.h>
44 #if defined(MOZ_X11)
45 # include <gtk/gtkx.h>
46 #endif
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,
74 gpointer user_data);
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,
82 gpointer aUserData);
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() {
98 ClipboardTargets ret;
99 ret.mCount = mCount;
100 if (mCount) {
101 ret.mTargets.reset(
102 reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * mCount)));
103 memcpy(ret.mTargets.get(), mTargets.get(), sizeof(GdkAtom) * mCount);
105 return ret;
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) {
114 mData = nullptr;
115 mLength = aData.Length();
116 if (mLength) {
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) {
123 mData = nullptr;
124 mLength = aData.Length();
125 if (mLength) {
126 mData.reset(
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,
161 GdkEvent* aEvent,
162 gpointer data) {
163 LOGCLIP("nsRetrievalContext::ClearCachedTargetsClipboard()");
164 sClipboardTargets.Clear();
167 void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard* aClipboard,
168 GdkEvent* aEvent,
169 gpointer data) {
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"
177 : "clipboard");
178 ClipboardTargets& storedTargets =
179 (aWhichClipboard == nsClipboard::kSelectionClipboard) ? sPrimaryTargets
180 : sClipboardTargets;
181 if (!storedTargets) {
182 LOGCLIP(" getting targets from system");
183 storedTargets.Set(GetTargetsImpl(aWhichClipboard));
184 } else {
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) {
218 #if defined(MOZ_X11)
219 if (widget::GdkIsX11Display()) {
220 mContext = new nsRetrievalContextX11();
222 #endif
223 #if defined(MOZ_WAYLAND)
224 if (widget::GdkIsWaylandDisplay()) {
225 mContext = new nsRetrievalContextWayland();
227 #endif
229 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
230 if (os) {
231 os->AddObserver(this, "xpcom-shutdown", false);
234 return NS_OK;
237 NS_IMETHODIMP
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
242 // runnable.
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));
247 }));
250 NS_IMETHODIMP
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())) {
262 return NS_OK;
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);
274 if (NS_FAILED(rv)) {
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);
289 continue;
292 if (nsContentUtils::IsFlavorImage(flavorStr)) {
293 // Don't bother adding image targets twice
294 if (!imagesAdded) {
295 // accept any writable image type
296 LOGCLIP(" adding IMAGE targets\n");
297 gtk_target_list_add_image_targets(list, 0, TRUE);
298 imagesAdded = true;
300 continue;
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);
307 continue;
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));
320 gint numTargets = 0;
321 GtkTargetEntry* gtkTargets =
322 gtk_target_table_new_from_list(list, &numTargets);
323 if (!gtkTargets || numTargets == 0) {
324 LOGCLIP(
325 " gtk_target_table_new_from_list() failed or empty list of "
326 "targets!\n");
327 // Clear references to the any old data and let GTK know that it is no
328 // longer available.
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;
344 } else {
345 mGlobalSequenceNumber++;
346 mGlobalTransferable = aTransferable;
347 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
350 rv = NS_OK;
351 } else {
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);
360 return rv;
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,
372 const char* aMime) {
373 for (const auto& flavorStr : aFlavourList) {
374 if (flavorStr.Equals(aMime)) {
375 return true;
378 return false;
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);
389 if (!targets) {
390 LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
391 return true;
394 for (const auto& atom : targets.AsSpan()) {
395 GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
396 if (!atom_name) {
397 continue;
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) {
404 continue;
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) {
411 continue;
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");
416 return true;
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));
423 if (!atom_name) {
424 continue;
426 if (IsMIMEAtFlavourList(aFlavors, atom_name.get())) {
427 clipboardFlavors.AppendElement(nsCString(atom_name.get()));
430 aFlavors.SwapElements(clipboardFlavors);
431 #ifdef MOZ_LOGGING
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());
436 #endif
437 return true;
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);
447 if (NS_FAILED(rv)) {
448 LOGCLIP(" FlavorsTransferableCanImport falied!\n");
449 return rv;
451 #ifdef MOZ_LOGGING
452 LOGCLIP(" Flavors which can be imported:");
453 for (const auto& flavor : aFlavors) {
454 LOGCLIP(" %s", flavor.get());
456 #endif
457 return NS_OK;
460 static bool TransferableSetFile(nsITransferable* aTransferable,
461 const nsACString& aURIList) {
462 nsresult rv;
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");
473 return true;
477 return false;
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
494 // get the decoder
495 auto encoding = Encoding::ForLabelNoReplacement(charset);
496 if (!encoding) {
497 LOGCLIP("TransferableSetHTML: get unicode decoder error (charset: %s)",
498 charset.get());
499 return false;
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);
518 #if MOZ_LOGGING
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",
524 decoderName.get());
526 #endif
527 if (NS_FAILED(rv)) {
528 LOGCLIP("TransferableSetHTML: failed to decode HTML");
529 return false;
531 SetTransferableData(aTransferable, mimeType,
532 (const char*)unicodeData.BeginReading(),
533 unicodeData.Length() * sizeof(char16_t));
534 return true;
537 NS_IMETHODIMP
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.
550 if (!mContext) {
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,
559 // see Bug 1611407
560 if (widget::GdkIsX11Display() &&
561 !FilterImportedFlavors(aWhichClipboard, flavors)) {
562 LOGCLIP(" Missing suitable clipboard data, quit.");
563 return NS_OK;
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());
580 auto clipboardData =
581 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
582 if (!clipboardData) {
583 LOGCLIP(" %s type is missing\n", flavorStr.get());
584 continue;
587 nsCOMPtr<nsIInputStream> byteStream;
588 NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
589 NS_ASSIGNMENT_COPY);
590 aTransferable->SetTransferData(flavorStr.get(), byteStream);
591 LOGCLIP(" got %s MIME data\n", flavorStr.get());
592 return NS_OK;
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
605 // iteration.
606 continue;
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());
616 return NS_OK;
619 if (flavorStr.EqualsLiteral(kFileMime)) {
620 LOGCLIP(" Getting %s file clipboard data\n", flavorStr.get());
622 auto clipboardData =
623 mContext->GetClipboardData(kURIListMime, aWhichClipboard);
624 if (!clipboardData) {
625 LOGCLIP(" text/uri-list type is missing\n");
626 continue;
629 nsDependentCSubstring fileName(clipboardData.AsSpan());
630 if (!TransferableSetFile(aTransferable, fileName)) {
631 continue;
633 return NS_OK;
636 LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr.get());
638 auto clipboardData =
639 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
641 #ifdef MOZ_LOGGING
642 if (!clipboardData) {
643 LOGCLIP(" %s type is missing\n", flavorStr.get());
645 #endif
647 if (clipboardData) {
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())) {
653 continue;
655 } else {
656 auto span = clipboardData.AsSpan();
657 SetTransferableData(aTransferable, flavorStr, span.data(),
658 span.Length());
660 return NS_OK;
664 LOGCLIP(" failed to get clipboard content.\n");
665 return NS_OK;
668 enum DataType {
669 DATATYPE_IMAGE,
670 DATATYPE_FILE,
671 DATATYPE_HTML,
672 DATATYPE_RAW,
675 struct DataCallbackHandler {
676 RefPtr<nsITransferable> mTransferable;
677 nsBaseClipboard::GetDataCallback mDataCallback;
678 nsCString mMimeType;
679 DataType mDataType;
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"
704 : "clipboard");
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);
717 return;
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,
734 DataType aDataType,
735 nsBaseClipboard::GetDataCallback&& aCallback) {
736 LOGCLIP("AsyncGetData() type '%s'",
737 aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
738 : "clipboard");
740 const char* gtkMIMEType = nullptr;
741 switch (aDataType) {
742 case DATATYPE_FILE:
743 // Don't ask Gtk for application/x-moz-file
744 gtkMIMEType = kURIListMime;
745 break;
746 case DATATYPE_IMAGE:
747 case DATATYPE_HTML:
748 case DATATYPE_RAW:
749 gtkMIMEType = aMimeType;
750 break;
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);
766 return;
768 const char* data = (const char*)gtk_selection_data_get_data(aSelection);
769 if (!data) {
770 ref->mDataCallback(NS_OK);
771 return;
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(),
780 byteStream);
781 break;
783 case DATATYPE_FILE: {
784 LOGCLIP(" set file clipboard data");
785 nsDependentCSubstring file(data, dataLength);
786 TransferableSetFile(ref->mTransferable, file);
787 break;
789 case DATATYPE_HTML: {
790 LOGCLIP(" html clipboard data");
791 Span dataSpan(data, dataLength);
792 TransferableSetHTML(ref->mTransferable, dataSpan);
793 break;
795 case DATATYPE_RAW: {
796 LOGCLIP(" raw clipboard data %s", ref->mMimeType.get());
797 SetTransferableData(ref->mTransferable, ref->mMimeType, data,
798 dataLength);
799 break;
802 ref->mDataCallback(NS_OK);
804 new DataCallbackHandler(aTransferable, std::move(aCallback), aMimeType,
805 aDataType));
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));
822 return;
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));
829 return;
831 if (aFlavorStr.EqualsLiteral(kFileMime)) {
832 LOGCLIP(" Getting file clipboard data\n");
833 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
834 DATATYPE_FILE, std::move(aCallback));
835 return;
837 if (aFlavorStr.EqualsLiteral(kHTMLMime)) {
838 LOGCLIP(" Getting HTML clipboard data");
839 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
840 DATATYPE_HTML, std::move(aCallback));
841 return;
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"
857 : "clipboard");
858 nsTArray<nsCString> importedFlavors;
859 nsresult rv = GetTransferableFlavors(aTransferable, importedFlavors);
860 if (NS_FAILED(rv)) {
861 aCallback(rv);
862 return;
865 auto flavorsNum = importedFlavors.Length();
866 if (!flavorsNum) {
867 aCallback(NS_OK);
868 return;
870 #ifdef MOZ_LOGGING
871 if (flavorsNum > 1) {
872 LOGCLIP(" Only first MIME type (%s) will be imported from clipboard!",
873 importedFlavors[0].get());
875 #endif
877 // Filter out MIME types on X11 to prevent unwanted conversions,
878 // see Bug 1611407
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());
886 return;
889 nsTArray<nsCString> clipboardFlavors =
890 std::move(aResultOrError.unwrap());
891 if (!clipboardFlavors.Length()) {
892 LOGCLIP(" no flavors in clipboard, quit.");
893 callback(NS_OK);
894 return;
897 AsyncGetDataFlavor(transferable, aWhichClipboard, clipboardFlavors[0],
898 std::move(callback));
900 return;
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);
919 } else {
920 if (mGlobalTransferable) {
921 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
922 MOZ_ASSERT(!mGlobalTransferable);
925 ClearCachedTargets(aWhichClipboard);
926 return NS_OK;
929 void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
930 if (aWhichClipboard == kSelectionClipboard) {
931 mSelectionSequenceNumber++;
932 mSelectionTransferable = nullptr;
933 } else {
934 mGlobalSequenceNumber++;
935 mGlobalTransferable = nullptr;
939 static bool FlavorMatchesTarget(const nsACString& aFlavor, GdkAtom aTarget) {
940 GUniquePtr<gchar> atom_name(gdk_atom_name(aTarget));
941 if (!atom_name) {
942 return false;
944 if (aFlavor.Equals(atom_name.get())) {
945 LOGCLIP(" has %s\n", atom_name.get());
946 return true;
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");
953 return true;
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");
959 return true;
961 return false;
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");
973 if (!mContext) {
974 return Err(NS_ERROR_FAILURE);
977 auto targets = mContext->GetTargets(aWhichClipboard);
978 if (!targets) {
979 LOGCLIP(" no targes at clipboard (null)\n");
980 return false;
983 #ifdef MOZ_LOGGING
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));
993 if (!atom_name) {
994 LOGCLIP(" failed to get MIME\n");
995 continue;
997 LOGCLIP(" MIME %s\n", atom_name.get());
1000 #endif
1002 // Walk through the provided types and try to match it to a
1003 // provided type.
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");
1010 return true;
1012 for (const auto& target : targets.AsSpan()) {
1013 if (FlavorMatchesTarget(flavor, target)) {
1014 return true;
1019 LOGCLIP(" no targes at clipboard (bad match)\n");
1020 return false;
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;
1061 if (targetsNum) {
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");
1068 continue;
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();
1087 else
1088 retval = mGlobalTransferable.get();
1090 return retval;
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
1098 // utf-8.
1100 int32_t whichClipboard;
1102 // which clipboard?
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;
1108 else
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);
1115 if (!trans) {
1116 // We have nothing to serve
1117 LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
1118 whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard");
1119 return;
1122 nsresult rv;
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");
1138 return;
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)) {
1151 LOGCLIP(
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());
1158 } else {
1159 gtk_selection_data_set_text(aSelectionData, utf8string.get(),
1160 utf8string.Length());
1162 return;
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]);
1178 continue;
1181 image = do_QueryInterface(imageItem);
1182 if (image) {
1183 LOGCLIP(" %s is available at GetTransferData()\n",
1184 imageMimeTypes[i]);
1185 break;
1189 if (!image) { // Not getting an image for an image mime type!?
1190 LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
1191 return;
1194 RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
1195 if (!pixbuf) {
1196 LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
1197 return;
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);
1203 return;
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);
1211 return;
1214 nsCOMPtr<nsISupportsString> wideString;
1215 wideString = do_QueryInterface(item);
1216 if (!wideString) {
1217 LOGCLIP(" failed to get wideString interface!");
1218 return;
1221 nsAutoString ucs2string;
1222 wideString->GetData(ucs2string);
1224 nsAutoCString html;
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());
1233 return;
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);
1242 return;
1245 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
1246 if (!file) {
1247 LOGCLIP(" failed to get nsIFile interface!");
1248 return;
1251 nsCOMPtr<nsIURI> fileURI;
1252 rv = NS_NewFileURI(getter_AddRefs(fileURI), file);
1253 if (NS_FAILED(rv)) {
1254 LOGCLIP(" failed to get fileURI\n");
1255 return;
1258 nsAutoCString uri;
1259 if (NS_FAILED(fileURI->GetSpec(uri))) {
1260 LOGCLIP(" failed to get fileURI spec\n");
1261 return;
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());
1267 return;
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));
1276 if (!target_name) {
1277 LOGCLIP(" Failed to get target name!\n");
1278 return;
1281 rv = trans->GetTransferData(target_name.get(), getter_AddRefs(item));
1282 // nothing found?
1283 if (NS_FAILED(rv) || !item) {
1284 LOGCLIP(" Failed to get anything from GetTransferData()!\n");
1285 return;
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");
1294 return;
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);
1308 } else {
1309 nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
1313 void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
1314 int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
1315 if (whichClipboard < 0) {
1316 return;
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) {
1329 return;
1331 LOGCLIP("nsClipboard::OwnerChangedEvent (%s)\n",
1332 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1333 GtkWidget* gtkWidget = [aEvent]() -> GtkWidget* {
1334 if (!aEvent->owner) {
1335 return nullptr;
1337 gpointer user_data = nullptr;
1338 gdk_window_get_user_data(aEvent->owner, &user_data);
1339 return GTK_WIDGET(user_data);
1340 }();
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.
1344 if (!gtkWidget) {
1345 if (whichClipboard == kSelectionClipboard) {
1346 mSelectionSequenceNumber++;
1347 } else {
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)) {
1402 start = end;
1403 htmlStr.EndReading(end);
1405 if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
1406 valueStart = end;
1407 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);
1417 return true;
1419 return false;