Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / widget / gtk / nsClipboard.cpp
blobed3a70d494e855d75adeba239d7c9bd9f4ab1e14
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 # include "nsWaylandDisplay.h"
18 #endif
19 #include "nsGtkUtils.h"
20 #include "nsIURI.h"
21 #include "nsIFile.h"
22 #include "nsNetUtil.h"
23 #include "nsContentUtils.h"
24 #include "HeadlessClipboard.h"
25 #include "nsSupportsPrimitives.h"
26 #include "nsString.h"
27 #include "nsReadableUtils.h"
28 #include "nsPrimitiveHelpers.h"
29 #include "nsImageToPixbuf.h"
30 #include "nsStringStream.h"
31 #include "nsIFileURL.h"
32 #include "nsIObserverService.h"
33 #include "mozilla/GRefPtr.h"
34 #include "mozilla/RefPtr.h"
35 #include "mozilla/SchedulerGroup.h"
36 #include "mozilla/Services.h"
37 #include "mozilla/StaticPrefs_widget.h"
38 #include "mozilla/TimeStamp.h"
39 #include "GRefPtr.h"
40 #include "WidgetUtilsGtk.h"
42 #include "imgIContainer.h"
44 #include <gtk/gtk.h>
45 #if defined(MOZ_X11)
46 # include <gtk/gtkx.h>
47 #endif
49 #include "mozilla/Encoding.h"
51 using namespace mozilla;
53 // Idle timeout for receiving selection and property notify events (microsec)
54 // Right now it's set to 1 sec.
55 const int kClipboardTimeout = 1000000;
57 // Defines how many event loop iterations will be done without sleep.
58 // We ususally get data in first 2-3 iterations unless some large object
59 // (an image for instance) is transferred through clipboard.
60 const int kClipboardFastIterationNum = 3;
62 // We add this prefix to HTML markup, so that GetHTMLCharset can correctly
63 // detect the HTML as UTF-8 encoded.
64 static const char kHTMLMarkupPrefix[] =
65 R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)";
67 static const char kURIListMime[] = "text/uri-list";
69 MOZ_CONSTINIT ClipboardTargets nsRetrievalContext::sClipboardTargets;
70 MOZ_CONSTINIT ClipboardTargets nsRetrievalContext::sPrimaryTargets;
72 // Callback when someone asks us for the data
73 static void clipboard_get_cb(GtkClipboard* aGtkClipboard,
74 GtkSelectionData* aSelectionData, guint info,
75 gpointer user_data);
77 // Callback when someone asks us to clear a clipboard
78 static void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
80 // Callback when owner of clipboard data is changed
81 static void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard,
82 GdkEventOwnerChange* aEvent,
83 gpointer aUserData);
85 static bool GetHTMLCharset(Span<const char> aData, nsCString& str);
87 static void SetTransferableData(nsITransferable* aTransferable,
88 const nsACString& aFlavor,
89 const char* aClipboardData,
90 uint32_t aClipboardDataLength) {
91 MOZ_CLIPBOARD_LOG("SetTransferableData MIME %s\n",
92 PromiseFlatCString(aFlavor).get());
93 nsCOMPtr<nsISupports> wrapper;
94 nsPrimitiveHelpers::CreatePrimitiveForData(
95 aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
96 aTransferable->SetTransferData(PromiseFlatCString(aFlavor).get(), wrapper);
99 ClipboardTargets ClipboardTargets::Clone() {
100 ClipboardTargets ret;
101 ret.mCount = mCount;
102 if (mCount) {
103 ret.mTargets.reset(
104 reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * mCount)));
105 memcpy(ret.mTargets.get(), mTargets.get(), sizeof(GdkAtom) * mCount);
107 return ret;
110 void ClipboardTargets::Set(ClipboardTargets aTargets) {
111 mCount = aTargets.mCount;
112 mTargets = std::move(aTargets.mTargets);
115 void ClipboardData::SetData(Span<const uint8_t> aData) {
116 mData = nullptr;
117 mLength = aData.Length();
118 if (mLength) {
119 mData.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength)));
120 memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
124 void ClipboardData::SetText(Span<const char> aData) {
125 mData = nullptr;
126 mLength = aData.Length();
127 if (mLength) {
128 mData.reset(
129 reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength + 1))));
130 memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
131 mData.get()[mLength] = '\0';
135 void ClipboardData::SetTargets(ClipboardTargets aTargets) {
136 mLength = aTargets.mCount;
137 mData.reset(reinterpret_cast<char*>(aTargets.mTargets.release()));
140 ClipboardTargets ClipboardData::ExtractTargets() {
141 GUniquePtr<GdkAtom> targets(reinterpret_cast<GdkAtom*>(mData.release()));
142 uint32_t length = std::exchange(mLength, 0);
143 return ClipboardTargets{std::move(targets), length};
146 GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
147 if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
148 return GDK_SELECTION_CLIPBOARD;
150 return GDK_SELECTION_PRIMARY;
153 Maybe<nsIClipboard::ClipboardType> GetGeckoClipboardType(
154 GtkClipboard* aGtkClipboard) {
155 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY)) {
156 return Some(nsClipboard::kSelectionClipboard);
158 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)) {
159 return Some(nsClipboard::kGlobalClipboard);
161 return Nothing(); // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
164 void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard* aClipboard,
165 GdkEvent* aEvent,
166 gpointer data) {
167 MOZ_CLIPBOARD_LOG("nsRetrievalContext::ClearCachedTargetsClipboard()");
168 sClipboardTargets.Clear();
171 void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard* aClipboard,
172 GdkEvent* aEvent,
173 gpointer data) {
174 MOZ_CLIPBOARD_LOG("nsRetrievalContext::ClearCachedTargetsPrimary()");
175 sPrimaryTargets.Clear();
178 ClipboardTargets nsRetrievalContext::GetTargets(int32_t aWhichClipboard) {
179 MOZ_CLIPBOARD_LOG("nsRetrievalContext::GetTargets(%s)\n",
180 aWhichClipboard == nsClipboard::kSelectionClipboard
181 ? "primary"
182 : "clipboard");
183 ClipboardTargets& storedTargets =
184 (aWhichClipboard == nsClipboard::kSelectionClipboard) ? sPrimaryTargets
185 : sClipboardTargets;
186 if (!storedTargets) {
187 MOZ_CLIPBOARD_LOG(" getting targets from system");
188 storedTargets.Set(GetTargetsImpl(aWhichClipboard));
189 } else {
190 MOZ_CLIPBOARD_LOG(" using cached targets");
192 return storedTargets.Clone();
195 nsRetrievalContext::~nsRetrievalContext() {
196 sClipboardTargets.Clear();
197 sPrimaryTargets.Clear();
200 nsClipboard::nsClipboard()
201 : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
202 #ifdef MOZ_WAYLAND
203 widget::GdkIsWaylandDisplay()
204 ? widget::WaylandDisplayGet()->IsPrimarySelectionEnabled()
205 : true,
206 #else
207 true, /* supportsSelectionClipboard */
208 #endif
209 false /* supportsFindClipboard */,
210 false /* supportsSelectionCache */)) {
211 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), "owner-change",
212 G_CALLBACK(clipboard_owner_change_cb), this);
213 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change",
214 G_CALLBACK(clipboard_owner_change_cb), this);
217 nsClipboard::~nsClipboard() {
218 g_signal_handlers_disconnect_by_func(
219 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
220 FuncToGpointer(clipboard_owner_change_cb), this);
221 g_signal_handlers_disconnect_by_func(
222 gtk_clipboard_get(GDK_SELECTION_PRIMARY),
223 FuncToGpointer(clipboard_owner_change_cb), this);
226 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
228 nsresult nsClipboard::Init(void) {
229 #if defined(MOZ_X11)
230 if (widget::GdkIsX11Display()) {
231 mContext = new nsRetrievalContextX11();
233 #endif
234 #if defined(MOZ_WAYLAND)
235 if (widget::GdkIsWaylandDisplay()) {
236 mContext = new nsRetrievalContextWayland();
238 #endif
240 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
241 if (os) {
242 os->AddObserver(this, "xpcom-shutdown", false);
245 return NS_OK;
248 NS_IMETHODIMP
249 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
250 const char16_t* aData) {
251 // Save global clipboard content to CLIPBOARD_MANAGER.
252 // gtk_clipboard_store() can run an event loop, so call from a dedicated
253 // runnable.
254 return SchedulerGroup::Dispatch(
255 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
256 MOZ_CLIPBOARD_LOG("nsClipboard storing clipboard content\n");
257 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
258 }));
261 NS_IMETHODIMP
262 nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable,
263 ClipboardType aWhichClipboard) {
264 MOZ_DIAGNOSTIC_ASSERT(aTransferable);
265 MOZ_DIAGNOSTIC_ASSERT(
266 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
268 // See if we can short cut
269 if ((aWhichClipboard == kGlobalClipboard &&
270 aTransferable == mGlobalTransferable.get()) ||
271 (aWhichClipboard == kSelectionClipboard &&
272 aTransferable == mSelectionTransferable.get())) {
273 return NS_OK;
276 MOZ_CLIPBOARD_LOG(
277 "nsClipboard::SetNativeClipboardData (%s)\n",
278 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
280 // List of suported targets
281 GtkTargetList* list = gtk_target_list_new(nullptr, 0);
283 // Get the types of supported flavors
284 nsTArray<nsCString> flavors;
285 nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
286 if (NS_FAILED(rv)) {
287 MOZ_CLIPBOARD_LOG(" FlavorsTransferableCanExport failed!\n");
288 // Fall through. |gtkTargets| will be null below.
291 // Add all the flavors to this widget's supported type.
292 bool imagesAdded = false;
293 for (uint32_t i = 0; i < flavors.Length(); i++) {
294 nsCString& flavorStr = flavors[i];
295 MOZ_CLIPBOARD_LOG(" processing target %s\n", flavorStr.get());
297 // Special case text/plain since we can handle all of the string types.
298 if (flavorStr.EqualsLiteral(kTextMime)) {
299 MOZ_CLIPBOARD_LOG(" adding TEXT targets\n");
300 gtk_target_list_add_text_targets(list, 0);
301 continue;
304 if (nsContentUtils::IsFlavorImage(flavorStr)) {
305 // Don't bother adding image targets twice
306 if (!imagesAdded) {
307 // accept any writable image type
308 MOZ_CLIPBOARD_LOG(" adding IMAGE targets\n");
309 gtk_target_list_add_image_targets(list, 0, TRUE);
310 imagesAdded = true;
312 continue;
315 if (flavorStr.EqualsLiteral(kFileMime)) {
316 MOZ_CLIPBOARD_LOG(" adding text/uri-list target\n");
317 GdkAtom atom = gdk_atom_intern(kURIListMime, FALSE);
318 gtk_target_list_add(list, atom, 0, 0);
319 continue;
322 // Add this to our list of valid targets
323 MOZ_CLIPBOARD_LOG(" adding OTHER target %s\n", flavorStr.get());
324 GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
325 gtk_target_list_add(list, atom, 0, 0);
328 // Get GTK clipboard (CLIPBOARD or PRIMARY)
329 GtkClipboard* gtkClipboard =
330 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
332 gint numTargets = 0;
333 GtkTargetEntry* gtkTargets =
334 gtk_target_table_new_from_list(list, &numTargets);
335 if (!gtkTargets || numTargets == 0) {
336 MOZ_CLIPBOARD_LOG(
337 " gtk_target_table_new_from_list() failed or empty list of "
338 "targets!\n");
339 // Clear references to the any old data and let GTK know that it is no
340 // longer available.
341 EmptyNativeClipboardData(aWhichClipboard);
342 return NS_ERROR_FAILURE;
345 ClearCachedTargets(aWhichClipboard);
347 // Set getcallback and request to store data after an application exit
348 if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
349 clipboard_get_cb, clipboard_clear_cb, this)) {
350 // We managed to set-up the clipboard so update internal state
351 // We have to set it now because gtk_clipboard_set_with_data() calls
352 // clipboard_clear_cb() which reset our internal state
353 if (aWhichClipboard == kSelectionClipboard) {
354 mSelectionSequenceNumber++;
355 mSelectionTransferable = aTransferable;
356 } else {
357 mGlobalSequenceNumber++;
358 mGlobalTransferable = aTransferable;
359 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
362 rv = NS_OK;
363 } else {
364 MOZ_CLIPBOARD_LOG(" gtk_clipboard_set_with_data() failed!\n");
365 EmptyNativeClipboardData(aWhichClipboard);
366 rv = NS_ERROR_FAILURE;
369 gtk_target_table_free(gtkTargets, numTargets);
370 gtk_target_list_unref(list);
372 return rv;
375 mozilla::Result<int32_t, nsresult>
376 nsClipboard::GetNativeClipboardSequenceNumber(ClipboardType aWhichClipboard) {
377 MOZ_DIAGNOSTIC_ASSERT(
378 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
379 return aWhichClipboard == kSelectionClipboard ? mSelectionSequenceNumber
380 : mGlobalSequenceNumber;
383 static bool IsMIMEAtFlavourList(const nsTArray<nsCString>& aFlavourList,
384 const char* aMime) {
385 for (const auto& flavorStr : aFlavourList) {
386 if (flavorStr.Equals(aMime)) {
387 return true;
390 return false;
393 // When clipboard contains only images, X11/Gtk tries to convert them
394 // to text when we request text instead of just fail to provide the data.
395 // So if clipboard contains images only remove text MIME offer.
396 bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
397 nsTArray<nsCString>& aFlavors) {
398 MOZ_CLIPBOARD_LOG("nsClipboard::FilterImportedFlavors");
400 auto targets = mContext->GetTargets(aWhichClipboard);
401 if (!targets) {
402 MOZ_CLIPBOARD_LOG(" X11: no targes at clipboard (null), quit.\n");
403 return true;
406 for (const auto& atom : targets.AsSpan()) {
407 GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
408 if (!atom_name) {
409 continue;
411 // Filter out system MIME types.
412 if (strcmp(atom_name.get(), "TARGETS") == 0 ||
413 strcmp(atom_name.get(), "TIMESTAMP") == 0 ||
414 strcmp(atom_name.get(), "SAVE_TARGETS") == 0 ||
415 strcmp(atom_name.get(), "MULTIPLE") == 0) {
416 continue;
418 // Filter out types which can't be converted to text.
419 if (strncmp(atom_name.get(), "image/", 6) == 0 ||
420 strncmp(atom_name.get(), "application/", 12) == 0 ||
421 strncmp(atom_name.get(), "audio/", 6) == 0 ||
422 strncmp(atom_name.get(), "video/", 6) == 0) {
423 continue;
425 // We have some other MIME type on clipboard which can be hopefully
426 // converted to text without any problem.
427 MOZ_CLIPBOARD_LOG(
428 " X11: text types in clipboard, no need to filter them.\n");
429 return true;
432 // So make sure we offer only types we have at clipboard.
433 nsTArray<nsCString> clipboardFlavors;
434 for (const auto& atom : targets.AsSpan()) {
435 GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
436 if (!atom_name) {
437 continue;
439 if (IsMIMEAtFlavourList(aFlavors, atom_name.get())) {
440 clipboardFlavors.AppendElement(nsCString(atom_name.get()));
443 aFlavors.SwapElements(clipboardFlavors);
444 #ifdef MOZ_LOGGING
445 MOZ_CLIPBOARD_LOG(" X11: Flavors which match clipboard content:\n");
446 for (uint32_t i = 0; i < aFlavors.Length(); i++) {
447 MOZ_CLIPBOARD_LOG(" %s\n", aFlavors[i].get());
449 #endif
450 return true;
453 static nsresult GetTransferableFlavors(nsITransferable* aTransferable,
454 nsTArray<nsCString>& aFlavors) {
455 if (!aTransferable) {
456 return NS_ERROR_FAILURE;
458 // Get a list of flavors this transferable can import
459 nsresult rv = aTransferable->FlavorsTransferableCanImport(aFlavors);
460 if (NS_FAILED(rv)) {
461 MOZ_CLIPBOARD_LOG(" FlavorsTransferableCanImport falied!\n");
462 return rv;
464 #ifdef MOZ_LOGGING
465 MOZ_CLIPBOARD_LOG(" Flavors which can be imported:");
466 for (const auto& flavor : aFlavors) {
467 MOZ_CLIPBOARD_LOG(" %s", flavor.get());
469 #endif
470 return NS_OK;
473 static bool TransferableSetFile(nsITransferable* aTransferable,
474 const nsACString& aURIList) {
475 nsresult rv;
476 nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(aURIList);
477 if (!uris.IsEmpty()) {
478 nsCOMPtr<nsIURI> fileURI;
479 NS_NewURI(getter_AddRefs(fileURI), uris[0]);
480 if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
481 nsCOMPtr<nsIFile> file;
482 rv = fileURL->GetFile(getter_AddRefs(file));
483 if (NS_SUCCEEDED(rv)) {
484 aTransferable->SetTransferData(kFileMime, file);
485 MOZ_CLIPBOARD_LOG(" successfully set file to clipboard\n");
486 return true;
490 return false;
493 static bool TransferableSetHTML(nsITransferable* aTransferable,
494 Span<const char> aData) {
495 nsLiteralCString mimeType(kHTMLMime);
497 // Convert text/html into our text format
498 nsAutoCString charset;
499 if (!GetHTMLCharset(aData, charset)) {
500 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
501 MOZ_CLIPBOARD_LOG(
502 "Failed to get html/text encoding, fall back to utf-8.\n");
503 charset.AssignLiteral("utf-8");
506 MOZ_CLIPBOARD_LOG("TransferableSetHTML: HTML detected charset %s",
507 charset.get());
508 // app which use "text/html" to copy&paste
509 // get the decoder
510 auto encoding = Encoding::ForLabelNoReplacement(charset);
511 if (!encoding) {
512 MOZ_CLIPBOARD_LOG(
513 "TransferableSetHTML: get unicode decoder error (charset: %s)",
514 charset.get());
515 return false;
518 // According to spec html UTF-16BE/LE should be switched to UTF-8
519 // https://html.spec.whatwg.org/#determining-the-character-encoding:utf-16-encoding-2
520 if (encoding == UTF_16LE_ENCODING || encoding == UTF_16BE_ENCODING) {
521 encoding = UTF_8_ENCODING;
524 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
525 // issues, but might confuse other users.
526 const size_t prefixLen = std::size(kHTMLMarkupPrefix) - 1;
527 if (aData.Length() >= prefixLen && nsDependentCSubstring(aData.To(prefixLen))
528 .EqualsLiteral(kHTMLMarkupPrefix)) {
529 aData = aData.From(prefixLen);
532 nsAutoString unicodeData;
533 auto [rv, enc] = encoding->Decode(AsBytes(aData), unicodeData);
534 #if MOZ_LOGGING
535 if (enc != UTF_8_ENCODING && MOZ_CLIPBOARD_LOG_ENABLED()) {
536 nsCString decoderName;
537 enc->Name(decoderName);
538 MOZ_CLIPBOARD_LOG("TransferableSetHTML: expected UTF-8 decoder but got %s",
539 decoderName.get());
541 #endif
542 if (NS_FAILED(rv)) {
543 MOZ_CLIPBOARD_LOG("TransferableSetHTML: failed to decode HTML");
544 return false;
546 SetTransferableData(aTransferable, mimeType,
547 (const char*)unicodeData.BeginReading(),
548 unicodeData.Length() * sizeof(char16_t));
549 return true;
552 NS_IMETHODIMP
553 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
554 ClipboardType aWhichClipboard) {
555 MOZ_DIAGNOSTIC_ASSERT(aTransferable);
556 MOZ_DIAGNOSTIC_ASSERT(
557 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
559 MOZ_CLIPBOARD_LOG(
560 "nsClipboard::GetNativeClipboardData (%s)\n",
561 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
563 // TODO: Ensure we don't re-enter here.
564 if (!mContext) {
565 return NS_ERROR_FAILURE;
568 nsTArray<nsCString> flavors;
569 nsresult rv = GetTransferableFlavors(aTransferable, flavors);
570 NS_ENSURE_SUCCESS(rv, rv);
572 // Filter out MIME types on X11 to prevent unwanted conversions,
573 // see Bug 1611407
574 if (widget::GdkIsX11Display() &&
575 !FilterImportedFlavors(aWhichClipboard, flavors)) {
576 MOZ_CLIPBOARD_LOG(" Missing suitable clipboard data, quit.");
577 return NS_OK;
580 for (uint32_t i = 0; i < flavors.Length(); i++) {
581 nsCString& flavorStr = flavors[i];
583 if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
584 flavorStr.EqualsLiteral(kJPGImageMime) ||
585 flavorStr.EqualsLiteral(kPNGImageMime) ||
586 flavorStr.EqualsLiteral(kGIFImageMime)) {
587 // Emulate support for image/jpg
588 if (flavorStr.EqualsLiteral(kJPGImageMime)) {
589 flavorStr.Assign(kJPEGImageMime);
592 MOZ_CLIPBOARD_LOG(" Getting image %s MIME clipboard data\n",
593 flavorStr.get());
595 auto clipboardData =
596 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
597 if (!clipboardData) {
598 MOZ_CLIPBOARD_LOG(" %s type is missing\n", flavorStr.get());
599 continue;
602 nsCOMPtr<nsIInputStream> byteStream;
603 NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
604 NS_ASSIGNMENT_COPY);
605 aTransferable->SetTransferData(flavorStr.get(), byteStream);
606 MOZ_CLIPBOARD_LOG(" got %s MIME data\n", flavorStr.get());
607 return NS_OK;
610 // Special case text/plain since we can convert any
611 // string into text/plain
612 if (flavorStr.EqualsLiteral(kTextMime)) {
613 MOZ_CLIPBOARD_LOG(" Getting text %s MIME clipboard data\n",
614 flavorStr.get());
616 auto clipboardData = mContext->GetClipboardText(aWhichClipboard);
617 if (!clipboardData) {
618 MOZ_CLIPBOARD_LOG(" failed to get text data\n");
619 // If the type was text/plain and we couldn't get
620 // text off the clipboard, run the next loop
621 // iteration.
622 continue;
625 // Convert utf-8 into our text format.
626 NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get());
627 SetTransferableData(aTransferable, flavorStr,
628 (const char*)ucs2string.BeginReading(),
629 ucs2string.Length() * 2);
631 MOZ_CLIPBOARD_LOG(" got text data, length %zd\n", ucs2string.Length());
632 return NS_OK;
635 if (flavorStr.EqualsLiteral(kFileMime)) {
636 MOZ_CLIPBOARD_LOG(" Getting %s file clipboard data\n",
637 flavorStr.get());
639 auto clipboardData =
640 mContext->GetClipboardData(kURIListMime, aWhichClipboard);
641 if (!clipboardData) {
642 MOZ_CLIPBOARD_LOG(" text/uri-list type is missing\n");
643 continue;
646 nsDependentCSubstring fileName(clipboardData.AsSpan());
647 if (!TransferableSetFile(aTransferable, fileName)) {
648 continue;
650 return NS_OK;
653 MOZ_CLIPBOARD_LOG(" Getting %s MIME clipboard data\n", flavorStr.get());
655 auto clipboardData =
656 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
658 #ifdef MOZ_LOGGING
659 if (!clipboardData) {
660 MOZ_CLIPBOARD_LOG(" %s type is missing\n", flavorStr.get());
662 #endif
664 if (clipboardData) {
665 MOZ_CLIPBOARD_LOG(" got %s mime type data.\n", flavorStr.get());
667 // Special case text/html since we can convert into UCS2
668 if (flavorStr.EqualsLiteral(kHTMLMime)) {
669 if (!TransferableSetHTML(aTransferable, clipboardData.AsSpan())) {
670 continue;
672 } else {
673 auto span = clipboardData.AsSpan();
674 SetTransferableData(aTransferable, flavorStr, span.data(),
675 span.Length());
677 return NS_OK;
681 MOZ_CLIPBOARD_LOG(" failed to get clipboard content.\n");
682 return NS_OK;
685 enum DataType {
686 DATATYPE_IMAGE,
687 DATATYPE_FILE,
688 DATATYPE_HTML,
689 DATATYPE_RAW,
692 struct DataCallbackHandler {
693 RefPtr<nsITransferable> mTransferable;
694 nsBaseClipboard::GetDataCallback mDataCallback;
695 nsCString mMimeType;
696 DataType mDataType;
698 explicit DataCallbackHandler(RefPtr<nsITransferable> aTransferable,
699 nsBaseClipboard::GetDataCallback&& aDataCallback,
700 const char* aMimeType,
701 DataType aDataType = DATATYPE_RAW)
702 : mTransferable(std::move(aTransferable)),
703 mDataCallback(std::move(aDataCallback)),
704 mMimeType(aMimeType),
705 mDataType(aDataType) {
706 MOZ_COUNT_CTOR(DataCallbackHandler);
707 MOZ_CLIPBOARD_LOG("DataCallbackHandler created [%p] MIME %s type %d", this,
708 mMimeType.get(), mDataType);
710 ~DataCallbackHandler() {
711 MOZ_CLIPBOARD_LOG("DataCallbackHandler deleted [%p]", this);
712 MOZ_COUNT_DTOR(DataCallbackHandler);
716 static void AsyncGetTextImpl(nsITransferable* aTransferable,
717 int32_t aWhichClipboard,
718 nsBaseClipboard::GetDataCallback&& aCallback) {
719 MOZ_CLIPBOARD_LOG("AsyncGetText() type '%s'",
720 aWhichClipboard == nsClipboard::kSelectionClipboard
721 ? "primary"
722 : "clipboard");
724 gtk_clipboard_request_text(
725 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
726 [](GtkClipboard* aClipboard, const gchar* aText, gpointer aData) -> void {
727 UniquePtr<DataCallbackHandler> ref(
728 static_cast<DataCallbackHandler*>(aData));
729 MOZ_CLIPBOARD_LOG("AsyncGetText async handler of [%p]", aData);
731 size_t dataLength = aText ? strlen(aText) : 0;
732 if (dataLength <= 0) {
733 MOZ_CLIPBOARD_LOG(" quit, text is not available");
734 ref->mDataCallback(NS_OK);
735 return;
738 // Convert utf-8 into our unicode format.
739 NS_ConvertUTF8toUTF16 utf16string(aText, dataLength);
740 nsLiteralCString flavor(kTextMime);
741 SetTransferableData(ref->mTransferable, flavor,
742 (const char*)utf16string.BeginReading(),
743 utf16string.Length() * 2);
744 MOZ_CLIPBOARD_LOG(" text is set, length = %d", (int)dataLength);
745 ref->mDataCallback(NS_OK);
747 new DataCallbackHandler(aTransferable, std::move(aCallback), kTextMime));
750 static void AsyncGetDataImpl(nsITransferable* aTransferable,
751 int32_t aWhichClipboard, const char* aMimeType,
752 DataType aDataType,
753 nsBaseClipboard::GetDataCallback&& aCallback) {
754 MOZ_CLIPBOARD_LOG("AsyncGetData() type '%s'",
755 aWhichClipboard == nsClipboard::kSelectionClipboard
756 ? "primary"
757 : "clipboard");
759 const char* gtkMIMEType = nullptr;
760 switch (aDataType) {
761 case DATATYPE_FILE:
762 // Don't ask Gtk for application/x-moz-file
763 gtkMIMEType = kURIListMime;
764 break;
765 case DATATYPE_IMAGE:
766 case DATATYPE_HTML:
767 case DATATYPE_RAW:
768 gtkMIMEType = aMimeType;
769 break;
772 gtk_clipboard_request_contents(
773 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
774 gdk_atom_intern(gtkMIMEType, FALSE),
775 [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
776 gpointer aData) -> void {
777 UniquePtr<DataCallbackHandler> ref(
778 static_cast<DataCallbackHandler*>(aData));
779 MOZ_CLIPBOARD_LOG("AsyncGetData async handler [%p] MIME %s type %d",
780 aData, ref->mMimeType.get(), ref->mDataType);
782 int dataLength = gtk_selection_data_get_length(aSelection);
783 if (dataLength <= 0) {
784 ref->mDataCallback(NS_OK);
785 return;
787 const char* data = (const char*)gtk_selection_data_get_data(aSelection);
788 if (!data) {
789 ref->mDataCallback(NS_OK);
790 return;
792 switch (ref->mDataType) {
793 case DATATYPE_IMAGE: {
794 MOZ_CLIPBOARD_LOG(" set image clipboard data");
795 nsCOMPtr<nsIInputStream> byteStream;
796 NS_NewByteInputStream(getter_AddRefs(byteStream),
797 Span(data, dataLength), NS_ASSIGNMENT_COPY);
798 ref->mTransferable->SetTransferData(ref->mMimeType.get(),
799 byteStream);
800 break;
802 case DATATYPE_FILE: {
803 MOZ_CLIPBOARD_LOG(" set file clipboard data");
804 nsDependentCSubstring file(data, dataLength);
805 TransferableSetFile(ref->mTransferable, file);
806 break;
808 case DATATYPE_HTML: {
809 MOZ_CLIPBOARD_LOG(" html clipboard data");
810 Span dataSpan(data, dataLength);
811 TransferableSetHTML(ref->mTransferable, dataSpan);
812 break;
814 case DATATYPE_RAW: {
815 MOZ_CLIPBOARD_LOG(" raw clipboard data %s", ref->mMimeType.get());
816 SetTransferableData(ref->mTransferable, ref->mMimeType, data,
817 dataLength);
818 break;
821 ref->mDataCallback(NS_OK);
823 new DataCallbackHandler(aTransferable, std::move(aCallback), aMimeType,
824 aDataType));
827 static void AsyncGetDataFlavor(nsITransferable* aTransferable,
828 int32_t aWhichClipboard, nsCString& aFlavorStr,
829 nsBaseClipboard::GetDataCallback&& aCallback) {
830 if (aFlavorStr.EqualsLiteral(kJPEGImageMime) ||
831 aFlavorStr.EqualsLiteral(kJPGImageMime) ||
832 aFlavorStr.EqualsLiteral(kPNGImageMime) ||
833 aFlavorStr.EqualsLiteral(kGIFImageMime)) {
834 // Emulate support for image/jpg
835 if (aFlavorStr.EqualsLiteral(kJPGImageMime)) {
836 aFlavorStr.Assign(kJPEGImageMime);
838 MOZ_CLIPBOARD_LOG(" Getting image %s MIME clipboard data",
839 aFlavorStr.get());
840 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
841 DATATYPE_IMAGE, std::move(aCallback));
842 return;
844 // Special case text/plain since we can convert any
845 // string into text/plain
846 if (aFlavorStr.EqualsLiteral(kTextMime)) {
847 MOZ_CLIPBOARD_LOG(" Getting unicode clipboard data");
848 AsyncGetTextImpl(aTransferable, aWhichClipboard, std::move(aCallback));
849 return;
851 if (aFlavorStr.EqualsLiteral(kFileMime)) {
852 MOZ_CLIPBOARD_LOG(" Getting file clipboard data\n");
853 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
854 DATATYPE_FILE, std::move(aCallback));
855 return;
857 if (aFlavorStr.EqualsLiteral(kHTMLMime)) {
858 MOZ_CLIPBOARD_LOG(" Getting HTML clipboard data");
859 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
860 DATATYPE_HTML, std::move(aCallback));
861 return;
863 MOZ_CLIPBOARD_LOG(" Getting raw %s MIME clipboard data\n", aFlavorStr.get());
864 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
865 DATATYPE_RAW, std::move(aCallback));
868 void nsClipboard::AsyncGetNativeClipboardData(nsITransferable* aTransferable,
869 ClipboardType aWhichClipboard,
870 GetDataCallback&& aCallback) {
871 MOZ_DIAGNOSTIC_ASSERT(aTransferable);
872 MOZ_DIAGNOSTIC_ASSERT(
873 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
875 MOZ_CLIPBOARD_LOG("nsClipboard::AsyncGetNativeClipboardData (%s)",
876 aWhichClipboard == nsClipboard::kSelectionClipboard
877 ? "primary"
878 : "clipboard");
879 nsTArray<nsCString> importedFlavors;
880 nsresult rv = GetTransferableFlavors(aTransferable, importedFlavors);
881 if (NS_FAILED(rv)) {
882 aCallback(rv);
883 return;
886 auto flavorsNum = importedFlavors.Length();
887 if (!flavorsNum) {
888 aCallback(NS_OK);
889 return;
891 #ifdef MOZ_LOGGING
892 if (flavorsNum > 1) {
893 MOZ_CLIPBOARD_LOG(
894 " Only first MIME type (%s) will be imported from clipboard!",
895 importedFlavors[0].get());
897 #endif
899 // Filter out MIME types on X11 to prevent unwanted conversions,
900 // see Bug 1611407
901 if (widget::GdkIsX11Display()) {
902 AsyncHasNativeClipboardDataMatchingFlavors(
903 importedFlavors, aWhichClipboard,
904 [aWhichClipboard, transferable = nsCOMPtr{aTransferable},
905 callback = std::move(aCallback)](auto aResultOrError) mutable {
906 if (aResultOrError.isErr()) {
907 callback(aResultOrError.unwrapErr());
908 return;
911 nsTArray<nsCString> clipboardFlavors =
912 std::move(aResultOrError.unwrap());
913 if (!clipboardFlavors.Length()) {
914 MOZ_CLIPBOARD_LOG(" no flavors in clipboard, quit.");
915 callback(NS_OK);
916 return;
919 AsyncGetDataFlavor(transferable, aWhichClipboard, clipboardFlavors[0],
920 std::move(callback));
922 return;
925 // Read clipboard directly on Wayland
926 AsyncGetDataFlavor(aTransferable, aWhichClipboard, importedFlavors[0],
927 std::move(aCallback));
930 nsresult nsClipboard::EmptyNativeClipboardData(ClipboardType aWhichClipboard) {
931 MOZ_DIAGNOSTIC_ASSERT(
932 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
934 MOZ_CLIPBOARD_LOG(
935 "nsClipboard::EmptyNativeClipboardData (%s)\n",
936 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
937 if (aWhichClipboard == kSelectionClipboard) {
938 if (mSelectionTransferable) {
939 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
940 MOZ_ASSERT(!mSelectionTransferable);
942 } else {
943 if (mGlobalTransferable) {
944 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
945 MOZ_ASSERT(!mGlobalTransferable);
948 ClearCachedTargets(aWhichClipboard);
949 return NS_OK;
952 void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
953 if (aWhichClipboard == kSelectionClipboard) {
954 mSelectionSequenceNumber++;
955 mSelectionTransferable = nullptr;
956 } else {
957 mGlobalSequenceNumber++;
958 mGlobalTransferable = nullptr;
962 static bool FlavorMatchesTarget(const nsACString& aFlavor, GdkAtom aTarget) {
963 GUniquePtr<gchar> atom_name(gdk_atom_name(aTarget));
964 if (!atom_name) {
965 return false;
967 if (aFlavor.Equals(atom_name.get())) {
968 MOZ_CLIPBOARD_LOG(" has %s\n", atom_name.get());
969 return true;
971 // X clipboard supports image/jpeg, but we want to emulate support
972 // for image/jpg as well
973 if (aFlavor.EqualsLiteral(kJPGImageMime) &&
974 !strcmp(atom_name.get(), kJPEGImageMime)) {
975 MOZ_CLIPBOARD_LOG(" has image/jpg\n");
976 return true;
978 // application/x-moz-file should be treated like text/uri-list
979 if (aFlavor.EqualsLiteral(kFileMime) &&
980 !strcmp(atom_name.get(), kURIListMime)) {
981 MOZ_CLIPBOARD_LOG(
982 " has text/uri-list treating as application/x-moz-file");
983 return true;
985 return false;
988 mozilla::Result<bool, nsresult>
989 nsClipboard::HasNativeClipboardDataMatchingFlavors(
990 const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard) {
991 MOZ_DIAGNOSTIC_ASSERT(
992 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
994 MOZ_CLIPBOARD_LOG(
995 "nsClipboard::HasNativeClipboardDataMatchingFlavors (%s)\n",
996 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
998 if (!mContext) {
999 return Err(NS_ERROR_FAILURE);
1002 auto targets = mContext->GetTargets(aWhichClipboard);
1003 if (!targets) {
1004 MOZ_CLIPBOARD_LOG(" no targes at clipboard (null)\n");
1005 return false;
1008 #ifdef MOZ_LOGGING
1009 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
1010 MOZ_CLIPBOARD_LOG(" Asking for content:\n");
1011 for (auto& flavor : aFlavorList) {
1012 MOZ_CLIPBOARD_LOG(" MIME %s\n", flavor.get());
1014 MOZ_CLIPBOARD_LOG(" Clipboard content (target nums %zu):\n",
1015 targets.AsSpan().Length());
1016 for (const auto& target : targets.AsSpan()) {
1017 GUniquePtr<gchar> atom_name(gdk_atom_name(target));
1018 if (!atom_name) {
1019 MOZ_CLIPBOARD_LOG(" failed to get MIME\n");
1020 continue;
1022 MOZ_CLIPBOARD_LOG(" MIME %s\n", atom_name.get());
1025 #endif
1027 // Walk through the provided types and try to match it to a
1028 // provided type.
1029 for (auto& flavor : aFlavorList) {
1030 // We special case text/plain here.
1031 if (flavor.EqualsLiteral(kTextMime) &&
1032 gtk_targets_include_text(targets.AsSpan().data(),
1033 targets.AsSpan().Length())) {
1034 MOZ_CLIPBOARD_LOG(" has kTextMime\n");
1035 return true;
1037 for (const auto& target : targets.AsSpan()) {
1038 if (FlavorMatchesTarget(flavor, target)) {
1039 return true;
1044 MOZ_CLIPBOARD_LOG(" no targes at clipboard (bad match)\n");
1045 return false;
1048 struct TragetCallbackHandler {
1049 TragetCallbackHandler(const nsTArray<nsCString>& aAcceptedFlavorList,
1050 nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback)
1051 : mAcceptedFlavorList(aAcceptedFlavorList.Clone()),
1052 mCallback(std::move(aCallback)) {
1053 MOZ_CLIPBOARD_LOG("TragetCallbackHandler(%p) created", this);
1055 ~TragetCallbackHandler() {
1056 MOZ_CLIPBOARD_LOG("TragetCallbackHandler(%p) deleted", this);
1058 nsTArray<nsCString> mAcceptedFlavorList;
1059 nsBaseClipboard::HasMatchingFlavorsCallback mCallback;
1062 void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1063 const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard,
1064 nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback) {
1065 MOZ_DIAGNOSTIC_ASSERT(
1066 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
1068 MOZ_CLIPBOARD_LOG(
1069 "nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors (%s)",
1070 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1072 gtk_clipboard_request_contents(
1073 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
1074 gdk_atom_intern("TARGETS", FALSE),
1075 [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
1076 gpointer aData) -> void {
1077 MOZ_CLIPBOARD_LOG("gtk_clipboard_request_contents async handler (%p)",
1078 aData);
1079 UniquePtr<TragetCallbackHandler> handler(
1080 static_cast<TragetCallbackHandler*>(aData));
1082 GdkAtom* targets = nullptr;
1083 gint targetsNum = 0;
1084 if (gtk_selection_data_get_length(aSelection) > 0) {
1085 gtk_selection_data_get_targets(aSelection, &targets, &targetsNum);
1087 #ifdef MOZ_LOGGING
1088 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
1089 MOZ_CLIPBOARD_LOG(" Clipboard content (target nums %d):\n",
1090 targetsNum);
1091 for (int i = 0; i < targetsNum; i++) {
1092 GUniquePtr<gchar> atom_name(gdk_atom_name(targets[i]));
1093 if (!atom_name) {
1094 MOZ_CLIPBOARD_LOG(" failed to get MIME\n");
1095 continue;
1097 MOZ_CLIPBOARD_LOG(" MIME %s\n", atom_name.get());
1100 #endif
1102 nsTArray<nsCString> results;
1103 if (targetsNum) {
1104 for (auto& flavor : handler->mAcceptedFlavorList) {
1105 MOZ_CLIPBOARD_LOG(" looking for %s", flavor.get());
1106 if (flavor.EqualsLiteral(kTextMime) &&
1107 gtk_targets_include_text(targets, targetsNum)) {
1108 results.AppendElement(flavor);
1109 MOZ_CLIPBOARD_LOG(" has kTextMime\n");
1110 continue;
1112 for (int i = 0; i < targetsNum; i++) {
1113 if (FlavorMatchesTarget(flavor, targets[i])) {
1114 results.AppendElement(flavor);
1119 handler->mCallback(std::move(results));
1121 new TragetCallbackHandler(aFlavorList, std::move(aCallback)));
1124 nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
1125 nsITransferable* retval;
1127 if (aWhichClipboard == kSelectionClipboard)
1128 retval = mSelectionTransferable.get();
1129 else
1130 retval = mGlobalTransferable.get();
1132 return retval;
1135 void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
1136 GtkSelectionData* aSelectionData) {
1137 // Someone has asked us to hand them something. The first thing
1138 // that we want to do is see if that something includes text. If
1139 // it does, try to give it text/plain after converting it to
1140 // utf-8.
1142 int32_t whichClipboard;
1144 // which clipboard?
1145 GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
1146 if (selection == GDK_SELECTION_PRIMARY)
1147 whichClipboard = kSelectionClipboard;
1148 else if (selection == GDK_SELECTION_CLIPBOARD)
1149 whichClipboard = kGlobalClipboard;
1150 else
1151 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
1153 MOZ_CLIPBOARD_LOG(
1154 "nsClipboard::SelectionGetEvent (%s)\n",
1155 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1157 nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
1158 if (!trans) {
1159 // We have nothing to serve
1160 MOZ_CLIPBOARD_LOG(
1161 "nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
1162 whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard");
1163 return;
1166 nsresult rv;
1167 nsCOMPtr<nsISupports> item;
1169 GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
1170 MOZ_CLIPBOARD_LOG(" selection target %s\n",
1171 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1173 // Check to see if the selection data is some text type.
1174 if (gtk_targets_include_text(&selectionTarget, 1)) {
1175 MOZ_CLIPBOARD_LOG(" providing text/plain data\n");
1176 // Try to convert our internal type into a text string. Get
1177 // the transferable for this clipboard and try to get the
1178 // text/plain type for it.
1179 rv = trans->GetTransferData("text/plain", getter_AddRefs(item));
1180 if (NS_FAILED(rv) || !item) {
1181 MOZ_CLIPBOARD_LOG(" GetTransferData() failed to get text/plain!\n");
1182 return;
1185 nsCOMPtr<nsISupportsString> wideString;
1186 wideString = do_QueryInterface(item);
1187 if (!wideString) return;
1189 nsAutoString ucs2string;
1190 wideString->GetData(ucs2string);
1191 NS_ConvertUTF16toUTF8 utf8string(ucs2string);
1193 MOZ_CLIPBOARD_LOG(" sent %zd bytes of utf-8 data\n", utf8string.Length());
1194 if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
1195 MOZ_CLIPBOARD_LOG(
1196 " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
1197 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
1198 // in some versions of GTK.
1199 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
1200 reinterpret_cast<const guchar*>(utf8string.get()),
1201 utf8string.Length());
1202 } else {
1203 gtk_selection_data_set_text(aSelectionData, utf8string.get(),
1204 utf8string.Length());
1206 return;
1209 // Check to see if the selection data is an image type
1210 if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
1211 MOZ_CLIPBOARD_LOG(" providing image data\n");
1212 // Look through our transfer data for the image
1213 static const char* const imageMimeTypes[] = {kNativeImageMime,
1214 kPNGImageMime, kJPEGImageMime,
1215 kJPGImageMime, kGIFImageMime};
1216 nsCOMPtr<nsISupports> imageItem;
1217 nsCOMPtr<imgIContainer> image;
1218 for (uint32_t i = 0; i < std::size(imageMimeTypes); i++) {
1219 rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
1220 if (NS_FAILED(rv)) {
1221 MOZ_CLIPBOARD_LOG(" %s is missing at GetTransferData()\n",
1222 imageMimeTypes[i]);
1223 continue;
1226 image = do_QueryInterface(imageItem);
1227 if (image) {
1228 MOZ_CLIPBOARD_LOG(" %s is available at GetTransferData()\n",
1229 imageMimeTypes[i]);
1230 break;
1234 if (!image) { // Not getting an image for an image mime type!?
1235 MOZ_CLIPBOARD_LOG(
1236 " Failed to get any image mime from GetTransferData()!\n");
1237 return;
1240 RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
1241 if (!pixbuf) {
1242 MOZ_CLIPBOARD_LOG(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
1243 return;
1246 MOZ_CLIPBOARD_LOG(" Setting pixbuf image data as %s\n",
1247 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1248 gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
1249 return;
1252 if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
1253 MOZ_CLIPBOARD_LOG(" providing %s data\n", kHTMLMime);
1254 rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
1255 if (NS_FAILED(rv) || !item) {
1256 MOZ_CLIPBOARD_LOG(" failed to get %s data by GetTransferData()!\n",
1257 kHTMLMime);
1258 return;
1261 nsCOMPtr<nsISupportsString> wideString;
1262 wideString = do_QueryInterface(item);
1263 if (!wideString) {
1264 MOZ_CLIPBOARD_LOG(" failed to get wideString interface!");
1265 return;
1268 nsAutoString ucs2string;
1269 wideString->GetData(ucs2string);
1271 nsAutoCString html;
1272 // Add the prefix so the encoding is correctly detected.
1273 html.AppendLiteral(kHTMLMarkupPrefix);
1274 AppendUTF16toUTF8(ucs2string, html);
1276 MOZ_CLIPBOARD_LOG(" Setting %zd bytes of %s data\n", html.Length(),
1277 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1278 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
1279 (const guchar*)html.get(), html.Length());
1280 return;
1283 // We put kFileMime onto the clipboard as kURIListMime.
1284 if (selectionTarget == gdk_atom_intern(kURIListMime, FALSE)) {
1285 MOZ_CLIPBOARD_LOG(" providing %s data\n", kURIListMime);
1286 rv = trans->GetTransferData(kFileMime, getter_AddRefs(item));
1287 if (NS_FAILED(rv) || !item) {
1288 MOZ_CLIPBOARD_LOG(" failed to get %s data by GetTransferData()!\n",
1289 kFileMime);
1290 return;
1293 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
1294 if (!file) {
1295 MOZ_CLIPBOARD_LOG(" failed to get nsIFile interface!");
1296 return;
1299 nsCOMPtr<nsIURI> fileURI;
1300 rv = NS_NewFileURI(getter_AddRefs(fileURI), file);
1301 if (NS_FAILED(rv)) {
1302 MOZ_CLIPBOARD_LOG(" failed to get fileURI\n");
1303 return;
1306 nsAutoCString uri;
1307 if (NS_FAILED(fileURI->GetSpec(uri))) {
1308 MOZ_CLIPBOARD_LOG(" failed to get fileURI spec\n");
1309 return;
1312 MOZ_CLIPBOARD_LOG(" Setting %zd bytes of data\n", uri.Length());
1313 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
1314 (const guchar*)uri.get(), uri.Length());
1315 return;
1318 MOZ_CLIPBOARD_LOG(" Try if we have anything at GetTransferData() for %s\n",
1319 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1321 // Try to match up the selection data target to something our
1322 // transferable provides.
1323 GUniquePtr<gchar> target_name(gdk_atom_name(selectionTarget));
1324 if (!target_name) {
1325 MOZ_CLIPBOARD_LOG(" Failed to get target name!\n");
1326 return;
1329 rv = trans->GetTransferData(target_name.get(), getter_AddRefs(item));
1330 // nothing found?
1331 if (NS_FAILED(rv) || !item) {
1332 MOZ_CLIPBOARD_LOG(" Failed to get anything from GetTransferData()!\n");
1333 return;
1336 void* primitive_data = nullptr;
1337 uint32_t dataLen = 0;
1338 nsPrimitiveHelpers::CreateDataFromPrimitive(
1339 nsDependentCString(target_name.get()), item, &primitive_data, &dataLen);
1340 if (!primitive_data) {
1341 MOZ_CLIPBOARD_LOG(" Failed to get primitive data!\n");
1342 return;
1345 MOZ_CLIPBOARD_LOG(" Setting %s as a primitive data type, %d bytes\n",
1346 target_name.get(), dataLen);
1347 gtk_selection_data_set(aSelectionData, selectionTarget,
1348 8, /* 8 bits in a unit */
1349 (const guchar*)primitive_data, dataLen);
1350 free(primitive_data);
1353 void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard) {
1354 if (aWhichClipboard == kSelectionClipboard) {
1355 nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr);
1356 } else {
1357 nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
1361 void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
1362 Maybe<ClipboardType> whichClipboard = GetGeckoClipboardType(aGtkClipboard);
1363 if (whichClipboard.isNothing()) {
1364 return;
1366 MOZ_CLIPBOARD_LOG(
1367 "nsClipboard::SelectionClearEvent (%s)\n",
1368 *whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1369 ClearCachedTargets(*whichClipboard);
1370 ClearTransferable(*whichClipboard);
1371 ClearClipboardCache(*whichClipboard);
1374 void nsClipboard::OwnerChangedEvent(GtkClipboard* aGtkClipboard,
1375 GdkEventOwnerChange* aEvent) {
1376 Maybe<ClipboardType> whichClipboard = GetGeckoClipboardType(aGtkClipboard);
1377 if (whichClipboard.isNothing()) {
1378 return;
1380 MOZ_CLIPBOARD_LOG(
1381 "nsClipboard::OwnerChangedEvent (%s)\n",
1382 *whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1383 GtkWidget* gtkWidget = [aEvent]() -> GtkWidget* {
1384 if (!aEvent->owner) {
1385 return nullptr;
1387 gpointer user_data = nullptr;
1388 gdk_window_get_user_data(aEvent->owner, &user_data);
1389 return GTK_WIDGET(user_data);
1390 }();
1391 // If we can get GtkWidget from the current clipboard owner, this
1392 // owner-changed event must be triggered by ourself via calling
1393 // gtk_clipboard_set_with_data, the sequence number should already be handled.
1394 if (!gtkWidget) {
1395 if (*whichClipboard == kSelectionClipboard) {
1396 mSelectionSequenceNumber++;
1397 } else {
1398 mGlobalSequenceNumber++;
1402 ClearCachedTargets(*whichClipboard);
1405 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
1406 GtkSelectionData* aSelectionData, guint info,
1407 gpointer user_data) {
1408 MOZ_CLIPBOARD_LOG("clipboard_get_cb() callback\n");
1409 nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
1410 clipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
1413 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
1414 MOZ_CLIPBOARD_LOG("clipboard_clear_cb() callback\n");
1415 nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
1416 clipboard->SelectionClearEvent(aGtkClipboard);
1419 void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard,
1420 GdkEventOwnerChange* aEvent,
1421 gpointer aUserData) {
1422 MOZ_CLIPBOARD_LOG("clipboard_owner_change_cb() callback\n");
1423 nsClipboard* clipboard = static_cast<nsClipboard*>(aUserData);
1424 clipboard->OwnerChangedEvent(aGtkClipboard, aEvent);
1428 * This function extracts the encoding label from the subset of HTML internal
1429 * encoding declaration syntax that uses the old long form with double quotes
1430 * and without spaces around the equals sign between the "content" attribute
1431 * name and the attribute value.
1433 * This was added for the sake of an ancient version of StarOffice
1434 * in the pre-UTF-8 era in bug 123389. It is unclear if supporting
1435 * non-UTF-8 encodings is still necessary and if this function
1436 * still needs to exist.
1438 * As of December 2022, both Gecko and LibreOffice emit an UTF-8
1439 * declaration that this function successfully extracts "UTF-8" from,
1440 * but that's also the default that we fall back on if this function
1441 * fails to extract a label.
1443 bool GetHTMLCharset(Span<const char> aData, nsCString& aFoundCharset) {
1444 // Assume ASCII first to find "charset" info
1445 const nsDependentCSubstring htmlStr(aData);
1446 nsACString::const_iterator start, end;
1447 htmlStr.BeginReading(start);
1448 htmlStr.EndReading(end);
1449 nsACString::const_iterator valueStart(start), valueEnd(start);
1451 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
1452 start = end;
1453 htmlStr.EndReading(end);
1455 if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
1456 valueStart = end;
1457 start = end;
1458 htmlStr.EndReading(end);
1460 if (FindCharInReadable('"', start, end)) valueEnd = start;
1463 // find "charset" in HTML
1464 if (valueStart != valueEnd) {
1465 aFoundCharset = Substring(valueStart, valueEnd);
1466 ToUpperCase(aFoundCharset);
1467 return true;
1469 return false;