Bug 1417892 - Implement primary clipboard selection under Wayland. r=jhorak, a=ritu
authorMartin Stransky <stransky@redhat.com>
Wed, 04 Apr 2018 14:49:21 +0200
changeset 463546 22117e0e107971a1504b12aaa68320866579ffbc
parent 463545 6d6f3196035ca18d2666f746a32ed8a6e04b2dbb
child 463547 344e70fd7bcd584a63b29da0c5ea8cb08c0cf267
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjhorak, ritu
bugs1417892
milestone60.0
Bug 1417892 - Implement primary clipboard selection under Wayland. r=jhorak, a=ritu MozReview-Commit-ID: 7TTBSbx8qPX
widget/gtk/moz.build
widget/gtk/nsClipboard.cpp
widget/gtk/nsClipboard.h
widget/gtk/nsClipboardWayland.cpp
widget/gtk/nsClipboardWayland.h
widget/gtk/nsClipboardX11.cpp
widget/gtk/nsClipboardX11.h
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -13,17 +13,17 @@ with Files("*CompositorWidget*"):
 with Files("*WindowSurface*"):
     BUG_COMPONENT = ("Core", "Graphics")
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
     DIRS += ['mozgtk']
 
 if CONFIG['MOZ_WAYLAND']:
     if CONFIG['GLIB_LIBS']:
-        DIRS += ['mozwayland']
+        DIRS += ['wayland', 'mozwayland']
     else:
         error('We support Wayland on glibc systems only, see Bug 1409707 for reference.')
 
 EXPORTS += [
     'mozcontainer.h',
     'nsGTKToolkit.h',
     'nsIImageToPixbuf.h',
 ]
--- a/widget/gtk/nsClipboard.cpp
+++ b/widget/gtk/nsClipboard.cpp
@@ -31,16 +31,19 @@
 #include <gtk/gtk.h>
 #include <gtk/gtkx.h>
 
 #include "mozilla/Encoding.h"
 
 
 using namespace mozilla;
 
+// Idle timeout for receiving selection and property notify events (microsec)
+const int kClipboardTimeout = 500000;
+
 // Callback when someone asks us for the data
 void
 clipboard_get_cb(GtkClipboard *aGtkClipboard,
                  GtkSelectionData *aSelectionData,
                  guint info,
                  gpointer user_data);
 
 // Callback when someone asks us to clear a clipboard
@@ -61,26 +64,18 @@ GdkAtom
 GetSelectionAtom(int32_t aWhichClipboard)
 {
     if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
         return GDK_SELECTION_CLIPBOARD;
 
     return GDK_SELECTION_PRIMARY;
 }
 
-// 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));
@@ -90,24 +85,35 @@ nsClipboard::~nsClipboard()
     }
 }
 
 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
 
 nsresult
 nsClipboard::Init(void)
 {
-    // create nsRetrievalContext
-    if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+    GdkDisplay *display = gdk_display_get_default();
+
+    // Create a nsRetrievalContext. If there's no default display
+    // create the X11 one as a fallback.
+    if (!display || GDK_IS_X11_DISPLAY(display)) {
         mContext = new nsRetrievalContextX11();
 #if defined(MOZ_WAYLAND)
     } else {
         mContext = new nsRetrievalContextWayland();
 #endif
     }
+    NS_ASSERTION(mContext, "Missing nsRetrievalContext for nsClipboard!");
+
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+        os->AddObserver(this, "quit-application", false);
+        os->AddObserver(this, "xpcom-shutdown", false);
+    }
+
     return NS_OK;
 }
 
 
 nsresult
 nsClipboard::Store(void)
 {
     if (mGlobalTransferable) {
@@ -427,19 +433,17 @@ nsClipboard::HasDataMatchingFlavors(cons
 
   g_free(targets);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::SupportsSelectionClipboard(bool *_retval)
 {
-    // yeah, unix supports the selection clipboard on X11
-    // TODO Wayland
-    *_retval = GDK_IS_X11_DISPLAY(gdk_display_get_default());
+    *_retval = mContext->HasSelectionSupport();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::SupportsFindClipboard(bool* _retval)
 {
   *_retval = false;
   return NS_OK;
--- a/widget/gtk/nsClipboard.h
+++ b/widget/gtk/nsClipboard.h
@@ -23,22 +23,19 @@ public:
     virtual const char* GetClipboardText(int32_t aWhichClipboard) = 0;
     virtual void ReleaseClipboardData(const char* aClipboardData) = 0;
 
     // Get data mime types which can be obtained from clipboard.
     // The returned array has to be released by g_free().
     virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
                                 int* aTargetNum) = 0;
 
-    nsRetrievalContext() {};
-    virtual ~nsRetrievalContext() {};
+    virtual bool HasSelectionSupport(void) = 0;
 
-protected:
-    // Idle timeout for receiving selection and property notify events (microsec)
-    static const int kClipboardTimeout;
+    virtual ~nsRetrievalContext() {};
 };
 
 class nsClipboard : public nsIClipboard,
                     public nsIObserver
 {
 public:
     nsClipboard();
 
@@ -75,11 +72,13 @@ private:
     // when asked.
     nsCOMPtr<nsIClipboardOwner>    mSelectionOwner;
     nsCOMPtr<nsIClipboardOwner>    mGlobalOwner;
     nsCOMPtr<nsITransferable>      mSelectionTransferable;
     nsCOMPtr<nsITransferable>      mGlobalTransferable;
     nsAutoPtr<nsRetrievalContext>  mContext;
 };
 
+extern const int kClipboardTimeout;
+
 GdkAtom GetSelectionAtom(int32_t aWhichClipboard);
 
 #endif /* __nsClipboard_h_ */
--- a/widget/gtk/nsClipboardWayland.cpp
+++ b/widget/gtk/nsClipboardWayland.cpp
@@ -31,91 +31,297 @@
 #include <sys/epoll.h>
 #include <stdlib.h>
 #include <string.h>
 #include <fcntl.h>
 #include <gtk/gtk.h>
 #include <gdk/gdkwayland.h>
 #include <errno.h>
 
-void
-nsRetrievalContextWayland::ResetMIMETypeList(void)
-{
-  mTargetMIMETypes.Clear();
-}
-
-void
-nsRetrievalContextWayland::AddMIMEType(const char *aMimeType)
-{
-  GdkAtom atom = gdk_atom_intern(aMimeType, FALSE);
-  mTargetMIMETypes.AppendElement(atom);
-}
+#include "wayland/gtk-primary-selection-client-protocol.h"
 
 void
-nsRetrievalContextWayland::SetDataOffer(wl_data_offer *aDataOffer)
+DataOffer::AddMIMEType(const char *aMimeType)
+{
+    GdkAtom atom = gdk_atom_intern(aMimeType, FALSE);
+    mTargetMIMETypes.AppendElement(atom);
+}
+
+GdkAtom*
+DataOffer::GetTargets(int* aTargetNum)
 {
-    if(mDataOffer) {
-        wl_data_offer_destroy(mDataOffer);
+    int length = mTargetMIMETypes.Length();
+    if (!length) {
+        *aTargetNum = 0;
+        return nullptr;
     }
-    mDataOffer = aDataOffer;
+
+    GdkAtom* targetList = reinterpret_cast<GdkAtom*>(
+        g_malloc(sizeof(GdkAtom)*length));
+    for (int32_t j = 0; j < length; j++) {
+        targetList[j] = mTargetMIMETypes[j];
+    }
+
+    *aTargetNum = length;
+    return targetList;
 }
 
-static void
-data_device_selection (void                  *data,
-                       struct wl_data_device *wl_data_device,
-                       struct wl_data_offer  *offer)
+char*
+DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
+                   uint32_t* aContentLength)
 {
-    nsRetrievalContextWayland *context =
-        static_cast<nsRetrievalContextWayland*>(data);
-    context->SetDataOffer(offer);
+    int pipe_fd[2];
+    if (pipe(pipe_fd) == -1)
+        return nullptr;
+
+    if (!RequestDataTransfer(aMimeType, pipe_fd[1])) {
+        NS_WARNING("DataOffer::RequestDataTransfer() failed!");
+        close(pipe_fd[0]);
+        close(pipe_fd[1]);
+        return nullptr;
+    }
+
+    close(pipe_fd[1]);
+    wl_display_flush(aDisplay);
+
+    struct pollfd fds;
+    fds.fd = pipe_fd[0];
+    fds.events = POLLIN;
+
+    // Choose some reasonable timeout here
+    int ret = poll(&fds, 1, kClipboardTimeout / 1000);
+    if (!ret || ret == -1) {
+        close(pipe_fd[0]);
+        return nullptr;
+    }
+
+    GIOChannel *channel = g_io_channel_unix_new(pipe_fd[0]);
+    GError* error = nullptr;
+    char* clipboardData;
+
+    g_io_channel_set_encoding(channel, nullptr, &error);
+    if (!error) {
+        gsize length = 0;
+        g_io_channel_read_to_end(channel, &clipboardData, &length, &error);
+        if (length == 0) {
+            // We don't have valid clipboard data although
+            // g_io_channel_read_to_end() allocated clipboardData for us.
+            // Release it now and return nullptr to indicate
+            // we don't have reqested data flavour.
+            g_free((void *)clipboardData);
+            clipboardData = nullptr;
+        }
+        *aContentLength = length;
+    }
+
+    if (error) {
+        NS_WARNING(
+            nsPrintfCString("Unexpected error when reading clipboard data: %s",
+                            error->message).get());
+        g_error_free(error);
+    }
+
+    g_io_channel_unref(channel);
+    close(pipe_fd[0]);
+
+    return clipboardData;
+}
+
+bool
+WaylandDataOffer::RequestDataTransfer(const char* aMimeType, int fd)
+{
+    if (mWaylandDataOffer) {
+        wl_data_offer_receive(mWaylandDataOffer, aMimeType, fd);
+        return true;
+    }
+
+    return false;
 }
 
 static void
 data_offer_offer (void                 *data,
                   struct wl_data_offer *wl_data_offer,
                   const char           *type)
 {
-  nsRetrievalContextWayland *context =
-      static_cast<nsRetrievalContextWayland*>(data);
-  context->AddMIMEType(type);
+    auto *offer = static_cast<DataOffer*>(data);
+    offer->AddMIMEType(type);
 }
 
 static void
 data_offer_source_actions(void *data,
                           struct wl_data_offer *wl_data_offer,
                           uint32_t source_actions)
 {
 }
 
 static void
 data_offer_action(void *data,
                   struct wl_data_offer *wl_data_offer,
                   uint32_t dnd_action)
 {
 }
 
+/* wl_data_offer callback description:
+ *
+ * data_offer_offer - Is called for each MIME type available at wl_data_offer.
+ * data_offer_source_actions - Exposes all available D&D actions.
+ * data_offer_action - Expose one actually selected D&D action.
+ */
 static const struct wl_data_offer_listener data_offer_listener = {
     data_offer_offer,
     data_offer_source_actions,
     data_offer_action
 };
 
+WaylandDataOffer::WaylandDataOffer(wl_data_offer* aWaylandDataOffer)
+  : mWaylandDataOffer(aWaylandDataOffer)
+{
+    wl_data_offer_add_listener(mWaylandDataOffer, &data_offer_listener, this);
+}
+
+WaylandDataOffer::~WaylandDataOffer(void)
+{
+    if(mWaylandDataOffer) {
+        wl_data_offer_destroy(mWaylandDataOffer);
+    }
+}
+
+bool
+PrimaryDataOffer::RequestDataTransfer(const char* aMimeType, int fd)
+{
+    if (mPrimaryDataOffer) {
+        gtk_primary_selection_offer_receive(mPrimaryDataOffer, aMimeType, fd);
+        return true;
+    }
+    return false;
+}
+
+static void
+primary_data_offer(void *data,
+                   gtk_primary_selection_offer *gtk_primary_selection_offer,
+                   const char *mime_type)
+{
+    auto *offer = static_cast<DataOffer*>(data);
+    offer->AddMIMEType(mime_type);
+}
+
+/* gtk_primary_selection_offer_listener callback description:
+ *
+ * primary_data_offer - Is called for each MIME type available at
+ *                      gtk_primary_selection_offer.
+ */
+static const struct gtk_primary_selection_offer_listener
+primary_selection_offer_listener = {
+    primary_data_offer
+};
+
+PrimaryDataOffer::PrimaryDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer)
+  : mPrimaryDataOffer(aPrimaryDataOffer)
+{
+    gtk_primary_selection_offer_add_listener(aPrimaryDataOffer,
+        &primary_selection_offer_listener, this);
+}
+
+PrimaryDataOffer::~PrimaryDataOffer(void)
+{
+    if(mPrimaryDataOffer) {
+        gtk_primary_selection_offer_destroy(mPrimaryDataOffer);
+    }
+}
+
+void
+nsRetrievalContextWayland::RegisterDataOffer(wl_data_offer *aWaylandDataOffer)
+{
+  DataOffer* dataOffer =
+      static_cast<DataOffer*>(g_hash_table_lookup(mActiveOffers,
+                                                  aWaylandDataOffer));
+  if (!dataOffer) {
+      dataOffer = new WaylandDataOffer(aWaylandDataOffer);
+      g_hash_table_insert(mActiveOffers, aWaylandDataOffer, dataOffer);
+  }
+}
+
+void
+nsRetrievalContextWayland::RegisterDataOffer(
+    gtk_primary_selection_offer *aPrimaryDataOffer)
+{
+  DataOffer* dataOffer =
+      static_cast<DataOffer*>(g_hash_table_lookup(mActiveOffers,
+                                                  aPrimaryDataOffer));
+  if (!dataOffer) {
+      dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
+      g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
+  }
+}
+
+void
+nsRetrievalContextWayland::SetClipboardDataOffer(wl_data_offer *aWaylandDataOffer)
+{
+    DataOffer* dataOffer =
+        static_cast<DataOffer*>(g_hash_table_lookup(mActiveOffers,
+                                                    aWaylandDataOffer));
+    NS_ASSERTION(dataOffer, "We're missing clipboard data offer!");
+    if (dataOffer) {
+        g_hash_table_remove(mActiveOffers, aWaylandDataOffer);
+        mClipboardOffer = dataOffer;
+    }
+}
+
+void
+nsRetrievalContextWayland::SetPrimaryDataOffer(
+      gtk_primary_selection_offer *aPrimaryDataOffer)
+{
+    if (aPrimaryDataOffer == nullptr) {
+        // Release any primary offer we have.
+        mPrimaryOffer = nullptr;
+    } else {
+        DataOffer* dataOffer =
+            static_cast<DataOffer*>(g_hash_table_lookup(mActiveOffers,
+                                                        aPrimaryDataOffer));
+        NS_ASSERTION(dataOffer, "We're missing primary data offer!");
+        if (dataOffer) {
+            g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
+            mPrimaryOffer = dataOffer;
+        }
+    }
+}
+
+void
+nsRetrievalContextWayland::ClearDataOffers(void)
+{
+    if (mClipboardOffer)
+        mClipboardOffer = nullptr;
+    if (mPrimaryOffer)
+        mPrimaryOffer = nullptr;
+}
+
+// We have a new fresh data content.
+// We should attach listeners to it and save for further use.
 static void
 data_device_data_offer (void                  *data,
                         struct wl_data_device *data_device,
                         struct wl_data_offer  *offer)
 {
     nsRetrievalContextWayland *context =
         static_cast<nsRetrievalContextWayland*>(data);
-
-    // We have a new fresh clipboard content
-    context->ResetMIMETypeList();
-    wl_data_offer_add_listener (offer, &data_offer_listener, data);
+    context->RegisterDataOffer(offer);
 }
 
+// The new fresh data content is clipboard.
+static void
+data_device_selection (void                  *data,
+                       struct wl_data_device *wl_data_device,
+                       struct wl_data_offer  *offer)
+{
+    nsRetrievalContextWayland *context =
+        static_cast<nsRetrievalContextWayland*>(data);
+    context->SetClipboardDataOffer(offer);
+}
+
+// The new fresh wayland data content is drag and drop.
 static void
 data_device_enter (void                  *data,
                    struct wl_data_device *data_device,
                    uint32_t               time,
                    struct wl_surface     *surface,
                    int32_t                x,
                    int32_t                y,
                    struct wl_data_offer  *offer)
@@ -138,26 +344,79 @@ data_device_motion (void                
 }
 
 static void
 data_device_drop (void                  *data,
                   struct wl_data_device *data_device)
 {
 }
 
+/* wl_data_device callback description:
+ *
+ * data_device_data_offer - It's called when there's a new wl_data_offer
+ *                          available. We need to attach wl_data_offer_listener
+ *                          to it to get available MIME types.
+ *
+ * data_device_selection - It's called when the new wl_data_offer
+ *                         is a clipboard content.
+ *
+ * data_device_enter - It's called when the new wl_data_offer is a drag & drop
+ *                     content and it's tied to actual wl_surface.
+ * data_device_leave - It's called when the wl_data_offer (drag & dop) is not
+ *                     valid any more.
+ * data_device_motion - It's called when the drag and drop selection moves across
+ *                      wl_surface.
+ * data_device_drop - It's called when D&D operation is sucessfully finished and
+ *                    we can read the data from D&D.
+ *                    It's generated only if we call wl_data_offer_accept() and
+ *                    wl_data_offer_set_actions() from data_device_motion callback.
+ */
 static const struct wl_data_device_listener data_device_listener = {
     data_device_data_offer,
     data_device_enter,
     data_device_leave,
     data_device_motion,
     data_device_drop,
     data_device_selection
 };
 
 static void
+primary_selection_data_offer (void                                *data,
+                              struct gtk_primary_selection_device *gtk_primary_selection_device,
+                              struct gtk_primary_selection_offer  *gtk_primary_offer)
+{
+    // create and add listener
+    nsRetrievalContextWayland *context =
+        static_cast<nsRetrievalContextWayland*>(data);
+    context->RegisterDataOffer(gtk_primary_offer);
+}
+
+static void
+primary_selection_selection (void                                *data,
+                             struct gtk_primary_selection_device *gtk_primary_selection_device,
+                             struct gtk_primary_selection_offer  *gtk_primary_offer)
+{
+    nsRetrievalContextWayland *context =
+        static_cast<nsRetrievalContextWayland*>(data);
+    context->SetPrimaryDataOffer(gtk_primary_offer);
+}
+
+static const struct
+gtk_primary_selection_device_listener primary_selection_device_listener = {
+    primary_selection_data_offer,
+    primary_selection_selection,
+};
+
+bool
+nsRetrievalContextWayland::HasSelectionSupport(void)
+{
+    return mPrimarySelectionDataDeviceManager != nullptr;
+}
+
+static void
 keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
                        uint32_t format, int fd, uint32_t size)
 {
 }
 
 static void
 keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
                       uint32_t serial, struct wl_surface *surface,
@@ -168,18 +427,17 @@ keyboard_handle_enter(void *data, struct
 static void
 keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
                       uint32_t serial, struct wl_surface *surface)
 {
     // We lost focus so our clipboard data are outdated
     nsRetrievalContextWayland *context =
         static_cast<nsRetrievalContextWayland*>(data);
 
-    context->ResetMIMETypeList();
-    context->SetDataOffer(nullptr);
+    context->ClearDataOffers();
 }
 
 static void
 keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
                     uint32_t serial, uint32_t time, uint32_t key,
                     uint32_t state)
 {
 }
@@ -228,44 +486,56 @@ static const struct wl_seat_listener sea
       seat_handle_capabilities,
 };
 
 void
 nsRetrievalContextWayland::InitDataDeviceManager(wl_registry *registry,
                                                  uint32_t id,
                                                  uint32_t version)
 {
-  int data_device_manager_version = MIN (version, 3);
-  mDataDeviceManager = (wl_data_device_manager *)wl_registry_bind(registry, id,
-      &wl_data_device_manager_interface, data_device_manager_version);
+    int data_device_manager_version = MIN (version, 3);
+    mDataDeviceManager = (wl_data_device_manager *)wl_registry_bind(registry, id,
+        &wl_data_device_manager_interface, data_device_manager_version);
 }
 
-void nsRetrievalContextWayland::InitSeat(wl_registry *registry,
-                                         uint32_t id, uint32_t version,
-                                         void *data)
+void
+nsRetrievalContextWayland::InitPrimarySelectionDataDeviceManager(
+  wl_registry *registry, uint32_t id)
 {
-  mSeat = (wl_seat*)wl_registry_bind(registry, id, &wl_seat_interface, 1);
-  wl_seat_add_listener(mSeat, &seat_listener, data);
+    mPrimarySelectionDataDeviceManager =
+        (gtk_primary_selection_device_manager *)wl_registry_bind(registry, id,
+            &gtk_primary_selection_device_manager_interface, 1);
+}
+
+void
+nsRetrievalContextWayland::InitSeat(wl_registry *registry,
+                                    uint32_t id, uint32_t version,
+                                    void *data)
+{
+    mSeat = (wl_seat*)wl_registry_bind(registry, id, &wl_seat_interface, 1);
+    wl_seat_add_listener(mSeat, &seat_listener, data);
 }
 
 static void
 gdk_registry_handle_global(void               *data,
                            struct wl_registry *registry,
                            uint32_t            id,
                            const char         *interface,
                            uint32_t            version)
 {
-  nsRetrievalContextWayland *context =
-      static_cast<nsRetrievalContextWayland*>(data);
+    nsRetrievalContextWayland *context =
+        static_cast<nsRetrievalContextWayland*>(data);
 
-  if (strcmp (interface, "wl_data_device_manager") == 0) {
-    context->InitDataDeviceManager(registry, id, version);
-  } else if (strcmp(interface, "wl_seat") == 0) {
-    context->InitSeat(registry, id, version, data);
-  }
+    if (strcmp (interface, "wl_data_device_manager") == 0) {
+        context->InitDataDeviceManager(registry, id, version);
+    } else if (strcmp(interface, "wl_seat") == 0) {
+        context->InitSeat(registry, id, version, data);
+    } else if (strcmp (interface, "gtk_primary_selection_device_manager") == 0) {
+        context->InitPrimarySelectionDataDeviceManager(registry, id);
+    }
 }
 
 static void
 gdk_registry_handle_global_remove(void               *data,
                                  struct wl_registry *registry,
                                  uint32_t            id)
 {
 }
@@ -274,26 +544,25 @@ static const struct wl_registry_listener
     gdk_registry_handle_global,
     gdk_registry_handle_global_remove
 };
 
 nsRetrievalContextWayland::nsRetrievalContextWayland(void)
   : mInitialized(false)
   , mSeat(nullptr)
   , mDataDeviceManager(nullptr)
-  , mDataOffer(nullptr)
+  , mPrimarySelectionDataDeviceManager(nullptr)
   , mKeyboard(nullptr)
+  , mActiveOffers(g_hash_table_new(NULL, NULL))
+  , mClipboardOffer(nullptr)
+  , mPrimaryOffer(nullptr)
   , mClipboardRequestNumber(0)
   , mClipboardData(nullptr)
   , mClipboardDataLength(0)
 {
-    const gchar* charset;
-    g_get_charset(&charset);
-    mTextPlainLocale = g_strdup_printf("text/plain;charset=%s", charset);
-
     // Available as of GTK 3.8+
     static auto sGdkWaylandDisplayGetWlDisplay =
         (wl_display *(*)(GdkDisplay *))
         dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_wl_display");
 
     mDisplay = sGdkWaylandDisplayGetWlDisplay(gdk_display_get_default());
     wl_registry_add_listener(wl_display_get_registry(mDisplay),
                              &clipboard_registry_listener, this);
@@ -312,42 +581,48 @@ nsRetrievalContextWayland::nsRetrievalCo
         wl_data_device_manager_get_data_device(mDataDeviceManager, mSeat);
     wl_data_device_add_listener(dataDevice, &data_device_listener, this);
     // We have to call wl_display_roundtrip() twice otherwise data_offer_listener
     // may not be processed because it's called from data_device_data_offer
     // callback.
     wl_display_roundtrip(mDisplay);
     wl_display_roundtrip(mDisplay);
 
+    if (mPrimarySelectionDataDeviceManager) {
+        gtk_primary_selection_device *primaryDataDevice =
+            gtk_primary_selection_device_manager_get_device(mPrimarySelectionDataDeviceManager,
+                                                            mSeat);
+        gtk_primary_selection_device_add_listener(primaryDataDevice,
+            &primary_selection_device_listener, this);
+    }
+
     mInitialized = true;
 }
 
 nsRetrievalContextWayland::~nsRetrievalContextWayland(void)
 {
-    g_free(mTextPlainLocale);
+    g_hash_table_destroy(mActiveOffers);
 }
 
 GdkAtom*
 nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard,
                                       int* aTargetNum)
 {
-    int length = mTargetMIMETypes.Length();
-    if (!length) {
-        *aTargetNum = 0;
-        return nullptr;
+    if (GetSelectionAtom(aWhichClipboard) == GDK_SELECTION_CLIPBOARD) {
+        if (mClipboardOffer) {
+            return mClipboardOffer->GetTargets(aTargetNum);
+        }
+    } else {
+        if (mPrimaryOffer) {
+            return mPrimaryOffer->GetTargets(aTargetNum);
+        }
     }
 
-    GdkAtom* targetList = reinterpret_cast<GdkAtom*>(
-        g_malloc(sizeof(GdkAtom)*length));
-    for (int32_t j = 0; j < length; j++) {
-        targetList[j] = mTargetMIMETypes[j];
-    }
-
-    *aTargetNum = length;
-    return targetList;
+    *aTargetNum = 0;
+    return nullptr;
 }
 
 struct FastTrackClipboard
 {
     FastTrackClipboard(int aClipboardRequestNumber,
                        nsRetrievalContextWayland* aRetrievalContex)
     : mClipboardRequestNumber(aClipboardRequestNumber)
     , mRetrievalContex(aRetrievalContex)
@@ -401,76 +676,27 @@ nsRetrievalContextWayland::GetClipboardD
     GdkAtom selection = GetSelectionAtom(aWhichClipboard);
     if (gdk_selection_owner_get(selection)) {
         mClipboardRequestNumber++;
         gtk_clipboard_request_contents(gtk_clipboard_get(selection),
             gdk_atom_intern(aMimeType, FALSE),
             wayland_clipboard_contents_received,
             new FastTrackClipboard(mClipboardRequestNumber, this));
     } else {
-        /* TODO: We need to implement GDK_SELECTION_PRIMARY (X11 text selection)
-         * for Wayland backend.
-         */
-        if (selection == GDK_SELECTION_PRIMARY)
-             return nullptr;
-
-        NS_ASSERTION(mDataOffer, "Requested data without valid data offer!");
-
-        if (!mDataOffer) {
-            // TODO
+        DataOffer* dataOffer = (selection == GDK_SELECTION_PRIMARY) ?
+                                  mPrimaryOffer : mClipboardOffer;
+        if (!dataOffer) {
             // Something went wrong. We're requested to provide clipboard data
-            // but we haven't got any from wayland. Looks like rhbz#1455915.
-            return nullptr;
-        }
-
-        int pipe_fd[2];
-        if (pipe(pipe_fd) == -1)
-            return nullptr;
-
-        wl_data_offer_receive(mDataOffer, aMimeType, pipe_fd[1]);
-        close(pipe_fd[1]);
-        wl_display_flush(mDisplay);
-
-        struct pollfd fds;
-        fds.fd = pipe_fd[0];
-        fds.events = POLLIN;
-
-        // Choose some reasonable timeout here
-        int ret = poll(&fds, 1, kClipboardTimeout / 1000);
-        if (!ret || ret == -1) {
-            close(pipe_fd[0]);
-            return nullptr;
-        }
-
-        GIOChannel *channel = g_io_channel_unix_new(pipe_fd[0]);
-        GError* error = nullptr;
-
-        g_io_channel_set_encoding(channel, nullptr, &error);
-        if (!error) {
-            gsize length = 0;
-            g_io_channel_read_to_end(channel, &mClipboardData, &length, &error);
-            mClipboardDataLength = length;
-        }
-
-        if (error) {
-            NS_WARNING(
-                nsPrintfCString("Unexpected error when reading clipboard data: %s",
-                                error->message).get());
-            g_error_free(error);
-        }
-
-        g_io_channel_unref(channel);
-        close(pipe_fd[0]);
-
-        // We don't have valid clipboard data although
-        // g_io_channel_read_to_end() allocated mClipboardData for us.
-        // Release it now and return nullptr to indicate
-        // we don't have reqested data flavour.
-        if (mClipboardData && mClipboardDataLength == 0) {
-            ReleaseClipboardData(mClipboardData);
+            // but we haven't got any from wayland.
+            NS_WARNING("Requested data without valid DataOffer!");
+            mClipboardData = nullptr;
+            mClipboardDataLength = 0;
+        } else {
+            mClipboardData = dataOffer->GetData(mDisplay,
+                aMimeType, &mClipboardDataLength);
         }
     }
 
     *aContentLength = mClipboardDataLength;
     return reinterpret_cast<const char*>(mClipboardData);
 }
 
 void nsRetrievalContextWayland::ReleaseClipboardData(const char* aClipboardData)
--- a/widget/gtk/nsClipboardWayland.h
+++ b/widget/gtk/nsClipboardWayland.h
@@ -4,54 +4,106 @@
 /* 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 __nsClipboardWayland_h_
 #define __nsClipboardWayland_h_
 
 #include "nsIClipboard.h"
+#include "wayland/gtk-primary-selection-client-protocol.h"
+
 #include <gtk/gtk.h>
 #include <gdk/gdkwayland.h>
 #include <nsTArray.h>
 
 struct FastTrackClipboard;
 
+class DataOffer
+{
+public:
+    void AddMIMEType(const char *aMimeType);
+
+    GdkAtom* GetTargets(int* aTargetNum);
+    char* GetData(wl_display* aDisplay, const char* aMimeType,
+                  uint32_t* aContentLength);
+
+    virtual ~DataOffer() {};
+private:
+    virtual bool RequestDataTransfer(const char* aMimeType, int fd) = 0;
+
+    nsTArray<GdkAtom> mTargetMIMETypes;
+};
+
+class WaylandDataOffer : public DataOffer
+{
+public:
+    WaylandDataOffer(wl_data_offer* aWaylandDataOffer);
+
+private:
+    virtual ~WaylandDataOffer();
+    bool RequestDataTransfer(const char* aMimeType, int fd) override;
+
+    wl_data_offer* mWaylandDataOffer;
+};
+
+class PrimaryDataOffer : public DataOffer
+{
+public:
+    PrimaryDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer);
+
+private:
+    virtual ~PrimaryDataOffer();
+    bool RequestDataTransfer(const char* aMimeType, int fd) override;
+
+    gtk_primary_selection_offer* mPrimaryDataOffer;
+};
+
 class nsRetrievalContextWayland : public nsRetrievalContext
 {
 public:
     nsRetrievalContextWayland();
 
     virtual const char* GetClipboardData(const char* aMimeType,
                                          int32_t aWhichClipboard,
                                          uint32_t* aContentLength) override;
     virtual void ReleaseClipboardData(const char* aClipboardData) override;
 
     virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
                                 int* aTargetNum) override;
+    virtual bool HasSelectionSupport(void) override;
 
-    void SetDataOffer(wl_data_offer *aDataOffer);
-    void AddMIMEType(const char *aMimeType);
-    void ResetMIMETypeList(void);
+    void RegisterDataOffer(wl_data_offer *aWaylandDataOffer);
+    void RegisterDataOffer(gtk_primary_selection_offer *aPrimaryDataOffer);
+
+    void SetClipboardDataOffer(wl_data_offer *aWaylandDataOffer);
+    void SetPrimaryDataOffer(gtk_primary_selection_offer *aPrimaryDataOffer);
+
+    void ClearDataOffers();
+
     void ConfigureKeyboard(wl_seat_capability caps);
     void TransferFastTrackClipboard(int aClipboardRequestNumber,
                                     GtkSelectionData *aSelectionData);
 
     void InitDataDeviceManager(wl_registry *registry, uint32_t id, uint32_t version);
+    void InitPrimarySelectionDataDeviceManager(wl_registry *registry, uint32_t id);
     void InitSeat(wl_registry *registry, uint32_t id, uint32_t version, void *data);
     virtual ~nsRetrievalContextWayland() override;
 
 private:
     bool                        mInitialized;
     wl_display                 *mDisplay;
     wl_seat                    *mSeat;
     wl_data_device_manager     *mDataDeviceManager;
-    wl_data_offer              *mDataOffer;
+    gtk_primary_selection_device_manager *mPrimarySelectionDataDeviceManager;
     wl_keyboard                *mKeyboard;
-    nsTArray<GdkAtom>           mTargetMIMETypes;
-    gchar                      *mTextPlainLocale;
+
+    // Data offers provided by Wayland data device
+    GHashTable*                 mActiveOffers;
+    nsAutoPtr<DataOffer>        mClipboardOffer;
+    nsAutoPtr<DataOffer>        mPrimaryOffer;
 
     int                         mClipboardRequestNumber;
     char*                       mClipboardData;
     uint32_t                    mClipboardDataLength;
 };
 
 #endif /* __nsClipboardWayland_h_ */
--- a/widget/gtk/nsClipboardX11.cpp
+++ b/widget/gtk/nsClipboardX11.cpp
@@ -32,16 +32,23 @@
 #include <sys/time.h>
 #include <sys/types.h>
 #include <errno.h>
 #include <unistd.h>
 #include "X11UndefineNone.h"
 
 using namespace mozilla;
 
+bool
+nsRetrievalContextX11::HasSelectionSupport(void)
+{
+  // yeah, unix supports the selection clipboard on X11.
+  return true;
+}
+
 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;
 
--- a/widget/gtk/nsClipboardX11.h
+++ b/widget/gtk/nsClipboardX11.h
@@ -26,16 +26,18 @@ public:
                                          int32_t aWhichClipboard,
                                          uint32_t* aContentLength) override;
     virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
     virtual void ReleaseClipboardData(const char* aClipboardData) override;
 
     virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
                                 int* aTargetNums) override;
 
+    virtual bool HasSelectionSupport(void) override;
+
     // Call this when data or text has been retrieved.
     void Complete(ClipboardDataType aDataType,
                   const void* aData,
                   int aDataRequestNumber);
 
     nsRetrievalContextX11();
     virtual ~nsRetrievalContextX11() override;