From 646adf404ec565b2dd60fd3d7437ac6ed0014cec Mon Sep 17 00:00:00 2001
From: stransky <stransky@redhat.com>
Date: Thu, 9 Sep 2021 09:46:09 +0000
Subject: [PATCH] Bug 1611407 [Linux/X11] Don't try to get text clipboard data
 of binary MIME types, r=jhorak

X11 tries to convert any binary clipboard data to text if text MIME type is missing in clipboard.
This leads to various errors, wrong conversions and timeouts.
So get clipboard content first and if there are only binary MIME types in clipboard advertise only these types.

Differential Revision: https://phabricator.services.mozilla.com/D123687
---
 widget/gtk/nsClipboard.cpp    | 79 ++++++++++++++++++++++++++++++++++-
 widget/gtk/nsClipboard.h      |  3 ++
 widget/gtk/nsClipboardX11.cpp | 11 +++--
 3 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp
index 75ad319a1ece8..48160b39a4544 100644
--- a/widget/gtk/nsClipboard.cpp
+++ b/widget/gtk/nsClipboard.cpp
@@ -253,6 +253,76 @@ void nsClipboard::SetTransferableData(nsITransferable* aTransferable,
   aTransferable->SetTransferData(aFlavor.get(), wrapper);
 }
 
+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"));
+
+  int targetNums;
+  GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
+  if (!targets) {
+    LOGCLIP(("    X11: no targes at clipboard (null), quit.\n"));
+    return false;
+  }
+
+  for (int i = 0; i < targetNums; i++) {
+    gchar* atom_name = gdk_atom_name(targets[i]);
+    if (!atom_name) {
+      continue;
+    }
+    // Filter out system MIME types.
+    if (strcmp(atom_name, "TARGETS") == 0 ||
+        strcmp(atom_name, "TIMESTAMP") == 0 ||
+        strcmp(atom_name, "SAVE_TARGETS") == 0 ||
+        strcmp(atom_name, "MULTIPLE") == 0) {
+      continue;
+    }
+    // Filter out types which can't be converted to text.
+    if (strncmp(atom_name, "image/", 6) == 0 ||
+        strncmp(atom_name, "application/", 12) == 0 ||
+        strncmp(atom_name, "audio/", 6) == 0 ||
+        strncmp(atom_name, "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 (int i = 0; i < targetNums; i++) {
+    gchar* atom_name = gdk_atom_name(targets[i]);
+    if (!atom_name) {
+      continue;
+    }
+    if (IsMIMEAtFlavourList(aFlavors, atom_name)) {
+      clipboardFlavors.AppendElement(nsCString(atom_name));
+    }
+  }
+  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;
+}
+
 NS_IMETHODIMP
 nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
   LOGCLIP(("nsClipboard::GetData (%s)\n",
@@ -270,7 +340,6 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
     LOGCLIP(("    FlavorsTransferableCanImport falied!\n"));
     return rv;
   }
-
 #ifdef MOZ_LOGGING
   LOGCLIP(("Flavors which can be imported:\n"));
   for (uint32_t i = 0; i < flavors.Length(); i++) {
@@ -278,6 +347,14 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
   }
 #endif
 
+  // Filter out MIME types on X11 to prevent unwanted conversions,
+  // see Bug 1611407
+  if (widget::GdkIsX11Display()) {
+    if (!FilterImportedFlavors(aWhichClipboard, flavors)) {
+      return NS_OK;
+    }
+  }
+
   for (uint32_t i = 0; i < flavors.Length(); i++) {
     nsCString& flavorStr = flavors[i];
 
diff --git a/widget/gtk/nsClipboard.h b/widget/gtk/nsClipboard.h
index 193020b0c5f4b..6c42ac81caa5f 100644
--- a/widget/gtk/nsClipboard.h
+++ b/widget/gtk/nsClipboard.h
@@ -80,6 +80,9 @@ class nsClipboard : public nsIClipboard, public nsIObserver {
 
   void ClearTransferable(int32_t aWhichClipboard);
 
+  bool FilterImportedFlavors(int32_t aWhichClipboard,
+                             nsTArray<nsCString>& aFlavors);
+
   // Hang on to our owners and transferables so we can transfer data
   // when asked.
   nsCOMPtr<nsIClipboardOwner> mSelectionOwner;
diff --git a/widget/gtk/nsClipboardX11.cpp b/widget/gtk/nsClipboardX11.cpp
index bf05cc30537b3..e1def2518169c 100644
--- a/widget/gtk/nsClipboardX11.cpp
+++ b/widget/gtk/nsClipboardX11.cpp
@@ -204,9 +204,11 @@ void nsRetrievalContextX11::Complete(ClipboardDataType aDataType,
 
         gint dataLength = gtk_selection_data_get_length(selection);
         const guchar* data = gtk_selection_data_get_data(selection);
-
-        LOGCLIP(("  got data %p len %d\n", data, dataLength));
-
+#ifdef MOZ_LOGGING
+        GdkAtom target = gtk_selection_data_get_target(selection);
+        LOGCLIP(("  got data %p len %d MIME %s\n", data, dataLength,
+                 gdk_atom_name(target)));
+#endif
         if (dataLength > 0) {
           mClipboardDataLength = dataLength;
           mClipboardData = moz_xmalloc(dataLength);
@@ -266,14 +268,17 @@ bool nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
 
   switch (aDataType) {
     case CLIPBOARD_DATA:
+      LOGCLIP(("  getting DATA MIME %s\n", aMimeType));
       gtk_clipboard_request_contents(clipboard,
                                      gdk_atom_intern(aMimeType, FALSE),
                                      clipboard_contents_received, handler);
       break;
     case CLIPBOARD_TEXT:
+      LOGCLIP(("  getting TEXT\n"));
       gtk_clipboard_request_text(clipboard, clipboard_text_received, handler);
       break;
     case CLIPBOARD_TARGETS:
+      LOGCLIP(("  getting TARGETS\n"));
       gtk_clipboard_request_contents(clipboard, mTargetMIMEType,
                                      clipboard_contents_received, handler);
       break;
-- 
GitLab