Bug 1417874 - Split clipboard implementation to shared (nsClipboard) and backend specific (nsClipboardX11) parts, r?jhorak draft
authorMartin Stransky <stransky@redhat.com>
Thu, 16 Nov 2017 14:00:00 +0100
changeset 706125 54c0d987c8f18cd9385700e0244f0fbcb5031134
parent 706025 a21f4e2ce5186e2dc9ee411b07e9348866b4ef30
child 742593 9c4bea99e9da3b24caa2ee4cbbc6c604fdf55869
push id91726
push userstransky@redhat.com
push dateFri, 01 Dec 2017 11:13:36 +0000
reviewersjhorak
bugs1417874
milestone59.0a1
Bug 1417874 - Split clipboard implementation to shared (nsClipboard) and backend specific (nsClipboardX11) parts, r?jhorak MozReview-Commit-ID: 5IebGayJl1
widget/gtk/moz.build
widget/gtk/mozgtk/mozgtk.c
widget/gtk/nsClipboard.cpp
widget/gtk/nsClipboard.h
widget/gtk/nsClipboardX11.cpp
widget/gtk/nsClipboardX11.h
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -74,16 +74,17 @@ if CONFIG['NS_PRINTING']:
         'nsPrintOptionsGTK.cpp',
         'nsPrintSettingsGTK.cpp',
         'nsPSPrinters.cpp',
     ]
 
 if CONFIG['MOZ_X11']:
     UNIFIED_SOURCES += [
         'nsClipboard.cpp',
+        'nsClipboardX11.cpp',
         'nsDragService.cpp',
         'WindowSurfaceProvider.cpp',
         'WindowSurfaceX11.cpp',
         'WindowSurfaceX11Image.cpp',
         'WindowSurfaceXRender.cpp',
     ]
     EXPORTS.mozilla.widget += [
         'WindowSurfaceProvider.h',
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -402,16 +402,17 @@ STUB(gtk_style_lookup_icon_set)
 STUB(gtk_table_attach)
 STUB(gtk_table_get_type)
 STUB(gtk_table_new)
 STUB(gtk_target_list_add)
 STUB(gtk_target_list_add_image_targets)
 STUB(gtk_target_list_new)
 STUB(gtk_target_list_unref)
 STUB(gtk_targets_include_image)
+STUB(gtk_targets_include_text)
 STUB(gtk_target_table_free)
 STUB(gtk_target_table_new_from_list)
 STUB(gtk_text_view_new)
 STUB(gtk_toggle_button_get_active)
 STUB(gtk_toggle_button_get_type)
 STUB(gtk_toggle_button_new)
 STUB(gtk_toggle_button_set_active)
 STUB(gtk_toggle_button_set_inconsistent)
--- a/widget/gtk/nsClipboard.cpp
+++ b/widget/gtk/nsClipboard.cpp
@@ -4,40 +4,37 @@
 /* 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"
+#include "nsClipboardX11.h"
+#if defined(MOZ_WAYLAND)
+#include "nsClipboardWayland.h"
+#endif
+#include "HeadlessClipboard.h"
 #include "nsSupportsPrimitives.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsPrimitiveHelpers.h"
 #include "nsIServiceManager.h"
 #include "nsImageToPixbuf.h"
 #include "nsStringStream.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
 
 #include "imgIContainer.h"
 
 #include <gtk/gtk.h>
-
-// For manipulation of the X event queue
-#include <X11/Xlib.h>
-#include <gdk/gdkx.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <unistd.h>
-#include "X11UndefineNone.h"
+#include <gtk/gtkx.h>
 
 #include "mozilla/Encoding.h"
 
 
 using namespace mozilla;
 
 // Callback when someone asks us for the data
 void
@@ -55,35 +52,36 @@ static void
 ConvertHTMLtoUCS2          (guchar             *data,
                             int32_t             dataLength,
                             char16_t         **unicodeData,
                             int32_t            &outUnicodeLen);
 
 static void
 GetHTMLCharset             (guchar * data, int32_t dataLength, nsCString& str);
 
-
-// Our own versions of gtk_clipboard_wait_for_contents and
-// gtk_clipboard_wait_for_text, which don't run the event loop while
-// waiting for the data.  This prevents a lot of problems related to
-// dispatching events at unexpected times.
+GdkAtom
+GetSelectionAtom(int32_t aWhichClipboard)
+{
+    if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
+        return GDK_SELECTION_CLIPBOARD;
 
-static GtkSelectionData *
-wait_for_contents          (GtkClipboard *clipboard, GdkAtom target);
+    return GDK_SELECTION_PRIMARY;
+}
 
-static gchar *
-wait_for_text              (GtkClipboard *clipboard);
-
-static GdkFilterReturn
-selection_request_filter   (GdkXEvent *gdk_xevent,
-                            GdkEvent *event,
-                            gpointer data);
+// Idle timeout for receiving selection and property notify events (microsec)
+const int
+nsRetrievalContext::kClipboardTimeout = 500000;
 
 nsClipboard::nsClipboard()
 {
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os) {
+      os->AddObserver(this, "quit-application", false);
+      os->AddObserver(this, "xpcom-shutdown", false);
+  }
 }
 
 nsClipboard::~nsClipboard()
 {
     // We have to clear clipboard before gdk_display_close() call.
     // See bug 531580 for details.
     if (mGlobalTransferable) {
         gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
@@ -93,51 +91,37 @@ nsClipboard::~nsClipboard()
     }
 }
 
 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
 
 nsresult
 nsClipboard::Init(void)
 {
-    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    if (!os)
-      return NS_ERROR_FAILURE;
-
-    os->AddObserver(this, "quit-application", false);
+    // create nsRetrievalContext
+    if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+        mContext = new nsRetrievalContextX11();
+    }
+    return NS_OK;
+}
 
-    // A custom event filter to workaround attempting to dereference a null
-    // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
-#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11)
-    if (gtk_check_version(3, 11, 3))
-        gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
-#endif
 
+nsresult
+nsClipboard::Store(void)
+{
+    GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    gtk_clipboard_store(clipboard);
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+nsClipboard::Observe(nsISupports *aSubject, const char *aTopic,
+                     const char16_t *aData)
 {
-    if (strcmp(aTopic, "quit-application") == 0) {
-        // application is going to quit, save clipboard content
-        Store();
-        gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
-    }
-    return NS_OK;
-}
-
-nsresult
-nsClipboard::Store(void)
-{
-    // Ask the clipboard manager to store the current clipboard content
-    if (mGlobalTransferable) {
-        GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
-        gtk_clipboard_store(clipboard);
-    }
+    Store();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::SetData(nsITransferable *aTransferable,
                      nsIClipboardOwner *aOwner, int32_t aWhichClipboard)
 {
     // See if we can short cut
@@ -242,133 +226,125 @@ nsClipboard::SetData(nsITransferable *aT
 }
 
 NS_IMETHODIMP
 nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
 {
     if (!aTransferable)
         return NS_ERROR_FAILURE;
 
-    GtkClipboard *clipboard;
-    clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
-
     guchar        *data = nullptr;
-    gint           length = 0;
+    uint32_t       length = 0;
     bool           foundData = false;
     nsAutoCString  foundFlavor;
 
     // Get a list of flavors this transferable can import
     nsCOMPtr<nsIArray> flavors;
     nsresult rv;
     rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
     if (!flavors || NS_FAILED(rv))
         return NS_ERROR_FAILURE;
 
     uint32_t count;
     flavors->GetLength(&count);
     for (uint32_t i=0; i < count; i++) {
         nsCOMPtr<nsISupportsCString> currentFlavor;
         currentFlavor = do_QueryElementAt(flavors, i);
+        if (!currentFlavor)
+            continue;
 
-        if (currentFlavor) {
-            nsCString flavorStr;
-            currentFlavor->ToString(getter_Copies(flavorStr));
+        nsCString flavorStr;
+        currentFlavor->ToString(getter_Copies(flavorStr));
 
-            // Special case text/unicode since we can convert any
-            // string into text/unicode
-            if (flavorStr.EqualsLiteral(kUnicodeMime)) {
-                gchar* new_text = wait_for_text(clipboard);
-                if (new_text) {
-                    // Convert utf-8 into our unicode format.
-                    NS_ConvertUTF8toUTF16 ucs2string(new_text);
-                    data = (guchar *)ToNewUnicode(ucs2string);
-                    length = ucs2string.Length() * 2;
-                    g_free(new_text);
-                    foundData = true;
-                    foundFlavor = kUnicodeMime;
-                    break;
-                }
+        // Special case text/unicode since we can convert any
+        // string into text/unicode
+        if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+            guchar* rawData =
+                mContext->WaitForClipboardContext(GTK_DEFAULT_MIME_TEXT,
+                                                  aWhichClipboard,
+                                                  &length);
+            if (!rawData) {
                 // If the type was text/unicode and we couldn't get
                 // text off the clipboard, run the next loop
                 // iteration.
                 continue;
             }
 
-            // For images, we must wrap the data in an nsIInputStream then return instead of break,
-            // because that code below won't help us.
-            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);
-                }
-
-                GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
+            // Convert utf-8 into our unicode format.
+            NS_ConvertUTF8toUTF16 ucs2string((const char *)rawData, length);
+            data = (guchar *)ToNewUnicode(ucs2string);
+            length = ucs2string.Length() * 2;
+            g_free(rawData);
 
-                GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
-                if (!selectionData)
-                    continue;
+            foundData = true;
+            foundFlavor = kUnicodeMime;
+            break;
+        }
 
-                nsCOMPtr<nsIInputStream> byteStream;
-                NS_NewByteInputStream(getter_AddRefs(byteStream), 
-                                      (const char*)gtk_selection_data_get_data(selectionData),
-                                      gtk_selection_data_get_length(selectionData), 
-                                      NS_ASSIGNMENT_COPY);
-                aTransferable->SetTransferData(flavorStr.get(), byteStream, sizeof(nsIInputStream*));
-                gtk_selection_data_free(selectionData);
-                return NS_OK;
+        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);
             }
 
-            // Get the atom for this type and try to request it off
-            // the clipboard.
-            GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
-            GtkSelectionData *selectionData;
-            selectionData = wait_for_contents(clipboard, atom);
-            if (selectionData) {
-                const guchar *clipboardData = gtk_selection_data_get_data(selectionData);
-                length = gtk_selection_data_get_length(selectionData);
-                // Special case text/html since we can convert into UCS2
-                if (flavorStr.EqualsLiteral(kHTMLMime)) {
-                    char16_t* htmlBody= nullptr;
-                    int32_t htmlBodyLen = 0;
-                    // Convert text/html into our unicode format
-                    ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length,
-                                      &htmlBody, htmlBodyLen);
-                    // Try next data format?
-                    if (!htmlBodyLen)
-                        continue;
-                    data = (guchar *)htmlBody;
-                    length = htmlBodyLen * 2;
-                } else {
-                    data = (guchar *)moz_xmalloc(length);
-                    if (!data)
-                        break;
-                    memcpy(data, clipboardData, length);
-                }
-                gtk_selection_data_free(selectionData);
-                foundData = true;
-                foundFlavor = flavorStr;
-                break;
-            }
+            data = mContext->WaitForClipboardContext(flavorStr.get(),
+                aWhichClipboard, &length);
+            if (!data)
+                continue;
+
+            nsCOMPtr<nsIInputStream> byteStream;
+            NS_NewByteInputStream(getter_AddRefs(byteStream),
+                                  (const char*)data,
+                                  length,
+                                  NS_ASSIGNMENT_COPY);
+            aTransferable->SetTransferData(flavorStr.get(), byteStream, sizeof(nsIInputStream*));
+            g_free(data);
+            return NS_OK;
         }
+
+        data = mContext->WaitForClipboardContext(flavorStr.get(),
+            aWhichClipboard, &length);
+        if (!data)
+            continue;
+
+        // Special case text/html since we can convert into UCS2
+        if (flavorStr.EqualsLiteral(kHTMLMime)) {
+            char16_t* htmlBody = nullptr;
+            int32_t htmlBodyLen = 0;
+            // Convert text/html into our unicode format
+            ConvertHTMLtoUCS2(data, length,
+                              &htmlBody, htmlBodyLen);
+            g_free(data);
+
+            // Try next data format?
+            if (!htmlBodyLen)
+                continue;
+            data = (guchar *)htmlBody;
+            length = htmlBodyLen * 2;
+        }
+
+        foundData = true;
+        foundFlavor = flavorStr;
+        break;
     }
 
     if (foundData) {
         nsCOMPtr<nsISupports> wrapper;
         nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor,
                                                    data, length,
                                                    getter_AddRefs(wrapper));
         aTransferable->SetTransferData(foundFlavor.get(),
                                        wrapper, length);
     }
 
     if (data)
-        free(data);
+        g_free(data);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
 {
     if (aWhichClipboard == kSelectionClipboard) {
@@ -388,101 +364,76 @@ nsClipboard::EmptyClipboard(int32_t aWhi
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
                                     int32_t aWhichClipboard, bool *_retval)
 {
-    if (!aFlavorList || !_retval)
-        return NS_ERROR_NULL_POINTER;
+  if (!aFlavorList || !_retval)
+      return NS_ERROR_NULL_POINTER;
 
-    *_retval = false;
+  *_retval = false;
+
+  int targetNum;
+  GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNum);
 
-    GtkSelectionData *selection_data =
-        GetTargets(GetSelectionAtom(aWhichClipboard));
-    if (!selection_data)
-        return NS_OK;
-
-    gint n_targets = 0;
-    GdkAtom *targets = nullptr;
-
-    if (!gtk_selection_data_get_targets(selection_data, 
-                                        &targets, &n_targets) ||
-        !n_targets)
-        return NS_OK;
+  // Walk through the provided types and try to match it to a
+  // provided type.
+  for (uint32_t i = 0; i < aLength && !*_retval; i++) {
+      // We special case text/unicode here.
+      if (!strcmp(aFlavorList[i], kUnicodeMime) &&
+          gtk_targets_include_text(targets, targetNum)) {
+          *_retval = true;
+          break;
+      }
 
-    // Walk through the provided types and try to match it to a
-    // provided type.
-    for (uint32_t i = 0; i < aLength && !*_retval; i++) {
-        // We special case text/unicode here.
-        if (!strcmp(aFlavorList[i], kUnicodeMime) && 
-            gtk_selection_data_targets_include_text(selection_data)) {
-            *_retval = true;
-            break;
-        }
+      for (int32_t j = 0; j < targetNum; j++) {
+          gchar *atom_name = gdk_atom_name(targets[j]);
+          if (!atom_name)
+              continue;
 
-        for (int32_t j = 0; j < n_targets; j++) {
-            gchar *atom_name = gdk_atom_name(targets[j]);
-            if (!atom_name)
-                continue;
+          if (!strcmp(atom_name, aFlavorList[i]))
+              *_retval = true;
 
-            if (!strcmp(atom_name, aFlavorList[i]))
-                *_retval = true;
-
-            // X clipboard supports image/jpeg, but we want to emulate support
-            // for image/jpg as well
-            if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime))
-                *_retval = true;
+          // X clipboard supports image/jpeg, but we want to emulate support
+          // for image/jpg as well
+          if (!strcmp(aFlavorList[i], kJPGImageMime) &&
+              !strcmp(atom_name, kJPEGImageMime)) {
+              *_retval = true;
+          }
 
-            g_free(atom_name);
+          g_free(atom_name);
 
-            if (*_retval)
-                break;
-        }
-    }
-    gtk_selection_data_free(selection_data);
-    g_free(targets);
+          if (*_retval)
+              break;
+      }
+  }
 
-    return NS_OK;
+  g_free(targets);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::SupportsSelectionClipboard(bool *_retval)
 {
-    *_retval = true; // yeah, unix supports the selection clipboard
+    // yeah, unix supports the selection clipboard on X11
+    // TODO Wayland
+    *_retval = GDK_IS_X11_DISPLAY(gdk_display_get_default());
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::SupportsFindClipboard(bool* _retval)
 {
   *_retval = false;
   return NS_OK;
 }
 
-/* static */
-GdkAtom
-nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
-{
-    if (aWhichClipboard == kGlobalClipboard)
-        return GDK_SELECTION_CLIPBOARD;
-
-    return GDK_SELECTION_PRIMARY;
-}
-
-/* static */
-GtkSelectionData *
-nsClipboard::GetTargets(GdkAtom aWhichClipboard)
-{
-    GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
-    return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
-}
-
 nsITransferable *
 nsClipboard::GetTransferable(int32_t aWhichClipboard)
 {
     nsITransferable *retval;
 
     if (aWhichClipboard == kSelectionClipboard)
         retval = mSelectionTransferable.get();
     else
@@ -550,17 +501,17 @@ nsClipboard::SelectionGetEvent(GtkClipbo
         wideString->GetData(ucs2string);
         char *utf8string = ToNewUTF8String(ucs2string);
         if (!utf8string)
             return;
         
         gtk_selection_data_set_text (aSelectionData, utf8string,
                                      strlen(utf8string));
 
-        free(utf8string);
+        g_free(utf8string);
         return;
     }
 
     // Check to see if the selection data is an image type
     if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
         // Look through our transfer data for the image
         static const char* const imageMimeTypes[] = {
             kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
@@ -611,31 +562,31 @@ nsClipboard::SelectionGetEvent(GtkClipbo
             /*
              * "text/html" can be encoded UCS2. It is recommended that
              * documents transmitted as UCS2 always begin with a ZERO-WIDTH
              * NON-BREAKING SPACE character (hexadecimal FEFF, also called
              * Byte Order Mark (BOM)). Adding BOM can help other app to
              * detect mozilla use UCS2 encoding when copy-paste.
              */
             guchar *buffer = (guchar *)
-                    moz_xmalloc((len * sizeof(guchar)) + sizeof(char16_t));
+                    g_malloc((len * sizeof(guchar)) + sizeof(char16_t));
             if (!buffer)
                 return;
             char16_t prefix = 0xFEFF;
             memcpy(buffer, &prefix, sizeof(prefix));
             memcpy(buffer + sizeof(prefix), primitive_data, len);
-            free((guchar *)primitive_data);
+            g_free((guchar *)primitive_data);
             primitive_data = (guchar *)buffer;
             len += sizeof(prefix);
         }
   
         gtk_selection_data_set(aSelectionData, selectionTarget,
                                8, /* 8 bits in a unit */
                                (const guchar *)primitive_data, len);
-        free(primitive_data);
+        g_free(primitive_data);
     }
 
     g_free(target_name);
                            
 }
 
 void
 nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
@@ -693,17 +644,17 @@ clipboard_clear_cb(GtkClipboard *aGtkCli
 void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
                        char16_t** unicodeData, int32_t& outUnicodeLen)
 {
     nsAutoCString charset;
     GetHTMLCharset(data, dataLength, charset);// get charset of HTML
     if (charset.EqualsLiteral("UTF-16")) {//current mozilla
         outUnicodeLen = (dataLength / 2) - 1;
         *unicodeData = reinterpret_cast<char16_t*>
-                                       (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
+                                       (g_malloc((outUnicodeLen + sizeof('\0')) *
                        sizeof(char16_t)));
         if (*unicodeData) {
             memcpy(*unicodeData, data + sizeof(char16_t),
                    outUnicodeLen * sizeof(char16_t));
             (*unicodeData)[outUnicodeLen] = '\0';
         }
     } else if (charset.EqualsLiteral("UNKNOWN")) {
         outUnicodeLen = 0;
@@ -724,17 +675,17 @@ void ConvertHTMLtoUCS2(guchar * data, in
         if (!needed.isValid() || needed.value() > INT32_MAX) {
           outUnicodeLen = 0;
           return;
         }
 
         outUnicodeLen = 0;
         if (needed.value()) {
           *unicodeData = reinterpret_cast<char16_t*>(
-            moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
+            g_malloc((needed.value() + 1) * sizeof(char16_t)));
           if (*unicodeData) {
             uint32_t result;
             size_t read;
             size_t written;
             bool hadErrors;
             Tie(result, read, written, hadErrors) =
               decoder->DecodeToUTF16(AsBytes(MakeSpan(data, dataLength)),
                                      MakeSpan(*unicodeData, needed.value()),
@@ -800,251 +751,8 @@ void GetHTMLCharset(guchar * data, int32
         ToUpperCase(str);
 #ifdef DEBUG_CLIPBOARD
         printf("Charset of HTML = %s\n", charsetUpperStr.get());
 #endif
         return;
     }
     str.AssignLiteral("UNKNOWN");
 }
-
-static void
-DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
-{
-    GdkEvent event;
-    event.selection.type = GDK_SELECTION_NOTIFY;
-    event.selection.window = gtk_widget_get_window(widget);
-    event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
-    event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
-    event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
-    event.selection.time = xevent->xselection.time;
-
-    gtk_widget_event(widget, &event);
-}
-
-static void
-DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
-{
-    GdkWindow *window = gtk_widget_get_window(widget);
-    if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
-        GdkEvent event;
-        event.property.type = GDK_PROPERTY_NOTIFY;
-        event.property.window = window;
-        event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
-        event.property.time = xevent->xproperty.time;
-        event.property.state = xevent->xproperty.state;
-
-        gtk_widget_event(widget, &event);
-    }
-}
-
-struct checkEventContext
-{
-    GtkWidget *cbWidget;
-    Atom       selAtom;
-};
-
-static Bool
-checkEventProc(Display *display, XEvent *event, XPointer arg)
-{
-    checkEventContext *context = (checkEventContext *) arg;
-
-    if (event->xany.type == SelectionNotify ||
-        (event->xany.type == PropertyNotify &&
-         event->xproperty.atom == context->selAtom)) {
-
-        GdkWindow *cbWindow = 
-            gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
-                                              event->xany.window);
-        if (cbWindow) {
-            GtkWidget *cbWidget = nullptr;
-            gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
-            if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
-                context->cbWidget = cbWidget;
-                return True;
-            }
-        }
-    }
-
-    return False;
-}
-
-// Idle timeout for receiving selection and property notify events (microsec)
-static const int kClipboardTimeout = 500000;
-
-static gchar* CopyRetrievedData(const gchar *aData)
-{
-    return g_strdup(aData);
-}
-
-static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
-{
-    // A negative length indicates that retrieving the data failed.
-    return gtk_selection_data_get_length(aData) >= 0 ?
-        gtk_selection_data_copy(aData) : nullptr;
-}
-
-class RetrievalContext {
-    ~RetrievalContext()
-    {
-        MOZ_ASSERT(!mData, "Wait() wasn't called");
-    }
-
-public:
-    NS_INLINE_DECL_REFCOUNTING(RetrievalContext)
-    enum State { INITIAL, COMPLETED, TIMED_OUT };
-
-    RetrievalContext() : mState(INITIAL), mData(nullptr) {}
-
-    /**
-     * Call this when data has been retrieved.
-     */
-    template <class T> void Complete(T *aData)
-    {
-        if (mState == INITIAL) {
-            mState = COMPLETED;
-            mData = CopyRetrievedData(aData);
-        } else {
-            // Already timed out
-            MOZ_ASSERT(mState == TIMED_OUT);
-        }
-    }
-
-    /**
-     * Spins X event loop until timing out or being completed. Returns
-     * null if we time out, otherwise returns the completed data (passing
-     * ownership to caller).
-     */
-    void *Wait();
-
-protected:
-    State mState;
-    void* mData;
-};
-
-void *
-RetrievalContext::Wait()
-{
-    if (mState == COMPLETED) { // the request completed synchronously
-        void *data = mData;
-        mData = nullptr;
-        return data;
-    }
-
-    GdkDisplay *gdkDisplay = gdk_display_get_default();
-    if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
-        Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
-        checkEventContext context;
-        context.cbWidget = nullptr;
-        context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
-                                                                FALSE));
-
-        // Send X events which are relevant to the ongoing selection retrieval
-        // to the clipboard widget.  Wait until either the operation completes, or
-        // we hit our timeout.  All other X events remain queued.
-
-        int select_result;
-
-        int cnumber = ConnectionNumber(xDisplay);
-        fd_set select_set;
-        FD_ZERO(&select_set);
-        FD_SET(cnumber, &select_set);
-        ++cnumber;
-        TimeStamp start = TimeStamp::Now();
-
-        do {
-            XEvent xevent;
-
-            while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
-                                 (XPointer) &context)) {
-
-                if (xevent.xany.type == SelectionNotify)
-                    DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
-                else
-                    DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
-
-                if (mState == COMPLETED) {
-                    void *data = mData;
-                    mData = nullptr;
-                    return data;
-                }
-            }
-
-            TimeStamp now = TimeStamp::Now();
-            struct timeval tv;
-            tv.tv_sec = 0;
-            tv.tv_usec = std::max<int32_t>(0,
-                kClipboardTimeout - (now - start).ToMicroseconds());
-            select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
-        } while (select_result == 1 ||
-                 (select_result == -1 && errno == EINTR));
-    }
-#ifdef DEBUG_CLIPBOARD
-    printf("exceeded clipboard timeout\n");
-#endif
-    mState = TIMED_OUT;
-    return nullptr;
-}
-
-static void
-clipboard_contents_received(GtkClipboard     *clipboard,
-                            GtkSelectionData *selection_data,
-                            gpointer          data)
-{
-    RetrievalContext *context = static_cast<RetrievalContext*>(data);
-    context->Complete(selection_data);
-    context->Release();
-}
-
-static GtkSelectionData *
-wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
-{
-    RefPtr<RetrievalContext> context = new RetrievalContext();
-    // Balanced by Release in clipboard_contents_received
-    context.get()->AddRef();
-    gtk_clipboard_request_contents(clipboard, target,
-                                   clipboard_contents_received,
-                                   context.get());
-    return static_cast<GtkSelectionData*>(context->Wait());
-}
-
-static void
-clipboard_text_received(GtkClipboard *clipboard,
-                        const gchar  *text,
-                        gpointer      data)
-{
-    RetrievalContext *context = static_cast<RetrievalContext*>(data);
-    context->Complete(text);
-    context->Release();
-}
-
-static gchar *
-wait_for_text(GtkClipboard *clipboard)
-{
-    RefPtr<RetrievalContext> context = new RetrievalContext();
-    // Balanced by Release in clipboard_text_received
-    context.get()->AddRef();
-    gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
-    return static_cast<gchar*>(context->Wait());
-}
-
-static GdkFilterReturn
-selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
-{
-    XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
-    if (xevent->xany.type == SelectionRequest) {
-        if (xevent->xselectionrequest.requestor == X11None)
-            return GDK_FILTER_REMOVE;
-
-        GdkDisplay *display = gdk_x11_lookup_xdisplay(
-                xevent->xselectionrequest.display);
-        if (!display)
-            return GDK_FILTER_REMOVE;
-
-        GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
-                xevent->xselectionrequest.requestor);
-        if (!window)
-            return GDK_FILTER_REMOVE;
-
-        g_object_unref(window);
-    }
-    return GDK_FILTER_CONTINUE;
-}
--- a/widget/gtk/nsClipboard.h
+++ b/widget/gtk/nsClipboard.h
@@ -5,54 +5,71 @@
  * 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/. */
 
 #ifndef __nsClipboard_h_
 #define __nsClipboard_h_
 
 #include "nsIClipboard.h"
 #include "nsIObserver.h"
+#include "nsIBinaryOutputStream.h"
 #include <gtk/gtk.h>
 
+// Default Gtk MIME for text
+#define GTK_DEFAULT_MIME_TEXT "UTF8_STRING"
+
+class nsRetrievalContext {
+public:
+    virtual guchar* WaitForClipboardContext(const char* aMimeType,
+                                            int32_t aWhichClipboard,
+                                            uint32_t* aContentLength) = 0;
+    virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
+                                int* aTargetNum) = 0;
+
+    nsRetrievalContext() {};
+    virtual ~nsRetrievalContext() {};
+
+protected:
+    // Idle timeout for receiving selection and property notify events (microsec)
+    static const int kClipboardTimeout;
+};
+
 class nsClipboard : public nsIClipboard,
                     public nsIObserver
 {
 public:
     nsClipboard();
     
     NS_DECL_ISUPPORTS
-    
+    NS_DECL_NSIOBSERVER
     NS_DECL_NSICLIPBOARD
-    NS_DECL_NSIOBSERVER
 
     // Make sure we are initialized, called from the factory
     // constructor
     nsresult  Init              (void);
 
     // Someone requested the selection
     void   SelectionGetEvent    (GtkClipboard     *aGtkClipboard,
                                  GtkSelectionData *aSelectionData);
     void   SelectionClearEvent  (GtkClipboard     *aGtkClipboard);
 
 private:
     virtual ~nsClipboard();
 
-    // Utility methods
-    static GdkAtom               GetSelectionAtom (int32_t aWhichClipboard);
-    static GtkSelectionData     *GetTargets       (GdkAtom aWhichClipboard);
-
     // Save global clipboard content to gtk
     nsresult                     Store            (void);
 
     // Get our hands on the correct transferable, given a specific
     // clipboard
     nsITransferable             *GetTransferable  (int32_t aWhichClipboard);
 
     // Hang on to our owners and transferables so we can transfer data
     // when asked.
-    nsCOMPtr<nsIClipboardOwner>  mSelectionOwner;
-    nsCOMPtr<nsIClipboardOwner>  mGlobalOwner;
-    nsCOMPtr<nsITransferable>    mSelectionTransferable;
-    nsCOMPtr<nsITransferable>    mGlobalTransferable;
-
+    nsCOMPtr<nsIClipboardOwner>    mSelectionOwner;
+    nsCOMPtr<nsIClipboardOwner>    mGlobalOwner;
+    nsCOMPtr<nsITransferable>      mSelectionTransferable;
+    nsCOMPtr<nsITransferable>      mGlobalTransferable;
+    nsAutoPtr<nsRetrievalContext>  mContext;
 };
 
+GdkAtom GetSelectionAtom(int32_t aWhichClipboard);
+
 #endif /* __nsClipboard_h_ */
new file mode 100644
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.cpp
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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"
+#include "nsClipboardX11.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsIServiceManager.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+
+// For manipulation of the X event queue
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include "X11UndefineNone.h"
+
+using namespace mozilla;
+
+static GdkFilterReturn
+selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
+{
+    XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
+    if (xevent->xany.type == SelectionRequest) {
+        if (xevent->xselectionrequest.requestor == X11None)
+            return GDK_FILTER_REMOVE;
+
+        GdkDisplay *display = gdk_x11_lookup_xdisplay(
+                xevent->xselectionrequest.display);
+        if (!display)
+            return GDK_FILTER_REMOVE;
+
+        GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
+                xevent->xselectionrequest.requestor);
+        if (!window)
+            return GDK_FILTER_REMOVE;
+
+        g_object_unref(window);
+    }
+    return GDK_FILTER_CONTINUE;
+}
+
+nsRetrievalContextX11::nsRetrievalContextX11()
+  : mState(INITIAL)
+  , mData(nullptr)
+  , mRequestStamp(0)
+{
+    // A custom event filter to workaround attempting to dereference a null
+    // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
+#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11)
+    if (gtk_check_version(3, 11, 3))
+        gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
+#endif
+}
+
+nsRetrievalContextX11::~nsRetrievalContextX11()
+{
+    gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
+}
+
+static void
+DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
+{
+    GdkEvent event;
+    event.selection.type = GDK_SELECTION_NOTIFY;
+    event.selection.window = gtk_widget_get_window(widget);
+    event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
+    event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
+    event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
+    event.selection.time = xevent->xselection.time;
+
+    gtk_widget_event(widget, &event);
+}
+
+static void
+DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
+{
+    GdkWindow *window = gtk_widget_get_window(widget);
+    if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
+        GdkEvent event;
+        event.property.type = GDK_PROPERTY_NOTIFY;
+        event.property.window = window;
+        event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
+        event.property.time = xevent->xproperty.time;
+        event.property.state = xevent->xproperty.state;
+
+        gtk_widget_event(widget, &event);
+    }
+}
+
+struct checkEventContext
+{
+    GtkWidget *cbWidget;
+    Atom       selAtom;
+};
+
+static Bool
+checkEventProc(Display *display, XEvent *event, XPointer arg)
+{
+    checkEventContext *context = (checkEventContext *) arg;
+
+    if (event->xany.type == SelectionNotify ||
+        (event->xany.type == PropertyNotify &&
+         event->xproperty.atom == context->selAtom)) {
+
+        GdkWindow *cbWindow =
+            gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
+                                              event->xany.window);
+        if (cbWindow) {
+            GtkWidget *cbWidget = nullptr;
+            gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
+            if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
+                context->cbWidget = cbWidget;
+                return True;
+            }
+        }
+    }
+
+    return False;
+}
+
+void *
+nsRetrievalContextX11::Wait()
+{
+    if (mState == COMPLETED) { // the request completed synchronously
+        void *data = mData;
+        mData = nullptr;
+        return data;
+    }
+
+    GdkDisplay *gdkDisplay = gdk_display_get_default();
+    if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+        Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+        checkEventContext context;
+        context.cbWidget = nullptr;
+        context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
+                                                                FALSE));
+
+        // Send X events which are relevant to the ongoing selection retrieval
+        // to the clipboard widget.  Wait until either the operation completes, or
+        // we hit our timeout.  All other X events remain queued.
+
+        int select_result;
+
+        int cnumber = ConnectionNumber(xDisplay);
+        fd_set select_set;
+        FD_ZERO(&select_set);
+        FD_SET(cnumber, &select_set);
+        ++cnumber;
+        TimeStamp start = TimeStamp::Now();
+
+        do {
+            XEvent xevent;
+
+            while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
+                                 (XPointer) &context)) {
+
+                if (xevent.xany.type == SelectionNotify)
+                    DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
+                else
+                    DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
+
+                if (mState == COMPLETED) {
+                    void *data = mData;
+                    mData = nullptr;
+                    return data;
+                }
+            }
+
+            TimeStamp now = TimeStamp::Now();
+            struct timeval tv;
+            tv.tv_sec = 0;
+            tv.tv_usec = std::max<int32_t>(0,
+                kClipboardTimeout - (now - start).ToMicroseconds());
+            select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
+        } while (select_result == 1 ||
+                 (select_result == -1 && errno == EINTR));
+    }
+#ifdef DEBUG_CLIPBOARD
+    printf("exceeded clipboard timeout\n");
+#endif
+    mState = TIMED_OUT;
+    return nullptr;
+}
+
+// Call this when data has been retrieved.
+void nsRetrievalContextX11::Complete(GtkSelectionData* aData, int aRequestStamp)
+{
+  if (mRequestStamp != aRequestStamp)
+      return;
+
+  if (mState == INITIAL) {
+      mState = COMPLETED;
+      mData = gtk_selection_data_get_length(aData) >= 0 ?
+              gtk_selection_data_copy(aData) : nullptr;
+  } else {
+      // Already timed out
+      MOZ_ASSERT(mState == TIMED_OUT);
+  }
+}
+
+static void
+clipboard_contents_received(GtkClipboard     *clipboard,
+                            GtkSelectionData *selection_data,
+                            gpointer          data)
+{
+    nsRequestStampShell *shell = static_cast<nsRequestStampShell*>(data);
+    shell->Complete(selection_data);
+    delete shell;
+}
+
+GtkSelectionData*
+nsRetrievalContextX11::WaitForContents(GtkClipboard *clipboard,
+                                       const char *aMimeType)
+{
+    mState = INITIAL;
+    NS_ASSERTION(!mData, "Leaking clipboard content!");
+
+    mRequestStamp++;
+    nsRequestStampShell* shell = new nsRequestStampShell(this, mRequestStamp);
+
+    gtk_clipboard_request_contents(clipboard,
+                                   gdk_atom_intern(aMimeType, FALSE),
+                                   clipboard_contents_received,
+                                   shell);
+    return static_cast<GtkSelectionData*>(Wait());
+}
+
+GdkAtom*
+nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard, int* aTargetNum)
+{
+  *aTargetNum = 0;
+
+  GtkClipboard *clipboard =
+      gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+  GtkSelectionData *selection_data = WaitForContents(clipboard, "TARGETS");
+  if (!selection_data)
+      return nullptr;
+
+  gint n_targets = 0;
+  GdkAtom *targets = nullptr;
+
+  if (!gtk_selection_data_get_targets(selection_data, &targets, &n_targets) ||
+      !n_targets) {
+      return nullptr;
+  }
+
+  gtk_selection_data_free(selection_data);
+
+  *aTargetNum = n_targets;
+  return targets;
+}
+
+guchar*
+nsRetrievalContextX11::WaitForClipboardContext(const char* aMimeType,
+                                               int32_t aWhichClipboard,
+                                               uint32_t* aContentLength)
+{
+    GtkClipboard *clipboard;
+    clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+    GtkSelectionData *selectionData = WaitForContents(clipboard, aMimeType);
+    if (!selectionData)
+        return nullptr;
+
+    int contentLength = gtk_selection_data_get_length(selectionData);
+    guchar* data = reinterpret_cast<guchar*>(g_malloc(sizeof(guchar)*contentLength));
+    memcpy(data, gtk_selection_data_get_data(selectionData),
+           sizeof(guchar)*contentLength);
+    gtk_selection_data_free(selectionData);
+
+    *aContentLength = contentLength;
+    return data;
+}
new file mode 100644
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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/. */
+
+#ifndef __nsClipboardX11_h_
+#define __nsClipboardX11_h_
+
+#include "nsIClipboard.h"
+#include <gtk/gtk.h>
+
+class nsRetrievalContextX11 : public nsRetrievalContext
+{
+public:
+    enum State { INITIAL, COMPLETED, TIMED_OUT };
+
+    virtual guchar* WaitForClipboardContext(const char* aMimeType,
+                                            int32_t aWhichClipboard,
+                                            uint32_t* aContentLength) override;
+    virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
+                                int* aTargetNum) override;
+
+    // Call this when data has been retrieved.
+    void Complete(GtkSelectionData* aData, int aRequestStamp);
+
+    nsRetrievalContextX11();
+    virtual ~nsRetrievalContextX11() override;
+
+private:
+    GtkSelectionData* WaitForContents(GtkClipboard *clipboard,
+                                      const char *aMimeType);
+    /**
+     * Spins X event loop until timing out or being completed. Returns
+     * null if we time out, otherwise returns the completed data (passing
+     * ownership to caller).
+     */
+    void *Wait();
+
+    State mState;
+    void* mData;
+    int   mRequestStamp;
+};
+
+class nsRequestStampShell
+{
+public:
+    nsRequestStampShell(nsRetrievalContextX11 *aContext, int aRequestStamp)
+      : mContext(aContext)
+      , mRequestStamp(aRequestStamp)
+      {}
+
+    void  Complete(GtkSelectionData* aData)
+    {
+      mContext->Complete(aData, mRequestStamp);
+    }
+
+private:
+    nsRetrievalContextX11 *mContext;
+    int                    mRequestStamp;
+};
+
+#endif /* __nsClipboardX11_h_ */