Bug 1417874 - Split clipboard implementation to shared (nsClipboard) and backend specific (nsClipboardX11) parts, r=jhorak
authorMartin Stransky <stransky@redhat.com>
Thu, 16 Nov 2017 14:00:00 +0100
changeset 395708 296dd5f8849a593383d991338a82024aac46b546
parent 395707 463d676df5dad6cf9d39f5e6be3e559a55a4abe4
child 395709 c9c66aa3cfe76422f25746fb802a042fe2c15730
push id33054
push userrgurzau@mozilla.com
push dateFri, 08 Dec 2017 21:57:23 +0000
treeherdermozilla-central@6e2181b6137c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjhorak
bugs1417874
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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,34 @@
 /* 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 "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 +49,35 @@ 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 +87,39 @@ nsClipboard::~nsClipboard()
     }
 }
 
 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
 
 nsresult
 nsClipboard::Init(void)
 {
-    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    if (!os)
-      return NS_ERROR_FAILURE;
+    // create nsRetrievalContext
+    if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+        mContext = new nsRetrievalContextX11();
+    }
+    return NS_OK;
+}
 
-    os->AddObserver(this, "quit-application", false);
 
-    // 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)
+{
+    if (mGlobalTransferable) {
+        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 +224,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 +362,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 targetNums;
+  GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
 
-    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, targetNums)) {
+          *_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 < targetNums; 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
@@ -611,31 +560,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 +642,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 +673,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 +749,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,304 @@
+/* -*- 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)
+  , mClipboardRequestNumber(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 aDataRequestNumber)
+{
+  if (mClipboardRequestNumber != aDataRequestNumber) {
+      NS_WARNING("nsRetrievalContextX11::Complete() got obsoleted clipboard data.");
+      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)
+{
+    ClipboardRequestHandler *handler =
+        static_cast<ClipboardRequestHandler*>(data);
+    handler->Complete(selection_data);
+    delete handler;
+}
+
+GtkSelectionData*
+nsRetrievalContextX11::WaitForContents(GtkClipboard *clipboard,
+                                       const char *aMimeType)
+{
+    mState = INITIAL;
+    NS_ASSERTION(!mData, "Leaking clipboard content!");
+
+    // Call ClipboardRequestHandler() with unique clipboard request number.
+    // The request number pairs gtk_clipboard_request_contents() data request
+    // with clipboard_contents_received() callback where the data
+    // is provided by Gtk.
+    mClipboardRequestNumber++;
+    ClipboardRequestHandler* handler =
+        new ClipboardRequestHandler(this, mClipboardRequestNumber);
+
+    gtk_clipboard_request_contents(clipboard,
+                                   gdk_atom_intern(aMimeType, FALSE),
+                                   clipboard_contents_received,
+                                   handler);
+    return static_cast<GtkSelectionData*>(Wait());
+}
+
+GdkAtom*
+nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard, int* aTargetNums)
+{
+  *aTargetNums = 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);
+
+  *aTargetNums = 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* aTargetNums) override;
+
+    // Call this when data has been retrieved.
+    void Complete(GtkSelectionData* aData, int aDataRequestNumber);
+
+    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   mClipboardRequestNumber;
+};
+
+class ClipboardRequestHandler
+{
+public:
+    ClipboardRequestHandler(nsRetrievalContextX11 *aContext, int aDataRequestNumber)
+      : mContext(aContext)
+      , mDataRequestNumber(aDataRequestNumber)
+      {}
+
+    void  Complete(GtkSelectionData* aData)
+    {
+      mContext->Complete(aData, mDataRequestNumber);
+    }
+
+private:
+    nsRetrievalContextX11 *mContext;
+    int                    mDataRequestNumber;
+};
+
+#endif /* __nsClipboardX11_h_ */