wayland: add a custom impl of OSExchangeDataProvider for Wayland

Having a custom implementation of OSExchangeDataProvider allows us,
amongst other things, to build up the mime types list, required to offer
data for drag-and-drop, in a generic way. To do so, this adds a new
class that inherits from OSExchangeDataProviderNonBacked and can inspect
its pickled data map and build mime types list out of it.

More data utility code will be moved into WaylandExchangeDataProvider in
upcoming CL(s).

R=msisov@igalia.com, sky@chromium.org

Bug: 1247063
Test: Covered by ozone_unittests.
Change-Id: Ia7f2279d1cdf091b3ad20c32256af68a0b5e39d9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3145735
Reviewed-by: Maksim Sisov <msisov@igalia.com>
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Nick Yamane <nickdiego@igalia.com>
Cr-Commit-Position: refs/heads/main@{#919514}
diff --git a/ui/base/dragdrop/os_exchange_data_provider_non_backed.h b/ui/base/dragdrop/os_exchange_data_provider_non_backed.h
index afee2ac6..3d3f8a98 100644
--- a/ui/base/dragdrop/os_exchange_data_provider_non_backed.h
+++ b/ui/base/dragdrop/os_exchange_data_provider_non_backed.h
@@ -78,6 +78,11 @@
   void SetSource(std::unique_ptr<DataTransferEndpoint> data_source) override;
   DataTransferEndpoint* GetSource() const override;
 
+ protected:
+  const std::map<ClipboardFormatType, base::Pickle>& pickle_data() const {
+    return pickle_data_;
+  }
+
  private:
   // Returns true if |formats_| contains a file format and the file name can be
   // parsed as a URL.
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 8a53edc..da84a1c 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -95,6 +95,8 @@
     "host/wayland_event_source.h",
     "host/wayland_event_watcher.cc",
     "host/wayland_event_watcher.h",
+    "host/wayland_exchange_data_provider.cc",
+    "host/wayland_exchange_data_provider.h",
     "host/wayland_input_method_context.cc",
     "host/wayland_input_method_context.h",
     "host/wayland_input_method_context_factory.cc",
@@ -222,6 +224,7 @@
     "//third_party/wayland-protocols:xdg_shell_protocol",
     "//ui/base",
     "//ui/base:buildflags",
+    "//ui/base:data_exchange",
     "//ui/base/cursor",
     "//ui/base/cursor:cursor_base",
     "//ui/base/cursor:theme_manager",
diff --git a/ui/ozone/platform/wayland/common/data_util.h b/ui/ozone/platform/wayland/common/data_util.h
index 39c0308..bdbbaa9 100644
--- a/ui/ozone/platform/wayland/common/data_util.h
+++ b/ui/ozone/platform/wayland/common/data_util.h
@@ -15,6 +15,8 @@
 
 namespace wl {
 
+// TODO(crbug.com/1247063): Move into WaylandExchangeDataProvider class.
+
 // Tells if |mime_type| is supported for Drag and Drop operations.
 bool IsMimeTypeSupported(const std::string& mime_type);
 
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
index 85b7a97..bdd1c04 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
@@ -10,12 +10,7 @@
 #include "base/check.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/no_destructor.h"
 #include "base/notreached.h"
-#include "base/pickle.h"
-#include "base/strings/strcat.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/clipboard/clipboard_constants.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
@@ -30,6 +25,7 @@
 #include "ui/ozone/platform/wayland/host/wayland_data_offer.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_source.h"
 #include "ui/ozone/platform/wayland/host/wayland_event_source.h"
+#include "ui/ozone/platform/wayland/host/wayland_exchange_data_provider.h"
 #include "ui/ozone/platform/wayland/host/wayland_serial_tracker.h"
 #include "ui/ozone/platform/wayland/host/wayland_shm_buffer.h"
 #include "ui/ozone/platform/wayland/host/wayland_surface.h"
@@ -81,14 +77,6 @@
   return icon_bitmap && !icon_bitmap->empty() ? icon_bitmap : nullptr;
 }
 
-// TODO(crbug.com/1247063): This duplicates code in bookmarks::BookmarkNodeData.
-// Remove once a generic mechanism for retrieving mime types is implemented.
-const ClipboardFormatType& GetChromiumBookmarkFormat() {
-  static const base::NoDestructor<ClipboardFormatType> format(
-      ClipboardFormatType::GetType("chromium/x-bookmark-entries"));
-  return *format;
-}
-
 }  // namespace
 
 WaylandDataDragController::WaylandDataDragController(
@@ -150,11 +138,11 @@
       // Corresponds to actual scale factor of the origin surface.
       icon_surface_->SetSurfaceBufferScale(origin_window_->window_scale());
     } else {
-      LOG(ERROR) << "Failed to create wl_surface";
+      LOG(ERROR) << "Failed to create drag icon surface.";
       icon_surface_.reset();
     }
   }
-  data_ = std::make_unique<OSExchangeData>(data.provider().Clone());
+  offered_data_ = std::make_unique<OSExchangeData>(data.provider().Clone());
 
   // Starts the wayland drag session setting |this| object as delegate.
   state_ = State::kStarted;
@@ -167,9 +155,9 @@
 }
 
 // Sessions initiated from Chromium, will have |data_source_| set. In which
-// case, |data_| is expected to be non-null as well.
+// case, |offered_data_| is expected to be non-null as well.
 bool WaylandDataDragController::IsDragSource() const {
-  DCHECK(!data_source_ || data_);
+  DCHECK(!data_source_ || offered_data_);
   return !!data_source_;
 }
 
@@ -214,12 +202,12 @@
   }
 
   if (IsDragSource()) {
-    // If the DND session was initiated from a Chromium window, |data_| already
-    // holds the data to be exchanged, so we don't need to read it through
-    // Wayland and can just copy it here.
-    DCHECK(data_);
-    PropagateOnDragEnter(
-        location, std::make_unique<OSExchangeData>(data_->provider().Clone()));
+    // If the DND session was initiated from a Chromium window, |offered_data_|
+    // already holds the data to be exchanged, so we don't need to read it
+    // through Wayland and can just copy it here.
+    DCHECK(offered_data_);
+    PropagateOnDragEnter(location, std::make_unique<OSExchangeData>(
+                                       offered_data_->provider().Clone()));
   } else {
     // Otherwise, we are about to accept data dragged from another application.
     // Reading the data may take some time so set |state_| to |kTrasferring|,
@@ -227,7 +215,7 @@
     // is ready.
     state_ = State::kTransferring;
     received_data_ = std::make_unique<OSExchangeData>(
-        std::make_unique<OSExchangeDataProviderNonBacked>());
+        std::make_unique<WaylandExchangeDataProvider>());
     last_drag_location_ = location;
     HandleUnprocessedMimeTypes(base::TimeTicks::Now());
   }
@@ -294,7 +282,7 @@
   window_manager_->RemoveObserver(this);
   data_source_.reset();
   data_offer_.reset();
-  data_.reset();
+  offered_data_.reset();
   data_device_->ResetDragDelegate();
   state_ = State::kIdle;
 }
@@ -303,8 +291,8 @@
                                                  std::string* buffer) {
   DCHECK(data_source_);
   DCHECK(buffer);
-  DCHECK(data_);
-  if (!wl::ExtractOSExchangeData(*data_, mime_type, buffer)) {
+  DCHECK(offered_data_);
+  if (!wl::ExtractOSExchangeData(*offered_data_, mime_type, buffer)) {
     LOG(WARNING) << "Cannot deliver data of type " << mime_type
                  << " and no text representation is available.";
   }
@@ -321,67 +309,9 @@
 void WaylandDataDragController::Offer(const OSExchangeData& data,
                                       int operations) {
   DCHECK(data_source_);
-
-  // Drag'n'drop manuals usually suggest putting data in order so the more
-  // specific a MIME type is, the earlier it occurs in the list.  Wayland
-  // specs don't say anything like that, but here we follow that common
-  // practice: begin with URIs and end with plain text.  Just in case.
-  std::vector<std::string> mime_types;
-  if (data.HasFile()) {
-    mime_types.push_back(kMimeTypeURIList);
-  }
-  if (data.HasURL(FilenameToURLPolicy::CONVERT_FILENAMES)) {
-    mime_types.push_back(kMimeTypeMozillaURL);
-  }
-  if (data.HasHtml()) {
-    mime_types.push_back(kMimeTypeHTML);
-  }
-  if (data.HasString()) {
-    mime_types.push_back(kMimeTypeTextUtf8);
-    mime_types.push_back(kMimeTypeText);
-  }
-  if (data.HasFileContents()) {
-    base::FilePath file_contents_filename;
-    std::string file_contents;
-    data.GetFileContents(&file_contents_filename, &file_contents);
-
-    std::string filename = file_contents_filename.value();
-    base::ReplaceChars(filename, "\\", "\\\\", &filename);
-    base::ReplaceChars(filename, "\"", "\\\"", &filename);
-    const std::string mime_type =
-        base::StrCat({kMimeTypeOctetStream, ";name=\"", filename, "\""});
-    mime_types.push_back(mime_type);
-  }
-  if (data.HasCustomFormat(ui::ClipboardFormatType::WebCustomDataType())) {
-    base::Pickle pickle;
-    data.GetPickledData(ui::ClipboardFormatType::WebCustomDataType(), &pickle);
-    base::PickleIterator iter(pickle);
-    uint32_t entry_count = 0;
-    if (iter.ReadUInt32(&entry_count)) {
-      for (uint32_t i = 0; i < entry_count; ++i) {
-        base::StringPiece16 type;
-        base::StringPiece16 data;
-        if (!iter.ReadStringPiece16(&type) || !iter.ReadStringPiece16(&data))
-          break;
-
-        // TODO(https://crbug.com/1236708): This logic duplicates the logic in
-        // tab_strip_ui::IsDraggedTab(). Factor it out to a common place.
-        const std::u16string kWebUITabIdDataType =
-            u"application/vnd.chromium.tab";
-        const std::u16string kWebUITabGroupIdDataType =
-            u"application/vnd.chromium.tabgroup";
-        if (type == kWebUITabIdDataType) {
-          mime_types.push_back(base::UTF16ToASCII(kWebUITabIdDataType));
-        } else if (type == kWebUITabGroupIdDataType) {
-          mime_types.push_back(base::UTF16ToASCII(kWebUITabIdDataType));
-        }
-      }
-    }
-  }
-  if (data.HasCustomFormat(GetChromiumBookmarkFormat()))
-    mime_types.push_back(GetChromiumBookmarkFormat().GetName());
-
-  data_source_->Offer(mime_types);
+  const auto* provider =
+      static_cast<const WaylandExchangeDataProvider*>(&data.provider());
+  data_source_->Offer(provider->BuildMimeTypesList());
   data_source_->SetDndActions(DragOperationsToDndActions(operations));
 }
 
@@ -437,7 +367,7 @@
       data_offer_->FinishOffer();
       data_offer_.reset();
     }
-    data_.reset();
+    offered_data_.reset();
     data_device_->ResetDragDelegate();
     is_leave_pending_ = false;
     return;
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h
index 4693069..e7565453 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h
@@ -150,9 +150,9 @@
   // Data offered by us to the other side.
   std::unique_ptr<WaylandDataSource> data_source_;
 
-  // When dragging is started from Chromium, |data_| holds the data to be sent
-  // through wl_data_device instance.
-  std::unique_ptr<ui::OSExchangeData> data_;
+  // When dragging is started from Chromium, |offered_data_| holds the data to
+  // be sent through wl_data_device instance.
+  std::unique_ptr<ui::OSExchangeData> offered_data_;
 
   // Offer to receive data from another process via drag-and-drop, or null if
   // no drag-and-drop from another process is in progress.
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
index 442599b1..da315ba 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
@@ -316,12 +316,10 @@
   bool restored_focus = window_->has_pointer_focus();
   FocusAndPressLeftPointerButton(window_.get(), &delegate_);
   OSExchangeData data(OSExchangeDataProviderFactory::CreateProvider());
-
-  // TODO(crbug.com/1247063): Add more custom formats once generic mechanism for
-  // retrieving mime types is implemented.
   ClipboardFormatType kCustomFormats[] = {
       ClipboardFormatType::WebCustomDataType(),
-      ClipboardFormatType::GetType("chromium/x-bookmark-entries")};
+      ClipboardFormatType::GetType("chromium/x-bookmark-entries"),
+      ClipboardFormatType::GetType("xyz/arbitrary-custom-type")};
   for (auto format : kCustomFormats)
     data.SetPickledData(format, {});
 
@@ -332,7 +330,7 @@
 
   ASSERT_TRUE(data_device_manager_->data_source());
   auto mime_types = data_device_manager_->data_source()->mime_types();
-  EXPECT_EQ(1u, mime_types.size());
+  EXPECT_EQ(2u, mime_types.size());
 
   for (auto format : kCustomFormats) {
     // TODO(crbug.com/1247063): Double-check whether offering custom format name
diff --git a/ui/ozone/platform/wayland/host/wayland_exchange_data_provider.cc b/ui/ozone/platform/wayland/host/wayland_exchange_data_provider.cc
new file mode 100644
index 0000000..21e77fa
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/wayland_exchange_data_provider.cc
@@ -0,0 +1,99 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/ozone/platform/wayland/host/wayland_exchange_data_provider.h"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "base/pickle.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/clipboard/clipboard_constants.h"
+#include "ui/base/clipboard/clipboard_format_type.h"
+
+namespace ui {
+
+namespace {
+
+// TODO(crbug.com/1236708): This duplicates logic in tab_strip_ui::IsDraggedTab.
+// Check if it is really needed to extract app-specific types from pickled data
+// and, if yes, factor it out to a common place and reuse it here instead.
+void AddTabDragMimeTypes(const base::Pickle& pickle,
+                         std::vector<std::string>* mime_types) {
+  DCHECK(mime_types);
+  base::PickleIterator iter(pickle);
+  uint32_t entry_count = 0;
+  if (iter.ReadUInt32(&entry_count)) {
+    for (uint32_t i = 0; i < entry_count; ++i) {
+      base::StringPiece16 type;
+      base::StringPiece16 data;
+      if (!iter.ReadStringPiece16(&type) || !iter.ReadStringPiece16(&data))
+        break;
+      const std::u16string kWebUITabIdDataType =
+          u"application/vnd.chromium.tab";
+      const std::u16string kWebUITabGroupIdDataType =
+          u"application/vnd.chromium.tabgroup";
+      if (type == kWebUITabIdDataType) {
+        mime_types->push_back(base::UTF16ToASCII(kWebUITabIdDataType));
+      } else if (type == kWebUITabGroupIdDataType) {
+        mime_types->push_back(base::UTF16ToASCII(kWebUITabIdDataType));
+      }
+    }
+  }
+}
+
+}  // namespace
+
+WaylandExchangeDataProvider::WaylandExchangeDataProvider() = default;
+
+WaylandExchangeDataProvider::~WaylandExchangeDataProvider() = default;
+
+std::vector<std::string> WaylandExchangeDataProvider::BuildMimeTypesList()
+    const {
+  // Drag'n'drop manuals usually suggest putting data in order so the more
+  // specific a MIME type is, the earlier it occurs in the list.  Wayland
+  // specs don't say anything like that, but here we follow that common
+  // practice: begin with URIs and end with plain text.  Just in case.
+  std::vector<std::string> mime_types;
+  if (HasFile())
+    mime_types.push_back(ui::kMimeTypeURIList);
+
+  if (HasURL(FilenameToURLPolicy::CONVERT_FILENAMES))
+    mime_types.push_back(ui::kMimeTypeMozillaURL);
+
+  if (HasHtml())
+    mime_types.push_back(ui::kMimeTypeHTML);
+
+  if (HasString()) {
+    mime_types.push_back(ui::kMimeTypeTextUtf8);
+    mime_types.push_back(ui::kMimeTypeText);
+  }
+  if (HasFileContents()) {
+    base::FilePath file_contents_filename;
+    std::string file_contents;
+    GetFileContents(&file_contents_filename, &file_contents);
+
+    std::string filename = file_contents_filename.value();
+    base::ReplaceChars(filename, "\\", "\\\\", &filename);
+    base::ReplaceChars(filename, "\"", "\\\"", &filename);
+    const std::string mime_type =
+        base::StrCat({ui::kMimeTypeOctetStream, ";name=\"", filename, "\""});
+    mime_types.push_back(mime_type);
+  }
+
+  for (auto item : pickle_data()) {
+    if (item.first == ClipboardFormatType::WebCustomDataType()) {
+      AddTabDragMimeTypes(item.second, &mime_types);
+      continue;
+    }
+    mime_types.push_back(item.first.GetName());
+  }
+
+  return mime_types;
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_exchange_data_provider.h b/ui/ozone/platform/wayland/host/wayland_exchange_data_provider.h
new file mode 100644
index 0000000..7eb4b8f
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/wayland_exchange_data_provider.h
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_EXCHANGE_DATA_PROVIDER_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_EXCHANGE_DATA_PROVIDER_H_
+
+#include <string>
+#include <vector>
+
+#include "ui/base/dragdrop/os_exchange_data_provider_non_backed.h"
+
+namespace ui {
+
+class WaylandExchangeDataProvider final
+    : public OSExchangeDataProviderNonBacked {
+ public:
+  WaylandExchangeDataProvider();
+  WaylandExchangeDataProvider(const WaylandExchangeDataProvider&) = delete;
+  WaylandExchangeDataProvider& operator=(const WaylandExchangeDataProvider&) =
+      delete;
+  ~WaylandExchangeDataProvider() override;
+
+  // Builds up the mime types list corresponding to the data formats available
+  // for this instance.
+  std::vector<std::string> BuildMimeTypesList() const;
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_EXCHANGE_DATA_PROVIDER_H_
diff --git a/ui/ozone/platform/wayland/ozone_platform_wayland.cc b/ui/ozone/platform/wayland/ozone_platform_wayland.cc
index 61829a4..64db5e8 100644
--- a/ui/ozone/platform/wayland/ozone_platform_wayland.cc
+++ b/ui/ozone/platform/wayland/ozone_platform_wayland.cc
@@ -16,6 +16,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "ui/base/buildflags.h"
 #include "ui/base/cursor/cursor_factory.h"
+#include "ui/base/dragdrop/os_exchange_data_provider_factory_ozone.h"
 #include "ui/base/ime/linux/input_method_auralinux.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/events/devices/device_data_manager.h"
@@ -33,6 +34,7 @@
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_connector.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_exchange_data_provider.h"
 #include "ui/ozone/platform/wayland/host/wayland_input_method_context_factory.h"
 #include "ui/ozone/platform/wayland/host/wayland_menu_utils.h"
 #include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
@@ -74,7 +76,8 @@
 
 namespace {
 
-class OzonePlatformWayland : public OzonePlatform {
+class OzonePlatformWayland : public OzonePlatform,
+                             public OSExchangeDataProviderFactoryOzone {
  public:
   OzonePlatformWayland()
       : old_synthesize_key_repeat_enabled_(
@@ -338,6 +341,11 @@
     connection_->SetShutdownCb(std::move(shutdown_cb));
   }
 
+  // OSExchangeDataProviderFactoryOzone:
+  std::unique_ptr<OSExchangeDataProvider> CreateProvider() override {
+    return std::make_unique<WaylandExchangeDataProvider>();
+  }
+
  private:
   // Keeps the old value of KeyEvent::IsSynthesizeKeyRepeatEnabled(), to
   // restore it on destruction.