summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsClipboard.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /widget/gtk/nsClipboard.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/gtk/nsClipboard.cpp')
-rw-r--r--widget/gtk/nsClipboard.cpp1425
1 files changed, 1425 insertions, 0 deletions
diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp
new file mode 100644
index 0000000000..571b43f1cc
--- /dev/null
+++ b/widget/gtk/nsClipboard.cpp
@@ -0,0 +1,1425 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#if defined(MOZ_X11)
+# include "nsClipboardX11.h"
+#endif
+#if defined(MOZ_WAYLAND)
+# include "nsClipboardWayland.h"
+# include "nsWaylandDisplay.h"
+#endif
+#include "nsGtkUtils.h"
+#include "nsIURI.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "HeadlessClipboard.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "nsIFileURL.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/TimeStamp.h"
+#include "GRefPtr.h"
+#include "WidgetUtilsGtk.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+#if defined(MOZ_X11)
+# include <gtk/gtkx.h>
+#endif
+
+#include "mozilla/Encoding.h"
+
+using namespace mozilla;
+
+// Idle timeout for receiving selection and property notify events (microsec)
+// Right now it's set to 1 sec.
+const int kClipboardTimeout = 1000000;
+
+// Defines how many event loop iterations will be done without sleep.
+// We ususally get data in first 2-3 iterations unless some large object
+// (an image for instance) is transferred through clipboard.
+const int kClipboardFastIterationNum = 3;
+
+// We add this prefix to HTML markup, so that GetHTMLCharset can correctly
+// detect the HTML as UTF-8 encoded.
+static const char kHTMLMarkupPrefix[] =
+ R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)";
+
+static const char kURIListMime[] = "text/uri-list";
+
+ClipboardTargets nsRetrievalContext::sClipboardTargets;
+ClipboardTargets nsRetrievalContext::sPrimaryTargets;
+
+// Callback when someone asks us for the data
+static void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data);
+
+// Callback when someone asks us to clear a clipboard
+static void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
+
+// Callback when owner of clipboard data is changed
+static void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard,
+ GdkEventOwnerChange* aEvent,
+ gpointer aUserData);
+
+static bool GetHTMLCharset(Span<const char> aData, nsCString& str);
+
+static void SetTransferableData(nsITransferable* aTransferable,
+ const nsACString& aFlavor,
+ const char* aClipboardData,
+ uint32_t aClipboardDataLength) {
+ LOGCLIP("SetTransferableData MIME %s\n", PromiseFlatCString(aFlavor).get());
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
+ aTransferable->SetTransferData(PromiseFlatCString(aFlavor).get(), wrapper);
+}
+
+ClipboardTargets ClipboardTargets::Clone() {
+ ClipboardTargets ret;
+ ret.mCount = mCount;
+ if (mCount) {
+ ret.mTargets.reset(
+ reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * mCount)));
+ memcpy(ret.mTargets.get(), mTargets.get(), sizeof(GdkAtom) * mCount);
+ }
+ return ret;
+}
+
+void ClipboardTargets::Set(ClipboardTargets aTargets) {
+ mCount = aTargets.mCount;
+ mTargets = std::move(aTargets.mTargets);
+}
+
+void ClipboardData::SetData(Span<const uint8_t> aData) {
+ mData = nullptr;
+ mLength = aData.Length();
+ if (mLength) {
+ mData.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength)));
+ memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
+ }
+}
+
+void ClipboardData::SetText(Span<const char> aData) {
+ mData = nullptr;
+ mLength = aData.Length();
+ if (mLength) {
+ mData.reset(
+ reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength + 1))));
+ memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
+ mData.get()[mLength] = '\0';
+ }
+}
+
+void ClipboardData::SetTargets(ClipboardTargets aTargets) {
+ mLength = aTargets.mCount;
+ mData.reset(reinterpret_cast<char*>(aTargets.mTargets.release()));
+}
+
+ClipboardTargets ClipboardData::ExtractTargets() {
+ GUniquePtr<GdkAtom> targets(reinterpret_cast<GdkAtom*>(mData.release()));
+ uint32_t length = std::exchange(mLength, 0);
+ return ClipboardTargets{std::move(targets), length};
+}
+
+GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
+ if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
+ return GDK_SELECTION_CLIPBOARD;
+
+ return GDK_SELECTION_PRIMARY;
+}
+
+int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
+ if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
+ return nsClipboard::kSelectionClipboard;
+ else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
+ return nsClipboard::kGlobalClipboard;
+
+ return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+}
+
+void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard* aClipboard,
+ GdkEvent* aEvent,
+ gpointer data) {
+ LOGCLIP("nsRetrievalContext::ClearCachedTargetsClipboard()");
+ sClipboardTargets.Clear();
+}
+
+void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard* aClipboard,
+ GdkEvent* aEvent,
+ gpointer data) {
+ LOGCLIP("nsRetrievalContext::ClearCachedTargetsPrimary()");
+ sPrimaryTargets.Clear();
+}
+
+ClipboardTargets nsRetrievalContext::GetTargets(int32_t aWhichClipboard) {
+ LOGCLIP("nsRetrievalContext::GetTargets(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+ ClipboardTargets& storedTargets =
+ (aWhichClipboard == nsClipboard::kSelectionClipboard) ? sPrimaryTargets
+ : sClipboardTargets;
+ if (!storedTargets) {
+ LOGCLIP(" getting targets from system");
+ storedTargets.Set(GetTargetsImpl(aWhichClipboard));
+ } else {
+ LOGCLIP(" using cached targets");
+ }
+ return storedTargets.Clone();
+}
+
+nsRetrievalContext::~nsRetrievalContext() {
+ sClipboardTargets.Clear();
+ sPrimaryTargets.Clear();
+}
+
+nsClipboard::nsClipboard()
+ : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
+#ifdef MOZ_WAYLAND
+ widget::GdkIsWaylandDisplay()
+ ? widget::WaylandDisplayGet()->IsPrimarySelectionEnabled()
+ : true,
+#else
+ true, /* supportsSelectionClipboard */
+#endif
+ false /* supportsFindClipboard */,
+ false /* supportsSelectionCache */)) {
+ g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), "owner-change",
+ G_CALLBACK(clipboard_owner_change_cb), this);
+ g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change",
+ G_CALLBACK(clipboard_owner_change_cb), this);
+}
+
+nsClipboard::~nsClipboard() {
+ g_signal_handlers_disconnect_by_func(
+ gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
+ FuncToGpointer(clipboard_owner_change_cb), this);
+ g_signal_handlers_disconnect_by_func(
+ gtk_clipboard_get(GDK_SELECTION_PRIMARY),
+ FuncToGpointer(clipboard_owner_change_cb), this);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
+
+nsresult nsClipboard::Init(void) {
+#if defined(MOZ_X11)
+ if (widget::GdkIsX11Display()) {
+ mContext = new nsRetrievalContextX11();
+ }
+#endif
+#if defined(MOZ_WAYLAND)
+ if (widget::GdkIsWaylandDisplay()) {
+ mContext = new nsRetrievalContextWayland();
+ }
+#endif
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "xpcom-shutdown", false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // Save global clipboard content to CLIPBOARD_MANAGER.
+ // gtk_clipboard_store() can run an event loop, so call from a dedicated
+ // runnable.
+ return SchedulerGroup::Dispatch(
+ NS_NewRunnableFunction("gtk_clipboard_store()", []() {
+ LOGCLIP("nsClipboard storing clipboard content\n");
+ gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }));
+}
+
+NS_IMETHODIMP
+nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(aTransferable);
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ // See if we can short cut
+ if ((aWhichClipboard == kGlobalClipboard &&
+ aTransferable == mGlobalTransferable.get()) ||
+ (aWhichClipboard == kSelectionClipboard &&
+ aTransferable == mSelectionTransferable.get())) {
+ return NS_OK;
+ }
+
+ LOGCLIP("nsClipboard::SetNativeClipboardData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ // List of suported targets
+ GtkTargetList* list = gtk_target_list_new(nullptr, 0);
+
+ // Get the types of supported flavors
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" FlavorsTransferableCanExport failed!\n");
+ // Fall through. |gtkTargets| will be null below.
+ }
+
+ // Add all the flavors to this widget's supported type.
+ bool imagesAdded = false;
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+ LOGCLIP(" processing target %s\n", flavorStr.get());
+
+ // Special case text/plain since we can handle all of the string types.
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ LOGCLIP(" adding TEXT targets\n");
+ gtk_target_list_add_text_targets(list, 0);
+ continue;
+ }
+
+ if (nsContentUtils::IsFlavorImage(flavorStr)) {
+ // Don't bother adding image targets twice
+ if (!imagesAdded) {
+ // accept any writable image type
+ LOGCLIP(" adding IMAGE targets\n");
+ gtk_target_list_add_image_targets(list, 0, TRUE);
+ imagesAdded = true;
+ }
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ LOGCLIP(" adding text/uri-list target\n");
+ GdkAtom atom = gdk_atom_intern(kURIListMime, FALSE);
+ gtk_target_list_add(list, atom, 0, 0);
+ continue;
+ }
+
+ // Add this to our list of valid targets
+ LOGCLIP(" adding OTHER target %s\n", flavorStr.get());
+ GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
+ gtk_target_list_add(list, atom, 0, 0);
+ }
+
+ // Get GTK clipboard (CLIPBOARD or PRIMARY)
+ GtkClipboard* gtkClipboard =
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ gint numTargets = 0;
+ GtkTargetEntry* gtkTargets =
+ gtk_target_table_new_from_list(list, &numTargets);
+ if (!gtkTargets || numTargets == 0) {
+ LOGCLIP(
+ " gtk_target_table_new_from_list() failed or empty list of "
+ "targets!\n");
+ // Clear references to the any old data and let GTK know that it is no
+ // longer available.
+ EmptyNativeClipboardData(aWhichClipboard);
+ return NS_ERROR_FAILURE;
+ }
+
+ ClearCachedTargets(aWhichClipboard);
+
+ // Set getcallback and request to store data after an application exit
+ if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
+ clipboard_get_cb, clipboard_clear_cb, this)) {
+ // We managed to set-up the clipboard so update internal state
+ // We have to set it now because gtk_clipboard_set_with_data() calls
+ // clipboard_clear_cb() which reset our internal state
+ if (aWhichClipboard == kSelectionClipboard) {
+ mSelectionSequenceNumber++;
+ mSelectionTransferable = aTransferable;
+ } else {
+ mGlobalSequenceNumber++;
+ mGlobalTransferable = aTransferable;
+ gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
+ }
+
+ rv = NS_OK;
+ } else {
+ LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
+ EmptyNativeClipboardData(aWhichClipboard);
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_table_free(gtkTargets, numTargets);
+ gtk_target_list_unref(list);
+
+ return rv;
+}
+
+mozilla::Result<int32_t, nsresult>
+nsClipboard::GetNativeClipboardSequenceNumber(int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+ return aWhichClipboard == kSelectionClipboard ? mSelectionSequenceNumber
+ : mGlobalSequenceNumber;
+}
+
+static bool IsMIMEAtFlavourList(const nsTArray<nsCString>& aFlavourList,
+ const char* aMime) {
+ for (const auto& flavorStr : aFlavourList) {
+ if (flavorStr.Equals(aMime)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// When clipboard contains only images, X11/Gtk tries to convert them
+// to text when we request text instead of just fail to provide the data.
+// So if clipboard contains images only remove text MIME offer.
+bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
+ nsTArray<nsCString>& aFlavors) {
+ LOGCLIP("nsClipboard::FilterImportedFlavors");
+
+ auto targets = mContext->GetTargets(aWhichClipboard);
+ if (!targets) {
+ LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
+ return true;
+ }
+
+ for (const auto& atom : targets.AsSpan()) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
+ if (!atom_name) {
+ continue;
+ }
+ // Filter out system MIME types.
+ if (strcmp(atom_name.get(), "TARGETS") == 0 ||
+ strcmp(atom_name.get(), "TIMESTAMP") == 0 ||
+ strcmp(atom_name.get(), "SAVE_TARGETS") == 0 ||
+ strcmp(atom_name.get(), "MULTIPLE") == 0) {
+ continue;
+ }
+ // Filter out types which can't be converted to text.
+ if (strncmp(atom_name.get(), "image/", 6) == 0 ||
+ strncmp(atom_name.get(), "application/", 12) == 0 ||
+ strncmp(atom_name.get(), "audio/", 6) == 0 ||
+ strncmp(atom_name.get(), "video/", 6) == 0) {
+ continue;
+ }
+ // We have some other MIME type on clipboard which can be hopefully
+ // converted to text without any problem.
+ LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
+ return true;
+ }
+
+ // So make sure we offer only types we have at clipboard.
+ nsTArray<nsCString> clipboardFlavors;
+ for (const auto& atom : targets.AsSpan()) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
+ if (!atom_name) {
+ continue;
+ }
+ if (IsMIMEAtFlavourList(aFlavors, atom_name.get())) {
+ clipboardFlavors.AppendElement(nsCString(atom_name.get()));
+ }
+ }
+ aFlavors.SwapElements(clipboardFlavors);
+#ifdef MOZ_LOGGING
+ LOGCLIP(" X11: Flavors which match clipboard content:\n");
+ for (uint32_t i = 0; i < aFlavors.Length(); i++) {
+ LOGCLIP(" %s\n", aFlavors[i].get());
+ }
+#endif
+ return true;
+}
+
+static nsresult GetTransferableFlavors(nsITransferable* aTransferable,
+ nsTArray<nsCString>& aFlavors) {
+ if (!aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+ // Get a list of flavors this transferable can import
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(aFlavors);
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" FlavorsTransferableCanImport falied!\n");
+ return rv;
+ }
+#ifdef MOZ_LOGGING
+ LOGCLIP(" Flavors which can be imported:");
+ for (const auto& flavor : aFlavors) {
+ LOGCLIP(" %s", flavor.get());
+ }
+#endif
+ return NS_OK;
+}
+
+static bool TransferableSetFile(nsITransferable* aTransferable,
+ const nsACString& aURIList) {
+ nsresult rv;
+ nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(aURIList);
+ if (!uris.IsEmpty()) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewURI(getter_AddRefs(fileURI), uris[0]);
+ if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(kFileMime, file);
+ LOGCLIP(" successfully set file to clipboard\n");
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool TransferableSetHTML(nsITransferable* aTransferable,
+ Span<const char> aData) {
+ nsLiteralCString mimeType(kHTMLMime);
+
+ // Convert text/html into our text format
+ nsAutoCString charset;
+ if (!GetHTMLCharset(aData, charset)) {
+ // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
+ LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
+ charset.AssignLiteral("utf-8");
+ }
+
+ LOGCLIP("TransferableSetHTML: HTML detected charset %s", charset.get());
+ // app which use "text/html" to copy&paste
+ // get the decoder
+ auto encoding = Encoding::ForLabelNoReplacement(charset);
+ if (!encoding) {
+ LOGCLIP("TransferableSetHTML: get unicode decoder error (charset: %s)",
+ charset.get());
+ return false;
+ }
+
+ // According to spec html UTF-16BE/LE should be switched to UTF-8
+ // https://html.spec.whatwg.org/#determining-the-character-encoding:utf-16-encoding-2
+ if (encoding == UTF_16LE_ENCODING || encoding == UTF_16BE_ENCODING) {
+ encoding = UTF_8_ENCODING;
+ }
+
+ // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
+ // issues, but might confuse other users.
+ const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
+ if (aData.Length() >= prefixLen && nsDependentCSubstring(aData.To(prefixLen))
+ .EqualsLiteral(kHTMLMarkupPrefix)) {
+ aData = aData.From(prefixLen);
+ }
+
+ nsAutoString unicodeData;
+ auto [rv, enc] = encoding->Decode(AsBytes(aData), unicodeData);
+#if MOZ_LOGGING
+ if (enc != UTF_8_ENCODING &&
+ MOZ_LOG_TEST(gClipboardLog, mozilla::LogLevel::Debug)) {
+ nsCString decoderName;
+ enc->Name(decoderName);
+ LOGCLIP("TransferableSetHTML: expected UTF-8 decoder but got %s",
+ decoderName.get());
+ }
+#endif
+ if (NS_FAILED(rv)) {
+ LOGCLIP("TransferableSetHTML: failed to decode HTML");
+ return false;
+ }
+ SetTransferableData(aTransferable, mimeType,
+ (const char*)unicodeData.BeginReading(),
+ unicodeData.Length() * sizeof(char16_t));
+ return true;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(aTransferable);
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::GetNativeClipboardData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ // TODO: Ensure we don't re-enter here.
+ if (!mContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<nsCString> flavors;
+ nsresult rv = GetTransferableFlavors(aTransferable, flavors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Filter out MIME types on X11 to prevent unwanted conversions,
+ // see Bug 1611407
+ if (widget::GdkIsX11Display() &&
+ !FilterImportedFlavors(aWhichClipboard, flavors)) {
+ LOGCLIP(" Missing suitable clipboard data, quit.");
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (flavorStr.EqualsLiteral(kJPGImageMime)) {
+ flavorStr.Assign(kJPEGImageMime);
+ }
+
+ LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr.get());
+
+ auto clipboardData =
+ mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP(" %s type is missing\n", flavorStr.get());
+ continue;
+ }
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr.get(), byteStream);
+ LOGCLIP(" got %s MIME data\n", flavorStr.get());
+ return NS_OK;
+ }
+
+ // Special case text/plain since we can convert any
+ // string into text/plain
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ LOGCLIP(" Getting text %s MIME clipboard data\n", flavorStr.get());
+
+ auto clipboardData = mContext->GetClipboardText(aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP(" failed to get text data\n");
+ // If the type was text/plain and we couldn't get
+ // text off the clipboard, run the next loop
+ // iteration.
+ continue;
+ }
+
+ // Convert utf-8 into our text format.
+ NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get());
+ SetTransferableData(aTransferable, flavorStr,
+ (const char*)ucs2string.BeginReading(),
+ ucs2string.Length() * 2);
+
+ LOGCLIP(" got text data, length %zd\n", ucs2string.Length());
+ return NS_OK;
+ }
+
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ LOGCLIP(" Getting %s file clipboard data\n", flavorStr.get());
+
+ auto clipboardData =
+ mContext->GetClipboardData(kURIListMime, aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP(" text/uri-list type is missing\n");
+ continue;
+ }
+
+ nsDependentCSubstring fileName(clipboardData.AsSpan());
+ if (!TransferableSetFile(aTransferable, fileName)) {
+ continue;
+ }
+ return NS_OK;
+ }
+
+ LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr.get());
+
+ auto clipboardData =
+ mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
+
+#ifdef MOZ_LOGGING
+ if (!clipboardData) {
+ LOGCLIP(" %s type is missing\n", flavorStr.get());
+ }
+#endif
+
+ if (clipboardData) {
+ LOGCLIP(" got %s mime type data.\n", flavorStr.get());
+
+ // Special case text/html since we can convert into UCS2
+ if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ if (!TransferableSetHTML(aTransferable, clipboardData.AsSpan())) {
+ continue;
+ }
+ } else {
+ auto span = clipboardData.AsSpan();
+ SetTransferableData(aTransferable, flavorStr, span.data(),
+ span.Length());
+ }
+ return NS_OK;
+ }
+ }
+
+ LOGCLIP(" failed to get clipboard content.\n");
+ return NS_OK;
+}
+
+enum DataType {
+ DATATYPE_IMAGE,
+ DATATYPE_FILE,
+ DATATYPE_HTML,
+ DATATYPE_RAW,
+};
+
+struct DataCallbackHandler {
+ RefPtr<nsITransferable> mTransferable;
+ nsBaseClipboard::GetDataCallback mDataCallback;
+ nsCString mMimeType;
+ DataType mDataType;
+
+ explicit DataCallbackHandler(RefPtr<nsITransferable> aTransferable,
+ nsBaseClipboard::GetDataCallback&& aDataCallback,
+ const char* aMimeType,
+ DataType aDataType = DATATYPE_RAW)
+ : mTransferable(std::move(aTransferable)),
+ mDataCallback(std::move(aDataCallback)),
+ mMimeType(aMimeType),
+ mDataType(aDataType) {
+ MOZ_COUNT_CTOR(DataCallbackHandler);
+ LOGCLIP("DataCallbackHandler created [%p] MIME %s type %d", this,
+ mMimeType.get(), mDataType);
+ }
+ ~DataCallbackHandler() {
+ LOGCLIP("DataCallbackHandler deleted [%p]", this);
+ MOZ_COUNT_DTOR(DataCallbackHandler);
+ }
+};
+
+static void AsyncGetTextImpl(nsITransferable* aTransferable,
+ int32_t aWhichClipboard,
+ nsBaseClipboard::GetDataCallback&& aCallback) {
+ LOGCLIP("AsyncGetText() type '%s'",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+
+ gtk_clipboard_request_text(
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
+ [](GtkClipboard* aClipboard, const gchar* aText, gpointer aData) -> void {
+ UniquePtr<DataCallbackHandler> ref(
+ static_cast<DataCallbackHandler*>(aData));
+ LOGCLIP("AsyncGetText async handler of [%p]", aData);
+
+ size_t dataLength = aText ? strlen(aText) : 0;
+ if (dataLength <= 0) {
+ LOGCLIP(" quit, text is not available");
+ ref->mDataCallback(NS_OK);
+ return;
+ }
+
+ // Convert utf-8 into our unicode format.
+ NS_ConvertUTF8toUTF16 utf16string(aText, dataLength);
+ nsLiteralCString flavor(kTextMime);
+ SetTransferableData(ref->mTransferable, flavor,
+ (const char*)utf16string.BeginReading(),
+ utf16string.Length() * 2);
+ LOGCLIP(" text is set, length = %d", (int)dataLength);
+ ref->mDataCallback(NS_OK);
+ },
+ new DataCallbackHandler(aTransferable, std::move(aCallback), kTextMime));
+}
+
+static void AsyncGetDataImpl(nsITransferable* aTransferable,
+ int32_t aWhichClipboard, const char* aMimeType,
+ DataType aDataType,
+ nsBaseClipboard::GetDataCallback&& aCallback) {
+ LOGCLIP("AsyncGetData() type '%s'",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+
+ const char* gtkMIMEType = nullptr;
+ switch (aDataType) {
+ case DATATYPE_FILE:
+ // Don't ask Gtk for application/x-moz-file
+ gtkMIMEType = kURIListMime;
+ break;
+ case DATATYPE_IMAGE:
+ case DATATYPE_HTML:
+ case DATATYPE_RAW:
+ gtkMIMEType = aMimeType;
+ break;
+ }
+
+ gtk_clipboard_request_contents(
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
+ gdk_atom_intern(gtkMIMEType, FALSE),
+ [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
+ gpointer aData) -> void {
+ UniquePtr<DataCallbackHandler> ref(
+ static_cast<DataCallbackHandler*>(aData));
+ LOGCLIP("AsyncGetData async handler [%p] MIME %s type %d", aData,
+ ref->mMimeType.get(), ref->mDataType);
+
+ int dataLength = gtk_selection_data_get_length(aSelection);
+ if (dataLength <= 0) {
+ ref->mDataCallback(NS_OK);
+ return;
+ }
+ const char* data = (const char*)gtk_selection_data_get_data(aSelection);
+ if (!data) {
+ ref->mDataCallback(NS_OK);
+ return;
+ }
+ switch (ref->mDataType) {
+ case DATATYPE_IMAGE: {
+ LOGCLIP(" set image clipboard data");
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ Span(data, dataLength), NS_ASSIGNMENT_COPY);
+ ref->mTransferable->SetTransferData(ref->mMimeType.get(),
+ byteStream);
+ break;
+ }
+ case DATATYPE_FILE: {
+ LOGCLIP(" set file clipboard data");
+ nsDependentCSubstring file(data, dataLength);
+ TransferableSetFile(ref->mTransferable, file);
+ break;
+ }
+ case DATATYPE_HTML: {
+ LOGCLIP(" html clipboard data");
+ Span dataSpan(data, dataLength);
+ TransferableSetHTML(ref->mTransferable, dataSpan);
+ break;
+ }
+ case DATATYPE_RAW: {
+ LOGCLIP(" raw clipboard data %s", ref->mMimeType.get());
+ SetTransferableData(ref->mTransferable, ref->mMimeType, data,
+ dataLength);
+ break;
+ }
+ }
+ ref->mDataCallback(NS_OK);
+ },
+ new DataCallbackHandler(aTransferable, std::move(aCallback), aMimeType,
+ aDataType));
+}
+
+static void AsyncGetDataFlavor(nsITransferable* aTransferable,
+ int32_t aWhichClipboard, nsCString& aFlavorStr,
+ nsBaseClipboard::GetDataCallback&& aCallback) {
+ if (aFlavorStr.EqualsLiteral(kJPEGImageMime) ||
+ aFlavorStr.EqualsLiteral(kJPGImageMime) ||
+ aFlavorStr.EqualsLiteral(kPNGImageMime) ||
+ aFlavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (aFlavorStr.EqualsLiteral(kJPGImageMime)) {
+ aFlavorStr.Assign(kJPEGImageMime);
+ }
+ LOGCLIP(" Getting image %s MIME clipboard data", aFlavorStr.get());
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_IMAGE, std::move(aCallback));
+ return;
+ }
+ // Special case text/plain since we can convert any
+ // string into text/plain
+ if (aFlavorStr.EqualsLiteral(kTextMime)) {
+ LOGCLIP(" Getting unicode clipboard data");
+ AsyncGetTextImpl(aTransferable, aWhichClipboard, std::move(aCallback));
+ return;
+ }
+ if (aFlavorStr.EqualsLiteral(kFileMime)) {
+ LOGCLIP(" Getting file clipboard data\n");
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_FILE, std::move(aCallback));
+ return;
+ }
+ if (aFlavorStr.EqualsLiteral(kHTMLMime)) {
+ LOGCLIP(" Getting HTML clipboard data");
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_HTML, std::move(aCallback));
+ return;
+ }
+ LOGCLIP(" Getting raw %s MIME clipboard data\n", aFlavorStr.get());
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_RAW, std::move(aCallback));
+}
+
+void nsClipboard::AsyncGetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard,
+ GetDataCallback&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(aTransferable);
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::AsyncGetNativeClipboardData (%s)",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+ nsTArray<nsCString> importedFlavors;
+ nsresult rv = GetTransferableFlavors(aTransferable, importedFlavors);
+ if (NS_FAILED(rv)) {
+ aCallback(rv);
+ return;
+ }
+
+ auto flavorsNum = importedFlavors.Length();
+ if (!flavorsNum) {
+ aCallback(NS_OK);
+ return;
+ }
+#ifdef MOZ_LOGGING
+ if (flavorsNum > 1) {
+ LOGCLIP(" Only first MIME type (%s) will be imported from clipboard!",
+ importedFlavors[0].get());
+ }
+#endif
+
+ // Filter out MIME types on X11 to prevent unwanted conversions,
+ // see Bug 1611407
+ if (widget::GdkIsX11Display()) {
+ AsyncHasNativeClipboardDataMatchingFlavors(
+ importedFlavors, aWhichClipboard,
+ [aWhichClipboard, transferable = nsCOMPtr{aTransferable},
+ callback = std::move(aCallback)](auto aResultOrError) mutable {
+ if (aResultOrError.isErr()) {
+ callback(aResultOrError.unwrapErr());
+ return;
+ }
+
+ nsTArray<nsCString> clipboardFlavors =
+ std::move(aResultOrError.unwrap());
+ if (!clipboardFlavors.Length()) {
+ LOGCLIP(" no flavors in clipboard, quit.");
+ callback(NS_OK);
+ return;
+ }
+
+ AsyncGetDataFlavor(transferable, aWhichClipboard, clipboardFlavors[0],
+ std::move(callback));
+ });
+ return;
+ }
+
+ // Read clipboard directly on Wayland
+ AsyncGetDataFlavor(aTransferable, aWhichClipboard, importedFlavors[0],
+ std::move(aCallback));
+}
+
+nsresult nsClipboard::EmptyNativeClipboardData(int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::EmptyNativeClipboardData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+ if (aWhichClipboard == kSelectionClipboard) {
+ if (mSelectionTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
+ MOZ_ASSERT(!mSelectionTransferable);
+ }
+ } else {
+ if (mGlobalTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ MOZ_ASSERT(!mGlobalTransferable);
+ }
+ }
+ ClearCachedTargets(aWhichClipboard);
+ return NS_OK;
+}
+
+void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
+ if (aWhichClipboard == kSelectionClipboard) {
+ mSelectionSequenceNumber++;
+ mSelectionTransferable = nullptr;
+ } else {
+ mGlobalSequenceNumber++;
+ mGlobalTransferable = nullptr;
+ }
+}
+
+static bool FlavorMatchesTarget(const nsACString& aFlavor, GdkAtom aTarget) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(aTarget));
+ if (!atom_name) {
+ return false;
+ }
+ if (aFlavor.Equals(atom_name.get())) {
+ LOGCLIP(" has %s\n", atom_name.get());
+ return true;
+ }
+ // X clipboard supports image/jpeg, but we want to emulate support
+ // for image/jpg as well
+ if (aFlavor.EqualsLiteral(kJPGImageMime) &&
+ !strcmp(atom_name.get(), kJPEGImageMime)) {
+ LOGCLIP(" has image/jpg\n");
+ return true;
+ }
+ // application/x-moz-file should be treated like text/uri-list
+ if (aFlavor.EqualsLiteral(kFileMime) &&
+ !strcmp(atom_name.get(), kURIListMime)) {
+ LOGCLIP(" has text/uri-list treating as application/x-moz-file");
+ return true;
+ }
+ return false;
+}
+
+mozilla::Result<bool, nsresult>
+nsClipboard::HasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::HasNativeClipboardDataMatchingFlavors (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ if (!mContext) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ auto targets = mContext->GetTargets(aWhichClipboard);
+ if (!targets) {
+ LOGCLIP(" no targes at clipboard (null)\n");
+ return false;
+ }
+
+#ifdef MOZ_LOGGING
+ if (LOGCLIP_ENABLED()) {
+ LOGCLIP(" Asking for content:\n");
+ for (auto& flavor : aFlavorList) {
+ LOGCLIP(" MIME %s\n", flavor.get());
+ }
+ LOGCLIP(" Clipboard content (target nums %zu):\n",
+ targets.AsSpan().Length());
+ for (const auto& target : targets.AsSpan()) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(target));
+ if (!atom_name) {
+ LOGCLIP(" failed to get MIME\n");
+ continue;
+ }
+ LOGCLIP(" MIME %s\n", atom_name.get());
+ }
+ }
+#endif
+
+ // Walk through the provided types and try to match it to a
+ // provided type.
+ for (auto& flavor : aFlavorList) {
+ // We special case text/plain here.
+ if (flavor.EqualsLiteral(kTextMime) &&
+ gtk_targets_include_text(targets.AsSpan().data(),
+ targets.AsSpan().Length())) {
+ LOGCLIP(" has kTextMime\n");
+ return true;
+ }
+ for (const auto& target : targets.AsSpan()) {
+ if (FlavorMatchesTarget(flavor, target)) {
+ return true;
+ }
+ }
+ }
+
+ LOGCLIP(" no targes at clipboard (bad match)\n");
+ return false;
+}
+
+struct TragetCallbackHandler {
+ TragetCallbackHandler(const nsTArray<nsCString>& aAcceptedFlavorList,
+ nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback)
+ : mAcceptedFlavorList(aAcceptedFlavorList.Clone()),
+ mCallback(std::move(aCallback)) {
+ LOGCLIP("TragetCallbackHandler(%p) created", this);
+ }
+ ~TragetCallbackHandler() {
+ LOGCLIP("TragetCallbackHandler(%p) deleted", this);
+ }
+ nsTArray<nsCString> mAcceptedFlavorList;
+ nsBaseClipboard::HasMatchingFlavorsCallback mCallback;
+};
+
+void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
+ nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors (%s)",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ gtk_clipboard_request_contents(
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
+ gdk_atom_intern("TARGETS", FALSE),
+ [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
+ gpointer aData) -> void {
+ LOGCLIP("gtk_clipboard_request_contents async handler (%p)", aData);
+ UniquePtr<TragetCallbackHandler> handler(
+ static_cast<TragetCallbackHandler*>(aData));
+
+ GdkAtom* targets = nullptr;
+ gint targetsNum = 0;
+ if (gtk_selection_data_get_length(aSelection) > 0) {
+ gtk_selection_data_get_targets(aSelection, &targets, &targetsNum);
+ }
+ nsTArray<nsCString> results;
+ if (targetsNum) {
+ for (auto& flavor : handler->mAcceptedFlavorList) {
+ LOGCLIP(" looking for %s", flavor.get());
+ if (flavor.EqualsLiteral(kTextMime) &&
+ gtk_targets_include_text(targets, targetsNum)) {
+ results.AppendElement(flavor);
+ LOGCLIP(" has kTextMime\n");
+ continue;
+ }
+ for (int i = 0; i < targetsNum; i++) {
+ if (FlavorMatchesTarget(flavor, targets[i])) {
+ results.AppendElement(flavor);
+ }
+ }
+ }
+ }
+ handler->mCallback(std::move(results));
+ },
+ new TragetCallbackHandler(aFlavorList, std::move(aCallback)));
+}
+
+nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
+ nsITransferable* retval;
+
+ if (aWhichClipboard == kSelectionClipboard)
+ retval = mSelectionTransferable.get();
+ else
+ retval = mGlobalTransferable.get();
+
+ return retval;
+}
+
+void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
+ GtkSelectionData* aSelectionData) {
+ // Someone has asked us to hand them something. The first thing
+ // that we want to do is see if that something includes text. If
+ // it does, try to give it text/plain after converting it to
+ // utf-8.
+
+ int32_t whichClipboard;
+
+ // which clipboard?
+ GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
+ if (selection == GDK_SELECTION_PRIMARY)
+ whichClipboard = kSelectionClipboard;
+ else if (selection == GDK_SELECTION_CLIPBOARD)
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
+ if (!trans) {
+ // We have nothing to serve
+ LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
+ whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard");
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> item;
+
+ GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
+ LOGCLIP(" selection target %s\n",
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+
+ // Check to see if the selection data is some text type.
+ if (gtk_targets_include_text(&selectionTarget, 1)) {
+ LOGCLIP(" providing text/plain data\n");
+ // Try to convert our internal type into a text string. Get
+ // the transferable for this clipboard and try to get the
+ // text/plain type for it.
+ rv = trans->GetTransferData("text/plain", getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" GetTransferData() failed to get text/plain!\n");
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString) return;
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+ NS_ConvertUTF16toUTF8 utf8string(ucs2string);
+
+ LOGCLIP(" sent %zd bytes of utf-8 data\n", utf8string.Length());
+ if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
+ LOGCLIP(
+ " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
+ // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
+ // in some versions of GTK.
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ reinterpret_cast<const guchar*>(utf8string.get()),
+ utf8string.Length());
+ } else {
+ gtk_selection_data_set_text(aSelectionData, utf8string.get(),
+ utf8string.Length());
+ }
+ return;
+ }
+
+ // Check to see if the selection data is an image type
+ if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
+ LOGCLIP(" providing image data\n");
+ // Look through our transfer data for the image
+ static const char* const imageMimeTypes[] = {kNativeImageMime,
+ kPNGImageMime, kJPEGImageMime,
+ kJPGImageMime, kGIFImageMime};
+ nsCOMPtr<nsISupports> imageItem;
+ nsCOMPtr<imgIContainer> image;
+ for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
+ rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes[i]);
+ continue;
+ }
+
+ image = do_QueryInterface(imageItem);
+ if (image) {
+ LOGCLIP(" %s is available at GetTransferData()\n",
+ imageMimeTypes[i]);
+ break;
+ }
+ }
+
+ if (!image) { // Not getting an image for an image mime type!?
+ LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
+ return;
+ }
+
+ RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf) {
+ LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
+ return;
+ }
+
+ LOGCLIP(" Setting pixbuf image data as %s\n",
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ return;
+ }
+
+ if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
+ LOGCLIP(" providing %s data\n", kHTMLMime);
+ rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime);
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString) {
+ LOGCLIP(" failed to get wideString interface!");
+ return;
+ }
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+
+ nsAutoCString html;
+ // Add the prefix so the encoding is correctly detected.
+ html.AppendLiteral(kHTMLMarkupPrefix);
+ AppendUTF16toUTF8(ucs2string, html);
+
+ LOGCLIP(" Setting %zd bytes of %s data\n", html.Length(),
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ (const guchar*)html.get(), html.Length());
+ return;
+ }
+
+ // We put kFileMime onto the clipboard as kURIListMime.
+ if (selectionTarget == gdk_atom_intern(kURIListMime, FALSE)) {
+ LOGCLIP(" providing %s data\n", kURIListMime);
+ rv = trans->GetTransferData(kFileMime, getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" failed to get %s data by GetTransferData()!\n", kFileMime);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(item);
+ if (!file) {
+ LOGCLIP(" failed to get nsIFile interface!");
+ return;
+ }
+
+ nsCOMPtr<nsIURI> fileURI;
+ rv = NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" failed to get fileURI\n");
+ return;
+ }
+
+ nsAutoCString uri;
+ if (NS_FAILED(fileURI->GetSpec(uri))) {
+ LOGCLIP(" failed to get fileURI spec\n");
+ return;
+ }
+
+ LOGCLIP(" Setting %zd bytes of data\n", uri.Length());
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ (const guchar*)uri.get(), uri.Length());
+ return;
+ }
+
+ LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+
+ // Try to match up the selection data target to something our
+ // transferable provides.
+ GUniquePtr<gchar> target_name(gdk_atom_name(selectionTarget));
+ if (!target_name) {
+ LOGCLIP(" Failed to get target name!\n");
+ return;
+ }
+
+ rv = trans->GetTransferData(target_name.get(), getter_AddRefs(item));
+ // nothing found?
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" Failed to get anything from GetTransferData()!\n");
+ return;
+ }
+
+ void* primitive_data = nullptr;
+ uint32_t dataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(
+ nsDependentCString(target_name.get()), item, &primitive_data, &dataLen);
+ if (!primitive_data) {
+ LOGCLIP(" Failed to get primitive data!\n");
+ return;
+ }
+
+ LOGCLIP(" Setting %s as a primitive data type, %d bytes\n",
+ target_name.get(), dataLen);
+ gtk_selection_data_set(aSelectionData, selectionTarget,
+ 8, /* 8 bits in a unit */
+ (const guchar*)primitive_data, dataLen);
+ free(primitive_data);
+}
+
+void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard) {
+ if (aWhichClipboard == kSelectionClipboard) {
+ nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr);
+ } else {
+ nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
+ }
+}
+
+void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
+ int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
+ if (whichClipboard < 0) {
+ return;
+ }
+ LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+ ClearCachedTargets(whichClipboard);
+ ClearTransferable(whichClipboard);
+ ClearClipboardCache(whichClipboard);
+}
+
+void nsClipboard::OwnerChangedEvent(GtkClipboard* aGtkClipboard,
+ GdkEventOwnerChange* aEvent) {
+ int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
+ if (whichClipboard < 0) {
+ return;
+ }
+ LOGCLIP("nsClipboard::OwnerChangedEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+ GtkWidget* gtkWidget = [aEvent]() -> GtkWidget* {
+ if (!aEvent->owner) {
+ return nullptr;
+ }
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(aEvent->owner, &user_data);
+ return GTK_WIDGET(user_data);
+ }();
+ // If we can get GtkWidget from the current clipboard owner, this
+ // owner-changed event must be triggered by ourself via calling
+ // gtk_clipboard_set_with_data, the sequence number should already be handled.
+ if (!gtkWidget) {
+ if (whichClipboard == kSelectionClipboard) {
+ mSelectionSequenceNumber++;
+ } else {
+ mGlobalSequenceNumber++;
+ }
+ }
+
+ ClearCachedTargets(whichClipboard);
+}
+
+void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data) {
+ LOGCLIP("clipboard_get_cb() callback\n");
+ nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
+ clipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
+}
+
+void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
+ LOGCLIP("clipboard_clear_cb() callback\n");
+ nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
+ clipboard->SelectionClearEvent(aGtkClipboard);
+}
+
+void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard,
+ GdkEventOwnerChange* aEvent,
+ gpointer aUserData) {
+ LOGCLIP("clipboard_owner_change_cb() callback\n");
+ nsClipboard* clipboard = static_cast<nsClipboard*>(aUserData);
+ clipboard->OwnerChangedEvent(aGtkClipboard, aEvent);
+}
+
+/*
+ * This function extracts the encoding label from the subset of HTML internal
+ * encoding declaration syntax that uses the old long form with double quotes
+ * and without spaces around the equals sign between the "content" attribute
+ * name and the attribute value.
+ *
+ * This was added for the sake of an ancient version of StarOffice
+ * in the pre-UTF-8 era in bug 123389. It is unclear if supporting
+ * non-UTF-8 encodings is still necessary and if this function
+ * still needs to exist.
+ *
+ * As of December 2022, both Gecko and LibreOffice emit an UTF-8
+ * declaration that this function successfully extracts "UTF-8" from,
+ * but that's also the default that we fall back on if this function
+ * fails to extract a label.
+ */
+bool GetHTMLCharset(Span<const char> aData, nsCString& aFoundCharset) {
+ // Assume ASCII first to find "charset" info
+ const nsDependentCSubstring htmlStr(aData);
+ nsACString::const_iterator start, end;
+ htmlStr.BeginReading(start);
+ htmlStr.EndReading(end);
+ nsACString::const_iterator valueStart(start), valueEnd(start);
+
+ if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
+ valueStart = end;
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (FindCharInReadable('"', start, end)) valueEnd = start;
+ }
+ }
+ // find "charset" in HTML
+ if (valueStart != valueEnd) {
+ aFoundCharset = Substring(valueStart, valueEnd);
+ ToUpperCase(aFoundCharset);
+ return true;
+ }
+ return false;
+}