Backed out 3 changesets (bug 1870106, bug 1845276) for causing doc generate failures...
[gecko.git] / widget / gtk / nsClipboard.cpp
blob571b43f1cc6020f9ae60d648a673a6ab1f7e98eb
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/Services.h"
34 #include "mozilla/RefPtr.h"
35 #include "mozilla/GRefPtr.h"
36 #include "mozilla/SchedulerGroup.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 ClipboardTargets nsRetrievalContext::sClipboardTargets;
70 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 LOGCLIP("SetTransferableData MIME %s\n", PromiseFlatCString(aFlavor).get());
92 nsCOMPtr<nsISupports> wrapper;
93 nsPrimitiveHelpers::CreatePrimitiveForData(
94 aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
95 aTransferable->SetTransferData(PromiseFlatCString(aFlavor).get(), wrapper);
98 ClipboardTargets ClipboardTargets::Clone() {
99 ClipboardTargets ret;
100 ret.mCount = mCount;
101 if (mCount) {
102 ret.mTargets.reset(
103 reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * mCount)));
104 memcpy(ret.mTargets.get(), mTargets.get(), sizeof(GdkAtom) * mCount);
106 return ret;
109 void ClipboardTargets::Set(ClipboardTargets aTargets) {
110 mCount = aTargets.mCount;
111 mTargets = std::move(aTargets.mTargets);
114 void ClipboardData::SetData(Span<const uint8_t> aData) {
115 mData = nullptr;
116 mLength = aData.Length();
117 if (mLength) {
118 mData.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength)));
119 memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
123 void ClipboardData::SetText(Span<const char> aData) {
124 mData = nullptr;
125 mLength = aData.Length();
126 if (mLength) {
127 mData.reset(
128 reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength + 1))));
129 memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
130 mData.get()[mLength] = '\0';
134 void ClipboardData::SetTargets(ClipboardTargets aTargets) {
135 mLength = aTargets.mCount;
136 mData.reset(reinterpret_cast<char*>(aTargets.mTargets.release()));
139 ClipboardTargets ClipboardData::ExtractTargets() {
140 GUniquePtr<GdkAtom> targets(reinterpret_cast<GdkAtom*>(mData.release()));
141 uint32_t length = std::exchange(mLength, 0);
142 return ClipboardTargets{std::move(targets), length};
145 GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
146 if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
147 return GDK_SELECTION_CLIPBOARD;
149 return GDK_SELECTION_PRIMARY;
152 int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
153 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
154 return nsClipboard::kSelectionClipboard;
155 else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
156 return nsClipboard::kGlobalClipboard;
158 return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
161 void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard* aClipboard,
162 GdkEvent* aEvent,
163 gpointer data) {
164 LOGCLIP("nsRetrievalContext::ClearCachedTargetsClipboard()");
165 sClipboardTargets.Clear();
168 void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard* aClipboard,
169 GdkEvent* aEvent,
170 gpointer data) {
171 LOGCLIP("nsRetrievalContext::ClearCachedTargetsPrimary()");
172 sPrimaryTargets.Clear();
175 ClipboardTargets nsRetrievalContext::GetTargets(int32_t aWhichClipboard) {
176 LOGCLIP("nsRetrievalContext::GetTargets(%s)\n",
177 aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
178 : "clipboard");
179 ClipboardTargets& storedTargets =
180 (aWhichClipboard == nsClipboard::kSelectionClipboard) ? sPrimaryTargets
181 : sClipboardTargets;
182 if (!storedTargets) {
183 LOGCLIP(" getting targets from system");
184 storedTargets.Set(GetTargetsImpl(aWhichClipboard));
185 } else {
186 LOGCLIP(" using cached targets");
188 return storedTargets.Clone();
191 nsRetrievalContext::~nsRetrievalContext() {
192 sClipboardTargets.Clear();
193 sPrimaryTargets.Clear();
196 nsClipboard::nsClipboard()
197 : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
198 #ifdef MOZ_WAYLAND
199 widget::GdkIsWaylandDisplay()
200 ? widget::WaylandDisplayGet()->IsPrimarySelectionEnabled()
201 : true,
202 #else
203 true, /* supportsSelectionClipboard */
204 #endif
205 false /* supportsFindClipboard */,
206 false /* supportsSelectionCache */)) {
207 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), "owner-change",
208 G_CALLBACK(clipboard_owner_change_cb), this);
209 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change",
210 G_CALLBACK(clipboard_owner_change_cb), this);
213 nsClipboard::~nsClipboard() {
214 g_signal_handlers_disconnect_by_func(
215 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
216 FuncToGpointer(clipboard_owner_change_cb), this);
217 g_signal_handlers_disconnect_by_func(
218 gtk_clipboard_get(GDK_SELECTION_PRIMARY),
219 FuncToGpointer(clipboard_owner_change_cb), this);
222 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
224 nsresult nsClipboard::Init(void) {
225 #if defined(MOZ_X11)
226 if (widget::GdkIsX11Display()) {
227 mContext = new nsRetrievalContextX11();
229 #endif
230 #if defined(MOZ_WAYLAND)
231 if (widget::GdkIsWaylandDisplay()) {
232 mContext = new nsRetrievalContextWayland();
234 #endif
236 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
237 if (os) {
238 os->AddObserver(this, "xpcom-shutdown", false);
241 return NS_OK;
244 NS_IMETHODIMP
245 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
246 const char16_t* aData) {
247 // Save global clipboard content to CLIPBOARD_MANAGER.
248 // gtk_clipboard_store() can run an event loop, so call from a dedicated
249 // runnable.
250 return SchedulerGroup::Dispatch(
251 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
252 LOGCLIP("nsClipboard storing clipboard content\n");
253 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
254 }));
257 NS_IMETHODIMP
258 nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable,
259 int32_t aWhichClipboard) {
260 MOZ_DIAGNOSTIC_ASSERT(aTransferable);
261 MOZ_DIAGNOSTIC_ASSERT(
262 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
264 // See if we can short cut
265 if ((aWhichClipboard == kGlobalClipboard &&
266 aTransferable == mGlobalTransferable.get()) ||
267 (aWhichClipboard == kSelectionClipboard &&
268 aTransferable == mSelectionTransferable.get())) {
269 return NS_OK;
272 LOGCLIP("nsClipboard::SetNativeClipboardData (%s)\n",
273 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
275 // List of suported targets
276 GtkTargetList* list = gtk_target_list_new(nullptr, 0);
278 // Get the types of supported flavors
279 nsTArray<nsCString> flavors;
280 nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
281 if (NS_FAILED(rv)) {
282 LOGCLIP(" FlavorsTransferableCanExport failed!\n");
283 // Fall through. |gtkTargets| will be null below.
286 // Add all the flavors to this widget's supported type.
287 bool imagesAdded = false;
288 for (uint32_t i = 0; i < flavors.Length(); i++) {
289 nsCString& flavorStr = flavors[i];
290 LOGCLIP(" processing target %s\n", flavorStr.get());
292 // Special case text/plain since we can handle all of the string types.
293 if (flavorStr.EqualsLiteral(kTextMime)) {
294 LOGCLIP(" adding TEXT targets\n");
295 gtk_target_list_add_text_targets(list, 0);
296 continue;
299 if (nsContentUtils::IsFlavorImage(flavorStr)) {
300 // Don't bother adding image targets twice
301 if (!imagesAdded) {
302 // accept any writable image type
303 LOGCLIP(" adding IMAGE targets\n");
304 gtk_target_list_add_image_targets(list, 0, TRUE);
305 imagesAdded = true;
307 continue;
310 if (flavorStr.EqualsLiteral(kFileMime)) {
311 LOGCLIP(" adding text/uri-list target\n");
312 GdkAtom atom = gdk_atom_intern(kURIListMime, FALSE);
313 gtk_target_list_add(list, atom, 0, 0);
314 continue;
317 // Add this to our list of valid targets
318 LOGCLIP(" adding OTHER target %s\n", flavorStr.get());
319 GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
320 gtk_target_list_add(list, atom, 0, 0);
323 // Get GTK clipboard (CLIPBOARD or PRIMARY)
324 GtkClipboard* gtkClipboard =
325 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
327 gint numTargets = 0;
328 GtkTargetEntry* gtkTargets =
329 gtk_target_table_new_from_list(list, &numTargets);
330 if (!gtkTargets || numTargets == 0) {
331 LOGCLIP(
332 " gtk_target_table_new_from_list() failed or empty list of "
333 "targets!\n");
334 // Clear references to the any old data and let GTK know that it is no
335 // longer available.
336 EmptyNativeClipboardData(aWhichClipboard);
337 return NS_ERROR_FAILURE;
340 ClearCachedTargets(aWhichClipboard);
342 // Set getcallback and request to store data after an application exit
343 if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
344 clipboard_get_cb, clipboard_clear_cb, this)) {
345 // We managed to set-up the clipboard so update internal state
346 // We have to set it now because gtk_clipboard_set_with_data() calls
347 // clipboard_clear_cb() which reset our internal state
348 if (aWhichClipboard == kSelectionClipboard) {
349 mSelectionSequenceNumber++;
350 mSelectionTransferable = aTransferable;
351 } else {
352 mGlobalSequenceNumber++;
353 mGlobalTransferable = aTransferable;
354 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
357 rv = NS_OK;
358 } else {
359 LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
360 EmptyNativeClipboardData(aWhichClipboard);
361 rv = NS_ERROR_FAILURE;
364 gtk_target_table_free(gtkTargets, numTargets);
365 gtk_target_list_unref(list);
367 return rv;
370 mozilla::Result<int32_t, nsresult>
371 nsClipboard::GetNativeClipboardSequenceNumber(int32_t aWhichClipboard) {
372 MOZ_DIAGNOSTIC_ASSERT(
373 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
374 return aWhichClipboard == kSelectionClipboard ? mSelectionSequenceNumber
375 : mGlobalSequenceNumber;
378 static bool IsMIMEAtFlavourList(const nsTArray<nsCString>& aFlavourList,
379 const char* aMime) {
380 for (const auto& flavorStr : aFlavourList) {
381 if (flavorStr.Equals(aMime)) {
382 return true;
385 return false;
388 // When clipboard contains only images, X11/Gtk tries to convert them
389 // to text when we request text instead of just fail to provide the data.
390 // So if clipboard contains images only remove text MIME offer.
391 bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
392 nsTArray<nsCString>& aFlavors) {
393 LOGCLIP("nsClipboard::FilterImportedFlavors");
395 auto targets = mContext->GetTargets(aWhichClipboard);
396 if (!targets) {
397 LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
398 return true;
401 for (const auto& atom : targets.AsSpan()) {
402 GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
403 if (!atom_name) {
404 continue;
406 // Filter out system MIME types.
407 if (strcmp(atom_name.get(), "TARGETS") == 0 ||
408 strcmp(atom_name.get(), "TIMESTAMP") == 0 ||
409 strcmp(atom_name.get(), "SAVE_TARGETS") == 0 ||
410 strcmp(atom_name.get(), "MULTIPLE") == 0) {
411 continue;
413 // Filter out types which can't be converted to text.
414 if (strncmp(atom_name.get(), "image/", 6) == 0 ||
415 strncmp(atom_name.get(), "application/", 12) == 0 ||
416 strncmp(atom_name.get(), "audio/", 6) == 0 ||
417 strncmp(atom_name.get(), "video/", 6) == 0) {
418 continue;
420 // We have some other MIME type on clipboard which can be hopefully
421 // converted to text without any problem.
422 LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
423 return true;
426 // So make sure we offer only types we have at clipboard.
427 nsTArray<nsCString> clipboardFlavors;
428 for (const auto& atom : targets.AsSpan()) {
429 GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
430 if (!atom_name) {
431 continue;
433 if (IsMIMEAtFlavourList(aFlavors, atom_name.get())) {
434 clipboardFlavors.AppendElement(nsCString(atom_name.get()));
437 aFlavors.SwapElements(clipboardFlavors);
438 #ifdef MOZ_LOGGING
439 LOGCLIP(" X11: Flavors which match clipboard content:\n");
440 for (uint32_t i = 0; i < aFlavors.Length(); i++) {
441 LOGCLIP(" %s\n", aFlavors[i].get());
443 #endif
444 return true;
447 static nsresult GetTransferableFlavors(nsITransferable* aTransferable,
448 nsTArray<nsCString>& aFlavors) {
449 if (!aTransferable) {
450 return NS_ERROR_FAILURE;
452 // Get a list of flavors this transferable can import
453 nsresult rv = aTransferable->FlavorsTransferableCanImport(aFlavors);
454 if (NS_FAILED(rv)) {
455 LOGCLIP(" FlavorsTransferableCanImport falied!\n");
456 return rv;
458 #ifdef MOZ_LOGGING
459 LOGCLIP(" Flavors which can be imported:");
460 for (const auto& flavor : aFlavors) {
461 LOGCLIP(" %s", flavor.get());
463 #endif
464 return NS_OK;
467 static bool TransferableSetFile(nsITransferable* aTransferable,
468 const nsACString& aURIList) {
469 nsresult rv;
470 nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(aURIList);
471 if (!uris.IsEmpty()) {
472 nsCOMPtr<nsIURI> fileURI;
473 NS_NewURI(getter_AddRefs(fileURI), uris[0]);
474 if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
475 nsCOMPtr<nsIFile> file;
476 rv = fileURL->GetFile(getter_AddRefs(file));
477 if (NS_SUCCEEDED(rv)) {
478 aTransferable->SetTransferData(kFileMime, file);
479 LOGCLIP(" successfully set file to clipboard\n");
480 return true;
484 return false;
487 static bool TransferableSetHTML(nsITransferable* aTransferable,
488 Span<const char> aData) {
489 nsLiteralCString mimeType(kHTMLMime);
491 // Convert text/html into our text format
492 nsAutoCString charset;
493 if (!GetHTMLCharset(aData, charset)) {
494 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
495 LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
496 charset.AssignLiteral("utf-8");
499 LOGCLIP("TransferableSetHTML: HTML detected charset %s", charset.get());
500 // app which use "text/html" to copy&paste
501 // get the decoder
502 auto encoding = Encoding::ForLabelNoReplacement(charset);
503 if (!encoding) {
504 LOGCLIP("TransferableSetHTML: get unicode decoder error (charset: %s)",
505 charset.get());
506 return false;
509 // According to spec html UTF-16BE/LE should be switched to UTF-8
510 // https://html.spec.whatwg.org/#determining-the-character-encoding:utf-16-encoding-2
511 if (encoding == UTF_16LE_ENCODING || encoding == UTF_16BE_ENCODING) {
512 encoding = UTF_8_ENCODING;
515 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
516 // issues, but might confuse other users.
517 const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
518 if (aData.Length() >= prefixLen && nsDependentCSubstring(aData.To(prefixLen))
519 .EqualsLiteral(kHTMLMarkupPrefix)) {
520 aData = aData.From(prefixLen);
523 nsAutoString unicodeData;
524 auto [rv, enc] = encoding->Decode(AsBytes(aData), unicodeData);
525 #if MOZ_LOGGING
526 if (enc != UTF_8_ENCODING &&
527 MOZ_LOG_TEST(gClipboardLog, mozilla::LogLevel::Debug)) {
528 nsCString decoderName;
529 enc->Name(decoderName);
530 LOGCLIP("TransferableSetHTML: expected UTF-8 decoder but got %s",
531 decoderName.get());
533 #endif
534 if (NS_FAILED(rv)) {
535 LOGCLIP("TransferableSetHTML: failed to decode HTML");
536 return false;
538 SetTransferableData(aTransferable, mimeType,
539 (const char*)unicodeData.BeginReading(),
540 unicodeData.Length() * sizeof(char16_t));
541 return true;
544 NS_IMETHODIMP
545 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
546 int32_t aWhichClipboard) {
547 MOZ_DIAGNOSTIC_ASSERT(aTransferable);
548 MOZ_DIAGNOSTIC_ASSERT(
549 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
551 LOGCLIP("nsClipboard::GetNativeClipboardData (%s)\n",
552 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
554 // TODO: Ensure we don't re-enter here.
555 if (!mContext) {
556 return NS_ERROR_FAILURE;
559 nsTArray<nsCString> flavors;
560 nsresult rv = GetTransferableFlavors(aTransferable, flavors);
561 NS_ENSURE_SUCCESS(rv, rv);
563 // Filter out MIME types on X11 to prevent unwanted conversions,
564 // see Bug 1611407
565 if (widget::GdkIsX11Display() &&
566 !FilterImportedFlavors(aWhichClipboard, flavors)) {
567 LOGCLIP(" Missing suitable clipboard data, quit.");
568 return NS_OK;
571 for (uint32_t i = 0; i < flavors.Length(); i++) {
572 nsCString& flavorStr = flavors[i];
574 if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
575 flavorStr.EqualsLiteral(kJPGImageMime) ||
576 flavorStr.EqualsLiteral(kPNGImageMime) ||
577 flavorStr.EqualsLiteral(kGIFImageMime)) {
578 // Emulate support for image/jpg
579 if (flavorStr.EqualsLiteral(kJPGImageMime)) {
580 flavorStr.Assign(kJPEGImageMime);
583 LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr.get());
585 auto clipboardData =
586 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
587 if (!clipboardData) {
588 LOGCLIP(" %s type is missing\n", flavorStr.get());
589 continue;
592 nsCOMPtr<nsIInputStream> byteStream;
593 NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
594 NS_ASSIGNMENT_COPY);
595 aTransferable->SetTransferData(flavorStr.get(), byteStream);
596 LOGCLIP(" got %s MIME data\n", flavorStr.get());
597 return NS_OK;
600 // Special case text/plain since we can convert any
601 // string into text/plain
602 if (flavorStr.EqualsLiteral(kTextMime)) {
603 LOGCLIP(" Getting text %s MIME clipboard data\n", flavorStr.get());
605 auto clipboardData = mContext->GetClipboardText(aWhichClipboard);
606 if (!clipboardData) {
607 LOGCLIP(" failed to get text data\n");
608 // If the type was text/plain and we couldn't get
609 // text off the clipboard, run the next loop
610 // iteration.
611 continue;
614 // Convert utf-8 into our text format.
615 NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get());
616 SetTransferableData(aTransferable, flavorStr,
617 (const char*)ucs2string.BeginReading(),
618 ucs2string.Length() * 2);
620 LOGCLIP(" got text data, length %zd\n", ucs2string.Length());
621 return NS_OK;
624 if (flavorStr.EqualsLiteral(kFileMime)) {
625 LOGCLIP(" Getting %s file clipboard data\n", flavorStr.get());
627 auto clipboardData =
628 mContext->GetClipboardData(kURIListMime, aWhichClipboard);
629 if (!clipboardData) {
630 LOGCLIP(" text/uri-list type is missing\n");
631 continue;
634 nsDependentCSubstring fileName(clipboardData.AsSpan());
635 if (!TransferableSetFile(aTransferable, fileName)) {
636 continue;
638 return NS_OK;
641 LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr.get());
643 auto clipboardData =
644 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
646 #ifdef MOZ_LOGGING
647 if (!clipboardData) {
648 LOGCLIP(" %s type is missing\n", flavorStr.get());
650 #endif
652 if (clipboardData) {
653 LOGCLIP(" got %s mime type data.\n", flavorStr.get());
655 // Special case text/html since we can convert into UCS2
656 if (flavorStr.EqualsLiteral(kHTMLMime)) {
657 if (!TransferableSetHTML(aTransferable, clipboardData.AsSpan())) {
658 continue;
660 } else {
661 auto span = clipboardData.AsSpan();
662 SetTransferableData(aTransferable, flavorStr, span.data(),
663 span.Length());
665 return NS_OK;
669 LOGCLIP(" failed to get clipboard content.\n");
670 return NS_OK;
673 enum DataType {
674 DATATYPE_IMAGE,
675 DATATYPE_FILE,
676 DATATYPE_HTML,
677 DATATYPE_RAW,
680 struct DataCallbackHandler {
681 RefPtr<nsITransferable> mTransferable;
682 nsBaseClipboard::GetDataCallback mDataCallback;
683 nsCString mMimeType;
684 DataType mDataType;
686 explicit DataCallbackHandler(RefPtr<nsITransferable> aTransferable,
687 nsBaseClipboard::GetDataCallback&& aDataCallback,
688 const char* aMimeType,
689 DataType aDataType = DATATYPE_RAW)
690 : mTransferable(std::move(aTransferable)),
691 mDataCallback(std::move(aDataCallback)),
692 mMimeType(aMimeType),
693 mDataType(aDataType) {
694 MOZ_COUNT_CTOR(DataCallbackHandler);
695 LOGCLIP("DataCallbackHandler created [%p] MIME %s type %d", this,
696 mMimeType.get(), mDataType);
698 ~DataCallbackHandler() {
699 LOGCLIP("DataCallbackHandler deleted [%p]", this);
700 MOZ_COUNT_DTOR(DataCallbackHandler);
704 static void AsyncGetTextImpl(nsITransferable* aTransferable,
705 int32_t aWhichClipboard,
706 nsBaseClipboard::GetDataCallback&& aCallback) {
707 LOGCLIP("AsyncGetText() type '%s'",
708 aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
709 : "clipboard");
711 gtk_clipboard_request_text(
712 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
713 [](GtkClipboard* aClipboard, const gchar* aText, gpointer aData) -> void {
714 UniquePtr<DataCallbackHandler> ref(
715 static_cast<DataCallbackHandler*>(aData));
716 LOGCLIP("AsyncGetText async handler of [%p]", aData);
718 size_t dataLength = aText ? strlen(aText) : 0;
719 if (dataLength <= 0) {
720 LOGCLIP(" quit, text is not available");
721 ref->mDataCallback(NS_OK);
722 return;
725 // Convert utf-8 into our unicode format.
726 NS_ConvertUTF8toUTF16 utf16string(aText, dataLength);
727 nsLiteralCString flavor(kTextMime);
728 SetTransferableData(ref->mTransferable, flavor,
729 (const char*)utf16string.BeginReading(),
730 utf16string.Length() * 2);
731 LOGCLIP(" text is set, length = %d", (int)dataLength);
732 ref->mDataCallback(NS_OK);
734 new DataCallbackHandler(aTransferable, std::move(aCallback), kTextMime));
737 static void AsyncGetDataImpl(nsITransferable* aTransferable,
738 int32_t aWhichClipboard, const char* aMimeType,
739 DataType aDataType,
740 nsBaseClipboard::GetDataCallback&& aCallback) {
741 LOGCLIP("AsyncGetData() type '%s'",
742 aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
743 : "clipboard");
745 const char* gtkMIMEType = nullptr;
746 switch (aDataType) {
747 case DATATYPE_FILE:
748 // Don't ask Gtk for application/x-moz-file
749 gtkMIMEType = kURIListMime;
750 break;
751 case DATATYPE_IMAGE:
752 case DATATYPE_HTML:
753 case DATATYPE_RAW:
754 gtkMIMEType = aMimeType;
755 break;
758 gtk_clipboard_request_contents(
759 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
760 gdk_atom_intern(gtkMIMEType, FALSE),
761 [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
762 gpointer aData) -> void {
763 UniquePtr<DataCallbackHandler> ref(
764 static_cast<DataCallbackHandler*>(aData));
765 LOGCLIP("AsyncGetData async handler [%p] MIME %s type %d", aData,
766 ref->mMimeType.get(), ref->mDataType);
768 int dataLength = gtk_selection_data_get_length(aSelection);
769 if (dataLength <= 0) {
770 ref->mDataCallback(NS_OK);
771 return;
773 const char* data = (const char*)gtk_selection_data_get_data(aSelection);
774 if (!data) {
775 ref->mDataCallback(NS_OK);
776 return;
778 switch (ref->mDataType) {
779 case DATATYPE_IMAGE: {
780 LOGCLIP(" set image clipboard data");
781 nsCOMPtr<nsIInputStream> byteStream;
782 NS_NewByteInputStream(getter_AddRefs(byteStream),
783 Span(data, dataLength), NS_ASSIGNMENT_COPY);
784 ref->mTransferable->SetTransferData(ref->mMimeType.get(),
785 byteStream);
786 break;
788 case DATATYPE_FILE: {
789 LOGCLIP(" set file clipboard data");
790 nsDependentCSubstring file(data, dataLength);
791 TransferableSetFile(ref->mTransferable, file);
792 break;
794 case DATATYPE_HTML: {
795 LOGCLIP(" html clipboard data");
796 Span dataSpan(data, dataLength);
797 TransferableSetHTML(ref->mTransferable, dataSpan);
798 break;
800 case DATATYPE_RAW: {
801 LOGCLIP(" raw clipboard data %s", ref->mMimeType.get());
802 SetTransferableData(ref->mTransferable, ref->mMimeType, data,
803 dataLength);
804 break;
807 ref->mDataCallback(NS_OK);
809 new DataCallbackHandler(aTransferable, std::move(aCallback), aMimeType,
810 aDataType));
813 static void AsyncGetDataFlavor(nsITransferable* aTransferable,
814 int32_t aWhichClipboard, nsCString& aFlavorStr,
815 nsBaseClipboard::GetDataCallback&& aCallback) {
816 if (aFlavorStr.EqualsLiteral(kJPEGImageMime) ||
817 aFlavorStr.EqualsLiteral(kJPGImageMime) ||
818 aFlavorStr.EqualsLiteral(kPNGImageMime) ||
819 aFlavorStr.EqualsLiteral(kGIFImageMime)) {
820 // Emulate support for image/jpg
821 if (aFlavorStr.EqualsLiteral(kJPGImageMime)) {
822 aFlavorStr.Assign(kJPEGImageMime);
824 LOGCLIP(" Getting image %s MIME clipboard data", aFlavorStr.get());
825 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
826 DATATYPE_IMAGE, std::move(aCallback));
827 return;
829 // Special case text/plain since we can convert any
830 // string into text/plain
831 if (aFlavorStr.EqualsLiteral(kTextMime)) {
832 LOGCLIP(" Getting unicode clipboard data");
833 AsyncGetTextImpl(aTransferable, aWhichClipboard, std::move(aCallback));
834 return;
836 if (aFlavorStr.EqualsLiteral(kFileMime)) {
837 LOGCLIP(" Getting file clipboard data\n");
838 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
839 DATATYPE_FILE, std::move(aCallback));
840 return;
842 if (aFlavorStr.EqualsLiteral(kHTMLMime)) {
843 LOGCLIP(" Getting HTML clipboard data");
844 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
845 DATATYPE_HTML, std::move(aCallback));
846 return;
848 LOGCLIP(" Getting raw %s MIME clipboard data\n", aFlavorStr.get());
849 AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
850 DATATYPE_RAW, std::move(aCallback));
853 void nsClipboard::AsyncGetNativeClipboardData(nsITransferable* aTransferable,
854 int32_t aWhichClipboard,
855 GetDataCallback&& aCallback) {
856 MOZ_DIAGNOSTIC_ASSERT(aTransferable);
857 MOZ_DIAGNOSTIC_ASSERT(
858 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
860 LOGCLIP("nsClipboard::AsyncGetNativeClipboardData (%s)",
861 aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
862 : "clipboard");
863 nsTArray<nsCString> importedFlavors;
864 nsresult rv = GetTransferableFlavors(aTransferable, importedFlavors);
865 if (NS_FAILED(rv)) {
866 aCallback(rv);
867 return;
870 auto flavorsNum = importedFlavors.Length();
871 if (!flavorsNum) {
872 aCallback(NS_OK);
873 return;
875 #ifdef MOZ_LOGGING
876 if (flavorsNum > 1) {
877 LOGCLIP(" Only first MIME type (%s) will be imported from clipboard!",
878 importedFlavors[0].get());
880 #endif
882 // Filter out MIME types on X11 to prevent unwanted conversions,
883 // see Bug 1611407
884 if (widget::GdkIsX11Display()) {
885 AsyncHasNativeClipboardDataMatchingFlavors(
886 importedFlavors, aWhichClipboard,
887 [aWhichClipboard, transferable = nsCOMPtr{aTransferable},
888 callback = std::move(aCallback)](auto aResultOrError) mutable {
889 if (aResultOrError.isErr()) {
890 callback(aResultOrError.unwrapErr());
891 return;
894 nsTArray<nsCString> clipboardFlavors =
895 std::move(aResultOrError.unwrap());
896 if (!clipboardFlavors.Length()) {
897 LOGCLIP(" no flavors in clipboard, quit.");
898 callback(NS_OK);
899 return;
902 AsyncGetDataFlavor(transferable, aWhichClipboard, clipboardFlavors[0],
903 std::move(callback));
905 return;
908 // Read clipboard directly on Wayland
909 AsyncGetDataFlavor(aTransferable, aWhichClipboard, importedFlavors[0],
910 std::move(aCallback));
913 nsresult nsClipboard::EmptyNativeClipboardData(int32_t aWhichClipboard) {
914 MOZ_DIAGNOSTIC_ASSERT(
915 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
917 LOGCLIP("nsClipboard::EmptyNativeClipboardData (%s)\n",
918 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
919 if (aWhichClipboard == kSelectionClipboard) {
920 if (mSelectionTransferable) {
921 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
922 MOZ_ASSERT(!mSelectionTransferable);
924 } else {
925 if (mGlobalTransferable) {
926 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
927 MOZ_ASSERT(!mGlobalTransferable);
930 ClearCachedTargets(aWhichClipboard);
931 return NS_OK;
934 void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
935 if (aWhichClipboard == kSelectionClipboard) {
936 mSelectionSequenceNumber++;
937 mSelectionTransferable = nullptr;
938 } else {
939 mGlobalSequenceNumber++;
940 mGlobalTransferable = nullptr;
944 static bool FlavorMatchesTarget(const nsACString& aFlavor, GdkAtom aTarget) {
945 GUniquePtr<gchar> atom_name(gdk_atom_name(aTarget));
946 if (!atom_name) {
947 return false;
949 if (aFlavor.Equals(atom_name.get())) {
950 LOGCLIP(" has %s\n", atom_name.get());
951 return true;
953 // X clipboard supports image/jpeg, but we want to emulate support
954 // for image/jpg as well
955 if (aFlavor.EqualsLiteral(kJPGImageMime) &&
956 !strcmp(atom_name.get(), kJPEGImageMime)) {
957 LOGCLIP(" has image/jpg\n");
958 return true;
960 // application/x-moz-file should be treated like text/uri-list
961 if (aFlavor.EqualsLiteral(kFileMime) &&
962 !strcmp(atom_name.get(), kURIListMime)) {
963 LOGCLIP(" has text/uri-list treating as application/x-moz-file");
964 return true;
966 return false;
969 mozilla::Result<bool, nsresult>
970 nsClipboard::HasNativeClipboardDataMatchingFlavors(
971 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
972 MOZ_DIAGNOSTIC_ASSERT(
973 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
975 LOGCLIP("nsClipboard::HasNativeClipboardDataMatchingFlavors (%s)\n",
976 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
978 if (!mContext) {
979 return Err(NS_ERROR_FAILURE);
982 auto targets = mContext->GetTargets(aWhichClipboard);
983 if (!targets) {
984 LOGCLIP(" no targes at clipboard (null)\n");
985 return false;
988 #ifdef MOZ_LOGGING
989 if (LOGCLIP_ENABLED()) {
990 LOGCLIP(" Asking for content:\n");
991 for (auto& flavor : aFlavorList) {
992 LOGCLIP(" MIME %s\n", flavor.get());
994 LOGCLIP(" Clipboard content (target nums %zu):\n",
995 targets.AsSpan().Length());
996 for (const auto& target : targets.AsSpan()) {
997 GUniquePtr<gchar> atom_name(gdk_atom_name(target));
998 if (!atom_name) {
999 LOGCLIP(" failed to get MIME\n");
1000 continue;
1002 LOGCLIP(" MIME %s\n", atom_name.get());
1005 #endif
1007 // Walk through the provided types and try to match it to a
1008 // provided type.
1009 for (auto& flavor : aFlavorList) {
1010 // We special case text/plain here.
1011 if (flavor.EqualsLiteral(kTextMime) &&
1012 gtk_targets_include_text(targets.AsSpan().data(),
1013 targets.AsSpan().Length())) {
1014 LOGCLIP(" has kTextMime\n");
1015 return true;
1017 for (const auto& target : targets.AsSpan()) {
1018 if (FlavorMatchesTarget(flavor, target)) {
1019 return true;
1024 LOGCLIP(" no targes at clipboard (bad match)\n");
1025 return false;
1028 struct TragetCallbackHandler {
1029 TragetCallbackHandler(const nsTArray<nsCString>& aAcceptedFlavorList,
1030 nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback)
1031 : mAcceptedFlavorList(aAcceptedFlavorList.Clone()),
1032 mCallback(std::move(aCallback)) {
1033 LOGCLIP("TragetCallbackHandler(%p) created", this);
1035 ~TragetCallbackHandler() {
1036 LOGCLIP("TragetCallbackHandler(%p) deleted", this);
1038 nsTArray<nsCString> mAcceptedFlavorList;
1039 nsBaseClipboard::HasMatchingFlavorsCallback mCallback;
1042 void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
1043 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1044 nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback) {
1045 MOZ_DIAGNOSTIC_ASSERT(
1046 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
1048 LOGCLIP("nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors (%s)",
1049 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1051 gtk_clipboard_request_contents(
1052 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
1053 gdk_atom_intern("TARGETS", FALSE),
1054 [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
1055 gpointer aData) -> void {
1056 LOGCLIP("gtk_clipboard_request_contents async handler (%p)", aData);
1057 UniquePtr<TragetCallbackHandler> handler(
1058 static_cast<TragetCallbackHandler*>(aData));
1060 GdkAtom* targets = nullptr;
1061 gint targetsNum = 0;
1062 if (gtk_selection_data_get_length(aSelection) > 0) {
1063 gtk_selection_data_get_targets(aSelection, &targets, &targetsNum);
1065 nsTArray<nsCString> results;
1066 if (targetsNum) {
1067 for (auto& flavor : handler->mAcceptedFlavorList) {
1068 LOGCLIP(" looking for %s", flavor.get());
1069 if (flavor.EqualsLiteral(kTextMime) &&
1070 gtk_targets_include_text(targets, targetsNum)) {
1071 results.AppendElement(flavor);
1072 LOGCLIP(" has kTextMime\n");
1073 continue;
1075 for (int i = 0; i < targetsNum; i++) {
1076 if (FlavorMatchesTarget(flavor, targets[i])) {
1077 results.AppendElement(flavor);
1082 handler->mCallback(std::move(results));
1084 new TragetCallbackHandler(aFlavorList, std::move(aCallback)));
1087 nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
1088 nsITransferable* retval;
1090 if (aWhichClipboard == kSelectionClipboard)
1091 retval = mSelectionTransferable.get();
1092 else
1093 retval = mGlobalTransferable.get();
1095 return retval;
1098 void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
1099 GtkSelectionData* aSelectionData) {
1100 // Someone has asked us to hand them something. The first thing
1101 // that we want to do is see if that something includes text. If
1102 // it does, try to give it text/plain after converting it to
1103 // utf-8.
1105 int32_t whichClipboard;
1107 // which clipboard?
1108 GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
1109 if (selection == GDK_SELECTION_PRIMARY)
1110 whichClipboard = kSelectionClipboard;
1111 else if (selection == GDK_SELECTION_CLIPBOARD)
1112 whichClipboard = kGlobalClipboard;
1113 else
1114 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
1116 LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
1117 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1119 nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
1120 if (!trans) {
1121 // We have nothing to serve
1122 LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
1123 whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard");
1124 return;
1127 nsresult rv;
1128 nsCOMPtr<nsISupports> item;
1130 GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
1131 LOGCLIP(" selection target %s\n",
1132 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1134 // Check to see if the selection data is some text type.
1135 if (gtk_targets_include_text(&selectionTarget, 1)) {
1136 LOGCLIP(" providing text/plain data\n");
1137 // Try to convert our internal type into a text string. Get
1138 // the transferable for this clipboard and try to get the
1139 // text/plain type for it.
1140 rv = trans->GetTransferData("text/plain", getter_AddRefs(item));
1141 if (NS_FAILED(rv) || !item) {
1142 LOGCLIP(" GetTransferData() failed to get text/plain!\n");
1143 return;
1146 nsCOMPtr<nsISupportsString> wideString;
1147 wideString = do_QueryInterface(item);
1148 if (!wideString) return;
1150 nsAutoString ucs2string;
1151 wideString->GetData(ucs2string);
1152 NS_ConvertUTF16toUTF8 utf8string(ucs2string);
1154 LOGCLIP(" sent %zd bytes of utf-8 data\n", utf8string.Length());
1155 if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
1156 LOGCLIP(
1157 " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
1158 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
1159 // in some versions of GTK.
1160 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
1161 reinterpret_cast<const guchar*>(utf8string.get()),
1162 utf8string.Length());
1163 } else {
1164 gtk_selection_data_set_text(aSelectionData, utf8string.get(),
1165 utf8string.Length());
1167 return;
1170 // Check to see if the selection data is an image type
1171 if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
1172 LOGCLIP(" providing image data\n");
1173 // Look through our transfer data for the image
1174 static const char* const imageMimeTypes[] = {kNativeImageMime,
1175 kPNGImageMime, kJPEGImageMime,
1176 kJPGImageMime, kGIFImageMime};
1177 nsCOMPtr<nsISupports> imageItem;
1178 nsCOMPtr<imgIContainer> image;
1179 for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
1180 rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
1181 if (NS_FAILED(rv)) {
1182 LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes[i]);
1183 continue;
1186 image = do_QueryInterface(imageItem);
1187 if (image) {
1188 LOGCLIP(" %s is available at GetTransferData()\n",
1189 imageMimeTypes[i]);
1190 break;
1194 if (!image) { // Not getting an image for an image mime type!?
1195 LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
1196 return;
1199 RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
1200 if (!pixbuf) {
1201 LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
1202 return;
1205 LOGCLIP(" Setting pixbuf image data as %s\n",
1206 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1207 gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
1208 return;
1211 if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
1212 LOGCLIP(" providing %s data\n", kHTMLMime);
1213 rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
1214 if (NS_FAILED(rv) || !item) {
1215 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime);
1216 return;
1219 nsCOMPtr<nsISupportsString> wideString;
1220 wideString = do_QueryInterface(item);
1221 if (!wideString) {
1222 LOGCLIP(" failed to get wideString interface!");
1223 return;
1226 nsAutoString ucs2string;
1227 wideString->GetData(ucs2string);
1229 nsAutoCString html;
1230 // Add the prefix so the encoding is correctly detected.
1231 html.AppendLiteral(kHTMLMarkupPrefix);
1232 AppendUTF16toUTF8(ucs2string, html);
1234 LOGCLIP(" Setting %zd bytes of %s data\n", html.Length(),
1235 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1236 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
1237 (const guchar*)html.get(), html.Length());
1238 return;
1241 // We put kFileMime onto the clipboard as kURIListMime.
1242 if (selectionTarget == gdk_atom_intern(kURIListMime, FALSE)) {
1243 LOGCLIP(" providing %s data\n", kURIListMime);
1244 rv = trans->GetTransferData(kFileMime, getter_AddRefs(item));
1245 if (NS_FAILED(rv) || !item) {
1246 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kFileMime);
1247 return;
1250 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
1251 if (!file) {
1252 LOGCLIP(" failed to get nsIFile interface!");
1253 return;
1256 nsCOMPtr<nsIURI> fileURI;
1257 rv = NS_NewFileURI(getter_AddRefs(fileURI), file);
1258 if (NS_FAILED(rv)) {
1259 LOGCLIP(" failed to get fileURI\n");
1260 return;
1263 nsAutoCString uri;
1264 if (NS_FAILED(fileURI->GetSpec(uri))) {
1265 LOGCLIP(" failed to get fileURI spec\n");
1266 return;
1269 LOGCLIP(" Setting %zd bytes of data\n", uri.Length());
1270 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
1271 (const guchar*)uri.get(), uri.Length());
1272 return;
1275 LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
1276 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
1278 // Try to match up the selection data target to something our
1279 // transferable provides.
1280 GUniquePtr<gchar> target_name(gdk_atom_name(selectionTarget));
1281 if (!target_name) {
1282 LOGCLIP(" Failed to get target name!\n");
1283 return;
1286 rv = trans->GetTransferData(target_name.get(), getter_AddRefs(item));
1287 // nothing found?
1288 if (NS_FAILED(rv) || !item) {
1289 LOGCLIP(" Failed to get anything from GetTransferData()!\n");
1290 return;
1293 void* primitive_data = nullptr;
1294 uint32_t dataLen = 0;
1295 nsPrimitiveHelpers::CreateDataFromPrimitive(
1296 nsDependentCString(target_name.get()), item, &primitive_data, &dataLen);
1297 if (!primitive_data) {
1298 LOGCLIP(" Failed to get primitive data!\n");
1299 return;
1302 LOGCLIP(" Setting %s as a primitive data type, %d bytes\n",
1303 target_name.get(), dataLen);
1304 gtk_selection_data_set(aSelectionData, selectionTarget,
1305 8, /* 8 bits in a unit */
1306 (const guchar*)primitive_data, dataLen);
1307 free(primitive_data);
1310 void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard) {
1311 if (aWhichClipboard == kSelectionClipboard) {
1312 nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr);
1313 } else {
1314 nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
1318 void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
1319 int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
1320 if (whichClipboard < 0) {
1321 return;
1323 LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
1324 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1325 ClearCachedTargets(whichClipboard);
1326 ClearTransferable(whichClipboard);
1327 ClearClipboardCache(whichClipboard);
1330 void nsClipboard::OwnerChangedEvent(GtkClipboard* aGtkClipboard,
1331 GdkEventOwnerChange* aEvent) {
1332 int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
1333 if (whichClipboard < 0) {
1334 return;
1336 LOGCLIP("nsClipboard::OwnerChangedEvent (%s)\n",
1337 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
1338 GtkWidget* gtkWidget = [aEvent]() -> GtkWidget* {
1339 if (!aEvent->owner) {
1340 return nullptr;
1342 gpointer user_data = nullptr;
1343 gdk_window_get_user_data(aEvent->owner, &user_data);
1344 return GTK_WIDGET(user_data);
1345 }();
1346 // If we can get GtkWidget from the current clipboard owner, this
1347 // owner-changed event must be triggered by ourself via calling
1348 // gtk_clipboard_set_with_data, the sequence number should already be handled.
1349 if (!gtkWidget) {
1350 if (whichClipboard == kSelectionClipboard) {
1351 mSelectionSequenceNumber++;
1352 } else {
1353 mGlobalSequenceNumber++;
1357 ClearCachedTargets(whichClipboard);
1360 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
1361 GtkSelectionData* aSelectionData, guint info,
1362 gpointer user_data) {
1363 LOGCLIP("clipboard_get_cb() callback\n");
1364 nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
1365 clipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
1368 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
1369 LOGCLIP("clipboard_clear_cb() callback\n");
1370 nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
1371 clipboard->SelectionClearEvent(aGtkClipboard);
1374 void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard,
1375 GdkEventOwnerChange* aEvent,
1376 gpointer aUserData) {
1377 LOGCLIP("clipboard_owner_change_cb() callback\n");
1378 nsClipboard* clipboard = static_cast<nsClipboard*>(aUserData);
1379 clipboard->OwnerChangedEvent(aGtkClipboard, aEvent);
1383 * This function extracts the encoding label from the subset of HTML internal
1384 * encoding declaration syntax that uses the old long form with double quotes
1385 * and without spaces around the equals sign between the "content" attribute
1386 * name and the attribute value.
1388 * This was added for the sake of an ancient version of StarOffice
1389 * in the pre-UTF-8 era in bug 123389. It is unclear if supporting
1390 * non-UTF-8 encodings is still necessary and if this function
1391 * still needs to exist.
1393 * As of December 2022, both Gecko and LibreOffice emit an UTF-8
1394 * declaration that this function successfully extracts "UTF-8" from,
1395 * but that's also the default that we fall back on if this function
1396 * fails to extract a label.
1398 bool GetHTMLCharset(Span<const char> aData, nsCString& aFoundCharset) {
1399 // Assume ASCII first to find "charset" info
1400 const nsDependentCSubstring htmlStr(aData);
1401 nsACString::const_iterator start, end;
1402 htmlStr.BeginReading(start);
1403 htmlStr.EndReading(end);
1404 nsACString::const_iterator valueStart(start), valueEnd(start);
1406 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
1407 start = end;
1408 htmlStr.EndReading(end);
1410 if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
1411 valueStart = end;
1412 start = end;
1413 htmlStr.EndReading(end);
1415 if (FindCharInReadable('"', start, end)) valueEnd = start;
1418 // find "charset" in HTML
1419 if (valueStart != valueEnd) {
1420 aFoundCharset = Substring(valueStart, valueEnd);
1421 ToUpperCase(aFoundCharset);
1422 return true;
1424 return false;