diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/ui/ozone/platform/wayland | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/ozone/platform/wayland')
83 files changed, 4380 insertions, 2255 deletions
diff --git a/chromium/ui/ozone/platform/wayland/BUILD.gn b/chromium/ui/ozone/platform/wayland/BUILD.gn index b0ec8a82a5e..e0d30f2575c 100644 --- a/chromium/ui/ozone/platform/wayland/BUILD.gn +++ b/chromium/ui/ozone/platform/wayland/BUILD.gn @@ -18,6 +18,8 @@ source_set("wayland") { sources = [ "client_native_pixmap_factory_wayland.cc", "client_native_pixmap_factory_wayland.h", + "common/data_util.cc", + "common/data_util.h", "common/wayland_object.cc", "common/wayland_object.h", "common/wayland_util.cc", @@ -41,8 +43,6 @@ source_set("wayland") { "host/gtk_primary_selection_device_manager.h", "host/gtk_primary_selection_offer.cc", "host/gtk_primary_selection_offer.h", - "host/gtk_primary_selection_source.cc", - "host/gtk_primary_selection_source.h", "host/shell_object_factory.cc", "host/shell_object_factory.h", "host/shell_popup_wrapper.cc", @@ -67,14 +67,14 @@ source_set("wayland") { "host/wayland_data_device_base.h", "host/wayland_data_device_manager.cc", "host/wayland_data_device_manager.h", + "host/wayland_data_drag_controller.cc", + "host/wayland_data_drag_controller.h", "host/wayland_data_offer.cc", "host/wayland_data_offer.h", "host/wayland_data_offer_base.cc", "host/wayland_data_offer_base.h", "host/wayland_data_source.cc", "host/wayland_data_source.h", - "host/wayland_data_source_base.cc", - "host/wayland_data_source_base.h", "host/wayland_drm.cc", "host/wayland_drm.h", "host/wayland_event_source.cc", @@ -105,10 +105,14 @@ source_set("wayland") { "host/wayland_subsurface.h", "host/wayland_surface.cc", "host/wayland_surface.h", + "host/wayland_toplevel_window.cc", + "host/wayland_toplevel_window.h", "host/wayland_touch.cc", "host/wayland_touch.h", "host/wayland_window.cc", "host/wayland_window.h", + "host/wayland_window_drag_controller.cc", + "host/wayland_window_drag_controller.h", "host/wayland_window_factory.cc", "host/wayland_window_manager.cc", "host/wayland_window_manager.h", @@ -140,6 +144,7 @@ source_set("wayland") { "//skia", "//third_party/wayland:wayland_client", "//third_party/wayland-protocols:gtk_primary_selection_protocol", + "//third_party/wayland-protocols:keyboard_extension_protocol", "//third_party/wayland-protocols:linux_dmabuf_protocol", "//third_party/wayland-protocols:presentation_time_protocol", "//third_party/wayland-protocols:text_input_protocol", @@ -147,6 +152,8 @@ source_set("wayland") { "//third_party/wayland-protocols:xdg_shell_protocol", "//ui/base", "//ui/base:buildflags", + "//ui/base/cursor", + "//ui/base/cursor:cursor_base", "//ui/base/ime/linux", "//ui/events", "//ui/events:dom_keycode_converter", @@ -242,6 +249,8 @@ source_set("test_support") { "test/mock_zwp_linux_dmabuf.h", "test/mock_zwp_text_input.cc", "test/mock_zwp_text_input.h", + "test/scoped_wl_array.cc", + "test/scoped_wl_array.h", "test/server_object.cc", "test/server_object.h", "test/test_compositor.cc", @@ -306,12 +315,14 @@ source_set("wayland_unittests") { "gpu/wayland_surface_factory_unittest.cc", "host/wayland_connection_unittest.cc", "host/wayland_data_device_unittest.cc", + "host/wayland_data_drag_controller_unittest.cc", "host/wayland_event_source_unittest.cc", "host/wayland_input_method_context_unittest.cc", "host/wayland_keyboard_unittest.cc", "host/wayland_pointer_unittest.cc", "host/wayland_screen_unittest.cc", "host/wayland_touch_unittest.cc", + "host/wayland_window_drag_controller_unittest.cc", "host/wayland_window_manager_unittests.cc", "host/wayland_window_unittest.cc", "test/wayland_test.cc", @@ -326,11 +337,13 @@ source_set("wayland_unittests") { "//testing/gmock", "//testing/gtest", "//third_party/wayland:wayland_server", + "//third_party/wayland-protocols:keyboard_extension_protocol", "//third_party/wayland-protocols:linux_dmabuf_protocol", "//third_party/wayland-protocols:text_input_protocol", "//third_party/wayland-protocols:xdg_shell_protocol", "//ui/base", "//ui/base:buildflags", + "//ui/base/cursor", "//ui/base/ime/linux", "//ui/events/ozone/layout", "//ui/gfx/linux:drm", diff --git a/chromium/ui/ozone/platform/wayland/DEPS b/chromium/ui/ozone/platform/wayland/DEPS index d61519af80c..44ef7beabfd 100644 --- a/chromium/ui/ozone/platform/wayland/DEPS +++ b/chromium/ui/ozone/platform/wayland/DEPS @@ -8,5 +8,5 @@ include_rules = [ "+ui/base/dragdrop/drag_drop_types.h", "+ui/base/dragdrop/file_info/file_info.h", "+ui/base/dragdrop/os_exchange_data.h", - "+ui/base/dragdrop/os_exchange_data_provider_aura.h", + "+ui/base/dragdrop/os_exchange_data_provider_non_backed.h", ] diff --git a/chromium/ui/ozone/platform/wayland/OWNERS b/chromium/ui/ozone/platform/wayland/OWNERS index ac018fd66db..1aa8e7e3f6f 100644 --- a/chromium/ui/ozone/platform/wayland/OWNERS +++ b/chromium/ui/ozone/platform/wayland/OWNERS @@ -1,2 +1,3 @@ msisov@igalia.com +nickdiego@igalia.com tonikitoo@igalia.com diff --git a/chromium/ui/ozone/platform/wayland/common/data_util.cc b/chromium/ui/ozone/platform/wayland/common/data_util.cc new file mode 100644 index 00000000000..55e594a07e3 --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/common/data_util.cc @@ -0,0 +1,218 @@ +// Copyright 2020 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/common/data_util.h" + +#include <vector> + +#include "base/check.h" +#include "base/logging.h" +#include "base/strings/string16.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "ui/base/clipboard/clipboard_constants.h" +#include "ui/base/dragdrop/file_info/file_info.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_non_backed.h" +#include "ui/ozone/public/platform_clipboard.h" +#include "url/gurl.h" +#include "url/url_canon.h" +#include "url/url_util.h" + +namespace wl { + +namespace { + +using ui::OSExchangeData; +using ui::PlatformClipboard; + +constexpr ui::FilenameToURLPolicy kFilenameToURLPolicy = + ui::FilenameToURLPolicy::CONVERT_FILENAMES; + +// Converts mime type string to OSExchangeData::Format, if supported, otherwise +// 0 is returned. +int MimeTypeToFormat(const std::string& mime_type) { + if (mime_type == ui::kMimeTypeText || mime_type == ui::kMimeTypeTextUtf8) + return OSExchangeData::STRING; + if (mime_type == ui::kMimeTypeURIList) + return OSExchangeData::FILE_NAME; + if (mime_type == ui::kMimeTypeMozillaURL) + return OSExchangeData::URL; + if (mime_type == ui::kMimeTypeHTML) + return OSExchangeData::HTML; + return 0; +} + +// Converts raw data to either narrow or wide string. +template <typename StringType> +StringType BytesTo(const PlatformClipboard::Data& bytes) { + if (bytes.size() % sizeof(typename StringType::value_type) != 0U) { + // This is suspicious. + LOG(WARNING) + << "Data is possibly truncated, or a wrong conversion is requested."; + } + + StringType result; + result.assign(reinterpret_cast<typename StringType::const_pointer>(&bytes[0]), + bytes.size() / sizeof(typename StringType::value_type)); + return result; +} + +void AddString(const PlatformClipboard::Data& data, + OSExchangeData* os_exchange_data) { + DCHECK(os_exchange_data); + + if (data.empty()) + return; + + os_exchange_data->SetString(base::UTF8ToUTF16(BytesTo<std::string>(data))); +} + +void AddHtml(const PlatformClipboard::Data& data, + OSExchangeData* os_exchange_data) { + DCHECK(os_exchange_data); + + if (data.empty()) + return; + + os_exchange_data->SetHtml(base::UTF8ToUTF16(BytesTo<std::string>(data)), + GURL()); +} + +// Parses |data| as if it had text/uri-list format. Its brief spec is: +// 1. Any lines beginning with the '#' character are comment lines. +// 2. Non-comment lines shall be URIs (URNs or URLs). +// 3. Lines are terminated with a CRLF pair. +// 4. URL encoding is used. +void AddFiles(const PlatformClipboard::Data& data, + OSExchangeData* os_exchange_data) { + DCHECK(os_exchange_data); + + std::string data_as_string = BytesTo<std::string>(data); + + const auto lines = base::SplitString( + data_as_string, "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + std::vector<ui::FileInfo> filenames; + for (const auto& line : lines) { + if (line.empty() || line[0] == '#') + continue; + GURL url(line); + if (!url.is_valid() || !url.SchemeIsFile()) { + LOG(WARNING) << "Invalid URI found: " << line; + continue; + } + + std::string url_path = url.path(); + url::RawCanonOutputT<base::char16> unescaped; + url::DecodeURLEscapeSequences(url_path.data(), url_path.size(), + url::DecodeURLMode::kUTF8OrIsomorphic, + &unescaped); + + std::string path8; + base::UTF16ToUTF8(unescaped.data(), unescaped.length(), &path8); + const base::FilePath path(path8); + filenames.push_back({path, path.BaseName()}); + } + if (filenames.empty()) + return; + + os_exchange_data->SetFilenames(filenames); +} + +// Parses |data| as if it had text/x-moz-url format, which is basically +// two lines separated with newline, where the first line is the URL and +// the second one is page title. The unpleasant feature of text/x-moz-url is +// that the URL has UTF-16 encoding. +void AddUrl(const PlatformClipboard::Data& data, + OSExchangeData* os_exchange_data) { + DCHECK(os_exchange_data); + + if (data.empty()) + return; + + base::string16 data_as_string16 = BytesTo<base::string16>(data); + + const auto lines = + base::SplitString(data_as_string16, base::ASCIIToUTF16("\r\n"), + base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (lines.size() != 2U) { + LOG(WARNING) << "Invalid data passed as text/x-moz-url; it must contain " + << "exactly 2 lines but has " << lines.size() << " instead."; + return; + } + GURL url(lines[0]); + if (!url.is_valid()) { + LOG(WARNING) << "Invalid data passed as text/x-moz-url; the first line " + << "must contain a valid URL but it doesn't."; + return; + } + + os_exchange_data->SetURL(url, lines[1]); +} + +} // namespace + +bool IsMimeTypeSupported(const std::string& mime_type) { + return MimeTypeToFormat(mime_type) != 0; +} + +bool ContainsMimeType(const OSExchangeData& exchange_data, + const std::string& mime_type) { + DCHECK(IsMimeTypeSupported(mime_type)); + return exchange_data.HasAnyFormat(MimeTypeToFormat(mime_type), {}); +} + +void AddToOSExchangeData(const PlatformClipboard::Data& data, + const std::string& mime_type, + OSExchangeData* exchange_data) { + DCHECK(IsMimeTypeSupported(mime_type)); + DCHECK(exchange_data); + int format = MimeTypeToFormat(mime_type); + switch (format) { + case OSExchangeData::STRING: + AddString(data, exchange_data); + break; + case OSExchangeData::HTML: + AddHtml(data, exchange_data); + break; + case OSExchangeData::URL: + AddUrl(data, exchange_data); + break; + case OSExchangeData::FILE_NAME: + AddFiles(data, exchange_data); + break; + } +} + +bool ExtractOSExchangeData(const OSExchangeData& exchange_data, + const std::string& mime_type, + std::string* out_content) { + DCHECK(out_content); + DCHECK(IsMimeTypeSupported(mime_type)); + + if (mime_type == ui::kMimeTypeMozillaURL && + exchange_data.HasURL(kFilenameToURLPolicy)) { + GURL url; + base::string16 title; + exchange_data.GetURLAndTitle(kFilenameToURLPolicy, &url, &title); + out_content->append(url.spec()); + return true; + } + if (mime_type == ui::kMimeTypeHTML && exchange_data.HasHtml()) { + base::string16 data; + GURL base_url; + exchange_data.GetHtml(&data, &base_url); + out_content->append(base::UTF16ToUTF8(data)); + return true; + } + if (exchange_data.HasString()) { + base::string16 data; + exchange_data.GetString(&data); + out_content->append(base::UTF16ToUTF8(data)); + return true; + } + return false; +} + +} // namespace wl diff --git a/chromium/ui/ozone/platform/wayland/common/data_util.h b/chromium/ui/ozone/platform/wayland/common/data_util.h new file mode 100644 index 00000000000..d6c2d10e7f5 --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/common/data_util.h @@ -0,0 +1,40 @@ +// Copyright 2020 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_COMMON_DATA_UTIL_H_ +#define UI_OZONE_PLATFORM_WAYLAND_COMMON_DATA_UTIL_H_ + +#include <string> + +#include "ui/ozone/public/platform_clipboard.h" + +namespace ui { +class OSExchangeData; +} // namespace ui + +namespace wl { + +// Tells if |mime_type| is supported for Drag and Drop operations. +bool IsMimeTypeSupported(const std::string& mime_type); + +// Tells if |exchange_data| contains |mime_type| content. +bool ContainsMimeType(const ui::OSExchangeData& exchange_data, + const std::string& mime_type); + +// Add clipboard |data| content with |mime_type| format to the |exchange_data|. +// |mime_type| is assumed to be supported (See IsMimeTypeSupported for more). +void AddToOSExchangeData(const ui::PlatformClipboard::Data& data, + const std::string& mime_type, + ui::OSExchangeData* exchange_data); + +// Extract |exchange_data| of type |mime_type| and put it into |buffer|. If such +// mime type is not present, false is returned and |buffer| keeps untouched. +// |mime_type| is assumed to be supported (See IsMimeTypeSupported for more). +bool ExtractOSExchangeData(const ui::OSExchangeData& exchange_data, + const std::string& mime_type, + std::string* buffer); + +} // namespace wl + +#endif // UI_OZONE_PLATFORM_WAYLAND_COMMON_DATA_UTIL_H_ diff --git a/chromium/ui/ozone/platform/wayland/common/wayland_object.cc b/chromium/ui/ozone/platform/wayland/common/wayland_object.cc index d0db0924463..9f9efeae72e 100644 --- a/chromium/ui/ozone/platform/wayland/common/wayland_object.cc +++ b/chromium/ui/ozone/platform/wayland/common/wayland_object.cc @@ -5,6 +5,7 @@ #include "ui/ozone/platform/wayland/common/wayland_object.h" #include <gtk-primary-selection-client-protocol.h> +#include <keyboard-extension-unstable-v1-client-protocol.h> #include <linux-dmabuf-unstable-v1-client-protocol.h> #include <presentation-time-client-protocol.h> #include <text-input-unstable-v1-client-protocol.h> @@ -189,6 +190,16 @@ const wl_interface* ObjectTraits<xdg_positioner>::interface = void (*ObjectTraits<xdg_positioner>::deleter)(xdg_positioner*) = &xdg_positioner_destroy; +const wl_interface* ObjectTraits<zcr_keyboard_extension_v1>::interface = + &zcr_keyboard_extension_v1_interface; +void (*ObjectTraits<zcr_keyboard_extension_v1>::deleter)( + zcr_keyboard_extension_v1*) = &zcr_keyboard_extension_v1_destroy; + +const wl_interface* ObjectTraits<zcr_extended_keyboard_v1>::interface = + &zcr_extended_keyboard_v1_interface; +void (*ObjectTraits<zcr_extended_keyboard_v1>::deleter)( + zcr_extended_keyboard_v1*) = &zcr_extended_keyboard_v1_destroy; + const wl_interface* ObjectTraits<zwp_linux_dmabuf_v1>::interface = &zwp_linux_dmabuf_v1_interface; void (*ObjectTraits<zwp_linux_dmabuf_v1>::deleter)(zwp_linux_dmabuf_v1*) = diff --git a/chromium/ui/ozone/platform/wayland/common/wayland_object.h b/chromium/ui/ozone/platform/wayland/common/wayland_object.h index 2d048ee3c5b..5c639f2db81 100644 --- a/chromium/ui/ozone/platform/wayland/common/wayland_object.h +++ b/chromium/ui/ozone/platform/wayland/common/wayland_object.h @@ -39,6 +39,8 @@ struct xdg_surface; struct xdg_toplevel; struct xdg_popup; struct xdg_positioner; +struct zcr_keyboard_extension_v1; +struct zcr_extended_keyboard_v1; struct zwp_linux_dmabuf_v1; struct zxdg_shell_v6; struct zxdg_surface_v6; @@ -246,6 +248,18 @@ struct ObjectTraits<xdg_positioner> { }; template <> +struct ObjectTraits<zcr_keyboard_extension_v1> { + static const wl_interface* interface; + static void (*deleter)(zcr_keyboard_extension_v1*); +}; + +template <> +struct ObjectTraits<zcr_extended_keyboard_v1> { + static const wl_interface* interface; + static void (*deleter)(zcr_extended_keyboard_v1*); +}; + +template <> struct ObjectTraits<zwp_linux_dmabuf_v1> { static const wl_interface* interface; static void (*deleter)(zwp_linux_dmabuf_v1*); diff --git a/chromium/ui/ozone/platform/wayland/gpu/drm_render_node_handle.cc b/chromium/ui/ozone/platform/wayland/gpu/drm_render_node_handle.cc index a049ee5be98..87ec9b08f00 100644 --- a/chromium/ui/ozone/platform/wayland/gpu/drm_render_node_handle.cc +++ b/chromium/ui/ozone/platform/wayland/gpu/drm_render_node_handle.cc @@ -7,6 +7,8 @@ #include <fcntl.h> #include <xf86drm.h> +#include "base/logging.h" + namespace ui { DrmRenderNodeHandle::DrmRenderNodeHandle() = default; diff --git a/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc b/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc index 95c840cbf77..734e8b2b699 100644 --- a/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc +++ b/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc @@ -18,12 +18,17 @@ namespace ui { namespace { -void WaitForFence(EGLDisplay display, EGLSyncKHR fence) { +void WaitForEGLFence(EGLDisplay display, EGLSyncKHR fence) { eglClientWaitSyncKHR(display, fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR); eglDestroySyncKHR(display, fence); } +void WaitForGpuFences(std::vector<std::unique_ptr<gfx::GpuFence>> fences) { + for (auto& fence : fences) + fence->Wait(); +} + } // namespace GbmSurfacelessWayland::GbmSurfacelessWayland( @@ -41,7 +46,7 @@ GbmSurfacelessWayland::GbmSurfacelessWayland( void GbmSurfacelessWayland::QueueOverlayPlane(OverlayPlane plane, uint32_t buffer_id) { - planes_.push_back({std::move(plane), buffer_id}); + unsubmitted_frames_.back()->planes.push_back({std::move(plane), buffer_id}); } bool GbmSurfacelessWayland::ScheduleOverlayPlane( @@ -87,7 +92,8 @@ void GbmSurfacelessWayland::SwapBuffersAsync( TRACE_EVENT0("wayland", "GbmSurfacelessWayland::SwapBuffersAsync"); // If last swap failed, don't try to schedule new ones. if (!last_swap_buffers_result_) { - std::move(completion_callback).Run(gfx::SwapResult::SWAP_FAILED, nullptr); + std::move(completion_callback) + .Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED)); // Notify the caller, the buffer is never presented on a screen. std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure()); return; @@ -102,21 +108,35 @@ void GbmSurfacelessWayland::SwapBuffersAsync( PendingFrame* frame = unsubmitted_frames_.back().get(); frame->completion_callback = std::move(completion_callback); frame->presentation_callback = std::move(presentation_callback); + frame->ScheduleOverlayPlanes(widget_); + unsubmitted_frames_.push_back(std::make_unique<PendingFrame>()); - if (!use_egl_fence_sync_) { + if (!use_egl_fence_sync_ || !frame->schedule_planes_succeeded) { frame->ready = true; SubmitFrame(); return; } - // TODO: the following should be replaced by a per surface flush as it gets - // implemented in GL drivers. - EGLSyncKHR fence = InsertFence(has_implicit_external_sync_); - CHECK_NE(fence, EGL_NO_SYNC_KHR) << "eglCreateSyncKHR failed"; + std::vector<std::unique_ptr<gfx::GpuFence>> fences; + // Uset in-fences provided in the overlays. If there are none, we insert our + // own fence and wait. + for (auto& plane : frame->planes) { + if (plane.plane.gpu_fence) + fences.push_back(std::move(plane.plane.gpu_fence)); + } + + base::OnceClosure fence_wait_task; + if (!fences.empty()) { + fence_wait_task = base::BindOnce(&WaitForGpuFences, std::move(fences)); + } else { + // TODO: the following should be replaced by a per surface flush as it gets + // implemented in GL drivers. + EGLSyncKHR fence = InsertFence(has_implicit_external_sync_); + CHECK_NE(fence, EGL_NO_SYNC_KHR) << "eglCreateSyncKHR failed"; - base::OnceClosure fence_wait_task = - base::BindOnce(&WaitForFence, GetDisplay(), fence); + fence_wait_task = base::BindOnce(&WaitForEGLFence, GetDisplay(), fence); + } base::OnceClosure fence_retired_callback = base::BindOnce( &GbmSurfacelessWayland::FenceRetired, weak_factory_.GetWeakPtr(), frame); @@ -181,12 +201,14 @@ GbmSurfacelessWayland::PendingFrame::PendingFrame() {} GbmSurfacelessWayland::PendingFrame::~PendingFrame() {} -bool GbmSurfacelessWayland::PendingFrame::ScheduleOverlayPlanes( +void GbmSurfacelessWayland::PendingFrame::ScheduleOverlayPlanes( gfx::AcceleratedWidget widget) { - for (auto& overlay : overlays) + for (auto& overlay : overlays) { if (!overlay.ScheduleOverlayPlane(widget)) - return false; - return true; + return; + } + schedule_planes_succeeded = true; + return; } void GbmSurfacelessWayland::PendingFrame::Flush() { @@ -201,14 +223,11 @@ void GbmSurfacelessWayland::SubmitFrame() { submitted_frame_ = std::move(unsubmitted_frames_.front()); unsubmitted_frames_.erase(unsubmitted_frames_.begin()); - bool schedule_planes_succeeded = - submitted_frame_->ScheduleOverlayPlanes(widget_); - - if (!schedule_planes_succeeded) { + if (!submitted_frame_->schedule_planes_succeeded) { last_swap_buffers_result_ = false; std::move(submitted_frame_->completion_callback) - .Run(gfx::SwapResult::SWAP_FAILED, nullptr); + .Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED)); // Notify the caller, the buffer is never presented on a screen. std::move(submitted_frame_->presentation_callback) .Run(gfx::PresentationFeedback::Failure()); @@ -217,11 +236,13 @@ void GbmSurfacelessWayland::SubmitFrame() { return; } - submitted_frame_->buffer_id = planes_.back().buffer_id; - buffer_manager_->CommitBuffer(widget_, submitted_frame_->buffer_id, + DCHECK_EQ(submitted_frame_->planes.size(), 1u); + submitted_frame_->buffer_id = submitted_frame_->planes.back().buffer_id; + buffer_manager_->CommitBuffer(widget_, + submitted_frame_->planes.back().buffer_id, submitted_frame_->damage_region_); - planes_.clear(); + submitted_frame_->planes.clear(); } } @@ -247,7 +268,8 @@ void GbmSurfacelessWayland::OnSubmission(uint32_t buffer_id, submitted_frame_->overlays.clear(); DCHECK_EQ(submitted_frame_->buffer_id, buffer_id); - std::move(submitted_frame_->completion_callback).Run(swap_result, nullptr); + std::move(submitted_frame_->completion_callback) + .Run(gfx::SwapCompletionResult(swap_result)); pending_presentation_frames_.push_back(std::move(submitted_frame_)); diff --git a/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h b/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h index 991d1718fad..137bea20432 100644 --- a/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h +++ b/chromium/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h @@ -71,11 +71,19 @@ class GbmSurfacelessWayland : public gl::SurfacelessEGL, void OnPresentation(uint32_t buffer_id, const gfx::PresentationFeedback& feedback) override; + struct PlaneData { + OverlayPlane plane; + // The id of the buffer, which represents buffer that backs this overlay + // plane. + const uint32_t buffer_id; + }; + struct PendingFrame { PendingFrame(); ~PendingFrame(); - bool ScheduleOverlayPlanes(gfx::AcceleratedWidget widget); + // Queues overlay configs to |planes|. + void ScheduleOverlayPlanes(gfx::AcceleratedWidget widget); void Flush(); bool ready = false; @@ -90,11 +98,9 @@ class GbmSurfacelessWayland : public gl::SurfacelessEGL, std::vector<gl::GLSurfaceOverlay> overlays; SwapCompletionCallback completion_callback; PresentationCallback presentation_callback; - }; - struct PlaneData { - OverlayPlane plane; - const uint32_t buffer_id; + bool schedule_planes_succeeded = false; + std::vector<PlaneData> planes; }; void SubmitFrame(); @@ -106,7 +112,6 @@ class GbmSurfacelessWayland : public gl::SurfacelessEGL, void SetNoGLFlushForTests(); WaylandBufferManagerGpu* const buffer_manager_; - std::vector<PlaneData> planes_; // The native surface. Deleting this is allowed to free the EGLNativeWindow. gfx::AcceleratedWidget widget_; diff --git a/chromium/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc b/chromium/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc index 60c70c7e82e..195d061926c 100644 --- a/chromium/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc +++ b/chromium/ui/ozone/platform/wayland/gpu/gl_surface_egl_readback_wayland.cc @@ -145,7 +145,8 @@ void GLSurfaceEglReadbackWayland::OnSubmission( in_flight_pixel_buffers_.pop_front(); DCHECK(!completion_callbacks_.empty()); - std::move(completion_callbacks_.front()).Run(swap_result, nullptr); + std::move(completion_callbacks_.front()) + .Run(gfx::SwapCompletionResult(swap_result)); completion_callbacks_.erase(completion_callbacks_.begin()); } diff --git a/chromium/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc b/chromium/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc index 585e5666283..aa649253a68 100644 --- a/chromium/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc +++ b/chromium/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc @@ -21,6 +21,7 @@ #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h" #include "ui/ozone/platform/wayland/host/wayland_window.h" #include "ui/ozone/platform/wayland/test/mock_surface.h" +#include "ui/ozone/platform/wayland/test/scoped_wl_array.h" #include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" #include "ui/ozone/platform/wayland/test/test_zwp_linux_buffer_params.h" #include "ui/ozone/platform/wayland/test/wayland_test.h" @@ -126,8 +127,7 @@ class CallbacksHelper { // way. void FinishSwapBuffersAsync(uint32_t local_swap_id, scoped_refptr<FakeGLImageNativePixmap> gl_image, - gfx::SwapResult result, - std::unique_ptr<gfx::GpuFence> gpu_fence) { + gfx::SwapCompletionResult result) { last_finish_swap_id_ = pending_local_swap_ids_.front(); pending_local_swap_ids_.pop(); diff --git a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc index 23e2eaf306f..1a68f9da34e 100644 --- a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc +++ b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.cc @@ -8,6 +8,7 @@ #include "ui/ozone/platform/wayland/host/gtk_primary_selection_offer.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" namespace ui { @@ -25,6 +26,14 @@ GtkPrimarySelectionDevice::GtkPrimarySelectionDevice( GtkPrimarySelectionDevice::~GtkPrimarySelectionDevice() = default; +void GtkPrimarySelectionDevice::SetSelectionSource( + GtkPrimarySelectionSource* source) { + DCHECK(source); + gtk_primary_selection_device_set_selection( + data_device_.get(), source->data_source(), connection()->serial()); + connection()->ScheduleFlush(); +} + // static void GtkPrimarySelectionDevice::OnDataOffer( void* data, diff --git a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.h b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.h index fc567936fe3..4bb586bffc3 100644 --- a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.h +++ b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device.h @@ -13,6 +13,7 @@ #include "base/macros.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" #include "ui/ozone/platform/wayland/host/wayland_data_device_base.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" struct gtk_primary_selection_device; @@ -31,6 +32,8 @@ class GtkPrimarySelectionDevice : public WaylandDataDeviceBase { return data_device_.get(); } + void SetSelectionSource(GtkPrimarySelectionSource* source); + private: // gtk_primary_selection_device_listener callbacks static void OnDataOffer(void* data, diff --git a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.cc b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.cc index 90a8225bd73..3d49d7a4576 100644 --- a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.cc +++ b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.cc @@ -6,33 +6,42 @@ #include <gtk-primary-selection-client-protocol.h> -#include "ui/ozone/platform/wayland/host/gtk_primary_selection_source.h" +#include <memory> + +#include "ui/ozone/platform/wayland/host/gtk_primary_selection_device.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" namespace ui { GtkPrimarySelectionDeviceManager::GtkPrimarySelectionDeviceManager( gtk_primary_selection_device_manager* manager, WaylandConnection* connection) - : gtk_primary_selection_device_manager_(manager), connection_(connection) { + : device_manager_(manager), connection_(connection) { DCHECK(connection_); - DCHECK(gtk_primary_selection_device_manager_); + DCHECK(device_manager_); } GtkPrimarySelectionDeviceManager::~GtkPrimarySelectionDeviceManager() = default; -gtk_primary_selection_device* GtkPrimarySelectionDeviceManager::GetDevice() { +GtkPrimarySelectionDevice* GtkPrimarySelectionDeviceManager::GetDevice() { DCHECK(connection_->seat()); - return gtk_primary_selection_device_manager_get_device( - gtk_primary_selection_device_manager_.get(), connection_->seat()); + if (!device_) { + device_ = std::make_unique<GtkPrimarySelectionDevice>( + connection_, gtk_primary_selection_device_manager_get_device( + device_manager_.get(), connection_->seat())); + } + DCHECK(device_); + return device_.get(); } std::unique_ptr<GtkPrimarySelectionSource> -GtkPrimarySelectionDeviceManager::CreateSource() { - gtk_primary_selection_source* data_source = - gtk_primary_selection_device_manager_create_source( - gtk_primary_selection_device_manager_.get()); - return std::make_unique<GtkPrimarySelectionSource>(data_source, connection_); +GtkPrimarySelectionDeviceManager::CreateSource( + GtkPrimarySelectionSource::Delegate* delegate) { + auto* data_source = + gtk_primary_selection_device_manager_create_source(device_manager_.get()); + return std::make_unique<GtkPrimarySelectionSource>(data_source, connection_, + delegate); } } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h index ea05aa38882..b059c2c8621 100644 --- a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h +++ b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h @@ -7,34 +7,38 @@ #include <memory> -#include "base/macros.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" - -struct gtk_primary_selection_device_manager; -struct gtk_primary_selection_device; +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" namespace ui { -class GtkPrimarySelectionSource; +class GtkPrimarySelectionDevice; class WaylandConnection; class GtkPrimarySelectionDeviceManager { public: + using DataSource = GtkPrimarySelectionSource; + using DataDevice = GtkPrimarySelectionDevice; + GtkPrimarySelectionDeviceManager( gtk_primary_selection_device_manager* manager, WaylandConnection* connection); + GtkPrimarySelectionDeviceManager(const GtkPrimarySelectionDeviceManager&) = + delete; + GtkPrimarySelectionDeviceManager& operator=( + const GtkPrimarySelectionDeviceManager&) = delete; ~GtkPrimarySelectionDeviceManager(); - gtk_primary_selection_device* GetDevice(); - std::unique_ptr<GtkPrimarySelectionSource> CreateSource(); + GtkPrimarySelectionDevice* GetDevice(); + std::unique_ptr<GtkPrimarySelectionSource> CreateSource( + GtkPrimarySelectionSource::Delegate* delegate); private: - wl::Object<gtk_primary_selection_device_manager> - gtk_primary_selection_device_manager_; + wl::Object<gtk_primary_selection_device_manager> device_manager_; - WaylandConnection* connection_; + WaylandConnection* const connection_; - DISALLOW_COPY_AND_ASSIGN(GtkPrimarySelectionDeviceManager); + std::unique_ptr<GtkPrimarySelectionDevice> device_; }; } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_source.cc b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_source.cc deleted file mode 100644 index 7160cdd83fa..00000000000 --- a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_source.cc +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2019 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/gtk_primary_selection_source.h" - -#include <gtk-primary-selection-client-protocol.h> - -#include "base/check.h" -#include "base/files/file_util.h" -#include "ui/base/clipboard/clipboard_constants.h" -#include "ui/ozone/platform/wayland/host/wayland_connection.h" - -namespace ui { - -GtkPrimarySelectionSource::GtkPrimarySelectionSource( - gtk_primary_selection_source* data_source, - WaylandConnection* connection) - : data_source_(data_source), connection_(connection) { - DCHECK(connection_); - DCHECK(data_source_); - - static const struct gtk_primary_selection_source_listener - kDataSourceListener = {GtkPrimarySelectionSource::OnSend, - GtkPrimarySelectionSource::OnCancelled}; - gtk_primary_selection_source_add_listener(data_source_.get(), - &kDataSourceListener, this); -} - -GtkPrimarySelectionSource::~GtkPrimarySelectionSource() = default; - -// static -void GtkPrimarySelectionSource::OnSend(void* data, - gtk_primary_selection_source* source, - const char* mime_type, - int32_t fd) { - GtkPrimarySelectionSource* self = - static_cast<GtkPrimarySelectionSource*>(data); - std::string contents; - base::Optional<std::vector<uint8_t>> mime_data; - self->GetClipboardData(mime_type, &mime_data); - if (!mime_data.has_value() && strcmp(mime_type, kMimeTypeTextUtf8) == 0) - self->GetClipboardData(kMimeTypeText, &mime_data); - contents.assign(mime_data->begin(), mime_data->end()); - bool result = - base::WriteFileDescriptor(fd, contents.data(), contents.length()); - DCHECK(result); - close(fd); -} - -// static -void GtkPrimarySelectionSource::OnCancelled( - void* data, - gtk_primary_selection_source* source) { - GtkPrimarySelectionSource* self = - static_cast<GtkPrimarySelectionSource*>(data); - self->connection_->clipboard()->DataSourceCancelled( - ClipboardBuffer::kSelection); -} - -void GtkPrimarySelectionSource::WriteToClipboard( - const PlatformClipboard::DataMap& data_map) { - for (const auto& data : data_map) { - gtk_primary_selection_source_offer(data_source_.get(), data.first.c_str()); - if (strcmp(data.first.c_str(), kMimeTypeText) == 0) - gtk_primary_selection_source_offer(data_source_.get(), kMimeTypeTextUtf8); - } - - gtk_primary_selection_device_set_selection( - connection_->primary_selection_device(), data_source_.get(), - connection_->serial()); - - connection_->ScheduleFlush(); -} - -} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_source.h b/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_source.h deleted file mode 100644 index 994fffa25d3..00000000000 --- a/chromium/ui/ozone/platform/wayland/host/gtk_primary_selection_source.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019 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_GTK_PRIMARY_SELECTION_SOURCE_H_ -#define UI_OZONE_PLATFORM_WAYLAND_HOST_GTK_PRIMARY_SELECTION_SOURCE_H_ - -#include "base/macros.h" -#include "ui/ozone/platform/wayland/common/wayland_object.h" -#include "ui/ozone/platform/wayland/host/wayland_data_source_base.h" -#include "ui/ozone/public/platform_clipboard.h" - -struct gtk_primary_selection_source; - -namespace ui { - -class WaylandConnection; - -class GtkPrimarySelectionSource : public WaylandDataSourceBase { - public: - // Takes ownership of data_source. - GtkPrimarySelectionSource(gtk_primary_selection_source* data_source, - WaylandConnection* connection); - ~GtkPrimarySelectionSource() override; - - void WriteToClipboard(const PlatformClipboard::DataMap& data_map) override; - - private: - // gtk_primary_selection_source_listener callbacks - static void OnSend(void* data, - gtk_primary_selection_source* source, - const char* mime_type, - int32_t fd); - static void OnCancelled(void* data, gtk_primary_selection_source* source); - - // The gtk_primary_selection_source wrapped by this instance. - wl::Object<gtk_primary_selection_source> data_source_; - - WaylandConnection* connection_ = nullptr; - - DISALLOW_COPY_AND_ASSIGN(GtkPrimarySelectionSource); -}; - -} // namespace ui - -#endif // UI_OZONE_PLATFORM_WAYLAND_HOST_GTK_PRIMARY_SELECTION_SOURCE_H_ diff --git a/chromium/ui/ozone/platform/wayland/host/shell_object_factory.cc b/chromium/ui/ozone/platform/wayland/host/shell_object_factory.cc index 57383be20da..dea71480eb6 100644 --- a/chromium/ui/ozone/platform/wayland/host/shell_object_factory.cc +++ b/chromium/ui/ozone/platform/wayland/host/shell_object_factory.cc @@ -4,6 +4,7 @@ #include "ui/ozone/platform/wayland/host/shell_object_factory.h" +#include "base/logging.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" #include "ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h" #include "ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h" @@ -44,4 +45,4 @@ std::unique_ptr<ShellPopupWrapper> ShellObjectFactory::CreateShellPopupWrapper( return nullptr; } -} // namespace ui
\ No newline at end of file +} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc b/chromium/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc index 1dc1f4d4f76..f9f5aaa0825 100644 --- a/chromium/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc +++ b/chromium/ui/ozone/platform/wayland/host/shell_popup_wrapper.cc @@ -4,6 +4,9 @@ #include "ui/ozone/platform/wayland/host/shell_popup_wrapper.h" +#include "base/check_op.h" +#include "base/notreached.h" + namespace ui { constexpr uint32_t kAnchorDefaultWidth = 1; diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc b/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc index 96c4099fdb8..8320574a540 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc @@ -16,6 +16,7 @@ #include "ui/ozone/platform/wayland/host/wayland_connection.h" #include "ui/ozone/platform/wayland/host/wayland_drm.h" #include "ui/ozone/platform/wayland/host/wayland_shm.h" +#include "ui/ozone/platform/wayland/host/wayland_surface.h" #include "ui/ozone/platform/wayland/host/wayland_window.h" #include "ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.h" @@ -55,10 +56,10 @@ std::string NumberToString(uint32_t number) { class WaylandBufferManagerHost::Surface { public: - Surface(WaylandWindow* window, + Surface(WaylandSurface* wayland_surface, WaylandConnection* connection, WaylandBufferManagerHost* buffer_manager) - : window_(window), + : wayland_surface_(wayland_surface), connection_(connection), buffer_manager_(buffer_manager) {} ~Surface() = default; @@ -67,7 +68,7 @@ class WaylandBufferManagerHost::Surface { DCHECK(!pending_buffer_); // The window has already been destroyed. - if (!window_) + if (!wayland_surface_) return true; WaylandBuffer* buffer = GetBuffer(buffer_id); @@ -95,23 +96,9 @@ class WaylandBufferManagerHost::Surface { if (buffer->attached && !buffer->wl_buffer) return false; - // This request may come earlier than the Wayland compositor has imported a - // wl_buffer. Wait until the buffer is created. The wait takes place only - // once. Though, the case when a request to attach a buffer comes earlier - // than the wl_buffer is created does not happen often. 1) Depending on the - // zwp linux dmabuf protocol version, the wl_buffer can be created - // immediately without asynchronous wait 2) the wl_buffer can have been - // created by this time. - // - // Another case, which always happen is waiting until the frame callback is - // completed. Thus, wait here when the Wayland compositor fires the frame - // callback. - if (!buffer->wl_buffer || wl_frame_callback_) { - pending_buffer_ = buffer; - return true; - } - - return CommitBufferInternal(buffer); + pending_buffer_ = buffer; + MaybeProcessPendingBuffer(); + return true; } bool CreateBuffer(const gfx::Size& size, uint32_t buffer_id) { @@ -140,7 +127,7 @@ class WaylandBufferManagerHost::Surface { // the client about successful swap. // If the window has already been destroyed, no need to complete the // submission. - if (buffer && !buffer->released && submitted_buffer_ && window_) + if (buffer && !buffer->released && submitted_buffer_ && wayland_surface_) CompleteSubmission(); if (prev_submitted_buffer_ == buffer) @@ -167,8 +154,8 @@ class WaylandBufferManagerHost::Surface { if (buffer->wl_buffer) SetupBufferReleaseListener(buffer); - if (pending_buffer_ == buffer && !wl_frame_callback_) - ProcessPendingBuffer(); + if (pending_buffer_ == buffer) + MaybeProcessPendingBuffer(); } void ClearState() { @@ -186,17 +173,23 @@ class WaylandBufferManagerHost::Surface { } void ResetSurfaceContents() { - if (!window_) + if (!wayland_surface_) return; - wl_surface_attach(window_->surface(), nullptr, 0, 0); - wl_surface_commit(window_->surface()); + wl_surface_attach(wayland_surface_->surface(), nullptr, 0, 0); + wl_surface_commit(wayland_surface_->surface()); // We cannot reset |prev_submitted_buffer_| here as long as the surface // might have attached a new buffer and is about to receive a release // callback. Check more comments below where the variable is declared. contents_reset_ = true; + // ResetSurfaceContents happens upon WaylandWindow::Hide call, which + // destroyes xdg_surface, xdg_popup, etc. They are going to be reinitialized + // once WaylandWindow::Show is called. Thus, they will have to be configured + // once again before buffers can be attached. + configured_ = false; + connection_->ScheduleFlush(); } @@ -207,8 +200,16 @@ class WaylandBufferManagerHost::Surface { bool HasBuffers() const { return !buffers_.empty(); } - void OnWindowRemoved() { window_ = nullptr; } - bool HasWindow() const { return !!window_; } + void OnWindowRemoved() { wayland_surface_ = nullptr; } + bool HasWindow() const { return !!wayland_surface_; } + + void OnWindowConfigured() { + if (configured_) + return; + + configured_ = true; + MaybeProcessPendingBuffer(); + } private: struct FeedbackInfo { @@ -226,7 +227,7 @@ class WaylandBufferManagerHost::Surface { using PresentationFeedbackQueue = std::vector<FeedbackInfo>; bool CommitBufferInternal(WaylandBuffer* buffer) { - DCHECK(buffer && window_); + DCHECK(buffer && wayland_surface_); DCHECK(!pending_buffer_); DCHECK(!submitted_buffer_); @@ -274,7 +275,7 @@ class WaylandBufferManagerHost::Surface { } void DamageBuffer(WaylandBuffer* buffer) { - DCHECK(window_); + DCHECK(wayland_surface_); gfx::Rect pending_damage_region = std::move(buffer->damage_region); // If the size of the damage region is empty, wl_surface_damage must be @@ -290,10 +291,10 @@ class WaylandBufferManagerHost::Surface { // https://bit.ly/2u00lv6 for details. // We don't need to apply any scaling because pending_damage_region is // already in buffer coordinates. - wl_surface_damage_buffer(window_->surface(), pending_damage_region.x(), - pending_damage_region.y(), - pending_damage_region.width(), - pending_damage_region.height()); + wl_surface_damage_buffer( + wayland_surface_->surface(), pending_damage_region.x(), + pending_damage_region.y(), pending_damage_region.width(), + pending_damage_region.height()); } else { // The calculation for damage region relies on two assumptions: // 1) The buffer is always attached at surface location (0, 0) @@ -304,8 +305,9 @@ class WaylandBufferManagerHost::Surface { // Note: The damage region may not be an integer multiple of scale. To // keep the implementation simple, the x() and y() coordinates round down, // and the width() and height() calculations always add an extra pixel. - int scale = window_->buffer_scale(); - wl_surface_damage(window_->surface(), pending_damage_region.x() / scale, + int scale = wayland_surface_->buffer_scale(); + wl_surface_damage(wayland_surface_->surface(), + pending_damage_region.x() / scale, pending_damage_region.y() / scale, pending_damage_region.width() / scale + 1, pending_damage_region.height() / scale + 1); @@ -313,31 +315,32 @@ class WaylandBufferManagerHost::Surface { } void AttachBuffer(WaylandBuffer* buffer) { - DCHECK(window_); + DCHECK(wayland_surface_ && configured_); // The logic in DamageBuffer currently relies on attachment coordinates of // (0, 0). If this changes, then the calculation in DamageBuffer will also // need to be updated. - wl_surface_attach(window_->surface(), buffer->wl_buffer.get(), 0, 0); + wl_surface_attach(wayland_surface_->surface(), buffer->wl_buffer.get(), 0, + 0); } void CommitSurface() { - DCHECK(window_); - wl_surface_commit(window_->surface()); + DCHECK(wayland_surface_); + wl_surface_commit(wayland_surface_->surface()); } void SetupFrameCallback() { - DCHECK(window_); + DCHECK(wayland_surface_); static const wl_callback_listener frame_listener = { &Surface::FrameCallbackDone}; DCHECK(!wl_frame_callback_); - wl_frame_callback_.reset(wl_surface_frame(window_->surface())); + wl_frame_callback_.reset(wl_surface_frame(wayland_surface_->surface())); wl_callback_add_listener(wl_frame_callback_.get(), &frame_listener, this); } void SetupPresentationFeedback(uint32_t buffer_id) { - DCHECK(window_); + DCHECK(wayland_surface_); // Set up presentation feedback. if (!connection_->presentation()) return; @@ -348,7 +351,7 @@ class WaylandBufferManagerHost::Surface { feedback_queue_.push_back( {wl::Object<struct wp_presentation_feedback>(wp_presentation_feedback( - connection_->presentation(), window_->surface())), + connection_->presentation(), wayland_surface_->surface())), buffer_id, /*feedback=*/base::nullopt, /*submission_completed=*/false}); wp_presentation_feedback_add_listener( @@ -372,7 +375,7 @@ class WaylandBufferManagerHost::Surface { DCHECK(wl_frame_callback_.get() == callback); wl_frame_callback_.reset(); - ProcessPendingBuffer(); + MaybeProcessPendingBuffer(); } // wl_callback_listener @@ -438,13 +441,13 @@ class WaylandBufferManagerHost::Surface { prev_submitted_buffer_ = submitted_buffer_; submitted_buffer_ = nullptr; - if (!window_) + if (!wayland_surface_) return; // We can now complete the latest submission. We had to wait for this // release because SwapCompletionCallback indicates to the client that the // previous buffer is available for reuse. - buffer_manager_->OnSubmission(window_->GetWidget(), id, + buffer_manager_->OnSubmission(wayland_surface_->GetRootWidget(), id, gfx::SwapResult::SWAP_ACK); // If presentation feedback is not supported, use a fake feedback. This @@ -452,7 +455,7 @@ class WaylandBufferManagerHost::Surface { if (!connection_->presentation()) { DCHECK(feedback_queue_.empty()); buffer_manager_->OnPresentation( - window_->GetWidget(), id, + wayland_surface_->GetWidget(), id, gfx::PresentationFeedback(base::TimeTicks::Now(), base::TimeDelta(), GetPresentationKindFlags(0))); } else { @@ -468,13 +471,20 @@ class WaylandBufferManagerHost::Surface { } void OnPresentation(struct wp_presentation_feedback* wp_presentation_feedback, - const gfx::PresentationFeedback& feedback) { + const gfx::PresentationFeedback& feedback, + bool discarded = false) { FeedbackInfo* feedback_info = nullptr; for (auto& info : feedback_queue_) { if (info.wp_presentation_feedback.get() == wp_presentation_feedback) { feedback_info = &info; break; - } else if (!info.feedback.has_value()) { // Feedback must come in order. + } else if (!info.feedback.has_value() && !discarded) { + // Feedback must come in order. However, if one of the feedbacks was + // discarded and the previous feedbacks haven't been received yet, don't + // mark previous feedbacks as failed as they will come later. For + // example, imagine you are waiting for f[0], f[1] and f[2]. f[2] gets + // discarded, previous ones mustn't be marked as failed as they will + // come later. info.feedback = gfx::PresentationFeedback::Failure(); } } @@ -499,15 +509,15 @@ class WaylandBufferManagerHost::Surface { // This function ensures that we send OnPresentation for each buffer that // already has had OnSubmission called for it (condition #2). void ProcessPresentationFeedbacks() { - if (!window_) + if (!wayland_surface_) return; while (!feedback_queue_.empty()) { const auto& info = feedback_queue_.front(); if (!info.submission_completed || !info.feedback.has_value()) break; - buffer_manager_->OnPresentation(window_->GetWidget(), info.buffer_id, - *info.feedback); + buffer_manager_->OnPresentation(wayland_surface_->GetWidget(), + info.buffer_id, *info.feedback); feedback_queue_.erase(feedback_queue_.begin()); } // This queue should be small - if not it's likely a bug. @@ -546,11 +556,31 @@ class WaylandBufferManagerHost::Surface { Surface* self = static_cast<Surface*>(data); DCHECK(self); self->OnPresentation(wp_presentation_feedback, - gfx::PresentationFeedback::Failure()); + gfx::PresentationFeedback::Failure(), + true /* discarded */); } - void ProcessPendingBuffer() { - if (!pending_buffer_ || !window_) + void MaybeProcessPendingBuffer() { + // There is nothing to process if there is no pending buffer or the window + // has been destroyed. + if (!pending_buffer_ || !wayland_surface_) + return; + + // This request may come earlier than the Wayland compositor has imported a + // wl_buffer. Wait until the buffer is created. The wait takes place only + // once. Though, the case when a request to attach a buffer comes earlier + // than the wl_buffer is created does not happen often. 1) Depending on the + // zwp linux dmabuf protocol version, the wl_buffer can be created + // immediately without asynchronous wait 2) the wl_buffer can have been + // created by this time. + // + // Another case, which always happen is waiting until the frame callback is + // completed. Thus, wait here when the Wayland compositor fires the frame + // callback. + // + // The third case happens if the window hasn't been configured until a + // request to attach a buffer to its surface is sent. + if (!pending_buffer_->wl_buffer || wl_frame_callback_ || !configured_) return; auto* buffer = pending_buffer_; @@ -562,7 +592,7 @@ class WaylandBufferManagerHost::Surface { // WaylandWindow. // Non-owned. The window this helper surface stores and submits buffers for. - const WaylandWindow* window_; + const WaylandSurface* wayland_surface_; // Non-owned pointer to the connection. WaylandConnection* const connection_; @@ -598,6 +628,11 @@ class WaylandBufferManagerHost::Surface { // a need to call submission callback manually. bool contents_reset_ = false; + // If WaylandWindow has never been configured, do not try to attach + // buffers to its surface. Otherwise, Wayland server will drop the connection + // and send an error - "The surface has never been configured.". + bool configured_ = false; + DISALLOW_COPY_AND_ASSIGN(Surface); }; @@ -619,7 +654,7 @@ WaylandBufferManagerHost::~WaylandBufferManagerHost() { void WaylandBufferManagerHost::OnWindowAdded(WaylandWindow* window) { DCHECK(window); surfaces_[window->GetWidget()] = - std::make_unique<Surface>(window, connection_, this); + std::make_unique<Surface>(window->wayland_surface(), connection_, this); } void WaylandBufferManagerHost::OnWindowRemoved(WaylandWindow* window) { @@ -632,6 +667,13 @@ void WaylandBufferManagerHost::OnWindowRemoved(WaylandWindow* window) { surfaces_.erase(it); } +void WaylandBufferManagerHost::OnWindowConfigured(WaylandWindow* window) { + DCHECK(window); + auto it = surfaces_.find(window->GetWidget()); + DCHECK(it != surfaces_.end()); + it->second->OnWindowConfigured(); +} + void WaylandBufferManagerHost::SetTerminateGpuCallback( base::OnceCallback<void(std::string)> terminate_callback) { terminate_gpu_cb_ = std::move(terminate_callback); @@ -923,11 +965,10 @@ bool WaylandBufferManagerHost::ValidateBufferIdFromGpu(uint32_t buffer_id) { return true; } -bool WaylandBufferManagerHost::ValidateDataFromGpu( - const base::ScopedFD& fd, - size_t length, - const gfx::Size& size, - uint32_t buffer_id) { +bool WaylandBufferManagerHost::ValidateDataFromGpu(const base::ScopedFD& fd, + size_t length, + const gfx::Size& size, + uint32_t buffer_id) { if (!ValidateBufferIdFromGpu(buffer_id)) return false; @@ -952,21 +993,20 @@ bool WaylandBufferManagerHost::ValidateDataFromGpu( void WaylandBufferManagerHost::OnCreateBufferComplete( uint32_t buffer_id, wl::Object<struct wl_buffer> new_buffer) { - auto it = anonymous_buffers_.find(buffer_id); - // It might have already been destroyed or stored by any of the surfaces. - if (it != anonymous_buffers_.end()) { - it->second->wl_buffer = std::move(new_buffer); - } else { - for (auto& surface : surfaces_) { - if (surface.second->BufferExists(buffer_id)) { - surface.second.get()->AttachWlBuffer(buffer_id, - std::move(new_buffer)); - break; - } + auto it = anonymous_buffers_.find(buffer_id); + // It might have already been destroyed or stored by any of the surfaces. + if (it != anonymous_buffers_.end()) { + it->second->wl_buffer = std::move(new_buffer); + } else { + for (auto& surface : surfaces_) { + if (surface.second->BufferExists(buffer_id)) { + surface.second.get()->AttachWlBuffer(buffer_id, std::move(new_buffer)); + break; } } - // There is no need for the buffer anymore. Let it go out of the scope and - // be destroyed. + } + // There is no need for the buffer anymore. Let it go out of the scope and + // be destroyed. } void WaylandBufferManagerHost::OnSubmission( diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h b/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h index c0c6809cad2..b467835f726 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h @@ -81,6 +81,7 @@ class WaylandBufferManagerHost : public ozone::mojom::WaylandBufferManagerHost, // WaylandWindowObserver implements: void OnWindowAdded(WaylandWindow* window) override; void OnWindowRemoved(WaylandWindow* window) override; + void OnWindowConfigured(WaylandWindow* window) override; void SetTerminateGpuCallback( base::OnceCallback<void(std::string)> terminate_gpu_cb); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.cc b/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.cc index 11add83d1b0..a0e848a216d 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.cc @@ -4,28 +4,137 @@ #include "ui/ozone/platform/wayland/host/wayland_clipboard.h" +#include <memory> #include <string> +#include "base/check.h" +#include "base/notreached.h" +#include "ui/base/clipboard/clipboard_buffer.h" +#include "ui/base/clipboard/clipboard_constants.h" +#include "ui/ozone/platform/wayland/common/wayland_object.h" #include "ui/ozone/platform/wayland/host/gtk_primary_selection_device.h" #include "ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h" -#include "ui/ozone/platform/wayland/host/gtk_primary_selection_source.h" +#include "ui/ozone/platform/wayland/host/wayland_connection.h" #include "ui/ozone/platform/wayland/host/wayland_data_device.h" #include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h" -#include "ui/ozone/platform/wayland/host/wayland_data_source_base.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" +#include "ui/ozone/public/platform_clipboard.h" + +namespace wl { + +// Internal Wayland Clipboard interface. A wl::Clipboard implementation handles +// a single ui::ClipboardBuffer. With this common interface it is possible to +// seamlessly support different clipboard buffers backed by different underlying +// Wayland protocol objects. +class Clipboard { + public: + virtual ~Clipboard() = default; + + // Synchronously retrieves the mime types list currently available to be read. + virtual std::vector<std::string> ReadMimeTypes() = 0; + + // Asynchronously reads clipboard content with |mime_type| format. The result + // data is expected to arrive through WaylandClipboard::SetData(). + // TODO(nickdiego): Decouple DataDevice impls from WaylandClipboard. + virtual bool Read(const std::string& mime_type) = 0; + + // Synchronously stores and announces |data| as available from this clipboard. + virtual void Write(const ui::PlatformClipboard::DataMap* data) = 0; + + // Tells if this clipboard instance is the current selection owner. + virtual bool IsSelectionOwner() const = 0; +}; + +// Templated wl::Clipboard implementation. Whereas DataSource is the data source +// class capable of creating data offers upon clipboard writes and communicates +// events through DataSource::Delegate, and DataDevice is its device counterpart +// providing read and write access to the underlying data selection-related +// protocol objects. See *_data_{source,device}.h for more details. +template <typename Manager, + typename DataSource = typename Manager::DataSource, + typename DataDevice = typename Manager::DataDevice> +class ClipboardImpl final : public Clipboard, public DataSource::Delegate { + public: + explicit ClipboardImpl(Manager* manager) : manager_(manager) {} + ClipboardImpl(const ClipboardImpl&) = delete; + ClipboardImpl& operator=(const ClipboardImpl&) = delete; + virtual ~ClipboardImpl() = default; + + virtual bool Read(const std::string& mime_type) override { + return GetDevice()->RequestSelectionData(mime_type); + } + + std::vector<std::string> ReadMimeTypes() override { + return GetDevice()->GetAvailableMimeTypes(); + } + + virtual void Write(const ui::PlatformClipboard::DataMap* data) override { + if (!data || data->empty()) { + data_.clear(); + source_.reset(); + } else { + data_ = *data; + if (!source_) + source_ = manager_->CreateSource(this); + source_->Offer(GetMimeTypes()); + GetDevice()->SetSelectionSource(source_.get()); + } + } + + bool IsSelectionOwner() const override { return !!source_; } + + private: + DataDevice* GetDevice() { return manager_->GetDevice(); } + + std::vector<std::string> GetMimeTypes() { + std::vector<std::string> mime_types; + for (const auto& data : data_) { + mime_types.push_back(data.first); + if (data.first == ui::kMimeTypeText) + mime_types.push_back(ui::kMimeTypeTextUtf8); + } + return mime_types; + } + + // WaylandDataSource::Delegate: + void OnDataSourceFinish(bool completed) override { + if (!completed) + Write(nullptr); + } + + void OnDataSourceSend(const std::string& mime_type, + std::string* contents) override { + DCHECK(contents); + auto it = data_.find(mime_type); + if (it == data_.end() && mime_type == ui::kMimeTypeTextUtf8) + it = data_.find(ui::kMimeTypeText); + if (it != data_.end()) + contents->assign(it->second.begin(), it->second.end()); + } + + // The device manager used to access data device and create data sources. + Manager* const manager_; + + // The current data source used to offer clipboard data. + std::unique_ptr<DataSource> source_; + + // The data currently stored in a given clipboard buffer. + ui::PlatformClipboard::DataMap data_; +}; + +} // namespace wl namespace ui { -WaylandClipboard::WaylandClipboard( - WaylandDataDeviceManager* data_device_manager, - WaylandDataDevice* data_device, - GtkPrimarySelectionDeviceManager* primary_selection_device_manager, - GtkPrimarySelectionDevice* primary_selection_device) - : data_device_manager_(data_device_manager), - data_device_(data_device), - primary_selection_device_manager_(primary_selection_device_manager), - primary_selection_device_(primary_selection_device) { - DCHECK(data_device_manager_); - DCHECK(data_device_); +WaylandClipboard::WaylandClipboard(WaylandConnection* connection, + WaylandDataDeviceManager* manager) + : connection_(connection), + copypaste_clipboard_( + std::make_unique<wl::ClipboardImpl<WaylandDataDeviceManager>>( + manager)) { + DCHECK(manager); + DCHECK(connection_); + DCHECK(copypaste_clipboard_); } WaylandClipboard::~WaylandClipboard() = default; @@ -34,25 +143,8 @@ void WaylandClipboard::OfferClipboardData( ClipboardBuffer buffer, const PlatformClipboard::DataMap& data_map, PlatformClipboard::OfferDataClosure callback) { - WaylandDataSourceBase* data_source = nullptr; - if (buffer == ClipboardBuffer::kCopyPaste) { - if (!clipboard_data_source_) - clipboard_data_source_ = data_device_manager_->CreateSource(); - data_source = clipboard_data_source_.get(); - } else { - if (!IsPrimarySelectionSupported()) { - std::move(callback).Run(); - return; - } - if (!primary_data_source_) - primary_data_source_ = primary_selection_device_manager_->CreateSource(); - data_source = primary_data_source_.get(); - } - - DCHECK(data_source); - data_source->WriteToClipboard(data_map); - data_source->set_data_map(data_map); - + if (auto* clipboard = GetClipboard(buffer)) + clipboard->Write(&data_map); std::move(callback).Run(); } @@ -61,25 +153,18 @@ void WaylandClipboard::RequestClipboardData( const std::string& mime_type, PlatformClipboard::DataMap* data_map, PlatformClipboard::RequestDataClosure callback) { - read_clipboard_closure_ = std::move(callback); DCHECK(data_map); data_map_ = data_map; - if (buffer == ClipboardBuffer::kCopyPaste) { - if (!data_device_->RequestSelectionData(mime_type)) - SetData({}, mime_type); - } else { - if (!IsPrimarySelectionSupported() || - !primary_selection_device_->RequestSelectionData(mime_type)) { - SetData({}, mime_type); - } - } + read_clipboard_closure_ = std::move(callback); + auto* clipboard = GetClipboard(buffer); + if (!clipboard || !clipboard->Read(mime_type)) + SetData({}, mime_type); } bool WaylandClipboard::IsSelectionOwner(ClipboardBuffer buffer) { - if (buffer == ClipboardBuffer::kCopyPaste) - return !!clipboard_data_source_; - else - return !!primary_data_source_; + if (auto* clipboard = GetClipboard(buffer)) + return clipboard->IsSelectionOwner(); + return false; } void WaylandClipboard::SetSequenceNumberUpdateCb( @@ -92,26 +177,10 @@ void WaylandClipboard::SetSequenceNumberUpdateCb( void WaylandClipboard::GetAvailableMimeTypes( ClipboardBuffer buffer, PlatformClipboard::GetMimeTypesClosure callback) { - if (buffer == ClipboardBuffer::kCopyPaste) { - std::move(callback).Run(data_device_->GetAvailableMimeTypes()); - } else { - std::move(callback).Run( - IsPrimarySelectionSupported() - ? primary_selection_device_->GetAvailableMimeTypes() - : std::vector<std::string>{}); - } -} - -void WaylandClipboard::DataSourceCancelled(ClipboardBuffer buffer) { - if (buffer == ClipboardBuffer::kCopyPaste) { - DCHECK(clipboard_data_source_); - SetData({}, {}); - clipboard_data_source_.reset(); - } else { - DCHECK(primary_data_source_); - SetData({}, {}); - primary_data_source_.reset(); - } + std::vector<std::string> mime_types; + if (auto* clipboard = GetClipboard(buffer)) + mime_types = clipboard->ReadMimeTypes(); + std::move(callback).Run(mime_types); } void WaylandClipboard::SetData(const std::vector<uint8_t>& contents, @@ -134,8 +203,24 @@ void WaylandClipboard::UpdateSequenceNumber(ClipboardBuffer buffer) { update_sequence_cb_.Run(buffer); } -bool WaylandClipboard::IsPrimarySelectionSupported() const { - return primary_selection_device_manager_ && primary_selection_device_; +wl::Clipboard* WaylandClipboard::GetClipboard(ClipboardBuffer buffer) { + if (buffer == ClipboardBuffer::kCopyPaste) + return copypaste_clipboard_.get(); + + if (buffer == ClipboardBuffer::kSelection) { + if (auto* manager = connection_->primary_selection_device_manager()) { + if (!primary_selection_clipboard_) { + primary_selection_clipboard_ = std::make_unique< + wl::ClipboardImpl<GtkPrimarySelectionDeviceManager>>(manager); + } + return primary_selection_clipboard_.get(); + } + // Primary selection extension not available. + return nullptr; + } + + NOTREACHED() << "Unsupported clipboard buffer: " << static_cast<int>(buffer); + return nullptr; } } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.h b/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.h index a4482490071..d4641406e0c 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_clipboard.h @@ -5,33 +5,36 @@ #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CLIPBOARD_H_ #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CLIPBOARD_H_ +#include <memory> #include <string> #include <vector> #include "base/callback.h" +#include "base/containers/flat_map.h" #include "base/macros.h" -#include "ui/ozone/platform/wayland/host/wayland_data_source.h" +#include "base/optional.h" +#include "ui/base/clipboard/clipboard_buffer.h" #include "ui/ozone/public/platform_clipboard.h" +namespace wl { +class Clipboard; +} // namespace wl + namespace ui { -class GtkPrimarySelectionDevice; -class GtkPrimarySelectionDeviceManager; -class GtkPrimarySelectionSource; -class WaylandDataDevice; +class WaylandConnection; class WaylandDataDeviceManager; // Handles clipboard operations. // -// Owned by WaylandConnection, which provides a data device and a data device -// manager. +// WaylandDataDeviceManager singleton is required to be up and running for +// WaylandClipboard to be minimally functional. class WaylandClipboard : public PlatformClipboard { public: - WaylandClipboard( - WaylandDataDeviceManager* data_device_manager, - WaylandDataDevice* data_device, - GtkPrimarySelectionDeviceManager* primary_selection_device_manager, - GtkPrimarySelectionDevice* primary_selection_device); + WaylandClipboard(WaylandConnection* connection, + WaylandDataDeviceManager* device_manager); + WaylandClipboard(const WaylandClipboard&) = delete; + WaylandClipboard& operator=(const WaylandClipboard&) = delete; ~WaylandClipboard() override; // PlatformClipboard. @@ -51,35 +54,34 @@ class WaylandClipboard : public PlatformClipboard { void SetSequenceNumberUpdateCb( PlatformClipboard::SequenceNumberUpdateCb cb) override; - void DataSourceCancelled(ClipboardBuffer buffer); + // TODO(nickdiego): Get rid of these methods once DataDevice implementations + // are decoupled from WaylandClipboard. void SetData(const std::vector<uint8_t>& contents, const std::string& mime_type); void UpdateSequenceNumber(ClipboardBuffer buffer); private: - bool IsPrimarySelectionSupported() const; + // Get the wl::Clipboard instance owning a given |buffer|. Can return null in + // case |buffer| is unsupported. E.g: primary selection is not available. + wl::Clipboard* GetClipboard(ClipboardBuffer buffer); + + // WaylandConnection providing optional data device managers, e.g: gtk + // primary selection. + WaylandConnection* const connection_; // Holds a temporary instance of the client's clipboard content // so that we can asynchronously write to it. PlatformClipboard::DataMap* data_map_ = nullptr; - // Notifies whenever clipboard sequence number is changed. Can be empty if not - // set. + // Notifies whenever clipboard sequence number is changed. Can be empty if + // not set. PlatformClipboard::SequenceNumberUpdateCb update_sequence_cb_; // Stores the callback to be invoked upon data reading from clipboard. PlatformClipboard::RequestDataClosure read_clipboard_closure_; - std::unique_ptr<WaylandDataSource> clipboard_data_source_; - std::unique_ptr<GtkPrimarySelectionSource> primary_data_source_; - - // These four instances are owned by the connection. - WaylandDataDeviceManager* const data_device_manager_; - WaylandDataDevice* const data_device_; - GtkPrimarySelectionDeviceManager* const primary_selection_device_manager_; - GtkPrimarySelectionDevice* const primary_selection_device_; - - DISALLOW_COPY_AND_ASSIGN(WaylandClipboard); + const std::unique_ptr<wl::Clipboard> copypaste_clipboard_; + std::unique_ptr<wl::Clipboard> primary_selection_clipboard_; }; } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_connection.cc b/chromium/ui/ozone/platform/wayland/host/wayland_connection.cc index d6c27854df9..0246bc58695 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_connection.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_connection.cc @@ -9,7 +9,7 @@ #include <algorithm> #include <memory> -#include <utility> +#include <vector> #include "base/bind.h" #include "base/logging.h" @@ -17,12 +17,16 @@ #include "base/message_loop/message_loop_current.h" #include "base/strings/string_util.h" #include "base/threading/thread_task_runner_handle.h" -#include "mojo/public/cpp/system/platform_handle.h" #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h" -#include "ui/gfx/swap_result.h" +#include "ui/gfx/geometry/point.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" +#include "ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h" #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h" +#include "ui/ozone/platform/wayland/host/wayland_clipboard.h" #include "ui/ozone/platform/wayland/host/wayland_cursor.h" +#include "ui/ozone/platform/wayland/host/wayland_cursor_position.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h" +#include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h" #include "ui/ozone/platform/wayland/host/wayland_drm.h" #include "ui/ozone/platform/wayland/host/wayland_event_source.h" #include "ui/ozone/platform/wayland/host/wayland_input_method_context.h" @@ -32,6 +36,7 @@ #include "ui/ozone/platform/wayland/host/wayland_shm.h" #include "ui/ozone/platform/wayland/host/wayland_touch.h" #include "ui/ozone/platform/wayland/host/wayland_window.h" +#include "ui/ozone/platform/wayland/host/wayland_window_drag_controller.h" #include "ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.h" namespace ui { @@ -39,6 +44,7 @@ namespace ui { namespace { constexpr uint32_t kMaxCompositorVersion = 4; constexpr uint32_t kMaxGtkPrimarySelectionDeviceManagerVersion = 1; +constexpr uint32_t kMaxKeyboardExtensionVersion = 1; constexpr uint32_t kMaxLinuxDmabufVersion = 3; constexpr uint32_t kMaxSeatVersion = 4; constexpr uint32_t kMaxShmVersion = 1; @@ -128,39 +134,10 @@ void WaylandConnection::SetCursorBitmap(const std::vector<SkBitmap>& bitmaps, cursor_->UpdateBitmap(bitmaps, location, serial_); } -void WaylandConnection::StartDrag(const ui::OSExchangeData& data, - int operation) { - if (!dragdrop_data_source_) - dragdrop_data_source_ = data_device_manager_->CreateSource(); - dragdrop_data_source_->Offer(data); - dragdrop_data_source_->SetAction(operation); - data_device_->StartDrag(dragdrop_data_source_->data_source(), data); -} - -void WaylandConnection::FinishDragSession(uint32_t dnd_action, - WaylandWindow* source_window) { - if (source_window) - source_window->OnDragSessionClose(dnd_action); - data_device_->ResetSourceData(); - dragdrop_data_source_.reset(); -} - -void WaylandConnection::DeliverDragData(const std::string& mime_type, - std::string* buffer) { - data_device_->DeliverDragData(mime_type, buffer); -} - -void WaylandConnection::RequestDragData( - const std::string& mime_type, - base::OnceCallback<void(const std::vector<uint8_t>&)> callback) { - data_device_->RequestDragData(mime_type, std::move(callback)); -} - -bool WaylandConnection::IsDragInProgress() { - // |data_device_| can be null when running on headless weston. - if (!data_device_) - return false; - return data_device_->IsDragEntered() || drag_data_source(); +bool WaylandConnection::IsDragInProgress() const { + // |data_drag_controller_| can be null when running on headless weston. + return data_drag_controller_ && data_drag_controller_->state() != + WaylandDataDragController::State::kIdle; } void WaylandConnection::Flush() { @@ -190,13 +167,8 @@ void WaylandConnection::UpdateInputDevices(wl_seat* seat, if (!has_keyboard) { keyboard_.reset(); - } else if (wl_keyboard* keyboard = wl_seat_get_keyboard(seat)) { - auto* layout_engine = - KeyboardLayoutEngineManager::GetKeyboardLayoutEngine(); - keyboard_ = std::make_unique<WaylandKeyboard>(keyboard, this, layout_engine, - event_source()); - } else { - LOG(ERROR) << "Failed to get wl_keyboard from seat"; + } else if (!CreateKeyboard()) { + LOG(ERROR) << "Failed to create WaylandKeyboard"; } if (!has_touch) { @@ -208,21 +180,34 @@ void WaylandConnection::UpdateInputDevices(wl_seat* seat, } } -void WaylandConnection::EnsureDataDevice() { - if (!data_device_manager_ || !seat_) - return; - DCHECK(!data_device_); - wl_data_device* data_device = data_device_manager_->GetDevice(); - data_device_ = std::make_unique<WaylandDataDevice>(this, data_device); +bool WaylandConnection::CreateKeyboard() { + wl_keyboard* keyboard = wl_seat_get_keyboard(seat_.get()); + if (!keyboard) + return false; - if (primary_selection_device_manager_) { - primary_selection_device_ = std::make_unique<GtkPrimarySelectionDevice>( - this, primary_selection_device_manager_->GetDevice()); - } + auto* layout_engine = KeyboardLayoutEngineManager::GetKeyboardLayoutEngine(); + // Make sure to destroy the old WaylandKeyboard (if it exists) before creating + // the new one. + keyboard_.reset(); + keyboard_.reset(new WaylandKeyboard(keyboard, keyboard_extension_v1_.get(), + this, layout_engine, event_source())); + return true; +} - clipboard_ = std::make_unique<WaylandClipboard>( - data_device_manager_.get(), data_device_.get(), - primary_selection_device_manager_.get(), primary_selection_device_.get()); +void WaylandConnection::CreateDataObjectsIfReady() { + if (data_device_manager_ && seat_) { + DCHECK(!data_drag_controller_); + data_drag_controller_ = std::make_unique<WaylandDataDragController>( + this, data_device_manager_.get()); + + DCHECK(!window_drag_controller_); + window_drag_controller_ = std::make_unique<WaylandWindowDragController>( + this, data_device_manager_.get(), event_source()); + + DCHECK(!clipboard_); + clipboard_ = + std::make_unique<WaylandClipboard>(this, data_device_manager_.get()); + } } // static @@ -268,7 +253,7 @@ void WaylandConnection::Global(void* data, return; } wl_seat_add_listener(connection->seat_.get(), &seat_listener, connection); - connection->EnsureDataDevice(); + connection->CreateDataObjectsIfReady(); } else if (!connection->shell_v6_ && strcmp(interface, "zxdg_shell_v6") == 0) { // Check for zxdg_shell_v6 first. @@ -323,7 +308,7 @@ void WaylandConnection::Global(void* data, connection->data_device_manager_ = std::make_unique<WaylandDataDeviceManager>( data_device_manager.release(), connection); - connection->EnsureDataDevice(); + connection->CreateDataObjectsIfReady(); } else if (!connection->primary_selection_device_manager_ && strcmp(interface, "gtk_primary_selection_device_manager") == 0) { wl::Object<gtk_primary_selection_device_manager> manager = @@ -343,6 +328,17 @@ void WaylandConnection::Global(void* data, (strcmp(interface, "wp_presentation") == 0)) { connection->presentation_ = wl::Bind<wp_presentation>(registry, name, kMaxWpPresentationVersion); + } else if (!connection->keyboard_extension_v1_ && + strcmp(interface, "zcr_keyboard_extension_v1") == 0) { + connection->keyboard_extension_v1_ = wl::Bind<zcr_keyboard_extension_v1>( + registry, name, kMaxKeyboardExtensionVersion); + if (!connection->keyboard_extension_v1_) { + LOG(ERROR) << "Failed to bind zcr_keyboard_extension_v1"; + return; + } + // CreateKeyboard may fail if we do not have keyboard seat capabilities yet. + // We will create the keyboard when get them in that case. + connection->CreateKeyboard(); } else if (!connection->text_input_manager_v1_ && strcmp(interface, "zwp_text_input_manager_v1") == 0) { connection->text_input_manager_v1_ = wl::Bind<zwp_text_input_manager_v1>( diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_connection.h b/chromium/ui/ozone/platform/wayland/host/wayland_connection.h index b3e0a8a6c3d..5c5754dd2fa 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_connection.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_connection.h @@ -6,21 +6,19 @@ #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CONNECTION_H_ #include <memory> -#include <string> #include <vector> -#include "ui/gfx/buffer_types.h" -#include "ui/gfx/native_widget_types.h" +#include "third_party/skia/include/core/SkBitmap.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" -#include "ui/ozone/platform/wayland/host/gtk_primary_selection_device.h" -#include "ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h" #include "ui/ozone/platform/wayland/host/wayland_clipboard.h" -#include "ui/ozone/platform/wayland/host/wayland_cursor_position.h" -#include "ui/ozone/platform/wayland/host/wayland_data_device.h" -#include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h" +#include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h" #include "ui/ozone/platform/wayland/host/wayland_data_source.h" #include "ui/ozone/platform/wayland/host/wayland_window_manager.h" +namespace gfx { +class Point; +} + namespace ui { class WaylandBufferManagerHost; @@ -32,8 +30,11 @@ class WaylandOutputManager; class WaylandPointer; class WaylandShm; class WaylandTouch; -class WaylandWindow; class WaylandZwpLinuxDmabuf; +class WaylandDataDeviceManager; +class WaylandCursorPosition; +class WaylandWindowDragController; +class GtkPrimarySelectionDeviceManager; class WaylandConnection { public: @@ -54,10 +55,6 @@ class WaylandConnection { xdg_wm_base* shell() const { return shell_.get(); } zxdg_shell_v6* shell_v6() const { return shell_v6_.get(); } wl_seat* seat() const { return seat_.get(); } - wl_data_device* data_device() const { return data_device_->data_device(); } - gtk_primary_selection_device* primary_selection_device() const { - return primary_selection_device_->data_device(); - } wp_presentation* presentation() const { return presentation_.get(); } zwp_text_input_manager_v1* text_input_manager_v1() const { return text_input_manager_v1_.get(); @@ -82,10 +79,6 @@ class WaylandConnection { WaylandClipboard* clipboard() const { return clipboard_.get(); } - WaylandDataSource* drag_data_source() const { - return dragdrop_data_source_.get(); - } - WaylandOutputManager* wayland_output_manager() const { return wayland_output_manager_.get(); } @@ -109,35 +102,36 @@ class WaylandConnection { return &wayland_window_manager_; } - WaylandDataDevice* wayland_data_device() const { return data_device_.get(); } - - // Starts drag with |data| to be delivered, |operation| supported by the - // source side initiated the dragging. - void StartDrag(const ui::OSExchangeData& data, int operation); - // Finishes drag and drop session. It happens when WaylandDataSource gets - // 'OnDnDFinished' or 'OnCancel', which means the drop is performed or - // canceled on others. - void FinishDragSession(uint32_t dnd_action, WaylandWindow* source_window); - // Delivers the data owned by Chromium which initiates drag-and-drop. |buffer| - // is an output parameter and it should be filled with the data corresponding - // to mime_type. - void DeliverDragData(const std::string& mime_type, std::string* buffer); - // Requests the data to the platform when Chromium gets drag-and-drop started - // by others. Once reading the data from platform is done, |callback| should - // be called with the data. - void RequestDragData( - const std::string& mime_type, - base::OnceCallback<void(const std::vector<uint8_t>&)> callback); + WaylandDataDeviceManager* data_device_manager() const { + return data_device_manager_.get(); + } + + GtkPrimarySelectionDeviceManager* primary_selection_device_manager() const { + return primary_selection_device_manager_.get(); + } + + WaylandDataDragController* data_drag_controller() const { + return data_drag_controller_.get(); + } + + WaylandWindowDragController* window_drag_controller() const { + return window_drag_controller_.get(); + } // Returns true when dragging is entered or started. - bool IsDragInProgress(); + bool IsDragInProgress() const; private: void Flush(); void UpdateInputDevices(wl_seat* seat, uint32_t capabilities); - // Make sure data device is properly initialized - void EnsureDataDevice(); + // Initialize data-related objects if required protocol objects are already + // in place, i.e: wl_seat and wl_data_device_manager. + void CreateDataObjectsIfReady(); + + // Creates WaylandKeyboard with the currently acquired protocol objects, if + // possible. Returns true iff WaylandKeyboard was created. + bool CreateKeyboard(); // wl_registry_listener static void Global(void* data, @@ -166,10 +160,11 @@ class WaylandConnection { wl::Object<xdg_wm_base> shell_; wl::Object<zxdg_shell_v6> shell_v6_; wl::Object<wp_presentation> presentation_; + wl::Object<zcr_keyboard_extension_v1> keyboard_extension_v1_; wl::Object<zwp_text_input_manager_v1> text_input_manager_v1_; - // Event source instance. Must be declared before input objects so it outlives - // them so thus being able to properly handle their destruction. + // Event source instance. Must be declared before input objects so it + // outlives them so thus being able to properly handle their destruction. std::unique_ptr<WaylandEventSource> event_source_; // Input device objects. @@ -179,9 +174,7 @@ class WaylandConnection { std::unique_ptr<WaylandCursor> cursor_; std::unique_ptr<WaylandDataDeviceManager> data_device_manager_; - std::unique_ptr<WaylandDataDevice> data_device_; std::unique_ptr<WaylandClipboard> clipboard_; - std::unique_ptr<WaylandDataSource> dragdrop_data_source_; std::unique_ptr<WaylandOutputManager> wayland_output_manager_; std::unique_ptr<WaylandCursorPosition> wayland_cursor_position_; std::unique_ptr<WaylandZwpLinuxDmabuf> zwp_dmabuf_; @@ -191,7 +184,9 @@ class WaylandConnection { std::unique_ptr<GtkPrimarySelectionDeviceManager> primary_selection_device_manager_; - std::unique_ptr<GtkPrimarySelectionDevice> primary_selection_device_; + + std::unique_ptr<WaylandDataDragController> data_drag_controller_; + std::unique_ptr<WaylandWindowDragController> window_drag_controller_; // Manages Wayland windows. WaylandWindowManager wayland_window_manager_; diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_cursor.cc b/chromium/ui/ozone/platform/wayland/host/wayland_cursor.cc index 0d495501f06..7ffd900fcae 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_cursor.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_cursor.cc @@ -7,6 +7,7 @@ #include <memory> #include <vector> +#include "base/logging.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/skia_util.h" #include "ui/ozone/platform/wayland/common/wayland_util.h" diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_device.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_device.cc index 361ffa519f1..1edd356006b 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_device.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_device.cc @@ -5,209 +5,62 @@ #include "ui/ozone/platform/wayland/host/wayland_data_device.h" #include <memory> +#include <string> #include <utility> -#include <vector> #include "base/bind.h" -#include "base/strings/string16.h" -#include "base/strings/string_split.h" -#include "base/strings/utf_string_conversions.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "third_party/skia/include/core/SkCanvas.h" -#include "ui/base/clipboard/clipboard_constants.h" -#include "ui/base/dragdrop/drag_drop_types.h" -#include "ui/base/dragdrop/file_info/file_info.h" -#include "ui/base/dragdrop/os_exchange_data.h" -#include "ui/base/dragdrop/os_exchange_data_provider_aura.h" +#include "base/files/scoped_file.h" +#include "ui/base/clipboard/clipboard_buffer.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/ozone/platform/wayland/common/data_util.h" +#include "ui/ozone/platform/wayland/common/wayland_object.h" #include "ui/ozone/platform/wayland/common/wayland_util.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" #include "ui/ozone/platform/wayland/host/wayland_data_offer.h" -#include "ui/ozone/platform/wayland/host/wayland_shm_buffer.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" #include "ui/ozone/platform/wayland/host/wayland_window.h" -#include "url/gurl.h" -#include "url/url_canon.h" -#include "url/url_util.h" namespace ui { -namespace { - -constexpr FilenameToURLPolicy kFilenameToURLPolicy = CONVERT_FILENAMES; - -// Converts raw data to either narrow or wide string. -template <typename StringType> -StringType BytesTo(const PlatformClipboard::Data& bytes) { - if (bytes.size() % sizeof(typename StringType::value_type) != 0U) { - // This is suspicious. - LOG(WARNING) - << "Data is possibly truncated, or a wrong conversion is requested."; - } - - StringType result; - result.assign(reinterpret_cast<typename StringType::const_pointer>(&bytes[0]), - bytes.size() / sizeof(typename StringType::value_type)); - return result; -} - -// Returns actions possible with the given source and drag'n'drop actions. -// Also converts enums: input params are wl_data_device_manager_dnd_action but -// the result is ui::DragDropTypes. -int GetPossibleActions(uint32_t source_actions, uint32_t dnd_action) { - // If drag'n'drop action is set, use it but check for ASK action (see below). - uint32_t action = dnd_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE - ? dnd_action - : source_actions; - - // We accept any action except ASK (see below). - int operation = DragDropTypes::DRAG_NONE; - if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) - operation |= DragDropTypes::DRAG_COPY; - if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) - operation |= DragDropTypes::DRAG_MOVE; - if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { - // This is very rare and non-standard. Chromium doesn't set this when - // anything is dragged from it, neither it provides any UI for asking - // the user about the desired drag'n'drop action when data is dragged - // from an external source. - // We are safe with not adding anything here. However, keep NOTIMPLEMENTED - // for an (unlikely) event of this being hit in distant future. - NOTIMPLEMENTED_LOG_ONCE(); - } - return operation; -} - -void AddString(const PlatformClipboard::Data& data, - OSExchangeData* os_exchange_data) { - DCHECK(os_exchange_data); - - if (data.empty()) - return; - - os_exchange_data->SetString(base::UTF8ToUTF16(BytesTo<std::string>(data))); -} - -void AddHtml(const PlatformClipboard::Data& data, - OSExchangeData* os_exchange_data) { - DCHECK(os_exchange_data); - - if (data.empty()) - return; - - os_exchange_data->SetHtml(base::UTF8ToUTF16(BytesTo<std::string>(data)), - GURL()); -} - -// Parses |data| as if it had text/uri-list format. Its brief spec is: -// 1. Any lines beginning with the '#' character are comment lines. -// 2. Non-comment lines shall be URIs (URNs or URLs). -// 3. Lines are terminated with a CRLF pair. -// 4. URL encoding is used. -void AddFiles(const PlatformClipboard::Data& data, - OSExchangeData* os_exchange_data) { - DCHECK(os_exchange_data); - - std::string data_as_string = BytesTo<std::string>(data); - - const auto lines = base::SplitString( - data_as_string, "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - std::vector<FileInfo> filenames; - for (const auto& line : lines) { - if (line.empty() || line[0] == '#') - continue; - GURL url(line); - if (!url.is_valid() || !url.SchemeIsFile()) { - LOG(WARNING) << "Invalid URI found: " << line; - continue; - } - - std::string url_path = url.path(); - url::RawCanonOutputT<base::char16> unescaped; - url::DecodeURLEscapeSequences(url_path.data(), url_path.size(), - url::DecodeURLMode::kUTF8OrIsomorphic, - &unescaped); - - std::string path8; - base::UTF16ToUTF8(unescaped.data(), unescaped.length(), &path8); - const base::FilePath path(path8); - filenames.push_back({path, path.BaseName()}); - } - if (filenames.empty()) - return; - - os_exchange_data->SetFilenames(filenames); -} - -// Parses |data| as if it had text/x-moz-url format, which is basically -// two lines separated with newline, where the first line is the URL and -// the second one is page title. The unpleasant feature of text/x-moz-url is -// that the URL has UTF-16 encoding. -void AddUrl(const PlatformClipboard::Data& data, - OSExchangeData* os_exchange_data) { - DCHECK(os_exchange_data); - - if (data.empty()) - return; - - base::string16 data_as_string16 = BytesTo<base::string16>(data); - - const auto lines = - base::SplitString(data_as_string16, base::ASCIIToUTF16("\r\n"), - base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - if (lines.size() != 2U) { - LOG(WARNING) << "Invalid data passed as text/x-moz-url; it must contain " - << "exactly 2 lines but has " << lines.size() << " instead."; - return; - } - GURL url(lines[0]); - if (!url.is_valid()) { - LOG(WARNING) << "Invalid data passed as text/x-moz-url; the first line " - << "must contain a valid URL but it doesn't."; - return; - } - - os_exchange_data->SetURL(url, lines[1]); -} - -void AddToOSExchangeData(const PlatformClipboard::Data& data, - const std::string& mime_type, - OSExchangeData* os_exchange_data) { - DCHECK(os_exchange_data); - if ((mime_type == kMimeTypeText || mime_type == kMimeTypeTextUtf8)) { - DCHECK(!os_exchange_data->HasString()); - AddString(data, os_exchange_data); - } else if (mime_type == kMimeTypeHTML) { - DCHECK(!os_exchange_data->HasHtml()); - AddHtml(data, os_exchange_data); - } else if (mime_type == kMimeTypeMozillaURL) { - DCHECK(!os_exchange_data->HasURL(kFilenameToURLPolicy)); - AddUrl(data, os_exchange_data); - } else if (mime_type == kMimeTypeURIList) { - DCHECK(!os_exchange_data->HasFile()); - AddFiles(data, os_exchange_data); - } else { - LOG(WARNING) << "Unhandled MIME type: " << mime_type; - } -} - -} // namespace - -// static WaylandDataDevice::WaylandDataDevice(WaylandConnection* connection, wl_data_device* data_device) : WaylandDataDeviceBase(connection), data_device_(data_device) { static const struct wl_data_device_listener kDataDeviceListener = { - WaylandDataDevice::OnDataOffer, WaylandDataDevice::OnEnter, - WaylandDataDevice::OnLeave, WaylandDataDevice::OnMotion, - WaylandDataDevice::OnDrop, WaylandDataDevice::OnSelection}; + WaylandDataDevice::OnOffer, WaylandDataDevice::OnEnter, + WaylandDataDevice::OnLeave, WaylandDataDevice::OnMotion, + WaylandDataDevice::OnDrop, WaylandDataDevice::OnSelection}; wl_data_device_add_listener(data_device_.get(), &kDataDeviceListener, this); } WaylandDataDevice::~WaylandDataDevice() = default; -void WaylandDataDevice::RequestDragData( - const std::string& mime_type, - base::OnceCallback<void(const PlatformClipboard::Data&)> callback) { - base::ScopedFD fd = drag_offer_->Receive(mime_type); +void WaylandDataDevice::StartDrag(const WaylandDataSource& data_source, + const WaylandWindow& origin_window, + wl_surface* icon_surface, + DragDelegate* delegate) { + DCHECK(delegate); + DCHECK(!drag_delegate_); + drag_delegate_ = delegate; + + wl_data_device_start_drag(data_device_.get(), data_source.data_source(), + origin_window.surface(), icon_surface, + connection()->serial()); + drag_delegate_->DrawIcon(); + connection()->ScheduleFlush(); +} + +void WaylandDataDevice::ResetDragDelegate() { + DCHECK(drag_delegate_); + drag_delegate_ = nullptr; +} + +void WaylandDataDevice::RequestData(WaylandDataOffer* offer, + const std::string& mime_type, + RequestDataCallback callback) { + DCHECK(offer); + DCHECK(wl::IsMimeTypeSupported(mime_type)); + + base::ScopedFD fd = offer->Receive(mime_type); if (!fd.is_valid()) { LOG(ERROR) << "Failed to open file descriptor."; return; @@ -221,55 +74,13 @@ void WaylandDataDevice::RequestDragData( RegisterDeferredReadCallback(); } -void WaylandDataDevice::DeliverDragData(const std::string& mime_type, - std::string* buffer) { - DCHECK(buffer); - DCHECK(source_data_); - - if (mime_type == kMimeTypeMozillaURL && - source_data_->HasURL(kFilenameToURLPolicy)) { - GURL url; - base::string16 title; - source_data_->GetURLAndTitle(kFilenameToURLPolicy, &url, &title); - buffer->append(url.spec()); - } else if (mime_type == kMimeTypeHTML && source_data_->HasHtml()) { - base::string16 data; - GURL base_url; - source_data_->GetHtml(&data, &base_url); - buffer->append(base::UTF16ToUTF8(data)); - } else if (source_data_->HasString()) { - base::string16 data; - source_data_->GetString(&data); - buffer->append(base::UTF16ToUTF8(data)); - } else { - LOG(WARNING) << "Cannot deliver data of type " << mime_type - << " and no text representation is available."; - } -} - -void WaylandDataDevice::StartDrag(wl_data_source* data_source, - const ui::OSExchangeData& data) { - DCHECK(data_source); - - WaylandWindow* window = - connection()->wayland_window_manager()->GetCurrentFocusedWindow(); - if (!window) { - LOG(ERROR) << "Failed to get focused window."; - return; - } - const SkBitmap* icon = PrepareDragIcon(data); - source_data_ = std::make_unique<ui::OSExchangeData>(data.provider().Clone()); - wl_data_device_start_drag(data_device_.get(), data_source, window->surface(), - icon_surface_.get(), connection()->serial()); - if (icon) - DrawDragIcon(icon); +void WaylandDataDevice::SetSelectionSource(WaylandDataSource* source) { + DCHECK(source); + wl_data_device_set_selection(data_device_.get(), source->data_source(), + connection()->serial()); connection()->ScheduleFlush(); } -void WaylandDataDevice::ResetSourceData() { - source_data_.reset(); -} - void WaylandDataDevice::ReadDragDataFromFD( base::ScopedFD fd, base::OnceCallback<void(const PlatformClipboard::Data&)> callback) { @@ -278,17 +89,10 @@ void WaylandDataDevice::ReadDragDataFromFD( std::move(callback).Run(contents); } -void WaylandDataDevice::HandleDeferredLeaveIfNeeded() { - if (!is_leaving_) - return; - - OnLeave(this, data_device_.get()); -} - // static -void WaylandDataDevice::OnDataOffer(void* data, - wl_data_device* data_device, - wl_data_offer* offer) { +void WaylandDataDevice::OnOffer(void* data, + wl_data_device* data_device, + wl_data_offer* offer) { auto* self = static_cast<WaylandDataDevice*>(data); self->connection()->clipboard()->UpdateSequenceNumber( @@ -305,40 +109,24 @@ void WaylandDataDevice::OnEnter(void* data, wl_fixed_t x, wl_fixed_t y, wl_data_offer* offer) { - WaylandWindow* window = - static_cast<WaylandWindow*>(wl_surface_get_user_data(surface)); + WaylandWindow* window = WaylandWindow::FromSurface(surface); if (!window) { LOG(ERROR) << "Failed to get window."; return; } auto* self = static_cast<WaylandDataDevice*>(data); + + // Null |drag_delegate_| here means that the DND session has been initiated by + // an external application. In this case, use the default data drag delegate. + if (!self->drag_delegate_) + self->drag_delegate_ = self->connection()->data_drag_controller(); + DCHECK(self->new_offer_); - DCHECK(!self->drag_offer_); - self->drag_offer_ = std::move(self->new_offer_); - self->window_ = window; - - // TODO(crbug.com/1004715): Set mime type the client can accept. Now it sets - // all mime types offered because current implementation doesn't decide - // action based on mime type. - self->unprocessed_mime_types_.clear(); - for (auto mime : self->drag_offer_->mime_types()) { - self->unprocessed_mime_types_.push_back(mime); - self->drag_offer_->Accept(serial, mime); - } + self->drag_delegate_->OnDragOffer(std::move(self->new_offer_)); gfx::PointF point(wl_fixed_to_double(x), wl_fixed_to_double(y)); - - // If |source_data_| is set, it means that dragging is started from the - // same window and it's not needed to read data through Wayland. - std::unique_ptr<OSExchangeData> dragged_data; - if (!self->IsDraggingExternalData()) - dragged_data = std::make_unique<OSExchangeData>( - self->source_data_->provider().Clone()); - self->window_->OnDragEnter( - point, std::move(dragged_data), - GetPossibleActions(self->drag_offer_->source_actions(), - self->drag_offer_->dnd_action())); + self->drag_delegate_->OnDragEnter(window, point, serial); } void WaylandDataDevice::OnMotion(void* data, @@ -347,65 +135,31 @@ void WaylandDataDevice::OnMotion(void* data, wl_fixed_t x, wl_fixed_t y) { auto* self = static_cast<WaylandDataDevice*>(data); - if (!self->window_) { - LOG(ERROR) << "Failed to get window."; - return; + if (self->drag_delegate_) { + gfx::PointF point(wl_fixed_to_double(x), wl_fixed_to_double(y)); + self->drag_delegate_->OnDragMotion(point); } - - gfx::PointF point(wl_fixed_to_double(x), wl_fixed_to_double(y)); - int client_operation = self->window_->OnDragMotion( - point, time, - GetPossibleActions(self->drag_offer_->source_actions(), - self->drag_offer_->dnd_action())); - self->SetOperation(client_operation); } void WaylandDataDevice::OnDrop(void* data, wl_data_device* data_device) { auto* self = static_cast<WaylandDataDevice*>(data); - if (!self->window_) { - LOG(ERROR) << "Failed to get window."; - return; - } - if (self->IsDraggingExternalData()) { - // We are about to accept data dragged from another application. - // Reading all the data may take some time so we set - // |is_handling_dropped_data_| that will postpone handling of OnLeave - // until reading is completed. - self->is_handling_dropped_data_ = true; - self->received_data_ = std::make_unique<OSExchangeData>( - std::make_unique<OSExchangeDataProviderAura>()); - self->HandleUnprocessedMimeTypes(); - } else { - // If the drag session had been started internally by chromium, - // |source_data_| already holds the data, and it is already forwarded to the - // delegate through OnDragEnter, so here we short-cut the data transfer by - // sending nullptr. - self->HandleReceivedData(nullptr); - } + if (self->drag_delegate_) + self->drag_delegate_->OnDragDrop(); } void WaylandDataDevice::OnLeave(void* data, wl_data_device* data_device) { - // While reading data, it could get OnLeave event. We don't handle OnLeave - // event directly if |is_handling_dropped_data_| is set. auto* self = static_cast<WaylandDataDevice*>(data); - if (!self->window_) { - LOG(ERROR) << "Failed to get window."; - return; - } + if (self->drag_delegate_) { + self->drag_delegate_->OnDragLeave(); - if (self->is_handling_dropped_data_) { - self->is_leaving_ = true; - return; + // When in a DND session initiated by an external application, + // |drag_delegate_| is set at OnEnter, and must be reset here to avoid + // potential use-after-free. + if (!self->drag_delegate_->IsDragSource()) + self->drag_delegate_ = nullptr; } - - self->window_->OnDragLeave(); - self->window_ = nullptr; - self->drag_offer_.reset(); - self->is_handling_dropped_data_ = false; - self->is_leaving_ = false; } -// static void WaylandDataDevice::OnSelection(void* data, wl_data_device* data_device, wl_data_offer* offer) { @@ -428,106 +182,4 @@ void WaylandDataDevice::OnSelection(void* data, self->data_offer()->EnsureTextMimeTypeIfNeeded(); } -const SkBitmap* WaylandDataDevice::PrepareDragIcon(const OSExchangeData& data) { - const SkBitmap* icon_bitmap = data.provider().GetDragImage().bitmap(); - if (!icon_bitmap || icon_bitmap->empty()) - return nullptr; - icon_surface_.reset(wl_compositor_create_surface(connection()->compositor())); - DCHECK(icon_surface_); - return icon_bitmap; -} - -void WaylandDataDevice::DrawDragIcon(const SkBitmap* icon_bitmap) { - DCHECK(icon_bitmap); - DCHECK(!icon_bitmap->empty()); - gfx::Size size(icon_bitmap->width(), icon_bitmap->height()); - - if (!shm_buffer_ || shm_buffer_->size() != size) { - shm_buffer_ = std::make_unique<WaylandShmBuffer>(connection()->shm(), size); - if (!shm_buffer_->IsValid()) { - LOG(ERROR) << "Failed to create drag icon buffer."; - return; - } - } - wl::DrawBitmap(*icon_bitmap, shm_buffer_.get()); - - wl_surface* surface = icon_surface_.get(); - wl_surface_attach(surface, shm_buffer_->get(), 0, 0); - wl_surface_damage(surface, 0, 0, size.width(), size.height()); - wl_surface_commit(surface); -} - -void WaylandDataDevice::HandleUnprocessedMimeTypes() { - std::string mime_type = SelectNextMimeType(); - if (mime_type.empty()) { - HandleReceivedData(std::move(received_data_)); - } else { - RequestDragData(mime_type, - base::BindOnce(&WaylandDataDevice::OnDragDataReceived, - base::Unretained(this))); - } -} - -void WaylandDataDevice::OnDragDataReceived( - const PlatformClipboard::Data& contents) { - if (!contents.empty()) { - AddToOSExchangeData(contents, unprocessed_mime_types_.front(), - received_data_.get()); - } - - unprocessed_mime_types_.pop_front(); - - // Continue reading data for other negotiated mime types. - HandleUnprocessedMimeTypes(); -} - -void WaylandDataDevice::HandleReceivedData( - std::unique_ptr<ui::OSExchangeData> received_data) { - unprocessed_mime_types_.clear(); - - window_->OnDragDrop(std::move(received_data)); - drag_offer_->FinishOffer(); - is_handling_dropped_data_ = false; - HandleDeferredLeaveIfNeeded(); -} - -std::string WaylandDataDevice::SelectNextMimeType() { - while (!unprocessed_mime_types_.empty()) { - const std::string& mime_type = unprocessed_mime_types_.front(); - if ((mime_type == kMimeTypeText || mime_type == kMimeTypeTextUtf8) && - !received_data_->HasString()) { - return mime_type; - } - if (mime_type == kMimeTypeURIList && !received_data_->HasFile()) { - return mime_type; - } - if (mime_type == kMimeTypeMozillaURL && - !received_data_->HasURL(kFilenameToURLPolicy)) { - return mime_type; - } - if (mime_type == kMimeTypeHTML && !received_data_->HasHtml()) { - return mime_type; - } - unprocessed_mime_types_.pop_front(); - } - return {}; -} - -void WaylandDataDevice::SetOperation(const int operation) { - uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - uint32_t preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - - if (operation & DragDropTypes::DRAG_COPY) { - dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; - preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; - } - - if (operation & DragDropTypes::DRAG_MOVE) { - dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; - if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) - preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; - } - drag_offer_->SetAction(dnd_actions, preferred_action); -} - } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_device.h b/chromium/ui/ozone/platform/wayland/host/wayland_data_device.h index cf2e8cc7a65..5512f4c4480 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_device.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_device.h @@ -7,73 +7,84 @@ #include <wayland-client.h> -#include <list> +#include <cstdint> #include <memory> #include <string> #include "base/callback.h" #include "base/files/scoped_file.h" -#include "base/macros.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" #include "ui/ozone/platform/wayland/host/wayland_data_device_base.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" #include "ui/ozone/public/platform_clipboard.h" -class SkBitmap; +namespace gfx { +class PointF; +} // namespace gfx namespace ui { -class OSExchangeData; class WaylandDataOffer; class WaylandConnection; -class WaylandShmBuffer; class WaylandWindow; // This class provides access to inter-client data transfer mechanisms // such as copy-and-paste and drag-and-drop mechanisms. class WaylandDataDevice : public WaylandDataDeviceBase { public: + using RequestDataCallback = + base::OnceCallback<void(const PlatformClipboard::Data&)>; + + // DragDelegate is responsible for handling drag and drop sessions. + class DragDelegate { + public: + virtual bool IsDragSource() const = 0; + virtual void DrawIcon() = 0; + virtual void OnDragOffer(std::unique_ptr<WaylandDataOffer> offer) = 0; + virtual void OnDragEnter(WaylandWindow* window, + const gfx::PointF& location, + uint32_t serial) = 0; + virtual void OnDragMotion(const gfx::PointF& location) = 0; + virtual void OnDragLeave() = 0; + virtual void OnDragDrop() = 0; + + protected: + virtual ~DragDelegate() = default; + }; + WaylandDataDevice(WaylandConnection* connection, wl_data_device* data_device); + WaylandDataDevice(const WaylandDataDevice&) = delete; + WaylandDataDevice& operator=(const WaylandDataDevice&) = delete; ~WaylandDataDevice() override; - // Requests the data to the platform when Chromium gets drag-and-drop started - // by others. Once reading the data from platform is done, |callback| should - // be called with the data. - void RequestDragData( - const std::string& mime_type, - base::OnceCallback<void(const PlatformClipboard::Data&)> callback); - // Delivers the data owned by Chromium which initiates drag-and-drop. |buffer| - // is an output parameter and it should be filled with the data corresponding - // to mime_type. - void DeliverDragData(const std::string& mime_type, std::string* buffer); - // Starts drag with |data| to be delivered, |operation| supported by the - // source side initiated the dragging. - void StartDrag(wl_data_source* data_source, const ui::OSExchangeData& data); - // Resets |source_data_| when the dragging is finished. - void ResetSourceData(); - - wl_data_device* data_device() const { return data_device_.get(); } + // Starts a wayland drag and drop session, controlled by |delegate|. + void StartDrag(const WaylandDataSource& data_source, + const WaylandWindow& origin_window, + wl_surface* icon_surface, + DragDelegate* delegate); - bool IsDragEntered() { return drag_offer_ != nullptr; } + // Reset the drag delegate, assuming there is one set. Any wl_data_device + // event received after this will be ignored until a new delegate is set. + void ResetDragDelegate(); - WaylandWindow* entered_window() const { return window_; } + // Requests data for an |offer| in a format specified by |mime_type|. The + // transfer happens asynchronously and |callback| is called when it is done. + void RequestData(WaylandDataOffer* offer, + const std::string& mime_type, + RequestDataCallback callback); - private: - void ReadDragDataFromFD( - base::ScopedFD fd, - base::OnceCallback<void(const PlatformClipboard::Data&)> callback); + // Returns the underlying wl_data_device singleton object. + wl_data_device* data_device() const { return data_device_.get(); } - // If source_data_ is not set, data is being dragged from an external - // application (non-chromium). - bool IsDraggingExternalData() const { return !source_data_; } + void SetSelectionSource(WaylandDataSource* source); - // If OnLeave event occurs while it's reading drag data, it defers handling - // it. Once reading data is completed, it's handled. - void HandleDeferredLeaveIfNeeded(); + private: + void ReadDragDataFromFD(base::ScopedFD fd, RequestDataCallback callback); // wl_data_device_listener callbacks - static void OnDataOffer(void* data, - wl_data_device* data_device, - wl_data_offer* id); + static void OnOffer(void* data, + wl_data_device* data_device, + wl_data_offer* id); static void OnEnter(void* data, wl_data_device* data_device, @@ -101,60 +112,18 @@ class WaylandDataDevice : public WaylandDataDeviceBase { wl_data_device* data_device, wl_data_offer* id); - // Returns the drag icon bitmap and creates and wayland surface to draw it - // on, if a valid drag image is present in |data|; otherwise returns null. - const SkBitmap* PrepareDragIcon(const OSExchangeData& data); - void DrawDragIcon(const SkBitmap* bitmap); - - void OnDragDataReceived(const PlatformClipboard::Data& contents); - - // HandleUnprocessedMimeTypes asynchronously request and read data for every - // negotiated mime type, one after another (OnDragDataReceived calls back - // HandleUnprocessedMimeTypes so it finish only when there's no more items in - // unprocessed_mime_types_ vector). HandleReceivedData is called once the - // process is finished. - void HandleUnprocessedMimeTypes(); - void HandleReceivedData(std::unique_ptr<ui::OSExchangeData> received_data); - // Returns the next MIME type to be received from the source process, or an - // empty string if there are no more interesting MIME types left to process. - std::string SelectNextMimeType(); - - // Set drag operation decided by client. - void SetOperation(const int operation); - // The wl_data_device wrapped by this WaylandDataDevice. wl::Object<wl_data_device> data_device_; + DragDelegate* drag_delegate_ = nullptr; + // There are two separate data offers at a time, the drag offer and the // selection offer, each with independent lifetimes. When we receive a new - // offer, it is not immediately possible to know whether the new offer is the - // drag offer or the selection offer. This variable is used to store ownership - // of new data offers temporarily until its identity becomes known. + // offer, it is not immediately possible to know whether the new offer is + // the drag offer or the selection offer. This variable is used to store + // new data offers temporarily until it is possible to determine which kind + // session it belongs to. std::unique_ptr<WaylandDataOffer> new_offer_; - - // Offer to receive data from another process via drag-and-drop, or null if no - // drag-and-drop from another process is in progress. - std::unique_ptr<WaylandDataOffer> drag_offer_; - - WaylandWindow* window_ = nullptr; - - bool is_handling_dropped_data_ = false; - bool is_leaving_ = false; - - std::unique_ptr<WaylandShmBuffer> shm_buffer_; - wl::Object<wl_surface> icon_surface_; - - // Mime types to be handled. - std::list<std::string> unprocessed_mime_types_; - - // The data delivered from Wayland - std::unique_ptr<ui::OSExchangeData> received_data_; - - // When dragging is started from Chromium, |source_data_| is forwarded to - // Wayland when they are ready to get the data. - std::unique_ptr<ui::OSExchangeData> source_data_; - - DISALLOW_COPY_AND_ASSIGN(WaylandDataDevice); }; } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.cc index e61c9b53816..3e020a31134 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.cc @@ -7,6 +7,7 @@ #include <utility> #include "base/bind.h" +#include "base/logging.h" #include "ui/ozone/platform/wayland/common/wayland_util.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" @@ -64,7 +65,7 @@ void WaylandDataDeviceBase::RegisterDeferredReadCallback() { deferred_read_callback_.reset(wl_display_sync(connection_->display())); static const wl_callback_listener kListener = { - GtkPrimarySelectionDevice::DeferredReadCallback}; + WaylandDataDeviceBase::DeferredReadCallback}; wl_callback_add_listener(deferred_read_callback_.get(), &kListener, this); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.h b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.h index 78abf99aec9..e331243fc5e 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_base.h @@ -25,6 +25,7 @@ class WaylandDataDeviceBase { // Returns MIME types given by the current data offer. const std::vector<std::string>& GetAvailableMimeTypes() const; + // Extracts data of the specified MIME type from the data offer. bool RequestSelectionData(const std::string& mime_type); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc index 5b4a79d7979..6c283794473 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.cc @@ -4,7 +4,12 @@ #include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h" +#include <wayland-client-protocol.h> + +#include <memory> + #include "ui/ozone/platform/wayland/host/wayland_connection.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device.h" #include "ui/ozone/platform/wayland/host/wayland_data_source.h" namespace ui { @@ -19,16 +24,23 @@ WaylandDataDeviceManager::WaylandDataDeviceManager( WaylandDataDeviceManager::~WaylandDataDeviceManager() = default; -wl_data_device* WaylandDataDeviceManager::GetDevice() { +WaylandDataDevice* WaylandDataDeviceManager::GetDevice() { DCHECK(connection_->seat()); - return wl_data_device_manager_get_data_device(device_manager_.get(), - connection_->seat()); + if (!device_) { + device_ = std::make_unique<WaylandDataDevice>( + connection_, wl_data_device_manager_get_data_device( + device_manager_.get(), connection_->seat())); + } + DCHECK(device_); + return device_.get(); } -std::unique_ptr<WaylandDataSource> WaylandDataDeviceManager::CreateSource() { +std::unique_ptr<WaylandDataSource> WaylandDataDeviceManager::CreateSource( + WaylandDataSource::Delegate* delegate) { wl_data_source* data_source = wl_data_device_manager_create_data_source(device_manager_.get()); - return std::make_unique<WaylandDataSource>(data_source, connection_); + return std::make_unique<WaylandDataSource>(data_source, connection_, + delegate); } } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.h b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.h index ea37eee0ba3..7ed3a330ea3 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_manager.h @@ -5,33 +5,37 @@ #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_DEVICE_MANAGER_H_ #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_DEVICE_MANAGER_H_ -#include <wayland-client.h> - #include <memory> -#include "base/macros.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" namespace ui { class WaylandConnection; -class WaylandDataSource; +class WaylandDataDevice; class WaylandDataDeviceManager { public: + using DataSource = WaylandDataSource; + using DataDevice = WaylandDataDevice; + WaylandDataDeviceManager(wl_data_device_manager* device_manager, WaylandConnection* connection); + WaylandDataDeviceManager(const WaylandDataDeviceManager&) = delete; + WaylandDataDeviceManager& operator=(const WaylandDataDeviceManager&) = delete; ~WaylandDataDeviceManager(); - wl_data_device* GetDevice(); - std::unique_ptr<WaylandDataSource> CreateSource(); + WaylandDataDevice* GetDevice(); + std::unique_ptr<WaylandDataSource> CreateSource( + WaylandDataSource::Delegate* delegate); private: wl::Object<wl_data_device_manager> device_manager_; - WaylandConnection* connection_; + WaylandConnection* const connection_; - DISALLOW_COPY_AND_ASSIGN(WaylandDataDeviceManager); + std::unique_ptr<WaylandDataDevice> device_; }; } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc index 9195d876030..b4bedb59ac9 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc @@ -14,9 +14,6 @@ #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/clipboard/clipboard_constants.h" -#include "ui/base/dragdrop/drag_drop_types.h" -#include "ui/base/dragdrop/file_info/file_info.h" -#include "ui/base/dragdrop/os_exchange_data.h" #include "ui/events/base_event_utils.h" #include "ui/ozone/platform/wayland/test/constants.h" #include "ui/ozone/platform/wayland/test/mock_surface.h" @@ -27,8 +24,6 @@ #include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" #include "ui/ozone/platform/wayland/test/wayland_test.h" #include "ui/ozone/public/platform_clipboard.h" -#include "ui/platform_window/platform_window_handler/wm_drop_handler.h" -#include "url/gurl.h" using testing::_; using testing::Mock; @@ -37,8 +32,6 @@ namespace ui { namespace { -constexpr FilenameToURLPolicy kFilenameToURLPolicy = CONVERT_FILENAMES; - template <typename StringType> ui::PlatformClipboard::Data ToClipboardData(const StringType& data_string) { ui::PlatformClipboard::Data result; @@ -52,39 +45,6 @@ ui::PlatformClipboard::Data ToClipboardData(const StringType& data_string) { } // namespace -class MockDropHandler : public WmDropHandler { - public: - MockDropHandler() = default; - ~MockDropHandler() override {} - - MOCK_METHOD3(OnDragEnter, - void(const gfx::PointF& point, - std::unique_ptr<OSExchangeData> data, - int operation)); - MOCK_METHOD2(OnDragMotion, int(const gfx::PointF& point, int operation)); - MOCK_METHOD0(MockOnDragDrop, void()); - MOCK_METHOD0(OnDragLeave, void()); - - void SetOnDropClosure(base::RepeatingClosure closure) { - on_drop_closure_ = closure; - } - - ui::OSExchangeData* dropped_data() { return dropped_data_.get(); } - - protected: - void OnDragDrop(std::unique_ptr<ui::OSExchangeData> data) override { - dropped_data_ = std::move(data); - MockOnDragDrop(); - on_drop_closure_.Run(); - on_drop_closure_.Reset(); - } - - private: - base::RepeatingClosure on_drop_closure_; - - std::unique_ptr<ui::OSExchangeData> dropped_data_; -}; - // This class mocks how a real clipboard/ozone client would // hook to PlatformClipboard, with one difference: real clients // have no access to the WaylandConnection instance like this @@ -143,15 +103,11 @@ class WaylandDataDeviceManagerTest : public WaylandTest { clipboard_client_ = std::make_unique<MockClipboardClient>(connection_.get()); - - drop_handler_ = std::make_unique<MockDropHandler>(); - SetWmDropHandler(window_.get(), drop_handler_.get()); } protected: wl::TestDataDeviceManager* data_device_manager_; std::unique_ptr<MockClipboardClient> clipboard_client_; - std::unique_ptr<MockDropHandler> drop_handler_; private: DISALLOW_COPY_AND_ASSIGN(WaylandDataDeviceManagerTest); @@ -231,255 +187,6 @@ TEST_P(WaylandDataDeviceManagerTest, IsSelectionOwner) { ASSERT_FALSE(clipboard_client_->IsSelectionOwner()); } -TEST_P(WaylandDataDeviceManagerTest, StartDrag) { - bool restored_focus = window_->has_pointer_focus(); - window_->SetPointerFocus(true); - - // The client starts dragging. - OSExchangeData os_exchange_data; - int operation = DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE; - connection_->StartDrag(os_exchange_data, operation); - - WaylandDataSource::DragDataMap data; - data[wl::kTextMimeTypeUtf8] = wl::kSampleTextForDragAndDrop; - connection_->drag_data_source()->SetDragData(data); - Sync(); - - // The server reads the data and the callback gets it. - base::RunLoop run_loop; - auto callback = base::BindOnce( - [](base::RunLoop* loop, PlatformClipboard::Data&& data) { - std::string result(data.begin(), data.end()); - EXPECT_EQ(wl::kSampleTextForDragAndDrop, result); - loop->Quit(); - }, - &run_loop); - data_device_manager_->data_source()->ReadData(wl::kTextMimeTypeUtf8, - std::move(callback)); - run_loop.Run(); - window_->SetPointerFocus(restored_focus); -} - -TEST_P(WaylandDataDeviceManagerTest, StartDragWithWrongMimeType) { - bool restored_focus = window_->has_pointer_focus(); - window_->SetPointerFocus(true); - - // The client starts dragging offering data with wl::kTextMimeTypeUtf8 - // mime type. - OSExchangeData os_exchange_data; - int operation = DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE; - connection_->StartDrag(os_exchange_data, operation); - - WaylandDataSource::DragDataMap data; - data[wl::kTextMimeTypeUtf8] = wl::kSampleTextForDragAndDrop; - connection_->drag_data_source()->SetDragData(data); - Sync(); - - // The server should get an empty data buffer in ReadData callback - // when trying to read it. - base::RunLoop run_loop; - auto callback = base::BindOnce( - [](base::RunLoop* loop, PlatformClipboard::Data&& data) { - std::string result(data.begin(), data.end()); - EXPECT_EQ("", result); - loop->Quit(); - }, - &run_loop); - data_device_manager_->data_source()->ReadData(ui::kMimeTypeText, - std::move(callback)); - run_loop.Run(); - window_->SetPointerFocus(restored_focus); -} - -TEST_P(WaylandDataDeviceManagerTest, ReceiveDrag) { - auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); - data_offer->OnOffer( - ui::kMimeTypeText, - ToClipboardData(std::string(wl::kSampleTextForDragAndDrop))); - - gfx::Point entered_point(10, 10); - // The server sends an enter event. - data_device_manager_->data_device()->OnEnter( - 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), - wl_fixed_from_int(entered_point.y()), data_offer); - - int64_t time = - (ui::EventTimeForNow() - base::TimeTicks()).InMilliseconds() & UINT32_MAX; - gfx::Point motion_point(11, 11); - - // The server sends an motion event. - data_device_manager_->data_device()->OnMotion( - time, wl_fixed_from_int(motion_point.x()), - wl_fixed_from_int(motion_point.y())); - - Sync(); - - auto callback = base::BindOnce([](const PlatformClipboard::Data& contents) { - std::string result; - result.assign(reinterpret_cast<std::string::const_pointer>(&contents[0]), - contents.size()); - EXPECT_EQ(wl::kSampleTextForDragAndDrop, result); - }); - - // The client requests the data and gets callback with it. - connection_->RequestDragData(ui::kMimeTypeText, std::move(callback)); - Sync(); - - data_device_manager_->data_device()->OnLeave(); -} - -TEST_P(WaylandDataDeviceManagerTest, DropSeveralMimeTypes) { - auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); - data_offer->OnOffer( - ui::kMimeTypeText, - ToClipboardData(std::string(wl::kSampleTextForDragAndDrop))); - data_offer->OnOffer( - ui::kMimeTypeMozillaURL, - ToClipboardData(base::UTF8ToUTF16("https://sample.com/\r\n" - "Sample"))); - data_offer->OnOffer( - ui::kMimeTypeURIList, - ToClipboardData(std::string("file:///home/user/file\r\n"))); - - EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1); - gfx::Point entered_point(10, 10); - data_device_manager_->data_device()->OnEnter( - 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), - wl_fixed_from_int(entered_point.y()), data_offer); - Sync(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - - EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1); - base::RunLoop loop; - drop_handler_->SetOnDropClosure(loop.QuitClosure()); - data_device_manager_->data_device()->OnDrop(); - - // Here we are expecting three data items, so there will be three roundtrips - // to the Wayland and back. Hence Sync() three times. - Sync(); - Sync(); - Sync(); - loop.Run(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - - EXPECT_TRUE(drop_handler_->dropped_data()->HasString()); - EXPECT_TRUE(drop_handler_->dropped_data()->HasFile()); - EXPECT_TRUE(drop_handler_->dropped_data()->HasURL(kFilenameToURLPolicy)); - - data_device_manager_->data_device()->OnLeave(); -} - -// Tests URI validation for text/uri-list MIME type. Log warnings rendered in -// the console when this test is running are the expected and valid side effect. -TEST_P(WaylandDataDeviceManagerTest, ValidateDroppedUriList) { - const struct { - std::string content; - base::flat_set<std::string> expected_uris; - } kCases[] = {{{}, {}}, - {"file:///home/user/file\r\n", {"/home/user/file"}}, - {"# Comment\r\n" - "file:///home/user/file\r\n" - "file:///home/guest/file\r\n" - "not a filename at all\r\n" - "https://valid.url/but/scheme/is/not/file/so/invalid\r\n", - {"/home/user/file", "/home/guest/file"}}}; - - for (const auto& kCase : kCases) { - auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); - data_offer->OnOffer(ui::kMimeTypeURIList, ToClipboardData(kCase.content)); - - EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1); - gfx::Point entered_point(10, 10); - data_device_manager_->data_device()->OnEnter( - 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), - wl_fixed_from_int(entered_point.y()), data_offer); - Sync(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - - EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1); - base::RunLoop loop; - drop_handler_->SetOnDropClosure(loop.QuitClosure()); - data_device_manager_->data_device()->OnDrop(); - - Sync(); - loop.Run(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - - if (kCase.expected_uris.empty()) { - EXPECT_FALSE(drop_handler_->dropped_data()->HasFile()); - } else { - EXPECT_TRUE(drop_handler_->dropped_data()->HasFile()); - std::vector<FileInfo> filenames; - EXPECT_TRUE(drop_handler_->dropped_data()->GetFilenames(&filenames)); - EXPECT_EQ(filenames.size(), kCase.expected_uris.size()); - for (const auto& filename : filenames) - EXPECT_EQ(kCase.expected_uris.count(filename.path.AsUTF8Unsafe()), 1U); - } - - EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1); - data_device_manager_->data_device()->OnLeave(); - Sync(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - } -} - -// Tests URI validation for text/x-moz-url MIME type. Log warnings rendered in -// the console when this test is running are the expected and valid side effect. -TEST_P(WaylandDataDeviceManagerTest, ValidateDroppedXMozUrl) { - const struct { - std::string content; - std::string expected_url; - std::string expected_title; - } kCases[] = { - {{}, {}, {}}, - {"http://sample.com/\r\nSample", "http://sample.com/", "Sample"}, - {"http://title.must.be.set/", {}, {}}, - {"url.must.be.valid/and/have.scheme\r\nInvalid URL", {}, {}}, - {"file:///files/are/ok\r\nThe policy allows that", "file:///files/are/ok", - "The policy allows that"}}; - - for (const auto& kCase : kCases) { - auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); - data_offer->OnOffer(ui::kMimeTypeMozillaURL, - ToClipboardData(base::UTF8ToUTF16(kCase.content))); - - EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1); - gfx::Point entered_point(10, 10); - data_device_manager_->data_device()->OnEnter( - 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), - wl_fixed_from_int(entered_point.y()), data_offer); - Sync(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - - EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1); - base::RunLoop loop; - drop_handler_->SetOnDropClosure(loop.QuitClosure()); - data_device_manager_->data_device()->OnDrop(); - - Sync(); - loop.Run(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - - const auto* const dropped_data = drop_handler_->dropped_data(); - if (kCase.expected_url.empty()) { - EXPECT_FALSE(dropped_data->HasURL(kFilenameToURLPolicy)); - } else { - EXPECT_TRUE(dropped_data->HasURL(kFilenameToURLPolicy)); - GURL url; - base::string16 title; - EXPECT_TRUE( - dropped_data->GetURLAndTitle(kFilenameToURLPolicy, &url, &title)); - EXPECT_EQ(url.spec(), kCase.expected_url); - EXPECT_EQ(title, base::UTF8ToUTF16(kCase.expected_title)); - } - - EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1); - data_device_manager_->data_device()->OnLeave(); - Sync(); - Mock::VerifyAndClearExpectations(drop_handler_.get()); - } -} - INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, WaylandDataDeviceManagerTest, ::testing::Values(kXdgShellStable)); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc new file mode 100644 index 00000000000..ec14c3e3b7f --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc @@ -0,0 +1,355 @@ +// Copyright 2020 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_data_drag_controller.h" + +#include <wayland-client-protocol.h> + +#include <cstdint> + +#include "base/check.h" +#include "base/notreached.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/clipboard/clipboard_constants.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_non_backed.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/ozone/platform/wayland/common/data_util.h" +#include "ui/ozone/platform/wayland/common/wayland_util.h" +#include "ui/ozone/platform/wayland/host/wayland_connection.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h" +#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_shm_buffer.h" +#include "ui/ozone/platform/wayland/host/wayland_window.h" +#include "ui/ozone/platform/wayland/host/wayland_window_manager.h" + +namespace ui { + +namespace { + +// Returns actions possible with the given source and drag'n'drop actions. +// Also converts enums: input params are wl_data_device_manager_dnd_action but +// the result is ui::DragDropTypes. +int GetPossibleActions(uint32_t source_actions, uint32_t dnd_action) { + // If drag'n'drop action is set, use it but check for ASK action (see below). + uint32_t action = dnd_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE + ? dnd_action + : source_actions; + + // We accept any action except ASK (see below). + int operation = DragDropTypes::DRAG_NONE; + if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + operation |= DragDropTypes::DRAG_COPY; + if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + operation |= DragDropTypes::DRAG_MOVE; + if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + // This is very rare and non-standard. Chromium doesn't set this when + // anything is dragged from it, neither it provides any UI for asking + // the user about the desired drag'n'drop action when data is dragged + // from an external source. + // We are safe with not adding anything here. However, keep NOTIMPLEMENTED + // for an (unlikely) event of this being hit in distant future. + NOTIMPLEMENTED_LOG_ONCE(); + } + return operation; +} + +const SkBitmap* GetDragImage(const OSExchangeData& data) { + const SkBitmap* icon_bitmap = data.provider().GetDragImage().bitmap(); + return icon_bitmap && !icon_bitmap->empty() ? icon_bitmap : nullptr; +} + +} // namespace + +WaylandDataDragController::WaylandDataDragController( + WaylandConnection* connection, + WaylandDataDeviceManager* data_device_manager) + : connection_(connection), + data_device_manager_(data_device_manager), + data_device_(data_device_manager->GetDevice()), + window_manager_(connection->wayland_window_manager()) { + DCHECK(connection_); + DCHECK(window_manager_); + DCHECK(data_device_manager_); + DCHECK(data_device_); +} + +WaylandDataDragController::~WaylandDataDragController() = default; + +void WaylandDataDragController::StartSession(const OSExchangeData& data, + int operation) { + DCHECK_EQ(state_, State::kIdle); + DCHECK(!origin_window_); + + origin_window_ = window_manager_->GetCurrentFocusedWindow(); + if (!origin_window_) { + LOG(ERROR) << "Failed to get focused window."; + return; + } + + // Create new new data source and offers |data|. + if (!data_source_) + data_source_ = data_device_manager_->CreateSource(this); + Offer(data, operation); + + // Create drag icon surface (if any) and store the data to be exchanged. + icon_surface_.reset(CreateIconSurfaceIfNeeded(data)); + data_ = std::make_unique<OSExchangeData>(data.provider().Clone()); + + // Starts the wayland drag session setting |this| object as delegate. + state_ = State::kStarted; + data_device_->StartDrag(*data_source_, *origin_window_, icon_surface_.get(), + this); +} + +// Sessions initiated from Chromium, will have |origin_window_| pointing to the +// window where the drag started in. In such cases, |data_| is expected to be +// non-null, which can be used to save some IO cycles. +bool WaylandDataDragController::IsDragSource() const { + DCHECK(!origin_window_ || data_); + return !!origin_window_; +} + +void WaylandDataDragController::DrawIcon() { + if (!icon_bitmap_) + return; + + DCHECK(!icon_bitmap_->empty()); + gfx::Size size(icon_bitmap_->width(), icon_bitmap_->height()); + + if (!shm_buffer_ || shm_buffer_->size() != size) { + shm_buffer_ = std::make_unique<WaylandShmBuffer>(connection_->shm(), size); + if (!shm_buffer_->IsValid()) { + LOG(ERROR) << "Failed to create drag icon buffer."; + return; + } + } + // TODO(crbug.com/1085418): Fix drag icon scaling + wl::DrawBitmap(*icon_bitmap_, shm_buffer_.get()); + wl_surface_attach(icon_surface_.get(), shm_buffer_->get(), 0, 0); + wl_surface_damage(icon_surface_.get(), 0, 0, size.width(), size.height()); + wl_surface_commit(icon_surface_.get()); +} + +void WaylandDataDragController::OnDragOffer( + std::unique_ptr<WaylandDataOffer> offer) { + DCHECK(!data_offer_); + data_offer_ = std::move(offer); +} + +void WaylandDataDragController::OnDragEnter(WaylandWindow* window, + const gfx::PointF& location, + uint32_t serial) { + DCHECK(window); + window_ = window; + + // TODO(crbug.com/1004715): Set mime type the client can accept. Now it sets + // all mime types offered because current implementation doesn't decide + // action based on mime type. + unprocessed_mime_types_.clear(); + for (auto mime : data_offer_->mime_types()) { + unprocessed_mime_types_.push_back(mime); + data_offer_->Accept(serial, mime); + } + + std::unique_ptr<OSExchangeData> dragged_data; + // If the DND session was initiated from a Chromium window, |data_| already + // holds the data to be exchanged, so no needed to read it through Wayland, + // thus just copy it here. + if (IsDragSource()) + dragged_data = std::make_unique<OSExchangeData>(data_->provider().Clone()); + window_->OnDragEnter(location, std::move(dragged_data), + GetPossibleActions(data_offer_->source_actions(), + data_offer_->dnd_action())); +} + +void WaylandDataDragController::OnDragMotion(const gfx::PointF& location) { + if (!window_) + return; + + int client_operation = window_->OnDragMotion( + location, GetPossibleActions(data_offer_->source_actions(), + data_offer_->dnd_action())); + SetOperation(client_operation); +} + +void WaylandDataDragController::OnDragLeave() { + if (!window_) + return; + + // Leave event can arrive while data is being transferred. As it cannot be + // handled right away, just mark it to be processed when the data is ready. + if (state_ == State::kTransferring) { + is_leave_pending_ = true; + return; + } + + window_->OnDragLeave(); + window_ = nullptr; + data_offer_.reset(); + is_leave_pending_ = false; +} + +void WaylandDataDragController::OnDragDrop() { + if (!window_) + return; + + if (IsDragSource()) { + // This means the data is being exchanged between Chromium windows. In this + // case, data is supposed to have already been sent to the drop handler + // before (see OnDragEnter()), expecting to receive null at this stage. + OnDataTransferFinished(nullptr); + return; + } + + // Otherwise, we are about to accept data dragged from another application. + // Reading the data may take some time so set |state_| to |kTrasfering|, which + // will make final "leave" event handling to be postponed until data is ready. + state_ = State::kTransferring; + received_data_ = std::make_unique<OSExchangeData>( + std::make_unique<OSExchangeDataProviderNonBacked>()); + HandleUnprocessedMimeTypes(); +} + +void WaylandDataDragController::OnDataSourceFinish(bool completed) { + DCHECK(data_source_); + if (origin_window_) + origin_window_->OnDragSessionClose(data_source_->dnd_action()); + + origin_window_ = nullptr; + data_source_.reset(); + data_offer_.reset(); + data_.reset(); + state_ = State::kIdle; +} + +void WaylandDataDragController::OnDataSourceSend(const std::string& mime_type, + std::string* buffer) { + DCHECK(data_source_); + DCHECK(buffer); + DCHECK(data_); + if (!wl::ExtractOSExchangeData(*data_, mime_type, buffer)) { + LOG(WARNING) << "Cannot deliver data of type " << mime_type + << " and no text representation is available."; + } +} + +void WaylandDataDragController::Offer(const OSExchangeData& data, + int operation) { + 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); + } + + DCHECK(!mime_types.empty()); + data_source_->Offer(mime_types); + data_source_->SetAction(operation); +} + +wl_surface* WaylandDataDragController::CreateIconSurfaceIfNeeded( + const OSExchangeData& data) { + icon_bitmap_ = GetDragImage(data); + return icon_bitmap_ ? wl_compositor_create_surface(connection_->compositor()) + : nullptr; +} + +// Asynchronously requests and reads data for every negotiated/supported mime +// type, one after another, OnMimeTypeDataTransferred calls back into this +// function once it finishes reading data for each mime type, until there is no +// more unprocessed mime types on the |unprocessed_mime_types_| queue. Once this +// process is finished, OnDataTransferFinished is called to deliver the +// |received_data_| to the drop handler. +void WaylandDataDragController::HandleUnprocessedMimeTypes() { + DCHECK_EQ(state_, State::kTransferring); + std::string mime_type = GetNextUnprocessedMimeType(); + if (mime_type.empty()) { + OnDataTransferFinished(std::move(received_data_)); + } else { + DCHECK(data_offer_); + data_device_->RequestData( + data_offer_.get(), mime_type, + base::BindOnce(&WaylandDataDragController::OnMimeTypeDataTransferred, + weak_factory_.GetWeakPtr())); + } +} + +void WaylandDataDragController::OnMimeTypeDataTransferred( + const PlatformClipboard::Data& contents) { + DCHECK_EQ(state_, State::kTransferring); + if (!contents.empty()) { + std::string mime_type = unprocessed_mime_types_.front(); + wl::AddToOSExchangeData(contents, mime_type, received_data_.get()); + } + unprocessed_mime_types_.pop_front(); + + // Continue reading data for other negotiated mime types. + HandleUnprocessedMimeTypes(); +} + +void WaylandDataDragController::OnDataTransferFinished( + std::unique_ptr<OSExchangeData> received_data) { + data_offer_->FinishOffer(); + window_->OnDragDrop(std::move(received_data)); + + unprocessed_mime_types_.clear(); + state_ = State::kIdle; + + // If |is_leave_pending_| is set, it means a 'leave' event was fired while + // data was on transit, so process it here (See OnDragLeave for more context). + if (is_leave_pending_) + OnDragLeave(); +} + +// Returns the next MIME type to be received from the source process, or an +// empty string if there are no more interesting MIME types left to process. +std::string WaylandDataDragController::GetNextUnprocessedMimeType() { + while (!unprocessed_mime_types_.empty()) { + const std::string& mime_type = unprocessed_mime_types_.front(); + // Skip unsupported or already processed mime types. + if (!wl::IsMimeTypeSupported(mime_type) || + wl::ContainsMimeType(*received_data_, mime_type)) { + unprocessed_mime_types_.pop_front(); + continue; + } + return mime_type; + } + return {}; +} + +void WaylandDataDragController::SetOperation(const int operation) { + uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + uint32_t preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + + if (operation & DragDropTypes::DRAG_COPY) { + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + + if (operation & DragDropTypes::DRAG_MOVE) { + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) + preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } + data_offer_->SetAction(dnd_actions, preferred_action); +} + +} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h b/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h new file mode 100644 index 00000000000..2d1e1b09d6d --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller.h @@ -0,0 +1,136 @@ +// Copyright 2020 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_DATA_DRAG_CONTROLLER_H_ +#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_DRAG_CONTROLLER_H_ + +#include <list> +#include <memory> +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/memory/weak_ptr.h" +#include "ui/ozone/platform/wayland/common/wayland_object.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" + +struct wl_surface; +class SkBitmap; + +namespace ui { + +class OSExchangeData; +class WaylandConnection; +class WaylandDataDeviceManager; +class WaylandDataOffer; +class WaylandWindow; +class WaylandWindowManager; +class WaylandShmBuffer; + +// WaylandDataDragController implements regular data exchanging between Chromium +// and other client applications on top of the Wayland Drag and Drop protocol. +// By implementing both DataDevice::DragDelegate and DataSource::Delegate, +// it is responsible for handling both DND sessions initiated from Chromium +// windows as well as those triggered by other clients. +class WaylandDataDragController : public WaylandDataDevice::DragDelegate, + public WaylandDataSource::Delegate { + public: + enum class State { kIdle, kStarted, kTransferring }; + + WaylandDataDragController(WaylandConnection* connection, + WaylandDataDeviceManager* data_device_manager); + WaylandDataDragController(const WaylandDataDragController&) = delete; + WaylandDataDragController& operator=(const WaylandDataDragController&) = + delete; + ~WaylandDataDragController() override; + + // Starts a data drag and drop session for |data|. Only one DND session can + // run at a given time. + void StartSession(const ui::OSExchangeData& data, int operation); + + State state() const { return state_; } + + // TODO(crbug.com/896640): Remove once focus is fixed during DND sessions. + WaylandWindow* entered_window() const { return window_; } + + private: + FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest, ReceiveDrag); + FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest, StartDrag); + FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest, StartDragWithText); + FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest, + StartDragWithWrongMimeType); + + // WaylandDataDevice::DragDelegate: + bool IsDragSource() const override; + void DrawIcon() override; + void OnDragOffer(std::unique_ptr<WaylandDataOffer> offer) override; + void OnDragEnter(WaylandWindow* window, + const gfx::PointF& location, + uint32_t serial) override; + void OnDragMotion(const gfx::PointF& location) override; + void OnDragLeave() override; + void OnDragDrop() override; + + // WaylandDataSource::Delegate: + void OnDataSourceFinish(bool completed) override; + void OnDataSourceSend(const std::string& mime_type, + std::string* contents) override; + + void Offer(const OSExchangeData& data, int operation); + wl_surface* CreateIconSurfaceIfNeeded(const OSExchangeData& data); + void HandleUnprocessedMimeTypes(); + void OnMimeTypeDataTransferred(const PlatformClipboard::Data& contents); + void OnDataTransferFinished( + std::unique_ptr<ui::OSExchangeData> received_data); + std::string GetNextUnprocessedMimeType(); + void SetOperation(const int operation); + + WaylandConnection* const connection_; + WaylandDataDeviceManager* const data_device_manager_; + WaylandDataDevice* const data_device_; + WaylandWindowManager* const window_manager_; + + State state_ = State::kIdle; + + 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_; + + // Offer to receive data from another process via drag-and-drop, or null if + // no drag-and-drop from another process is in progress. + // + // The data offer from another Wayland client through wl_data_device, that + // triggered the current drag and drop session. If null, either there is no + // dnd session running or Chromium is the data source. + std::unique_ptr<WaylandDataOffer> data_offer_; + + // Mime types to be handled. + std::list<std::string> unprocessed_mime_types_; + + // The window that initiated the drag session. Can be null when the session + // has been started by an external Wayland client. + WaylandWindow* origin_window_ = nullptr; + + // Current window under pointer. + WaylandWindow* window_ = nullptr; + + // The data delivered from Wayland + std::unique_ptr<ui::OSExchangeData> received_data_; + + // Set when 'leave' event is fired while data is being transferred. + bool is_leave_pending_ = false; + + // Drag icon related variables. + wl::Object<wl_surface> icon_surface_; + std::unique_ptr<WaylandShmBuffer> shm_buffer_; + const SkBitmap* icon_bitmap_ = nullptr; + + base::WeakPtrFactory<WaylandDataDragController> weak_factory_{this}; +}; + +} // namespace ui + +#endif // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_DRAG_CONTROLLER_H_ diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc new file mode 100644 index 00000000000..a320c70dd1d --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc @@ -0,0 +1,406 @@ +// Copyright 2018 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 <wayland-server.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/containers/flat_set.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/clipboard/clipboard_constants.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/file_info/file_info.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/events/base_event_utils.h" +#include "ui/ozone/platform/wayland/common/data_util.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h" +#include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" +#include "ui/ozone/platform/wayland/test/constants.h" +#include "ui/ozone/platform/wayland/test/mock_surface.h" +#include "ui/ozone/platform/wayland/test/test_data_device.h" +#include "ui/ozone/platform/wayland/test/test_data_device_manager.h" +#include "ui/ozone/platform/wayland/test/test_data_offer.h" +#include "ui/ozone/platform/wayland/test/test_data_source.h" +#include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" +#include "ui/ozone/platform/wayland/test/wayland_test.h" +#include "ui/ozone/public/platform_clipboard.h" +#include "ui/platform_window/platform_window_handler/wm_drop_handler.h" +#include "url/gurl.h" + +using testing::_; +using testing::Mock; + +namespace ui { + +namespace { + +constexpr FilenameToURLPolicy kFilenameToURLPolicy = + FilenameToURLPolicy::CONVERT_FILENAMES; + +template <typename StringType> +PlatformClipboard::Data ToClipboardData(const StringType& data_string) { + PlatformClipboard::Data result; + auto* begin = + reinterpret_cast<typename PlatformClipboard::Data::const_pointer>( + data_string.data()); + result.assign(begin, begin + (data_string.size() * + sizeof(typename StringType::value_type))); + return result; +} + +} // namespace + +class MockDropHandler : public WmDropHandler { + public: + MockDropHandler() = default; + ~MockDropHandler() override = default; + + MOCK_METHOD3(OnDragEnter, + void(const gfx::PointF& point, + std::unique_ptr<OSExchangeData> data, + int operation)); + MOCK_METHOD2(OnDragMotion, int(const gfx::PointF& point, int operation)); + MOCK_METHOD0(MockOnDragDrop, void()); + MOCK_METHOD0(OnDragLeave, void()); + + void SetOnDropClosure(base::RepeatingClosure closure) { + on_drop_closure_ = closure; + } + + OSExchangeData* dropped_data() { return dropped_data_.get(); } + + protected: + void OnDragDrop(std::unique_ptr<OSExchangeData> data) override { + dropped_data_ = std::move(data); + MockOnDragDrop(); + on_drop_closure_.Run(); + on_drop_closure_.Reset(); + } + + private: + base::RepeatingClosure on_drop_closure_; + + std::unique_ptr<OSExchangeData> dropped_data_; +}; + +class WaylandDataDragControllerTest : public WaylandTest { + public: + WaylandDataDragControllerTest() = default; + + void SetUp() override { + WaylandTest::SetUp(); + + Sync(); + + data_device_manager_ = server_.data_device_manager(); + DCHECK(data_device_manager_); + + drop_handler_ = std::make_unique<MockDropHandler>(); + SetWmDropHandler(window_.get(), drop_handler_.get()); + } + + WaylandDataDragController* drag_controller() const { + return connection_->data_drag_controller(); + } + + WaylandDataDevice* data_device() const { + return connection_->data_device_manager()->GetDevice(); + } + + base::string16 sample_text_for_dnd() const { + static auto text = base::ASCIIToUTF16(wl::kSampleTextForDragAndDrop); + return text; + } + + protected: + wl::TestDataDeviceManager* data_device_manager_; + std::unique_ptr<MockDropHandler> drop_handler_; +}; + +TEST_P(WaylandDataDragControllerTest, StartDrag) { + bool restored_focus = window_->has_pointer_focus(); + window_->SetPointerFocus(true); + + // The client starts dragging. + OSExchangeData os_exchange_data; + os_exchange_data.SetString(sample_text_for_dnd()); + int operation = DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE; + drag_controller()->StartSession(os_exchange_data, operation); + Sync(); + + // The server reads the data and the callback gets it. + base::RunLoop run_loop; + auto callback = base::BindOnce( + [](base::RunLoop* loop, PlatformClipboard::Data&& data) { + std::string result(data.begin(), data.end()); + EXPECT_EQ(wl::kSampleTextForDragAndDrop, result); + loop->Quit(); + }, + &run_loop); + data_device_manager_->data_source()->ReadData(wl::kTextMimeTypeUtf8, + std::move(callback)); + run_loop.Run(); + window_->SetPointerFocus(restored_focus); +} + +TEST_P(WaylandDataDragControllerTest, StartDragWithWrongMimeType) { + bool restored_focus = window_->has_pointer_focus(); + window_->SetPointerFocus(true); + + // The client starts dragging offering data with |kMimeTypeHTML| + OSExchangeData os_exchange_data; + os_exchange_data.SetHtml(sample_text_for_dnd(), {}); + int operation = DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE; + drag_controller()->StartSession(os_exchange_data, operation); + Sync(); + + // The server should get an empty data buffer in ReadData callback when trying + // to read it with a different mime type. + base::RunLoop run_loop; + auto callback = base::BindOnce( + [](base::RunLoop* loop, PlatformClipboard::Data&& data) { + std::string result(data.begin(), data.end()); + EXPECT_TRUE(result.empty()); + loop->Quit(); + }, + &run_loop); + data_device_manager_->data_source()->ReadData(kMimeTypeText, + std::move(callback)); + run_loop.Run(); + window_->SetPointerFocus(restored_focus); +} + +TEST_P(WaylandDataDragControllerTest, StartDragWithText) { + bool restored_focus = window_->has_pointer_focus(); + window_->SetPointerFocus(true); + + // The client starts dragging offering text mime type. + OSExchangeData os_exchange_data; + os_exchange_data.SetString(sample_text_for_dnd()); + int operation = DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE; + drag_controller()->StartSession(os_exchange_data, operation); + Sync(); + + // The server should get a "text" representation in ReadData callback when + // trying to read it as mime type other than |kMimeTypeText| and + // |kTextMimeTypeUtf8|. + base::RunLoop run_loop; + auto callback = base::BindOnce( + [](base::RunLoop* loop, PlatformClipboard::Data&& data) { + std::string result(data.begin(), data.end()); + EXPECT_EQ(wl::kSampleTextForDragAndDrop, result); + loop->Quit(); + }, + &run_loop); + data_device_manager_->data_source()->ReadData(kMimeTypeMozillaURL, + std::move(callback)); + run_loop.Run(); + window_->SetPointerFocus(restored_focus); +} + +TEST_P(WaylandDataDragControllerTest, ReceiveDrag) { + auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); + data_offer->OnOffer( + kMimeTypeText, + ToClipboardData(std::string(wl::kSampleTextForDragAndDrop))); + + gfx::Point entered_point(10, 10); + // The server sends an enter event. + data_device_manager_->data_device()->OnEnter( + 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), + wl_fixed_from_int(entered_point.y()), data_offer); + + int64_t time = + (EventTimeForNow() - base::TimeTicks()).InMilliseconds() & UINT32_MAX; + gfx::Point motion_point(11, 11); + + // The server sends an motion event. + data_device_manager_->data_device()->OnMotion( + time, wl_fixed_from_int(motion_point.x()), + wl_fixed_from_int(motion_point.y())); + + Sync(); + + auto callback = base::BindOnce([](const PlatformClipboard::Data& contents) { + std::string result; + result.assign(reinterpret_cast<std::string::const_pointer>(&contents[0]), + contents.size()); + EXPECT_EQ(wl::kSampleTextForDragAndDrop, result); + }); + + // The client requests the data and gets callback with it. + data_device()->RequestData(drag_controller()->data_offer_.get(), + kMimeTypeText, std::move(callback)); + Sync(); + + data_device_manager_->data_device()->OnLeave(); +} + +TEST_P(WaylandDataDragControllerTest, DropSeveralMimeTypes) { + auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); + data_offer->OnOffer( + kMimeTypeText, + ToClipboardData(std::string(wl::kSampleTextForDragAndDrop))); + data_offer->OnOffer(kMimeTypeMozillaURL, ToClipboardData(base::UTF8ToUTF16( + "https://sample.com/\r\n" + "Sample"))); + data_offer->OnOffer( + kMimeTypeURIList, + ToClipboardData(std::string("file:///home/user/file\r\n"))); + + EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1); + gfx::Point entered_point(10, 10); + data_device_manager_->data_device()->OnEnter( + 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), + wl_fixed_from_int(entered_point.y()), data_offer); + Sync(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + + EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1); + base::RunLoop loop; + drop_handler_->SetOnDropClosure(loop.QuitClosure()); + data_device_manager_->data_device()->OnDrop(); + + // Here we are expecting three data items, so there will be three roundtrips + // to the Wayland and back. Hence Sync() three times. + Sync(); + Sync(); + Sync(); + loop.Run(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + + EXPECT_TRUE(drop_handler_->dropped_data()->HasString()); + EXPECT_TRUE(drop_handler_->dropped_data()->HasFile()); + EXPECT_TRUE(drop_handler_->dropped_data()->HasURL(kFilenameToURLPolicy)); + + data_device_manager_->data_device()->OnLeave(); +} + +// Tests URI validation for text/uri-list MIME type. Log warnings rendered in +// the console when this test is running are the expected and valid side effect. +TEST_P(WaylandDataDragControllerTest, ValidateDroppedUriList) { + const struct { + std::string content; + base::flat_set<std::string> expected_uris; + } kCases[] = {{{}, {}}, + {"file:///home/user/file\r\n", {"/home/user/file"}}, + {"# Comment\r\n" + "file:///home/user/file\r\n" + "file:///home/guest/file\r\n" + "not a filename at all\r\n" + "https://valid.url/but/scheme/is/not/file/so/invalid\r\n", + {"/home/user/file", "/home/guest/file"}}}; + + for (const auto& kCase : kCases) { + auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); + data_offer->OnOffer(kMimeTypeURIList, ToClipboardData(kCase.content)); + + EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1); + gfx::Point entered_point(10, 10); + data_device_manager_->data_device()->OnEnter( + 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), + wl_fixed_from_int(entered_point.y()), data_offer); + Sync(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + + EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1); + base::RunLoop loop; + drop_handler_->SetOnDropClosure(loop.QuitClosure()); + data_device_manager_->data_device()->OnDrop(); + + Sync(); + loop.Run(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + + if (kCase.expected_uris.empty()) { + EXPECT_FALSE(drop_handler_->dropped_data()->HasFile()); + } else { + EXPECT_TRUE(drop_handler_->dropped_data()->HasFile()); + std::vector<FileInfo> filenames; + EXPECT_TRUE(drop_handler_->dropped_data()->GetFilenames(&filenames)); + EXPECT_EQ(filenames.size(), kCase.expected_uris.size()); + for (const auto& filename : filenames) + EXPECT_EQ(kCase.expected_uris.count(filename.path.AsUTF8Unsafe()), 1U); + } + + EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1); + data_device_manager_->data_device()->OnLeave(); + Sync(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + } +} + +// Tests URI validation for text/x-moz-url MIME type. Log warnings rendered in +// the console when this test is running are the expected and valid side effect. +TEST_P(WaylandDataDragControllerTest, ValidateDroppedXMozUrl) { + const struct { + std::string content; + std::string expected_url; + std::string expected_title; + } kCases[] = { + {{}, {}, {}}, + {"http://sample.com/\r\nSample", "http://sample.com/", "Sample"}, + {"http://title.must.be.set/", {}, {}}, + {"url.must.be.valid/and/have.scheme\r\nInvalid URL", {}, {}}, + {"file:///files/are/ok\r\nThe policy allows that", "file:///files/are/ok", + "The policy allows that"}}; + + for (const auto& kCase : kCases) { + auto* data_offer = data_device_manager_->data_device()->OnDataOffer(); + data_offer->OnOffer(kMimeTypeMozillaURL, + ToClipboardData(base::UTF8ToUTF16(kCase.content))); + + EXPECT_CALL(*drop_handler_, OnDragEnter(_, _, _)).Times(1); + gfx::Point entered_point(10, 10); + data_device_manager_->data_device()->OnEnter( + 1002, surface_->resource(), wl_fixed_from_int(entered_point.x()), + wl_fixed_from_int(entered_point.y()), data_offer); + Sync(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + + EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1); + base::RunLoop loop; + drop_handler_->SetOnDropClosure(loop.QuitClosure()); + data_device_manager_->data_device()->OnDrop(); + + Sync(); + loop.Run(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + + const auto* const dropped_data = drop_handler_->dropped_data(); + if (kCase.expected_url.empty()) { + EXPECT_FALSE(dropped_data->HasURL(kFilenameToURLPolicy)); + } else { + EXPECT_TRUE(dropped_data->HasURL(kFilenameToURLPolicy)); + GURL url; + base::string16 title; + EXPECT_TRUE( + dropped_data->GetURLAndTitle(kFilenameToURLPolicy, &url, &title)); + EXPECT_EQ(url.spec(), kCase.expected_url); + EXPECT_EQ(title, base::UTF8ToUTF16(kCase.expected_title)); + } + + EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1); + data_device_manager_->data_device()->OnLeave(); + Sync(); + Mock::VerifyAndClearExpectations(drop_handler_.get()); + } +} + +INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, + WaylandDataDragControllerTest, + ::testing::Values(kXdgShellStable)); + +INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test, + WaylandDataDragControllerTest, + ::testing::Values(kXdgShellV6)); + +} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_source.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_source.cc index 0b92ceaeba7..1d66a9f74cb 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_source.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_source.cc @@ -4,156 +4,148 @@ #include "ui/ozone/platform/wayland/host/wayland_data_source.h" +#include <gtk-primary-selection-client-protocol.h> +#include <wayland-client-protocol.h> + +#include <cstdint> #include <vector> #include "base/files/file_util.h" -#include "base/optional.h" -#include "ui/base/clipboard/clipboard_constants.h" #include "ui/base/dragdrop/drag_drop_types.h" -#include "ui/base/dragdrop/os_exchange_data.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" -#include "ui/ozone/platform/wayland/host/wayland_window.h" -namespace ui { +namespace wl { -WaylandDataSource::WaylandDataSource(wl_data_source* data_source, - WaylandConnection* connection) - : data_source_(data_source), connection_(connection) { - static const struct wl_data_source_listener kDataSourceListener = { - WaylandDataSource::OnTarget, WaylandDataSource::OnSend, - WaylandDataSource::OnCancel, WaylandDataSource::OnDnDDropPerformed, - WaylandDataSource::OnDnDFinished, WaylandDataSource::OnAction}; - wl_data_source_add_listener(data_source, &kDataSourceListener, this); +template <typename T> +DataSource<T>::DataSource(T* data_source, + ui::WaylandConnection* connection, + Delegate* delegate) + : data_source_(data_source), connection_(connection), delegate_(delegate) { + DCHECK(data_source_); + DCHECK(connection_); + DCHECK(delegate_); + + Initialize(); } -WaylandDataSource::~WaylandDataSource() = default; +template <typename T> +void DataSource<T>::HandleFinishEvent(bool completed) { + delegate_->OnDataSourceFinish(/*completed=*/false); +} -void WaylandDataSource::WriteToClipboard( - const PlatformClipboard::DataMap& data_map) { - for (const auto& data : data_map) { - wl_data_source_offer(data_source_.get(), data.first.c_str()); - if (strcmp(data.first.c_str(), kMimeTypeText) == 0) - wl_data_source_offer(data_source_.get(), kMimeTypeTextUtf8); - } - wl_data_device_set_selection(connection_->data_device(), data_source_.get(), - connection_->serial()); +template <typename T> +void DataSource<T>::HandleSendEvent(const std::string& mime_type, int32_t fd) { + std::string contents; + delegate_->OnDataSourceSend(mime_type, &contents); + bool done = base::WriteFileDescriptor(fd, contents.data(), contents.length()); + DCHECK(done); + close(fd); +} - connection_->ScheduleFlush(); +// static +template <typename T> +void DataSource<T>::OnSend(void* data, + T* source, + const char* mime_type, + int32_t fd) { + auto* self = static_cast<DataSource<T>*>(data); + self->HandleSendEvent(mime_type, fd); } -void WaylandDataSource::Offer(const ui::OSExchangeData& data) { - // 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(ui::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); - } +template <typename T> +void DataSource<T>::OnCancel(void* data, T* source) { + auto* self = static_cast<DataSource<T>*>(data); + self->HandleFinishEvent(/*completed=*/false); +} - source_window_ = - connection_->wayland_window_manager()->GetCurrentFocusedWindow(); - for (auto& mime_type : mime_types) - wl_data_source_offer(data_source_.get(), mime_type.data()); +template <typename T> +void DataSource<T>::OnDnDFinished(void* data, T* source) { + auto* self = static_cast<DataSource<T>*>(data); + self->HandleFinishEvent(/*completed=*/true); } -void WaylandDataSource::SetDragData(const DragDataMap& data_map) { - DCHECK(drag_data_map_.empty()); - drag_data_map_ = data_map; +template <typename T> +void DataSource<T>::OnAction(void* data, T* source, uint32_t dnd_action) { + auto* self = static_cast<DataSource<T>*>(data); + self->dnd_action_ = dnd_action; } -void WaylandDataSource::SetAction(int operation) { - if (wl_data_source_get_version(data_source_.get()) >= - WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { - uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; - if (operation & DragDropTypes::DRAG_COPY) - dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; - if (operation & DragDropTypes::DRAG_MOVE) - dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; - wl_data_source_set_actions(data_source_.get(), dnd_actions); - } +template <typename T> +void DataSource<T>::OnTarget(void* data, T* source, const char* mime_type) { + NOTIMPLEMENTED_LOG_ONCE(); } -// static -void WaylandDataSource::OnTarget(void* data, - wl_data_source* source, - const char* mime_type) { +template <typename T> +void DataSource<T>::OnDnDDropPerformed(void* data, T* source) { NOTIMPLEMENTED_LOG_ONCE(); } -// static -void WaylandDataSource::OnSend(void* data, - wl_data_source* source, - const char* mime_type, - int32_t fd) { - WaylandDataSource* self = static_cast<WaylandDataSource*>(data); - std::string contents; - if (self->source_window_) { - // If |source_window_| is valid when OnSend() is called, it means that DnD - // is working. - self->GetDragData(mime_type, &contents); - } else { - base::Optional<std::vector<uint8_t>> mime_data; - self->GetClipboardData(mime_type, &mime_data); - if (!mime_data.has_value() && strcmp(mime_type, kMimeTypeTextUtf8) == 0) - self->GetClipboardData(kMimeTypeText, &mime_data); - contents.assign(mime_data->begin(), mime_data->end()); - } - bool result = - base::WriteFileDescriptor(fd, contents.data(), contents.length()); - DCHECK(result); - close(fd); +////////////////////////////////////////////////////////////////////////////// +// wl_data_source specializations and instantiation +////////////////////////////////////////////////////////////////////////////// + +template <> +void DataSource<wl_data_source>::Initialize() { + static const struct wl_data_source_listener kDataSourceListener = { + DataSource<wl_data_source>::OnTarget, + DataSource<wl_data_source>::OnSend, + DataSource<wl_data_source>::OnCancel, + DataSource<wl_data_source>::OnDnDDropPerformed, + DataSource<wl_data_source>::OnDnDFinished, + DataSource<wl_data_source>::OnAction}; + wl_data_source_add_listener(data_source_.get(), &kDataSourceListener, this); } -// static -void WaylandDataSource::OnCancel(void* data, wl_data_source* source) { - WaylandDataSource* self = static_cast<WaylandDataSource*>(data); - if (self->source_window_) { - // If it has |source_window_|, it is in the middle of 'drag and drop'. it - // cancels 'drag and drop'. - self->connection_->FinishDragSession(self->dnd_action_, - self->source_window_); - } else { - self->connection_->clipboard()->DataSourceCancelled( - ClipboardBuffer::kCopyPaste); - } +template <> +void DataSource<wl_data_source>::Offer( + const std::vector<std::string>& mime_types) { + for (auto& mime_type : mime_types) + wl_data_source_offer(data_source_.get(), mime_type.c_str()); + connection_->ScheduleFlush(); } -void WaylandDataSource::OnDnDDropPerformed(void* data, wl_data_source* source) { +template <typename T> +void DataSource<T>::SetAction(int operation) { NOTIMPLEMENTED_LOG_ONCE(); } -void WaylandDataSource::OnDnDFinished(void* data, wl_data_source* source) { - WaylandDataSource* self = static_cast<WaylandDataSource*>(data); - self->connection_->FinishDragSession(self->dnd_action_, self->source_window_); +template <> +void DataSource<wl_data_source>::SetAction(int operation) { + if (wl_data_source_get_version(data_source_.get()) >= + WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { + uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (operation & ui::DragDropTypes::DRAG_COPY) + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (operation & ui::DragDropTypes::DRAG_MOVE) + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + wl_data_source_set_actions(data_source_.get(), dnd_actions); + } } -void WaylandDataSource::OnAction(void* data, - wl_data_source* source, - uint32_t dnd_action) { - WaylandDataSource* self = static_cast<WaylandDataSource*>(data); - self->dnd_action_ = dnd_action; -} +template class DataSource<wl_data_source>; -void WaylandDataSource::GetDragData(const std::string& mime_type, - std::string* contents) { - auto it = drag_data_map_.find(mime_type); - if (it != drag_data_map_.end()) { - *contents = it->second; - return; - } +////////////////////////////////////////////////////////////////////////////// +// gtk_primary_selection_source specializations and instantiation +////////////////////////////////////////////////////////////////////////////// + +template <> +void DataSource<gtk_primary_selection_source>::Initialize() { + static const struct gtk_primary_selection_source_listener + kDataSourceListener = { + DataSource<gtk_primary_selection_source>::OnSend, + DataSource<gtk_primary_selection_source>::OnCancel}; + gtk_primary_selection_source_add_listener(data_source_.get(), + &kDataSourceListener, this); +} - connection_->DeliverDragData(mime_type, contents); +template <> +void DataSource<gtk_primary_selection_source>::Offer( + const std::vector<std::string>& mime_types) { + for (const auto& mime_type : mime_types) + gtk_primary_selection_source_offer(data_source_.get(), mime_type.c_str()); + connection_->ScheduleFlush(); } -} // namespace ui +template class DataSource<gtk_primary_selection_source>; + +} // namespace wl diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_source.h b/chromium/ui/ozone/platform/wayland/host/wayland_data_source.h index 4f66465a699..2bcee213cc5 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_source.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_data_source.h @@ -5,76 +5,98 @@ #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_SOURCE_H_ #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_SOURCE_H_ -#include <wayland-client.h> - -#include <map> +#include <cstdint> #include <string> +#include <vector> -#include "base/logging.h" #include "base/macros.h" -#include "base/optional.h" +#include "base/notreached.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" -#include "ui/ozone/platform/wayland/host/wayland_data_source_base.h" -#include "ui/ozone/public/platform_clipboard.h" + +struct wl_data_source; +struct gtk_primary_selection_source; + +namespace wl { +template <typename T> +class DataSource; +} // namespace wl namespace ui { -class OSExchangeData; class WaylandConnection; -class WaylandWindow; - -// The WaylandDataSource object represents the source side of a -// WaylandDataOffer. It is created by the source client in a data -// transfer and provides a way to describe the offered data -// (wl_data_source_offer) // and a way to respond to requests to -// transfer the data (OnSend listener). -class WaylandDataSource : public WaylandDataSourceBase { - public: - using DragDataMap = std::map<std::string, std::string>; - // Takes ownership of data_source. - explicit WaylandDataSource(wl_data_source* data_source, - WaylandConnection* connection); - ~WaylandDataSource() override; +// DataSource represents the source side of a DataOffer. It is created by the +// source client in a data transfer and provides a way to describe the offered +// data and a way to respond to requests to transfer the data. There are a few +// variants of Wayland protocol objects and extensions supporting different +// features. E.g: regular copy/paste and drag operations are implemented by +// wl_data_source (along with its _device and _offer counterparts), etc. +// Implementation wise, these variants are share a single class template, with +// specializations defined for each underlying supported extensions. Below are +// the type aliases for the variants currently supported. +// +// TODO(crbug.com/1088132): Support standard primary selection extension. + +using WaylandDataSource = wl::DataSource<wl_data_source>; + +using GtkPrimarySelectionSource = wl::DataSource<gtk_primary_selection_source>; + +} // namespace ui - void set_connection(WaylandConnection* connection) { - DCHECK(connection); - connection_ = connection; - } +namespace wl { - void WriteToClipboard(const PlatformClipboard::DataMap& data_map) override; - void Offer(const ui::OSExchangeData& data); +// Template class implementing DataSource, whereas T is the underlying source +// type, e.g: wl_data_source, gtk_primary_selection_source, etc. This class +// is not supposed to be used directly, instead use the aliases defined above. +template <typename T> +class DataSource { + public: + class Delegate { + public: + virtual void OnDataSourceFinish(bool completed) = 0; + virtual void OnDataSourceSend(const std::string& mime_type, + std::string* contents) = 0; + + protected: + virtual ~Delegate() = default; + }; + + // Takes ownership of |data_source|. + DataSource(T* data_source, + ui::WaylandConnection* connection, + Delegate* delegate); + DataSource(const DataSource<T>&) = delete; + DataSource& operator=(const DataSource<T>&) = delete; + ~DataSource() = default; + + void Initialize(); + void Offer(const std::vector<std::string>& mime_types); void SetAction(int operation); - void SetDragData(const DragDataMap& data_map); - wl_data_source* data_source() const { return data_source_.get(); } + uint32_t dnd_action() const { return dnd_action_; } + T* data_source() const { return data_source_.get(); } private: - static void OnTarget(void* data, - wl_data_source* source, - const char* mime_type); - static void OnSend(void* data, - wl_data_source* source, - const char* mime_type, - int32_t fd); - static void OnCancel(void* data, wl_data_source* source); - static void OnDnDDropPerformed(void* data, wl_data_source* source); - static void OnDnDFinished(void* data, wl_data_source* source); - static void OnAction(void* data, wl_data_source* source, uint32_t dnd_action); - - void GetDragData(const std::string& mime_type, std::string* contents); - - wl::Object<wl_data_source> data_source_; - WaylandConnection* connection_ = nullptr; - WaylandWindow* source_window_ = nullptr; - - DragDataMap drag_data_map_; + void HandleFinishEvent(bool completed); + void HandleSendEvent(const std::string& mime_type, int32_t fd); + + static void OnSend(void* data, T* source, const char* mime_type, int32_t fd); + static void OnCancel(void* data, T* source); + static void OnDnDFinished(void* data, T* source); + static void OnAction(void* data, T* source, uint32_t dnd_action); + static void OnTarget(void* data, T* source, const char* mime_type); + static void OnDnDDropPerformed(void* data, T* source); + + wl::Object<T> data_source_; + + ui::WaylandConnection* const connection_; + + Delegate* const delegate_; + // Action selected by the compositor uint32_t dnd_action_; - - DISALLOW_COPY_AND_ASSIGN(WaylandDataSource); }; -} // namespace ui +} // namespace wl #endif // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_SOURCE_H_ diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_source_base.cc b/chromium/ui/ozone/platform/wayland/host/wayland_data_source_base.cc deleted file mode 100644 index c7bba1d2538..00000000000 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_source_base.cc +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019 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_data_source_base.h" - -namespace ui { - -WaylandDataSourceBase::WaylandDataSourceBase() = default; -WaylandDataSourceBase::~WaylandDataSourceBase() = default; - -void WaylandDataSourceBase::GetClipboardData( - const std::string& mime_type, - base::Optional<std::vector<uint8_t>>* data) const { - auto it = data_map_.find(mime_type); - if (it == data_map_.end()) - return; - data->emplace(it->second); -} - -} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_data_source_base.h b/chromium/ui/ozone/platform/wayland/host/wayland_data_source_base.h deleted file mode 100644 index 4e4d66b1607..00000000000 --- a/chromium/ui/ozone/platform/wayland/host/wayland_data_source_base.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2019 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_DATA_SOURCE_BASE_H_ -#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_SOURCE_BASE_H_ - -#include "base/macros.h" -#include "ui/ozone/public/platform_clipboard.h" - -namespace ui { - -// Implements high level (protocol-agnostic) interface to a Wayland data source. -class WaylandDataSourceBase { - public: - WaylandDataSourceBase(); - virtual ~WaylandDataSourceBase(); - - void set_data_map(const PlatformClipboard::DataMap& data_map) { - data_map_ = data_map; - } - - // Writes data to the system clipboard using the protocol-defined data source. - virtual void WriteToClipboard(const PlatformClipboard::DataMap& data_map) = 0; - - protected: - void GetClipboardData(const std::string& mime_type, - base::Optional<std::vector<uint8_t>>* data) const; - - private: - PlatformClipboard::DataMap data_map_; - - DISALLOW_COPY_AND_ASSIGN(WaylandDataSourceBase); -}; - -} // namespace ui - -#endif // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_DATA_SOURCE_BASE_H_ diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_drm.cc b/chromium/ui/ozone/platform/wayland/host/wayland_drm.cc index 12a91eed83a..e387e48c686 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_drm.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_drm.cc @@ -8,6 +8,7 @@ #include <xf86drm.h> #include "base/files/scoped_file.h" +#include "base/logging.h" #include "ui/gfx/buffer_format_util.h" #include "ui/gfx/linux/drm_util_linux.h" #include "ui/ozone/platform/wayland/host/wayland_connection.h" diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_event_source.cc b/chromium/ui/ozone/platform/wayland/host/wayland_event_source.cc index a83c0bfe87c..48d500ae5fb 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_event_source.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_event_source.cc @@ -96,21 +96,21 @@ void WaylandEventSource::OnKeyboardModifiersChanged(int modifiers) { keyboard_modifiers_ = modifiers; } -void WaylandEventSource::OnKeyboardKeyEvent(EventType type, - DomCode dom_code, - DomKey dom_key, - KeyboardCode key_code, - bool repeat, - base::TimeTicks timestamp) { +uint32_t WaylandEventSource::OnKeyboardKeyEvent(EventType type, + DomCode dom_code, + DomKey dom_key, + KeyboardCode key_code, + bool repeat, + base::TimeTicks timestamp) { DCHECK(type == ET_KEY_PRESSED || type == ET_KEY_RELEASED); if (!keyboard_) - return; + return POST_DISPATCH_NONE; // try to decode key, if not yet. if (dom_key == DomKey::NONE && !keyboard_->Decode(dom_code, keyboard_modifiers_, &dom_key, &key_code)) { LOG(ERROR) << "Failed to decode key event."; - return; + return POST_DISPATCH_NONE; } if (!repeat) { @@ -121,7 +121,7 @@ void WaylandEventSource::OnKeyboardKeyEvent(EventType type, KeyEvent event(type, key_code, dom_code, keyboard_modifiers_, dom_key, timestamp); event.set_source_device_id(keyboard_->device_id()); - DispatchEvent(&event); + return DispatchEvent(&event); } void WaylandEventSource::OnPointerCreated(WaylandPointer* pointer) { @@ -133,15 +133,13 @@ void WaylandEventSource::OnPointerDestroyed(WaylandPointer* pointer) { DCHECK_EQ(pointer_, pointer); // Clear focused window, if any. - if (auto* focused_window = window_manager_->GetCurrentFocusedWindow()) - HandlePointerFocusChange(focused_window, false); + HandlePointerFocusChange(nullptr); ResetPointerFlags(); pointer_ = nullptr; } void WaylandEventSource::OnPointerFocusChanged(WaylandWindow* window, - bool focused, const gfx::PointF& location) { if (!pointer_) return; @@ -149,8 +147,9 @@ void WaylandEventSource::OnPointerFocusChanged(WaylandWindow* window, // Save new pointer location. pointer_location_ = location; + bool focused = !!window; if (focused) - HandlePointerFocusChange(window, focused); + HandlePointerFocusChange(window); EventType type = focused ? ET_MOUSE_ENTERED : ET_MOUSE_EXITED; MouseEvent event(type, location, location, EventTimeForNow(), pointer_flags_, @@ -158,7 +157,7 @@ void WaylandEventSource::OnPointerFocusChanged(WaylandWindow* window, DispatchEvent(&event); if (!focused) - HandlePointerFocusChange(window, focused); + HandlePointerFocusChange(nullptr); } void WaylandEventSource::OnPointerButtonEvent(EventType type, @@ -172,6 +171,7 @@ void WaylandEventSource::OnPointerButtonEvent(EventType type, pointer_flags_ = type == ET_MOUSE_PRESSED ? (pointer_flags_ | changed_button) : (pointer_flags_ & ~changed_button); + last_pointer_button_pressed_ = changed_button; // MouseEvent's flags should contain the button that was released too. int flags = pointer_flags_ | keyboard_modifiers_ | changed_button; MouseEvent event(type, pointer_location_, pointer_location_, @@ -322,22 +322,16 @@ void WaylandEventSource::HandleKeyboardFocusChange(WaylandWindow* window, window->set_keyboard_focus(focused); } -void WaylandEventSource::HandlePointerFocusChange(WaylandWindow* window, - bool focused) { - // window can be null on wl_pointer::leave events, for example. - if (window) - window->SetPointerFocus(focused); - - if (focused) { - DCHECK(window); - window_with_pointer_focus_ = window; - } else { - // Focused window might have been destroyed at this point (eg: context - // menus), in this case, |window| is null, otherwise it must be equal to - // |window_with_pointer_focus_|. In both cases, they must be equal. - DCHECK_EQ(window_with_pointer_focus_, window); - window_with_pointer_focus_ = nullptr; - } +void WaylandEventSource::HandlePointerFocusChange(WaylandWindow* window) { + // Focused window might have been destroyed at this point (eg: context menus), + // in this case, |window| is null. + if (window_with_pointer_focus_) + window_with_pointer_focus_->SetPointerFocus(false); + + window_with_pointer_focus_ = window; + + if (window_with_pointer_focus_) + window_with_pointer_focus_->SetPointerFocus(true); } void WaylandEventSource::HandleTouchFocusChange(WaylandWindow* window, diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_event_source.h b/chromium/ui/ozone/platform/wayland/host/wayland_event_source.h index 7f1a565c613..7c9c409e702 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_event_source.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_event_source.h @@ -55,6 +55,10 @@ class WaylandEventSource : public PlatformEventSource, WaylandEventSource& operator=(const WaylandEventSource&) = delete; ~WaylandEventSource() override; + int last_pointer_button_pressed() const { + return last_pointer_button_pressed_; + } + // Starts polling for events from the wayland connection file descriptor. // This method assumes connection is already estabilished and input objects // are already bound and properly initialized. @@ -76,18 +80,17 @@ class WaylandEventSource : public PlatformEventSource, void OnKeyboardDestroyed(WaylandKeyboard* keyboard) override; void OnKeyboardFocusChanged(WaylandWindow* window, bool focused) override; void OnKeyboardModifiersChanged(int modifiers) override; - void OnKeyboardKeyEvent(EventType type, - DomCode dom_code, - DomKey dom_key, - KeyboardCode key_code, - bool repeat, - base::TimeTicks timestamp) override; + uint32_t OnKeyboardKeyEvent(EventType type, + DomCode dom_code, + DomKey dom_key, + KeyboardCode key_code, + bool repeat, + base::TimeTicks timestamp) override; // WaylandPointer::Delegate void OnPointerCreated(WaylandPointer* pointer) override; void OnPointerDestroyed(WaylandPointer* pointer) override; void OnPointerFocusChanged(WaylandWindow* window, - bool focused, const gfx::PointF& location) override; void OnPointerButtonEvent(EventType evtype, int changed_button) override; void OnPointerMotionEvent(const gfx::PointF& location) override; @@ -117,7 +120,7 @@ class WaylandEventSource : public PlatformEventSource, void UpdateKeyboardModifiers(int modifier, bool down); void HandleKeyboardFocusChange(WaylandWindow* window, bool focused); - void HandlePointerFocusChange(WaylandWindow* window, bool focused); + void HandlePointerFocusChange(WaylandWindow* window); void HandleTouchFocusChange(WaylandWindow* window, bool focused, base::Optional<PointerId> id = base::nullopt); @@ -133,6 +136,9 @@ class WaylandEventSource : public PlatformEventSource, // Bitmask of EventFlags used to keep track of the the pointer state. int pointer_flags_ = 0; + // Bitmask of EventFlags used to keep track of the last changed button. + int last_pointer_button_pressed_ = 0; + // Bitmask of EventFlags used to keep track of the the keyboard state. int keyboard_modifiers_ = 0; diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.cc b/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.cc index 0258d157776..7de0fe694d7 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.cc @@ -34,10 +34,12 @@ const wl_callback_listener WaylandKeyboard::callback_listener_ = { WaylandKeyboard::SyncCallback, }; -WaylandKeyboard::WaylandKeyboard(wl_keyboard* keyboard, - WaylandConnection* connection, - KeyboardLayoutEngine* layout_engine, - Delegate* delegate) +WaylandKeyboard::WaylandKeyboard( + wl_keyboard* keyboard, + zcr_keyboard_extension_v1* keyboard_extension_v1, + WaylandConnection* connection, + KeyboardLayoutEngine* layout_engine, + Delegate* delegate) : obj_(keyboard), connection_(connection), delegate_(delegate), @@ -58,6 +60,10 @@ WaylandKeyboard::WaylandKeyboard(wl_keyboard* keyboard, wl_keyboard_add_listener(obj_.get(), &listener, this); // TODO(tonikitoo): Default auto-repeat to ON here? + + if (keyboard_extension_v1) + extended_keyboard_v1_.reset(zcr_keyboard_extension_v1_get_extended_keyboard( + keyboard_extension_v1, obj_.get())); } WaylandKeyboard::~WaylandKeyboard() { @@ -193,9 +199,15 @@ void WaylandKeyboard::DispatchKey(uint32_t key, // Pass empty DomKey and KeyboardCode here so the delegate can pre-process // and decode it when needed. - delegate_->OnKeyboardKeyEvent(down ? ET_KEY_PRESSED : ET_KEY_RELEASED, - dom_code, DomKey::NONE, - KeyboardCode::VKEY_UNKNOWN, repeat, timestamp); + uint32_t result = delegate_->OnKeyboardKeyEvent( + down ? ET_KEY_PRESSED : ET_KEY_RELEASED, dom_code, DomKey::NONE, + KeyboardCode::VKEY_UNKNOWN, repeat, timestamp); + + if (extended_keyboard_v1_) { + bool handled = result & POST_DISPATCH_STOP_PROPAGATION; + zcr_extended_keyboard_v1_ack_key(extended_keyboard_v1_.get(), + connection_->serial(), handled); + } } bool WaylandKeyboard::Decode(DomCode dom_code, diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.h b/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.h index 1630d8aa681..e449da0be85 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_keyboard.h @@ -5,6 +5,7 @@ #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_KEYBOARD_H_ #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_KEYBOARD_H_ +#include <keyboard-extension-unstable-v1-client-protocol.h> #include <wayland-client.h> #include "base/time/time.h" @@ -30,6 +31,7 @@ class WaylandKeyboard : public EventAutoRepeatHandler::Delegate { class Delegate; WaylandKeyboard(wl_keyboard* keyboard, + zcr_keyboard_extension_v1* keyboard_extension_v1, WaylandConnection* connection, KeyboardLayoutEngine* keyboard_layout_engine, Delegate* delegate); @@ -88,6 +90,7 @@ class WaylandKeyboard : public EventAutoRepeatHandler::Delegate { int flags) override; wl::Object<wl_keyboard> obj_; + wl::Object<zcr_extended_keyboard_v1> extended_keyboard_v1_; WaylandConnection* const connection_; Delegate* const delegate_; @@ -110,12 +113,18 @@ class WaylandKeyboard::Delegate { virtual void OnKeyboardDestroyed(WaylandKeyboard* keyboard) = 0; virtual void OnKeyboardFocusChanged(WaylandWindow* window, bool focused) = 0; virtual void OnKeyboardModifiersChanged(int modifiers) = 0; - virtual void OnKeyboardKeyEvent(EventType type, - DomCode dom_code, - DomKey dom_key, - KeyboardCode key_code, - bool repeat, - base::TimeTicks timestamp) = 0; + // Returns a mask of ui::PostDispatchAction indicating how the event was + // dispatched. + virtual uint32_t OnKeyboardKeyEvent(EventType type, + DomCode dom_code, + DomKey dom_key, + KeyboardCode key_code, + bool repeat, + base::TimeTicks timestamp) = 0; + + protected: + // Prevent deletion through a WaylandKeyboard::Delegate pointer. + virtual ~Delegate() = default; }; } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_pointer.cc b/chromium/ui/ozone/platform/wayland/host/wayland_pointer.cc index 8a2da9f409a..1658a68cc93 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_pointer.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_pointer.cc @@ -45,10 +45,10 @@ void WaylandPointer::Enter(void* data, wl_fixed_t surface_y) { DCHECK(data); WaylandPointer* pointer = static_cast<WaylandPointer*>(data); - gfx::PointF location(wl_fixed_to_double(surface_x), - wl_fixed_to_double(surface_y)); - pointer->delegate_->OnPointerFocusChanged(WaylandWindow::FromSurface(surface), - /*focused=*/true, location); + WaylandWindow* window = WaylandWindow::FromSurface(surface); + gfx::PointF location{wl_fixed_to_double(surface_x), + wl_fixed_to_double(surface_y)}; + pointer->delegate_->OnPointerFocusChanged(window, location); } // static @@ -58,8 +58,7 @@ void WaylandPointer::Leave(void* data, wl_surface* surface) { DCHECK(data); WaylandPointer* pointer = static_cast<WaylandPointer*>(data); - pointer->delegate_->OnPointerFocusChanged(WaylandWindow::FromSurface(surface), - /*focused=*/false, {}); + pointer->delegate_->OnPointerFocusChanged(nullptr, {}); } // static diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_pointer.h b/chromium/ui/ozone/platform/wayland/host/wayland_pointer.h index c4c9ecfbc35..b3f3a6ccbfa 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_pointer.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_pointer.h @@ -73,7 +73,6 @@ class WaylandPointer::Delegate { virtual void OnPointerCreated(WaylandPointer* pointer) = 0; virtual void OnPointerDestroyed(WaylandPointer* pointer) = 0; virtual void OnPointerFocusChanged(WaylandWindow* window, - bool focused, const gfx::PointF& location) = 0; virtual void OnPointerButtonEvent(EventType evtype, int changed_button) = 0; virtual void OnPointerMotionEvent(const gfx::PointF& location) = 0; diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc b/chromium/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc index f20ee54c250..cf12f91f1b7 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_pointer_unittest.cc @@ -237,7 +237,7 @@ TEST_P(WaylandPointerTest, SetBitmapOnPointerFocus) { BitmapCursorFactoryOzone cursor_factory; PlatformCursor cursor = - cursor_factory.CreateImageCursor(dummy_cursor, gfx::Point(5, 8), 1.0f); + cursor_factory.CreateImageCursor(dummy_cursor, gfx::Point(5, 8)); scoped_refptr<BitmapCursorOzone> bitmap = BitmapCursorFactoryOzone::GetBitmapCursor(cursor); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_popup.cc b/chromium/ui/ozone/platform/wayland/host/wayland_popup.cc index 116c7be47d1..ba52979990a 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_popup.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_popup.cc @@ -144,9 +144,7 @@ bool WaylandPopup::OnInitialize(PlatformWindowInitProperties properties) { } gfx::Rect WaylandPopup::AdjustPopupWindowPosition() { - auto* top_level_parent = wl::IsMenuType(parent_window()->type()) - ? parent_window()->parent_window() - : parent_window(); + auto* top_level_parent = GetRootParentWindow(); DCHECK(top_level_parent); DCHECK(buffer_scale() == top_level_parent->buffer_scale()); DCHECK(ui_scale() == top_level_parent->ui_scale()); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_shm_buffer.cc b/chromium/ui/ozone/platform/wayland/host/wayland_shm_buffer.cc index 298ea68b29f..e4fa21fa09a 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_shm_buffer.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_shm_buffer.cc @@ -4,6 +4,7 @@ #include "ui/ozone/platform/wayland/host/wayland_shm_buffer.h" +#include "base/logging.h" #include "base/memory/platform_shared_memory_region.h" #include "base/memory/unsafe_shared_memory_region.h" #include "ui/gfx/skia_util.h" diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_subsurface.cc b/chromium/ui/ozone/platform/wayland/host/wayland_subsurface.cc index 28f435f39de..e76aa6376dd 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_subsurface.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_subsurface.cc @@ -7,6 +7,7 @@ #include "ui/ozone/platform/wayland/common/wayland_util.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_data_drag_controller.h" #include "ui/ozone/platform/wayland/host/wayland_window_manager.h" namespace ui { @@ -15,7 +16,7 @@ namespace { gfx::Rect AdjustSubsurfaceBounds(const gfx::Rect& bounds_px, const gfx::Rect& parent_bounds_px, - int32_t ui_scale, + float ui_scale, int32_t buffer_scale) { const auto parent_bounds_dip = gfx::ScaleToRoundedRect(parent_bounds_px, 1.0 / ui_scale); @@ -78,7 +79,7 @@ void WaylandSubsurface::CreateSubsurface() { // windows. If we are in a drag process, use the entered window. Otherwise, // it must be a tooltip. if (connection()->IsDragInProgress()) { - parent = connection()->wayland_data_device()->entered_window(); + parent = connection()->data_drag_controller()->entered_window(); set_parent_window(parent); } else { // If Aura does not not provide a reference parent window, needed by @@ -114,6 +115,11 @@ void WaylandSubsurface::CreateSubsurface() { wl_subsurface_set_desync(subsurface_.get()); wl_surface_commit(parent->surface()); connection()->ScheduleFlush(); + + // Notify the observers the window has been configured. Please note that + // subsurface doesn't send ack configure events. Thus, notify the observers as + // soon as the subsurface is created. + connection()->wayland_window_manager()->NotifyWindowConfigured(this); } bool WaylandSubsurface::OnInitialize(PlatformWindowInitProperties properties) { diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc b/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc index 6f72776d0d3..c2cf21baf62 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_surface.cc @@ -4,350 +4,21 @@ #include "ui/ozone/platform/wayland/host/wayland_surface.h" -#include "ui/base/dragdrop/drag_drop_types.h" -#include "ui/base/dragdrop/os_exchange_data.h" -#include "ui/base/hit_test.h" -#include "ui/gfx/native_widget_types.h" -#include "ui/ozone/platform/wayland/host/shell_object_factory.h" -#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.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_event_source.h" -#include "ui/platform_window/platform_window_handler/wm_drop_handler.h" +#include "ui/ozone/platform/wayland/host/wayland_window.h" namespace ui { -WaylandSurface::WaylandSurface(PlatformWindowDelegate* delegate, - WaylandConnection* connection) - : WaylandWindow(delegate, connection), - state_(PlatformWindowState::kNormal) { - // Set a class property key, which allows |this| to be used for interactive - // events, e.g. move or resize. - SetWmMoveResizeHandler(this, AsWmMoveResizeHandler()); +WaylandSurface::WaylandSurface() = default; +WaylandSurface::~WaylandSurface() = default; - // Set a class property key, which allows |this| to be used for drag action. - SetWmDragHandler(this, this); +gfx::AcceleratedWidget WaylandSurface::GetWidget() const { + if (!surface_) + return gfx::kNullAcceleratedWidget; + return surface_.id(); } -WaylandSurface::~WaylandSurface() { - if (drag_closed_callback_) { - std::move(drag_closed_callback_) - .Run(DragDropTypes::DragOperation::DRAG_NONE); - } -} - -bool WaylandSurface::CreateShellSurface() { - ShellObjectFactory factory; - shell_surface_ = factory.CreateShellSurfaceWrapper(connection(), this); - if (!shell_surface_) { - LOG(ERROR) << "Failed to create a ShellSurface."; - return false; - } - - shell_surface_->SetAppId(app_id_); - shell_surface_->SetTitle(window_title_); - SetSizeConstraints(); - TriggerStateChanges(); - return true; -} - -void WaylandSurface::ApplyPendingBounds() { - if (pending_bounds_dip_.IsEmpty()) - return; - DCHECK(shell_surface_); - - SetBoundsDip(pending_bounds_dip_); - shell_surface_->SetWindowGeometry(pending_bounds_dip_); - pending_bounds_dip_ = gfx::Rect(); - connection()->ScheduleFlush(); -} - -void WaylandSurface::DispatchHostWindowDragMovement( - int hittest, - const gfx::Point& pointer_location_in_px) { - DCHECK(shell_surface_); - - connection()->event_source()->ResetPointerFlags(); - if (hittest == HTCAPTION) - shell_surface_->SurfaceMove(connection()); - else - shell_surface_->SurfaceResize(connection(), hittest); - - connection()->ScheduleFlush(); -} - -void WaylandSurface::StartDrag(const ui::OSExchangeData& data, - int operation, - gfx::NativeCursor cursor, - base::OnceCallback<void(int)> callback) { - DCHECK(!drag_closed_callback_); - drag_closed_callback_ = std::move(callback); - connection()->StartDrag(data, operation); -} - -void WaylandSurface::Show(bool inactive) { - if (shell_surface_) - return; - - if (!CreateShellSurface()) { - Close(); - return; - } - - UpdateBufferScale(false); -} - -void WaylandSurface::Hide() { - if (!shell_surface_) - return; - - if (child_window()) { - child_window()->Hide(); - set_child_window(nullptr); - } - - shell_surface_.reset(); - connection()->ScheduleFlush(); - - // Detach buffer from surface in order to completely shutdown menus and - // tooltips, and release resources. - connection()->buffer_manager_host()->ResetSurfaceContents(GetWidget()); -} - -bool WaylandSurface::IsVisible() const { - // X and Windows return true if the window is minimized. For consistency, do - // the same. - return !!shell_surface_ || state_ == PlatformWindowState::kMinimized; -} - -void WaylandSurface::SetTitle(const base::string16& title) { - if (window_title_ == title) - return; - - window_title_ = title; - - if (shell_surface_) { - shell_surface_->SetTitle(title); - connection()->ScheduleFlush(); - } -} - -void WaylandSurface::ToggleFullscreen() { - // TODO(msisov, tonikitoo): add multiscreen support. As the documentation says - // if xdg_toplevel_set_fullscreen() is not provided with wl_output, it's up - // to the compositor to choose which display will be used to map this surface. - - // We must track the previous state to correctly say our state as long as it - // can be the maximized instead of normal one. - PlatformWindowState new_state = PlatformWindowState::kUnknown; - if (state_ == PlatformWindowState::kFullScreen) { - if (previous_state_ == PlatformWindowState::kMaximized) - new_state = previous_state_; - else - new_state = PlatformWindowState::kNormal; - } else { - new_state = PlatformWindowState::kFullScreen; - } - - SetWindowState(new_state); -} - -void WaylandSurface::Maximize() { - SetWindowState(PlatformWindowState::kMaximized); -} - -void WaylandSurface::Minimize() { - SetWindowState(PlatformWindowState::kMinimized); -} - -void WaylandSurface::Restore() { - DCHECK(shell_surface_); - SetWindowState(PlatformWindowState::kNormal); -} - -PlatformWindowState WaylandSurface::GetPlatformWindowState() const { - return state_; -} - -void WaylandSurface::SizeConstraintsChanged() { - // Size constraints only make sense for normal windows. - if (!shell_surface_) - return; - - DCHECK(delegate()); - min_size_ = delegate()->GetMinimumSizeForWindow(); - max_size_ = delegate()->GetMaximumSizeForWindow(); - SetSizeConstraints(); -} - -void WaylandSurface::HandleSurfaceConfigure(int32_t width, - int32_t height, - bool is_maximized, - bool is_fullscreen, - bool is_activated) { - // Store the old state to propagte state changes if Wayland decides to change - // the state to something else. - PlatformWindowState old_state = state_; - if (state_ == PlatformWindowState::kMinimized && !is_activated) { - state_ = PlatformWindowState::kMinimized; - } else if (is_fullscreen) { - state_ = PlatformWindowState::kFullScreen; - } else if (is_maximized) { - state_ = PlatformWindowState::kMaximized; - } else { - state_ = PlatformWindowState::kNormal; - } - - const bool state_changed = old_state != state_; - const bool is_normal = state_ == PlatformWindowState::kNormal; - - // Update state before notifying delegate. - const bool did_active_change = is_active_ != is_activated; - is_active_ = is_activated; - - // Rather than call SetBounds here for every configure event, just save the - // most recent bounds, and have WaylandConnection call ApplyPendingBounds - // when it has finished processing events. We may get many configure events - // in a row during an interactive resize, and only the last one matters. - // - // Width or height set to 0 means that we should decide on width and height by - // ourselves, but we don't want to set them to anything else. Use restored - // bounds size or the current bounds iff the current state is normal (neither - // maximized nor fullscreen). - // - // Note: if the browser was started with --start-fullscreen and a user exits - // the fullscreen mode, wayland may set the width and height to be 1. Instead, - // explicitly set the bounds to the current desired ones or the previous - // bounds. - if (width > 1 && height > 1) { - pending_bounds_dip_ = gfx::Rect(0, 0, width, height); - } else if (is_normal) { - pending_bounds_dip_.set_size( - gfx::ScaleToRoundedSize(GetRestoredBoundsInPixels().IsEmpty() - ? GetBounds().size() - : GetRestoredBoundsInPixels().size(), - - 1.0 / buffer_scale())); - } - - // Store the restored bounds of current state differs from the normal state. - // It can be client or compositor side change from normal to something else. - // Thus, we must store previous bounds to restore later. - SetOrResetRestoredBounds(); - ApplyPendingBounds(); - - if (state_changed) - delegate()->OnWindowStateChanged(state_); - - if (did_active_change) - delegate()->OnActivationChanged(is_active_); -} - -void WaylandSurface::OnDragEnter(const gfx::PointF& point, - std::unique_ptr<OSExchangeData> data, - int operation) { - WmDropHandler* drop_handler = GetWmDropHandler(*this); - if (!drop_handler) - return; - - // Wayland sends locations in DIP so they need to be translated to - // physical pixels. - drop_handler->OnDragEnter( - gfx::ScalePoint(point, buffer_scale(), buffer_scale()), std::move(data), - operation); -} - -int WaylandSurface::OnDragMotion(const gfx::PointF& point, - uint32_t time, - int operation) { - WmDropHandler* drop_handler = GetWmDropHandler(*this); - if (!drop_handler) - return 0; - - // Wayland sends locations in DIP so they need to be translated to - // physical pixels. - return drop_handler->OnDragMotion( - gfx::ScalePoint(point, buffer_scale(), buffer_scale()), operation); -} - -void WaylandSurface::OnDragDrop(std::unique_ptr<OSExchangeData> data) { - WmDropHandler* drop_handler = GetWmDropHandler(*this); - if (!drop_handler) - return; - drop_handler->OnDragDrop(std::move(data)); -} - -void WaylandSurface::OnDragLeave() { - WmDropHandler* drop_handler = GetWmDropHandler(*this); - if (!drop_handler) - return; - drop_handler->OnDragLeave(); -} - -void WaylandSurface::OnDragSessionClose(uint32_t dnd_action) { - std::move(drag_closed_callback_).Run(dnd_action); - connection()->event_source()->ResetPointerFlags(); -} - -bool WaylandSurface::OnInitialize(PlatformWindowInitProperties properties) { - app_id_ = properties.wm_class_class; - return true; -} - -void WaylandSurface::TriggerStateChanges() { - if (!shell_surface_) - return; - - if (state_ == PlatformWindowState::kFullScreen) - shell_surface_->SetFullscreen(); - else - shell_surface_->UnSetFullscreen(); - - // Call UnSetMaximized only if current state is normal. Otherwise, if the - // current state is fullscreen and the previous is maximized, calling - // UnSetMaximized may result in wrong restored window position that clients - // are not allowed to know about. - if (state_ == PlatformWindowState::kMaximized) - shell_surface_->SetMaximized(); - else if (state_ == PlatformWindowState::kNormal) - shell_surface_->UnSetMaximized(); - - if (state_ == PlatformWindowState::kMinimized) - shell_surface_->SetMinimized(); - - connection()->ScheduleFlush(); -} - -void WaylandSurface::SetWindowState(PlatformWindowState state) { - previous_state_ = state_; - state_ = state; - TriggerStateChanges(); -} - -WmMoveResizeHandler* WaylandSurface::AsWmMoveResizeHandler() { - return static_cast<WmMoveResizeHandler*>(this); -} - -void WaylandSurface::SetSizeConstraints() { - if (min_size_.has_value()) - shell_surface_->SetMinSize(min_size_->width(), min_size_->height()); - if (max_size_.has_value()) - shell_surface_->SetMaxSize(max_size_->width(), max_size_->height()); - - connection()->ScheduleFlush(); -} - -void WaylandSurface::SetOrResetRestoredBounds() { - // The |restored_bounds_| are used when the window gets back to normal - // state after it went maximized or fullscreen. So we reset these if the - // window has just become normal and store the current bounds if it is - // either going out of normal state or simply changes the state and we don't - // have any meaningful value stored. - if (GetPlatformWindowState() == PlatformWindowState::kNormal) { - SetRestoredBoundsInPixels({}); - } else if (GetRestoredBoundsInPixels().IsEmpty()) { - SetRestoredBoundsInPixels(GetBounds()); - } +gfx::AcceleratedWidget WaylandSurface::GetRootWidget() const { + return root_window_->GetWidget(); } } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_surface.h b/chromium/ui/ozone/platform/wayland/host/wayland_surface.h index ceda32d24a7..e432ceb7e7d 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_surface.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_surface.h @@ -5,121 +5,38 @@ #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_SURFACE_H_ #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_SURFACE_H_ -#include "ui/ozone/platform/wayland/host/wayland_window.h" - -#include "ui/platform_window/platform_window_handler/wm_drag_handler.h" -#include "ui/platform_window/platform_window_handler/wm_move_resize_handler.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/ozone/platform/wayland/common/wayland_object.h" namespace ui { -class ShellSurfaceWrapper; +class WaylandWindow; -class WaylandSurface : public WaylandWindow, - public WmMoveResizeHandler, - public WmDragHandler { +// Wrapper of a wl_surface, owned by a WaylandWindow or a WlSubsurface. +class WaylandSurface { public: - WaylandSurface(PlatformWindowDelegate* delegate, - WaylandConnection* connection); - ~WaylandSurface() override; - - ShellSurfaceWrapper* shell_surface() const { return shell_surface_.get(); } - - // Apply the bounds specified in the most recent configure event. This should - // be called after processing all pending events in the wayland connection. - void ApplyPendingBounds(); + WaylandSurface(); + WaylandSurface(const WaylandSurface&) = delete; + WaylandSurface& operator=(const WaylandSurface&) = delete; + ~WaylandSurface(); - // WmMoveResizeHandler - void DispatchHostWindowDragMovement( - int hittest, - const gfx::Point& pointer_location_in_px) override; + WaylandWindow* root_window() const { return root_window_; } + wl_surface* surface() const { return surface_.get(); } + int32_t buffer_scale() const { return buffer_scale_; } - // WmDragHandler - void StartDrag(const ui::OSExchangeData& data, - int operation, - gfx::NativeCursor cursor, - base::OnceCallback<void(int)> callback) override; - - // PlatformWindow - void Show(bool inactive) override; - void Hide() override; - bool IsVisible() const override; - void SetTitle(const base::string16& title) override; - void ToggleFullscreen() override; - void Maximize() override; - void Minimize() override; - void Restore() override; - PlatformWindowState GetPlatformWindowState() const override; - void SizeConstraintsChanged() override; + // gfx::AcceleratedWidget identifies a wl_surface or a ui::WaylandWindow. Note + // that GetWidget() and GetRootWidget() do not necessarily return the same + // result. + gfx::AcceleratedWidget GetWidget() const; + gfx::AcceleratedWidget GetRootWidget() const; private: - // WaylandWindow overrides: - void HandleSurfaceConfigure(int32_t widht, - int32_t height, - bool is_maximized, - bool is_fullscreen, - bool is_activated) override; - void OnDragEnter(const gfx::PointF& point, - std::unique_ptr<OSExchangeData> data, - int operation) override; - int OnDragMotion(const gfx::PointF& point, - uint32_t time, - int operation) override; - void OnDragDrop(std::unique_ptr<OSExchangeData> data) override; - void OnDragLeave() override; - void OnDragSessionClose(uint32_t dnd_action) override; - bool OnInitialize(PlatformWindowInitProperties properties) override; - - void TriggerStateChanges(); - void SetWindowState(PlatformWindowState state); - - // Creates a surface window, which is visible as a main window. - bool CreateShellSurface(); - - WmMoveResizeHandler* AsWmMoveResizeHandler(); - - // Propagates the |min_size_| and |max_size_| to the ShellSurface. - void SetSizeConstraints(); - - void SetOrResetRestoredBounds(); - - // Wrappers around shell surface. - std::unique_ptr<ShellSurfaceWrapper> shell_surface_; - - base::OnceCallback<void(int)> drag_closed_callback_; - - // These bounds attributes below have suffices that indicate units used. - // Wayland operates in DIP but the platform operates in physical pixels so - // our WaylandSurface is the link that has to translate the units. See also - // comments in the implementation. - // - // Bounds that will be applied when the window state is finalized. The window - // may get several configuration events that update the pending bounds, and - // only upon finalizing the state is the latest value stored as the current - // bounds via |ApplyPendingBounds|. Measured in DIP because updated in the - // handler that receives DIP from Wayland. - gfx::Rect pending_bounds_dip_; - - // Contains the current state of the window. - PlatformWindowState state_; - // Contains the previous state of the window. - PlatformWindowState previous_state_; - - bool is_active_ = false; - - // Id of the chromium app passed through - // PlatformWindowInitProperties::wm_class_class. This is used by Wayland - // compositor to identify the app, unite it's windows into the same stack of - // windows and find *.desktop file to set various preferences including icons. - std::string app_id_; - - // Title of the ShellSurface. - base::string16 window_title_; - - // Max and min sizes of the WaylandSurface window. - base::Optional<gfx::Size> min_size_; - base::Optional<gfx::Size> max_size_; - - DISALLOW_COPY_AND_ASSIGN(WaylandSurface); + WaylandWindow* root_window_ = nullptr; + wl::Object<wl_surface> surface_; + // Wayland's scale factor for the output that this window currently belongs + // to. + int32_t buffer_scale_ = 1; + friend class WaylandWindow; }; } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/chromium/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc new file mode 100644 index 00000000000..10ecbcf7b47 --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc @@ -0,0 +1,368 @@ +// Copyright 2020 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_toplevel_window.h" + +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/hit_test.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/ozone/platform/wayland/host/shell_object_factory.h" +#include "ui/ozone/platform/wayland/host/shell_surface_wrapper.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_data_drag_controller.h" +#include "ui/ozone/platform/wayland/host/wayland_event_source.h" +#include "ui/ozone/platform/wayland/host/wayland_window_drag_controller.h" +#include "ui/platform_window/platform_window_handler/wm_drop_handler.h" + +namespace ui { + +WaylandToplevelWindow::WaylandToplevelWindow(PlatformWindowDelegate* delegate, + WaylandConnection* connection) + : WaylandWindow(delegate, connection), + state_(PlatformWindowState::kNormal) { + // Set a class property key, which allows |this| to be used for interactive + // events, e.g. move or resize. + SetWmMoveResizeHandler(this, AsWmMoveResizeHandler()); + + // Set a class property key, which allows |this| to be used for drag action. + SetWmDragHandler(this, this); +} + +WaylandToplevelWindow::~WaylandToplevelWindow() { + if (drag_handler_delegate_) { + drag_handler_delegate_->OnDragFinished( + DragDropTypes::DragOperation::DRAG_NONE); + } +} + +bool WaylandToplevelWindow::CreateShellSurface() { + ShellObjectFactory factory; + shell_surface_ = factory.CreateShellSurfaceWrapper(connection(), this); + if (!shell_surface_) { + LOG(ERROR) << "Failed to create a ShellSurface."; + return false; + } + + shell_surface_->SetAppId(app_id_); + shell_surface_->SetTitle(window_title_); + SetSizeConstraints(); + TriggerStateChanges(); + return true; +} + +void WaylandToplevelWindow::ApplyPendingBounds() { + if (pending_bounds_dip_.IsEmpty()) + return; + DCHECK(shell_surface_); + + SetBoundsDip(pending_bounds_dip_); + shell_surface_->SetWindowGeometry(pending_bounds_dip_); + pending_bounds_dip_ = gfx::Rect(); + connection()->ScheduleFlush(); +} + +void WaylandToplevelWindow::DispatchHostWindowDragMovement( + int hittest, + const gfx::Point& pointer_location_in_px) { + DCHECK(shell_surface_); + + connection()->event_source()->ResetPointerFlags(); + if (hittest == HTCAPTION) + shell_surface_->SurfaceMove(connection()); + else + shell_surface_->SurfaceResize(connection(), hittest); + + connection()->ScheduleFlush(); +} + +void WaylandToplevelWindow::StartDrag(const ui::OSExchangeData& data, + int operation, + gfx::NativeCursor cursor, + WmDragHandler::Delegate* delegate) { + DCHECK(!drag_handler_delegate_); + drag_handler_delegate_ = delegate; + connection()->data_drag_controller()->StartSession(data, operation); +} + +void WaylandToplevelWindow::Show(bool inactive) { + if (shell_surface_) + return; + + if (!CreateShellSurface()) { + Close(); + return; + } + + UpdateBufferScale(false); +} + +void WaylandToplevelWindow::Hide() { + if (!shell_surface_) + return; + + if (child_window()) { + child_window()->Hide(); + set_child_window(nullptr); + } + + shell_surface_.reset(); + connection()->ScheduleFlush(); + + // Detach buffer from surface in order to completely shutdown menus and + // tooltips, and release resources. + connection()->buffer_manager_host()->ResetSurfaceContents(GetWidget()); +} + +bool WaylandToplevelWindow::IsVisible() const { + // X and Windows return true if the window is minimized. For consistency, do + // the same. + return !!shell_surface_ || state_ == PlatformWindowState::kMinimized; +} + +void WaylandToplevelWindow::SetTitle(const base::string16& title) { + if (window_title_ == title) + return; + + window_title_ = title; + + if (shell_surface_) { + shell_surface_->SetTitle(title); + connection()->ScheduleFlush(); + } +} + +void WaylandToplevelWindow::ToggleFullscreen() { + // TODO(msisov, tonikitoo): add multiscreen support. As the documentation says + // if xdg_toplevel_set_fullscreen() is not provided with wl_output, it's up + // to the compositor to choose which display will be used to map this surface. + + // We must track the previous state to correctly say our state as long as it + // can be the maximized instead of normal one. + PlatformWindowState new_state = PlatformWindowState::kUnknown; + if (state_ == PlatformWindowState::kFullScreen) { + if (previous_state_ == PlatformWindowState::kMaximized) + new_state = previous_state_; + else + new_state = PlatformWindowState::kNormal; + } else { + new_state = PlatformWindowState::kFullScreen; + } + + SetWindowState(new_state); +} + +void WaylandToplevelWindow::Maximize() { + SetWindowState(PlatformWindowState::kMaximized); +} + +void WaylandToplevelWindow::Minimize() { + SetWindowState(PlatformWindowState::kMinimized); +} + +void WaylandToplevelWindow::Restore() { + DCHECK(shell_surface_); + SetWindowState(PlatformWindowState::kNormal); +} + +PlatformWindowState WaylandToplevelWindow::GetPlatformWindowState() const { + return state_; +} + +void WaylandToplevelWindow::SizeConstraintsChanged() { + // Size constraints only make sense for normal windows. + if (!shell_surface_) + return; + + DCHECK(delegate()); + min_size_ = delegate()->GetMinimumSizeForWindow(); + max_size_ = delegate()->GetMaximumSizeForWindow(); + SetSizeConstraints(); +} + +void WaylandToplevelWindow::HandleSurfaceConfigure(int32_t width, + int32_t height, + bool is_maximized, + bool is_fullscreen, + bool is_activated) { + // Store the old state to propagte state changes if Wayland decides to change + // the state to something else. + PlatformWindowState old_state = state_; + if (state_ == PlatformWindowState::kMinimized && !is_activated) { + state_ = PlatformWindowState::kMinimized; + } else if (is_fullscreen) { + state_ = PlatformWindowState::kFullScreen; + } else if (is_maximized) { + state_ = PlatformWindowState::kMaximized; + } else { + state_ = PlatformWindowState::kNormal; + } + + const bool state_changed = old_state != state_; + const bool is_normal = state_ == PlatformWindowState::kNormal; + + // Update state before notifying delegate. + const bool did_active_change = is_active_ != is_activated; + is_active_ = is_activated; + + // Rather than call SetBounds here for every configure event, just save the + // most recent bounds, and have WaylandConnection call ApplyPendingBounds + // when it has finished processing events. We may get many configure events + // in a row during an interactive resize, and only the last one matters. + // + // Width or height set to 0 means that we should decide on width and height by + // ourselves, but we don't want to set them to anything else. Use restored + // bounds size or the current bounds iff the current state is normal (neither + // maximized nor fullscreen). + // + // Note: if the browser was started with --start-fullscreen and a user exits + // the fullscreen mode, wayland may set the width and height to be 1. Instead, + // explicitly set the bounds to the current desired ones or the previous + // bounds. + if (width > 1 && height > 1) { + pending_bounds_dip_ = gfx::Rect(0, 0, width, height); + } else if (is_normal) { + pending_bounds_dip_.set_size( + gfx::ScaleToRoundedSize(GetRestoredBoundsInPixels().IsEmpty() + ? GetBounds().size() + : GetRestoredBoundsInPixels().size(), + + 1.0 / buffer_scale())); + } + + // Store the restored bounds of current state differs from the normal state. + // It can be client or compositor side change from normal to something else. + // Thus, we must store previous bounds to restore later. + SetOrResetRestoredBounds(); + ApplyPendingBounds(); + + if (state_changed) + delegate()->OnWindowStateChanged(state_); + + if (did_active_change) + delegate()->OnActivationChanged(is_active_); +} + +void WaylandToplevelWindow::OnDragEnter(const gfx::PointF& point, + std::unique_ptr<OSExchangeData> data, + int operation) { + WmDropHandler* drop_handler = GetWmDropHandler(*this); + if (!drop_handler) + return; + + // Wayland sends locations in DIP so they need to be translated to + // physical pixels. + drop_handler->OnDragEnter( + gfx::ScalePoint(point, buffer_scale(), buffer_scale()), std::move(data), + operation); +} + +int WaylandToplevelWindow::OnDragMotion(const gfx::PointF& point, + int operation) { + WmDropHandler* drop_handler = GetWmDropHandler(*this); + if (!drop_handler) + return 0; + + // Wayland sends locations in DIP so they need to be translated to + // physical pixels. + return drop_handler->OnDragMotion( + gfx::ScalePoint(point, buffer_scale(), buffer_scale()), operation); +} + +void WaylandToplevelWindow::OnDragDrop(std::unique_ptr<OSExchangeData> data) { + WmDropHandler* drop_handler = GetWmDropHandler(*this); + if (!drop_handler) + return; + drop_handler->OnDragDrop(std::move(data)); +} + +void WaylandToplevelWindow::OnDragLeave() { + WmDropHandler* drop_handler = GetWmDropHandler(*this); + if (!drop_handler) + return; + drop_handler->OnDragLeave(); +} + +void WaylandToplevelWindow::OnDragSessionClose(uint32_t dnd_action) { + DCHECK(drag_handler_delegate_); + drag_handler_delegate_->OnDragFinished(dnd_action); + drag_handler_delegate_ = nullptr; + connection()->event_source()->ResetPointerFlags(); +} + +bool WaylandToplevelWindow::OnInitialize( + PlatformWindowInitProperties properties) { + app_id_ = properties.wm_class_class; + SetWmMoveLoopHandler(this, static_cast<WmMoveLoopHandler*>(this)); + return true; +} + +bool WaylandToplevelWindow::RunMoveLoop(const gfx::Vector2d& drag_offset) { + DCHECK(connection()->window_drag_controller()); + return connection()->window_drag_controller()->Drag(this, drag_offset); +} + +void WaylandToplevelWindow::EndMoveLoop() { + DCHECK(connection()->window_drag_controller()); + connection()->window_drag_controller()->StopDragging(); +} + +void WaylandToplevelWindow::TriggerStateChanges() { + if (!shell_surface_) + return; + + if (state_ == PlatformWindowState::kFullScreen) + shell_surface_->SetFullscreen(); + else + shell_surface_->UnSetFullscreen(); + + // Call UnSetMaximized only if current state is normal. Otherwise, if the + // current state is fullscreen and the previous is maximized, calling + // UnSetMaximized may result in wrong restored window position that clients + // are not allowed to know about. + if (state_ == PlatformWindowState::kMaximized) + shell_surface_->SetMaximized(); + else if (state_ == PlatformWindowState::kNormal) + shell_surface_->UnSetMaximized(); + + if (state_ == PlatformWindowState::kMinimized) + shell_surface_->SetMinimized(); + + connection()->ScheduleFlush(); +} + +void WaylandToplevelWindow::SetWindowState(PlatformWindowState state) { + previous_state_ = state_; + state_ = state; + TriggerStateChanges(); +} + +WmMoveResizeHandler* WaylandToplevelWindow::AsWmMoveResizeHandler() { + return static_cast<WmMoveResizeHandler*>(this); +} + +void WaylandToplevelWindow::SetSizeConstraints() { + if (min_size_.has_value()) + shell_surface_->SetMinSize(min_size_->width(), min_size_->height()); + if (max_size_.has_value()) + shell_surface_->SetMaxSize(max_size_->width(), max_size_->height()); + + connection()->ScheduleFlush(); +} + +void WaylandToplevelWindow::SetOrResetRestoredBounds() { + // The |restored_bounds_| are used when the window gets back to normal + // state after it went maximized or fullscreen. So we reset these if the + // window has just become normal and store the current bounds if it is + // either going out of normal state or simply changes the state and we don't + // have any meaningful value stored. + if (GetPlatformWindowState() == PlatformWindowState::kNormal) { + SetRestoredBoundsInPixels({}); + } else if (GetRestoredBoundsInPixels().IsEmpty()) { + SetRestoredBoundsInPixels(GetBounds()); + } +} + +} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_toplevel_window.h b/chromium/ui/ozone/platform/wayland/host/wayland_toplevel_window.h new file mode 100644 index 00000000000..0455078ee38 --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_toplevel_window.h @@ -0,0 +1,131 @@ +// Copyright 2019 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_TOPLEVEL_WINDOW_H_ +#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_TOPLEVEL_WINDOW_H_ + +#include "ui/gfx/geometry/vector2d.h" +#include "ui/ozone/platform/wayland/host/wayland_window.h" +#include "ui/platform_window/platform_window_handler/wm_drag_handler.h" +#include "ui/platform_window/platform_window_handler/wm_move_loop_handler.h" +#include "ui/platform_window/platform_window_handler/wm_move_resize_handler.h" + +namespace ui { + +class ShellSurfaceWrapper; + +class WaylandToplevelWindow : public WaylandWindow, + public WmMoveResizeHandler, + public WmDragHandler, + public WmMoveLoopHandler { + public: + WaylandToplevelWindow(PlatformWindowDelegate* delegate, + WaylandConnection* connection); + WaylandToplevelWindow(const WaylandToplevelWindow&) = delete; + WaylandToplevelWindow& operator=(const WaylandToplevelWindow&) = delete; + ~WaylandToplevelWindow() override; + + ShellSurfaceWrapper* shell_surface() const { return shell_surface_.get(); } + + // Apply the bounds specified in the most recent configure event. This should + // be called after processing all pending events in the wayland connection. + void ApplyPendingBounds(); + + // WmMoveResizeHandler + void DispatchHostWindowDragMovement( + int hittest, + const gfx::Point& pointer_location_in_px) override; + + // WmDragHandler + void StartDrag(const ui::OSExchangeData& data, + int operation, + gfx::NativeCursor cursor, + WmDragHandler::Delegate* delegate) override; + + // PlatformWindow + void Show(bool inactive) override; + void Hide() override; + bool IsVisible() const override; + void SetTitle(const base::string16& title) override; + void ToggleFullscreen() override; + void Maximize() override; + void Minimize() override; + void Restore() override; + PlatformWindowState GetPlatformWindowState() const override; + void SizeConstraintsChanged() override; + + private: + // WaylandWindow overrides: + void HandleSurfaceConfigure(int32_t widht, + int32_t height, + bool is_maximized, + bool is_fullscreen, + bool is_activated) override; + void OnDragEnter(const gfx::PointF& point, + std::unique_ptr<OSExchangeData> data, + int operation) override; + int OnDragMotion(const gfx::PointF& point, int operation) override; + void OnDragDrop(std::unique_ptr<OSExchangeData> data) override; + void OnDragLeave() override; + void OnDragSessionClose(uint32_t dnd_action) override; + bool OnInitialize(PlatformWindowInitProperties properties) override; + + // WmMoveLoopHandler: + bool RunMoveLoop(const gfx::Vector2d& drag_offset) override; + void EndMoveLoop() override; + + void TriggerStateChanges(); + void SetWindowState(PlatformWindowState state); + + // Creates a surface window, which is visible as a main window. + bool CreateShellSurface(); + + WmMoveResizeHandler* AsWmMoveResizeHandler(); + + // Propagates the |min_size_| and |max_size_| to the ShellSurface. + void SetSizeConstraints(); + + void SetOrResetRestoredBounds(); + + // Wrappers around shell surface. + std::unique_ptr<ShellSurfaceWrapper> shell_surface_; + + WmDragHandler::Delegate* drag_handler_delegate_ = nullptr; + + // These bounds attributes below have suffices that indicate units used. + // Wayland operates in DIP but the platform operates in physical pixels so + // our WaylandToplevelWindow is the link that has to translate the units. See + // also comments in the implementation. + // + // Bounds that will be applied when the window state is finalized. The window + // may get several configuration events that update the pending bounds, and + // only upon finalizing the state is the latest value stored as the current + // bounds via |ApplyPendingBounds|. Measured in DIP because updated in the + // handler that receives DIP from Wayland. + gfx::Rect pending_bounds_dip_; + + // Contains the current state of the window. + PlatformWindowState state_; + // Contains the previous state of the window. + PlatformWindowState previous_state_; + + bool is_active_ = false; + + // Id of the chromium app passed through + // PlatformWindowInitProperties::wm_class_class. This is used by Wayland + // compositor to identify the app, unite it's windows into the same stack of + // windows and find *.desktop file to set various preferences including icons. + std::string app_id_; + + // Title of the ShellSurface. + base::string16 window_title_; + + // Max and min sizes of the WaylandToplevelWindow window. + base::Optional<gfx::Size> min_size_; + base::Optional<gfx::Size> max_size_; +}; + +} // namespace ui + +#endif // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_TOPLEVEL_WINDOW_H_ diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window.cc index 9298cb3acbc..21ebb192e5a 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window.cc @@ -30,7 +30,7 @@ WaylandWindow::WaylandWindow(PlatformWindowDelegate* delegate, WaylandWindow::~WaylandWindow() { PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); - if (surface_) + if (surface()) connection_->wayland_window_manager()->RemoveWindow(GetWidget()); if (parent_window_) @@ -61,7 +61,7 @@ void WaylandWindow::UpdateBufferScale(bool update_bounds) { int32_t new_scale = 0; if (parent_window_) { - new_scale = parent_window_->buffer_scale_; + new_scale = parent_window_->buffer_scale(); ui_scale_ = parent_window_->ui_scale_; } else { const auto display = (widget == gfx::kNullAcceleratedWidget) @@ -80,9 +80,7 @@ void WaylandWindow::UpdateBufferScale(bool update_bounds) { } gfx::AcceleratedWidget WaylandWindow::GetWidget() const { - if (!surface_) - return gfx::kNullAcceleratedWidget; - return surface_.id(); + return wayland_surface_.GetWidget(); } void WaylandWindow::SetPointerFocus(bool focus) { has_pointer_focus_ = focus; @@ -163,7 +161,7 @@ void WaylandWindow::Restore() {} PlatformWindowState WaylandWindow::GetPlatformWindowState() const { // Remove normal state for all the other types of windows as it's only the - // WaylandSurface that supports state changes. + // WaylandToplevelWindow that supports state changes. return PlatformWindowState::kNormal; } @@ -250,6 +248,12 @@ uint32_t WaylandWindow::DispatchEvent(const PlatformEvent& native_event) { auto* event_grabber = connection_->wayland_window_manager()->located_events_grabber(); auto* root_parent_window = GetRootParentWindow(); + + // Wayland sends locations in DIP so they need to be translated to + // physical pixels. + event->AsLocatedEvent()->set_location_f(gfx::ScalePoint( + event->AsLocatedEvent()->location_f(), buffer_scale(), buffer_scale())); + // We must reroute the events to the event grabber iff these windows belong // to the same root parent window. For example, there are 2 top level // Wayland windows. One of them (window_1) has a child menu window that is @@ -297,9 +301,7 @@ void WaylandWindow::OnDragEnter(const gfx::PointF& point, std::unique_ptr<OSExchangeData> data, int operation) {} -int WaylandWindow::OnDragMotion(const gfx::PointF& point, - uint32_t time, - int operation) { +int WaylandWindow::OnDragMotion(const gfx::PointF& point, int operation) { return -1; } @@ -310,24 +312,26 @@ void WaylandWindow::OnDragLeave() {} void WaylandWindow::OnDragSessionClose(uint32_t dnd_action) {} void WaylandWindow::SetBoundsDip(const gfx::Rect& bounds_dip) { - SetBounds(gfx::ScaleToRoundedRect(bounds_dip, buffer_scale_)); + SetBounds(gfx::ScaleToRoundedRect(bounds_dip, buffer_scale())); } bool WaylandWindow::Initialize(PlatformWindowInitProperties properties) { // Properties contain DIP bounds but the buffer scale is initially 1 so it's // OK to assign. The bounds will be recalculated when the buffer scale // changes. - DCHECK_EQ(buffer_scale_, 1); + DCHECK_EQ(buffer_scale(), 1); bounds_px_ = properties.bounds; opacity_ = properties.opacity; type_ = properties.type; - surface_.reset(wl_compositor_create_surface(connection_->compositor())); - if (!surface_) { + wayland_surface_.surface_.reset( + wl_compositor_create_surface(connection_->compositor())); + wayland_surface_.root_window_ = this; + if (!surface()) { LOG(ERROR) << "Failed to create wl_surface"; return false; } - wl_surface_set_user_data(surface_.get(), this); + wl_surface_set_user_data(surface(), this); AddSurfaceListener(); connection_->wayland_window_manager()->AddWindow(GetWidget(), this); @@ -350,16 +354,16 @@ bool WaylandWindow::Initialize(PlatformWindowInitProperties properties) { void WaylandWindow::SetBufferScale(int32_t new_scale, bool update_bounds) { DCHECK_GT(new_scale, 0); - if (new_scale == buffer_scale_) + if (new_scale == buffer_scale()) return; - auto old_scale = buffer_scale_; - buffer_scale_ = new_scale; + auto old_scale = buffer_scale(); + wayland_surface_.buffer_scale_ = new_scale; if (update_bounds) SetBoundsDip(gfx::ScaleToRoundedRect(bounds_px_, 1.0 / old_scale)); DCHECK(surface()); - wl_surface_set_buffer_scale(surface(), buffer_scale_); + wl_surface_set_buffer_scale(surface(), buffer_scale()); connection_->ScheduleFlush(); } @@ -377,11 +381,12 @@ WaylandWindow* WaylandWindow::GetParentWindow( // Another case is a notifcation window or a drop down window, which do not // have a parent in aura. In this case, take the current focused window as a // parent. - if (parent_window && parent_window->child_window_) - return parent_window->child_window_; + if (!parent_window) - return connection_->wayland_window_manager()->GetCurrentFocusedWindow(); - return parent_window; + parent_window = + connection_->wayland_window_manager()->GetCurrentFocusedWindow(); + + return parent_window ? parent_window->GetTopMostChildWindow() : nullptr; } WaylandWindow* WaylandWindow::GetRootParentWindow() { @@ -393,7 +398,7 @@ void WaylandWindow::AddSurfaceListener() { &WaylandWindow::Enter, &WaylandWindow::Leave, }; - wl_surface_add_listener(surface_.get(), &surface_listener, this); + wl_surface_add_listener(surface(), &surface_listener, this); } void WaylandWindow::AddEnteredOutputId(struct wl_output* output) { @@ -468,6 +473,10 @@ WaylandWindow* WaylandWindow::GetTopLevelWindow() { return parent_window_ ? parent_window_->GetTopLevelWindow() : this; } +WaylandWindow* WaylandWindow::GetTopMostChildWindow() { + return child_window_ ? child_window_->GetTopMostChildWindow() : this; +} + void WaylandWindow::MaybeUpdateOpaqueRegion() { if (!IsOpaqueWindow()) return; @@ -490,10 +499,10 @@ uint32_t WaylandWindow::DispatchEventToDelegate( if (event->IsLocatedEvent()) UpdateCursorPositionFromEvent(Event::Clone(*event)); - DispatchEventFromNativeUiEvent( + bool handled = DispatchEventFromNativeUiEvent( native_event, base::BindOnce(&PlatformWindowDelegate::DispatchEvent, base::Unretained(delegate_))); - return POST_DISPATCH_STOP_PROPAGATION; + return handled ? POST_DISPATCH_STOP_PROPAGATION : POST_DISPATCH_NONE; } // static @@ -502,7 +511,7 @@ void WaylandWindow::Enter(void* data, struct wl_output* output) { auto* window = static_cast<WaylandWindow*>(data); if (window) { - DCHECK(window->surface_.get() == wl_surface); + DCHECK(window->surface() == wl_surface); window->AddEnteredOutputId(output); } } @@ -513,7 +522,7 @@ void WaylandWindow::Leave(void* data, struct wl_output* output) { auto* window = static_cast<WaylandWindow*>(data); if (window) { - DCHECK(window->surface_.get() == wl_surface); + DCHECK(window->surface() == wl_surface); window->RemoveEnteredOutputId(output); } } diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window.h b/chromium/ui/ozone/platform/wayland/host/wayland_window.h index 43cfa54d4cc..9c42eb59c80 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window.h @@ -17,6 +17,7 @@ #include "ui/gfx/geometry/rect.h" #include "ui/gfx/native_widget_types.h" #include "ui/ozone/platform/wayland/common/wayland_object.h" +#include "ui/ozone/platform/wayland/host/wayland_surface.h" #include "ui/platform_window/platform_window.h" #include "ui/platform_window/platform_window_delegate.h" #include "ui/platform_window/platform_window_init_properties.h" @@ -36,7 +37,7 @@ class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher { ~WaylandWindow() override; // A factory method that can create any of the derived types of WaylandWindow - // (WaylandSurface, WaylandPopup and WaylandSubsurface). + // (WaylandToplevelWindow, WaylandPopup and WaylandSubsurface). static std::unique_ptr<WaylandWindow> Create( PlatformWindowDelegate* delegate, WaylandConnection* connection, @@ -53,7 +54,8 @@ class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher { // to do so (this is not needed upon window initialization). void UpdateBufferScale(bool update_bounds); - wl_surface* surface() const { return surface_.get(); } + WaylandSurface* wayland_surface() { return &wayland_surface_; } + wl_surface* surface() const { return wayland_surface_.surface(); } void set_parent_window(WaylandWindow* parent_window) { parent_window_ = parent_window; @@ -80,7 +82,7 @@ class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher { void set_child_window(WaylandWindow* window) { child_window_ = window; } WaylandWindow* child_window() const { return child_window_; } - int32_t buffer_scale() const { return buffer_scale_; } + int32_t buffer_scale() const { return wayland_surface_.buffer_scale(); } int32_t ui_scale() const { return ui_scale_; } const base::flat_set<uint32_t>& entered_outputs_ids() const { @@ -144,12 +146,17 @@ class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher { std::unique_ptr<OSExchangeData> data, int operation); virtual int OnDragMotion(const gfx::PointF& point, - uint32_t time, int operation); virtual void OnDragDrop(std::unique_ptr<OSExchangeData> data); virtual void OnDragLeave(); virtual void OnDragSessionClose(uint32_t dnd_action); + // Returns a root parent window within the same hierarchy. + WaylandWindow* GetRootParentWindow(); + + // Returns a top most child window within the same hierarchy. + WaylandWindow* GetTopMostChildWindow(); + protected: WaylandWindow(PlatformWindowDelegate* delegate, WaylandConnection* connection); @@ -175,9 +182,6 @@ class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher { // Initializes the WaylandWindow with supplied properties. bool Initialize(PlatformWindowInitProperties properties); - // Returns a root parent window. - WaylandWindow* GetRootParentWindow(); - // Install a surface listener and start getting wl_output enter/leave events. void AddSurfaceListener(); @@ -212,7 +216,7 @@ class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher { WaylandWindow* parent_window_ = nullptr; WaylandWindow* child_window_ = nullptr; - wl::Object<wl_surface> surface_; + WaylandSurface wayland_surface_; // The current cursor bitmap (immutable). scoped_refptr<BitmapCursorOzone> bitmap_; @@ -225,9 +229,6 @@ class WaylandWindow : public PlatformWindow, public PlatformEventDispatcher { bool has_pointer_focus_ = false; bool has_keyboard_focus_ = false; bool has_touch_focus_ = false; - // Wayland's scale factor for the output that this window currently belongs - // to. - int32_t buffer_scale_ = 1; // The UI scale may be forced through the command line, which means that it // replaces the default value that is equal to the natural device scale. // We need it to place and size the menus properly. diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc new file mode 100644 index 00000000000..08d5cb633ca --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc @@ -0,0 +1,325 @@ +// Copyright 2020 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_window_drag_controller.h" + +#include <cstdint> +#include <memory> +#include <ostream> +#include <utility> + +#include "base/callback.h" +#include "base/check.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop_current.h" +#include "base/notreached.h" +#include "base/run_loop.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/events/platform/scoped_event_dispatcher.h" +#include "ui/events/platform_event.h" +#include "ui/events/types/event_type.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/ozone/platform/wayland/host/wayland_connection.h" +#include "ui/ozone/platform/wayland/host/wayland_cursor_position.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h" +#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_pointer.h" +#include "ui/ozone/platform/wayland/host/wayland_surface.h" +#include "ui/ozone/platform/wayland/host/wayland_window.h" +#include "ui/ozone/platform/wayland/host/wayland_window_manager.h" + +namespace ui { + +namespace { + +// Custom mime type used for window dragging DND sessions. +constexpr char kMimeTypeChromiumWindow[] = "chromium/x-window"; + +// DND action used in window dragging DND sessions. +constexpr uint32_t kDndActionWindowDrag = + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + +} // namespace + +WaylandWindowDragController::WaylandWindowDragController( + WaylandConnection* connection, + WaylandDataDeviceManager* device_manager, + WaylandPointer::Delegate* pointer_delegate) + : connection_(connection), + data_device_manager_(device_manager), + data_device_(device_manager->GetDevice()), + window_manager_(connection_->wayland_window_manager()), + pointer_delegate_(pointer_delegate) { + DCHECK(data_device_); + DCHECK(pointer_delegate_); +} + +WaylandWindowDragController::~WaylandWindowDragController() = default; + +bool WaylandWindowDragController::Drag(WaylandToplevelWindow* window, + const gfx::Vector2d& offset) { + DCHECK_LE(state_, State::kAttached); + DCHECK(window); + + if (!OfferWindow()) + return false; + + DCHECK_EQ(state_, State::kAttached); + dragged_window_ = window; + drag_offset_ = offset; + RunLoop(); + + DCHECK(state_ == State::kAttached || state_ == State::kDropped); + bool dropped = state_ == State::kDropped; + if (dropped) + HandleDropAndResetState(); + return dropped; +} + +void WaylandWindowDragController::StopDragging() { + if (state_ != State::kDetached) + return; + + VLOG(1) << "End drag loop requested. state=" << state_; + + // This function is supposed to be called to indicate that the window was just + // snapped into a tab strip. So switch to |kAttached| state and ask to quit + // the nested loop. + state_ = State::kAttached; + QuitLoop(); +} + +bool WaylandWindowDragController::IsDragSource() const { + DCHECK(data_source_); + return true; +} + +// Icon drawing and update for window/tab dragging is handled by buffer manager. +void WaylandWindowDragController::DrawIcon() {} + +void WaylandWindowDragController::OnDragOffer( + std::unique_ptr<WaylandDataOffer> offer) { + DCHECK_GE(state_, State::kAttached); + DCHECK(offer); + DCHECK(!data_offer_); + data_offer_ = std::move(offer); +} + +void WaylandWindowDragController::OnDragEnter(WaylandWindow* window, + const gfx::PointF& location, + uint32_t serial) { + DCHECK_GE(state_, State::kAttached); + DCHECK(window); + + // Forward focus change event to the input delegate, so other components, such + // as WaylandScreen, are able to properly retrieve focus related info during + // window dragging sesstions. + pointer_delegate_->OnPointerFocusChanged(window, location); + + VLOG(1) << "OnEnter. widget=" << window->GetWidget(); + + // Ensure this is a valid "window drag" offer. + DCHECK(data_offer_); + DCHECK_EQ(data_offer_->mime_types().size(), 1u); + DCHECK_EQ(data_offer_->mime_types().front(), kMimeTypeChromiumWindow); + + // Accept the offer and set the dnd action. + data_offer_->SetAction(kDndActionWindowDrag, kDndActionWindowDrag); + data_offer_->Accept(serial, kMimeTypeChromiumWindow); +} + +void WaylandWindowDragController::OnDragMotion(const gfx::PointF& location) { + DCHECK_GE(state_, State::kAttached); + VLOG(2) << "OnMotion. location=" << location.ToString(); + + // Forward cursor location update info to the input handling delegate. + pointer_delegate_->OnPointerMotionEvent(location); +} + +void WaylandWindowDragController::OnDragLeave() { + DCHECK_GE(state_, State::kAttached); + DCHECK_LE(state_, State::kDetached); + + // In order to guarantee ET_MOUSE_RELEASED event is delivered once the DND + // session finishes, the focused window is not reset here. This is similar to + // the "implicit grab" behavior implemented by Wayland compositors for + // wl_pointer events. Additionally, this makes it possible for the drag + // controller to overcome deviations in the order that wl_data_source and + // wl_pointer events arrive when the drop happens. For example, unlike Weston + // and Sway, Gnome Shell <= 2.26 sends them in the following order: + // + // wl_data_device.leave > wl_pointer.enter > wl_data_source.cancel/finish + // + // which would require hacky workarounds in HandleDropAndResetState function + // to properly detect and handle such cases. + + VLOG(1) << "OnLeave"; + + data_offer_.reset(); +} + +void WaylandWindowDragController::OnDragDrop() { + // Not used for window dragging sessions. Handling of drop events is fully + // done at OnDataSourceFinish function, i.e: wl_data_source::{cancel,finish}. +} + +void WaylandWindowDragController::OnDataSourceFinish(bool completed) { + DCHECK_GE(state_, State::kAttached); + DCHECK(data_source_); + + VLOG(1) << "Drop received. state=" << state_; + + // Release DND objects. + data_offer_.reset(); + data_source_.reset(); + icon_surface_.reset(); + dragged_window_ = nullptr; + + // Transition to |kDropped| state and determine the next action to take. If + // drop happened while the move loop was running (i.e: kDetached), ask to quit + // the loop, otherwise notify session end and reset state right away. + State state_when_dropped = std::exchange(state_, State::kDropped); + if (state_when_dropped == State::kDetached) + QuitLoop(); + else + HandleDropAndResetState(); + + data_device_->ResetDragDelegate(); +} + +void WaylandWindowDragController::OnDataSourceSend(const std::string& mime_type, + std::string* contents) { + // There is no actual data exchange in DnD window dragging sessions. Window + // snapping, for example, is supposed to be handled at higher level UI layers. +} + +bool WaylandWindowDragController::CanDispatchEvent(const PlatformEvent& event) { + return state_ == State::kDetached; +} + +uint32_t WaylandWindowDragController::DispatchEvent( + const PlatformEvent& event) { + DCHECK_EQ(state_, State::kDetached); + DCHECK(base::MessageLoopCurrentForUI::IsSet()); + + VLOG(2) << "Dispatch. event=" << event->GetName(); + + if (event->type() == ET_MOUSE_MOVED || event->type() == ET_MOUSE_DRAGGED) { + HandleMotionEvent(event->AsMouseEvent()); + return POST_DISPATCH_STOP_PROPAGATION; + } + return POST_DISPATCH_PERFORM_DEFAULT; +} + +bool WaylandWindowDragController::OfferWindow() { + DCHECK_LE(state_, State::kAttached); + + auto* window = window_manager_->GetCurrentFocusedWindow(); + if (!window) { + LOG(ERROR) << "Failed to get focused window."; + return false; + } + + if (!data_source_) + data_source_ = data_device_manager_->CreateSource(this); + + if (state_ == State::kIdle) { + DCHECK(!icon_surface_); + icon_surface_.reset( + wl_compositor_create_surface(connection_->compositor())); + + VLOG(1) << "Starting DND session."; + state_ = State::kAttached; + data_source_->Offer({kMimeTypeChromiumWindow}); + data_source_->SetAction(DragDropTypes::DRAG_MOVE); + data_device_->StartDrag(*data_source_, *window, icon_surface_.get(), this); + } + return true; +} + +void WaylandWindowDragController::HandleMotionEvent(MouseEvent* event) { + DCHECK_EQ(state_, State::kDetached); + DCHECK(dragged_window_); + DCHECK(event); + + // Update current cursor position, so it can be retrieved later on through + // |Screen::GetCursorScreenPoint| API. + int32_t scale = dragged_window_->buffer_scale(); + gfx::PointF scaled_location = + gfx::ScalePoint(event->location_f(), scale, scale); + connection_->wayland_cursor_position()->OnCursorPositionChanged( + gfx::ToFlooredPoint(scaled_location)); + + // Notify listeners about window bounds change (i.e: re-positioning) event. + // To do so, set the new bounds as per the motion event location and the drag + // offset. Note that setting a new location (i.e: bounds.origin()) for a + // surface has no visual effect in ozone/wayland backend. Actual window + // re-positioning during dragging session is done through the drag icon. + gfx::Point new_location = event->location() - drag_offset_; + gfx::Size size = dragged_window_->GetBounds().size(); + dragged_window_->SetBounds({new_location, size}); +} + +// Dispatch mouse release event (to tell clients that the drop just happened) +// clear focus and reset internal state. Must be called when the session is +// about to finish. +void WaylandWindowDragController::HandleDropAndResetState() { + DCHECK_EQ(state_, State::kDropped); + DCHECK(window_manager_->GetCurrentFocusedWindow()); + VLOG(1) << "Notifying drop. window=" + << window_manager_->GetCurrentFocusedWindow(); + + EventFlags pointer_button = EF_LEFT_MOUSE_BUTTON; + DCHECK(connection_->event_source()->IsPointerButtonPressed(pointer_button)); + pointer_delegate_->OnPointerButtonEvent(ET_MOUSE_RELEASED, pointer_button); + + state_ = State::kIdle; +} + +void WaylandWindowDragController::RunLoop() { + DCHECK_EQ(state_, State::kAttached); + DCHECK(dragged_window_); + + VLOG(1) << "Starting drag loop. widget=" << dragged_window_->GetWidget(); + + // TODO(crbug.com/896640): Handle cursor + auto old_dispatcher = std::move(nested_dispatcher_); + nested_dispatcher_ = + PlatformEventSource::GetInstance()->OverrideDispatcher(this); + + base::WeakPtr<WaylandWindowDragController> alive(weak_factory_.GetWeakPtr()); + + state_ = State::kDetached; + base::RunLoop loop(base::RunLoop::Type::kNestableTasksAllowed); + quit_loop_closure_ = loop.QuitClosure(); + loop.Run(); + + if (!alive) + return; + + nested_dispatcher_ = std::move(old_dispatcher); + + VLOG(1) << "Quitting drag loop " << state_; +} + +void WaylandWindowDragController::QuitLoop() { + DCHECK(!quit_loop_closure_.is_null()); + + nested_dispatcher_.reset(); + std::move(quit_loop_closure_).Run(); +} + +std::ostream& operator<<(std::ostream& out, + WaylandWindowDragController::State state) { + return out << static_cast<int>(state); +} + +} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h b/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h new file mode 100644 index 00000000000..071cc9672bc --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller.h @@ -0,0 +1,126 @@ +// Copyright 2020 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_WINDOW_DRAG_CONTROLLER_H_ +#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_WINDOW_DRAG_CONTROLLER_H_ + +#include <cstdint> +#include <iosfwd> +#include <memory> +#include <string> + +#include "base/callback_forward.h" +#include "base/memory/weak_ptr.h" +#include "ui/events/event.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/scoped_event_dispatcher.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/ozone/platform/wayland/gpu/wayland_surface_factory.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device.h" +#include "ui/ozone/platform/wayland/host/wayland_data_source.h" +#include "ui/ozone/platform/wayland/host/wayland_pointer.h" +#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h" + +namespace ui { + +class WaylandConnection; +class WaylandDataDeviceManager; +class WaylandDataOffer; +class WaylandWindow; +class WaylandWindowManager; + +// Drag controller implementation that drives window moving sessions (aka: tab +// dragging). Wayland Drag and Drop protocol is used, under the hood, to keep +// track of cursor location and surface focus. +// +// TODO(crbug.com/896640): Use drag icon to emulate window moving. +class WaylandWindowDragController : public WaylandDataDevice::DragDelegate, + public WaylandDataSource::Delegate, + public PlatformEventDispatcher { + public: + // Constants used to keep track of the drag controller state. + enum class State { + kIdle, // No DnD session nor drag loop running. + kAttached, // DnD session ongoing but no drag loop running. + kDetached, // Drag loop running. ie: blocked in a Drag() call. + kDropped // Drop event was just received. + }; + + WaylandWindowDragController(WaylandConnection* connection, + WaylandDataDeviceManager* device_manager, + WaylandPointer::Delegate* pointer_delegate); + WaylandWindowDragController(const WaylandWindowDragController&) = delete; + WaylandWindowDragController& operator=(const WaylandWindowDragController&) = + delete; + ~WaylandWindowDragController() override; + + bool Drag(WaylandToplevelWindow* surface, const gfx::Vector2d& offset); + void StopDragging(); + + State state() const { return state_; } + + private: + // WaylandDataDevice::DragDelegate: + bool IsDragSource() const override; + void DrawIcon() override; + void OnDragOffer(std::unique_ptr<WaylandDataOffer> offer) override; + void OnDragEnter(WaylandWindow* window, + const gfx::PointF& location, + uint32_t serial) override; + void OnDragMotion(const gfx::PointF& location) override; + void OnDragLeave() override; + void OnDragDrop() override; + + // WaylandDataSource::Delegate + void OnDataSourceFinish(bool completed) override; + void OnDataSourceSend(const std::string& mime_type, + std::string* contents) override; + + // PlatformEventDispatcher + bool CanDispatchEvent(const PlatformEvent& event) override; + uint32_t DispatchEvent(const PlatformEvent& event) override; + + // Offers the focused window as available to be dragged. A new data source is + // setup and the underlying DnD session is started, if not done yet. + bool OfferWindow(); + // Handles drag/move mouse |event|, while in |kDetached| mode, forwarding it + // as a bounds change event to the upper layer handlers. + void HandleMotionEvent(MouseEvent* event); + // Handles the mouse button release (i.e: drop). Dispatches the required + // events and resets the internal state. + void HandleDropAndResetState(); + // Registers as the top level PlatformEvent dispatcher and runs a nested + // RunLoop, which blocks until the DnD session finishes. + void RunLoop(); + // Unregisters the internal event dispatcher and asks to quit the nested loop. + void QuitLoop(); + + WaylandConnection* const connection_; + WaylandDataDeviceManager* const data_device_manager_; + WaylandDataDevice* const data_device_; + WaylandWindowManager* const window_manager_; + WaylandPointer::Delegate* const pointer_delegate_; + + State state_ = State::kIdle; + WaylandToplevelWindow* dragged_window_ = nullptr; + gfx::Vector2d drag_offset_; + + std::unique_ptr<WaylandDataSource> data_source_; + std::unique_ptr<WaylandDataOffer> data_offer_; + wl::Object<wl_surface> icon_surface_; + + std::unique_ptr<ScopedEventDispatcher> nested_dispatcher_; + base::OnceClosure quit_loop_closure_; + + base::WeakPtrFactory<WaylandWindowDragController> weak_factory_{this}; +}; + +// Stream operator so WaylandWindowDragController::State can be used in +// log/assertion statements. +std::ostream& operator<<(std::ostream& out, + WaylandWindowDragController::State state); + +} // namespace ui + +#endif // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_WINDOW_DRAG_CONTROLLER_H_ diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc new file mode 100644 index 00000000000..56a13b46e48 --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc @@ -0,0 +1,519 @@ +// Copyright 2020 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 <linux/input-event-codes.h> +#include <wayland-server-protocol.h> +#include <wayland-server.h> +#include <wayland-util.h> + +#include <cstdint> + +#include "base/notreached.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/base_event_utils.h" +#include "ui/events/event.h" +#include "ui/events/types/event_type.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/ozone/platform/wayland/host/wayland_data_device.h" +#include "ui/ozone/platform/wayland/host/wayland_screen.h" +#include "ui/ozone/platform/wayland/host/wayland_window.h" +#include "ui/ozone/platform/wayland/host/wayland_window_drag_controller.h" +#include "ui/ozone/platform/wayland/host/wayland_window_manager.h" +#include "ui/ozone/platform/wayland/test/constants.h" +#include "ui/ozone/platform/wayland/test/mock_pointer.h" +#include "ui/ozone/platform/wayland/test/mock_surface.h" +#include "ui/ozone/platform/wayland/test/test_data_device.h" +#include "ui/ozone/platform/wayland/test/test_data_device_manager.h" +#include "ui/ozone/platform/wayland/test/test_data_offer.h" +#include "ui/ozone/platform/wayland/test/test_data_source.h" +#include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" +#include "ui/ozone/platform/wayland/test/wayland_test.h" +#include "ui/ozone/test/mock_platform_window_delegate.h" +#include "ui/platform_window/platform_window_delegate.h" +#include "ui/platform_window/platform_window_handler/wm_move_loop_handler.h" + +using testing::_; +using testing::Mock; + +namespace ui { + +class WaylandWindowDragControllerTest : public WaylandTest, + public wl::TestDataDevice::Delegate { + public: + WaylandWindowDragControllerTest() = default; + ~WaylandWindowDragControllerTest() override = default; + + void SetUp() override { + WaylandTest::SetUp(); + screen_ = std::make_unique<WaylandScreen>(connection_.get()); + + wl_seat_send_capabilities(server_.seat()->resource(), + WL_SEAT_CAPABILITY_POINTER); + Sync(); + pointer_ = server_.seat()->pointer(); + ASSERT_TRUE(pointer_); + + EXPECT_FALSE(window_->has_pointer_focus()); + EXPECT_EQ(State::kIdle, drag_controller()->state()); + + data_device_manager_ = server_.data_device_manager(); + DCHECK(data_device_manager_); + + source_ = nullptr; + data_device_manager_->data_device()->set_delegate(this); + } + + void TearDown() override { + data_device_manager_->data_device()->set_delegate(nullptr); + } + + WaylandWindowDragController* drag_controller() const { + return connection_->window_drag_controller(); + } + + WaylandWindowManager* window_manager() const { + return connection_->wayland_window_manager(); + } + + uint32_t NextSerial() const { + static uint32_t serial = 0; + return ++serial; + } + + uint32_t NextTime() const { + static uint32_t timestamp = 0; + return ++timestamp; + } + + protected: + using State = WaylandWindowDragController::State; + + // wl::TestDataDevice::Delegate: + void StartDrag(wl::TestDataSource* source, + wl::MockSurface* origin, + uint32_t serial) override { + EXPECT_FALSE(source_); + source_ = source; + OfferAndEnter(origin); + } + + // Helper functions + void SendDndMotion(const gfx::Point& location) { + EXPECT_TRUE(source_); + wl_fixed_t x = wl_fixed_from_int(location.x()); + wl_fixed_t y = wl_fixed_from_int(location.y()); + data_device_manager_->data_device()->OnMotion(NextTime(), x, y); + } + + void SendDndEnter(WaylandWindow* window) { + EXPECT_TRUE(window); + OfferAndEnter(server_.GetObject<wl::MockSurface>(window->GetWidget())); + } + + void SendDndLeave() { + EXPECT_TRUE(source_); + data_device_manager_->data_device()->OnLeave(); + } + + void SendDndDrop() { + EXPECT_TRUE(source_); + source_->OnCancelled(); + } + + void SendPointerEnter(WaylandWindow* window, + MockPlatformWindowDelegate* delegate) { + auto* surface = server_.GetObject<wl::MockSurface>(window->GetWidget()); + wl_pointer_send_enter(pointer_->resource(), NextSerial(), + surface->resource(), 0, 0); + EXPECT_CALL(*delegate, DispatchEvent(_)).Times(1); + Sync(); + + EXPECT_EQ(window, window_manager()->GetCurrentFocusedWindow()); + } + + void SendPointerPress(WaylandWindow* window, + MockPlatformWindowDelegate* delegate, + int button) { + wl_pointer_send_button(pointer_->resource(), NextSerial(), NextTime(), + button, WL_POINTER_BUTTON_STATE_PRESSED); + EXPECT_CALL(*delegate, DispatchEvent(_)).Times(1); + Sync(); + + EXPECT_EQ(window, window_manager()->GetCurrentFocusedWindow()); + } + + void SendPointerMotion(WaylandWindow* window, + MockPlatformWindowDelegate* delegate, + gfx::Point location) { + wl_fixed_t x = wl_fixed_from_int(location.x()); + wl_fixed_t y = wl_fixed_from_int(location.y()); + wl_pointer_send_motion(pointer_->resource(), NextTime(), x, y); + EXPECT_CALL(*delegate, DispatchEvent(_)).WillOnce([](Event* event) { + EXPECT_TRUE(event->IsMouseEvent()); + EXPECT_EQ(ET_MOUSE_DRAGGED, event->type()); + }); + Sync(); + + EXPECT_EQ(window->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint(location, {})); + } + + void OfferAndEnter(wl::MockSurface* surface) { + EXPECT_TRUE(source_); + auto* data_device = data_device_manager_->data_device(); + auto* offer = data_device->OnDataOffer(); + EXPECT_EQ(1u, source_->mime_types().size()); + for (const auto& mime_type : source_->mime_types()) + offer->OnOffer(mime_type, {}); + + wl_data_device_send_enter(data_device->resource(), NextSerial(), + surface->resource(), 0, 0, offer->resource()); + } + + // client objects + std::unique_ptr<WaylandScreen> screen_; + + // server objects + wl::TestDataDeviceManager* data_device_manager_; + wl::TestDataSource* source_; + wl::MockPointer* pointer_; +}; + +// Check the following flow works as expected: +// 1. With a single 1 window open, +// 2. Move pointer into it, press left button, move cursor a bit (drag), +// 3. Run move loop, drag it within the window bounds and drop. +TEST_P(WaylandWindowDragControllerTest, DragInsideWindowAndDrop) { + // Ensure there is no window currently focused + EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow()); + EXPECT_EQ(gfx::kNullAcceleratedWidget, + screen_->GetLocalProcessWidgetAtPoint({10, 10}, {})); + + SendPointerEnter(window_.get(), &delegate_); + SendPointerPress(window_.get(), &delegate_, BTN_LEFT); + SendPointerMotion(window_.get(), &delegate_, {10, 10}); + + // Set up an "interaction flow" and RunMoveLoop: + // - Event dispatching and bounds changes are monitored + // - At each event, emulates a new event at server side and proceeds to the + // next test step. + auto* move_loop_handler = GetWmMoveLoopHandler(*window_); + DCHECK(move_loop_handler); + + enum { kStarted, kDragging, kDropping, kDone } test_step = kStarted; + + EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) { + EXPECT_TRUE(event->IsMouseEvent()); + switch (test_step) { + case kStarted: + EXPECT_EQ(ET_MOUSE_ENTERED, event->type()); + EXPECT_EQ(State::kDetached, drag_controller()->state()); + // Ensure PlatformScreen keeps consistent. + EXPECT_EQ(window_->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({10, 10}, {})); + // Drag it a bit more. + SendDndMotion({20, 20}); + test_step = kDragging; + break; + case kDropping: + EXPECT_EQ(ET_MOUSE_RELEASED, event->type()); + EXPECT_EQ(State::kDropped, drag_controller()->state()); + // Ensure PlatformScreen keeps consistent. + EXPECT_EQ(gfx::Point(20, 20), screen_->GetCursorScreenPoint()); + EXPECT_EQ(window_->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({20, 20}, {})); + test_step = kDone; + break; + case kDone: + EXPECT_EQ(ET_MOUSE_EXITED, event->type()); + EXPECT_EQ(window_->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({20, 20}, {})); + break; + case kDragging: + default: + FAIL() << " event=" << event->GetName() + << " state=" << drag_controller()->state() + << " step=" << static_cast<int>(test_step); + return; + } + }); + + EXPECT_CALL(delegate_, OnBoundsChanged(_)) + .WillOnce([&](const gfx::Rect& bounds) { + EXPECT_EQ(State::kDetached, drag_controller()->state()); + EXPECT_EQ(kDragging, test_step); + EXPECT_EQ(gfx::Point(20, 20), bounds.origin()); + + SendDndDrop(); + test_step = kDropping; + }); + + // RunMoveLoop() blocks until the dragging session ends, so resume test + // server's run loop until it returns. + server_.Resume(); + move_loop_handler->RunMoveLoop({}); + server_.Pause(); + + SendPointerEnter(window_.get(), &delegate_); + Sync(); + + EXPECT_EQ(State::kIdle, drag_controller()->state()); + EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow()); + EXPECT_EQ(window_->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({20, 20}, {})); +} + +// Check the following flow works as expected: +// 1. With only 1 window open; +// 2. Move pointer into it, press left button, move cursor a bit (drag); +// 3. Run move loop, +// 4. Drag pointer to outside the window and release the mouse button, and make +// sure RELEASE and EXIT mouse events are delivered even when the drop +// happens outside the bounds of any surface. +TEST_P(WaylandWindowDragControllerTest, DragExitWindowAndDrop) { + // Ensure there is no window currently focused + EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow()); + EXPECT_EQ(gfx::kNullAcceleratedWidget, + screen_->GetLocalProcessWidgetAtPoint({10, 10}, {})); + + SendPointerEnter(window_.get(), &delegate_); + SendPointerPress(window_.get(), &delegate_, BTN_LEFT); + SendPointerMotion(window_.get(), &delegate_, {10, 10}); + + // Sets up an "interaction flow" and RunMoveLoop: + // - Event dispatching and bounds changes are monitored + // - At each event, emulates a new event on server side and proceeds to the + // next test step. + auto* move_loop_handler = GetWmMoveLoopHandler(*window_); + DCHECK(move_loop_handler); + + enum { + kStarted, + kDragging, + kExitedWindow, + kDropping, + kDone + } test_step = kStarted; + + EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) { + EXPECT_TRUE(event->IsMouseEvent()); + switch (test_step) { + case kStarted: + EXPECT_EQ(ET_MOUSE_ENTERED, event->type()); + EXPECT_EQ(State::kDetached, drag_controller()->state()); + // Ensure PlatformScreen keeps consistent. + EXPECT_EQ(window_->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({10, 10}, {})); + // Drag window a bit more. + SendDndMotion({20, 20}); + test_step = kDragging; + break; + case kExitedWindow: + EXPECT_EQ(ET_MOUSE_EXITED, event->type()); + // Release mouse button with no window foucsed. + SendDndDrop(); + test_step = kDropping; + break; + case kDropping: + EXPECT_EQ(ET_MOUSE_RELEASED, event->type()); + EXPECT_EQ(State::kDropped, drag_controller()->state()); + // Ensure PlatformScreen keeps consistent. + EXPECT_EQ(gfx::Point(20, 20), screen_->GetCursorScreenPoint()); + EXPECT_EQ(window_->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({20, 20}, {})); + test_step = kDone; + break; + case kDone: + EXPECT_EQ(ET_MOUSE_EXITED, event->type()); + break; + case kDragging: + default: + FAIL() << " event=" << event->GetName() + << " state=" << drag_controller()->state() + << " step=" << static_cast<int>(test_step); + return; + } + }); + + EXPECT_CALL(delegate_, OnBoundsChanged(_)) + .WillOnce([&](const gfx::Rect& bounds) { + EXPECT_EQ(State::kDetached, drag_controller()->state()); + EXPECT_EQ(kDragging, test_step); + EXPECT_EQ(gfx::Point(20, 20), bounds.origin()); + + SendDndDrop(); + test_step = kDropping; + }); + + // RunMoveLoop() blocks until the dragging sessions ends, so resume test + // server's run loop until it returns. + server_.Resume(); + move_loop_handler->RunMoveLoop({}); + server_.Pause(); + + SendPointerEnter(window_.get(), &delegate_); + Sync(); + + EXPECT_EQ(State::kIdle, drag_controller()->state()); + EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow()); + EXPECT_EQ(window_->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({20, 20}, {})); +} + +// Check the following flow works as expected: +// 1. With 2 windows open, +// 2. Focus window 1, starts dragging, +// 3. Run move loop, +// 4. Drag the pointer out of window 1 and then into window 2, +// 5. Drag it a bit more (within window 2) and then calls EndMoveLoop(), +// emulating a window snap), and then +// 6. With the window in "snapped" state, drag it further and then drop. +TEST_P(WaylandWindowDragControllerTest, DragToOtherWindowSnapDragDrop) { + // Init and open |target_window|. + PlatformWindowInitProperties properties{gfx::Rect{80, 80}}; + properties.type = PlatformWindowType::kWindow; + EXPECT_CALL(delegate_, OnAcceleratedWidgetAvailable(_)).Times(1); + auto window_2 = WaylandWindow::Create(&delegate_, connection_.get(), + std::move(properties)); + ASSERT_NE(gfx::kNullAcceleratedWidget, window_2->GetWidget()); + Sync(); + + // Ensure there is no window currently focused + EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow()); + EXPECT_EQ(gfx::kNullAcceleratedWidget, + screen_->GetLocalProcessWidgetAtPoint({10, 10}, {})); + + auto* source_window = window_.get(); + auto* target_window = window_2.get(); + EXPECT_TRUE(source_window); + EXPECT_TRUE(target_window); + + SendPointerEnter(source_window, &delegate_); + SendPointerPress(source_window, &delegate_, BTN_LEFT); + SendPointerMotion(source_window, &delegate_, {10, 10}); + + // Sets up an "interaction flow" and RunMoveLoop: + // - Event dispatching and bounds changes are monitored + // - At each event, emulates a new event on server side and proceeds to the + // next test step. + auto* move_loop_handler = GetWmMoveLoopHandler(*window_); + DCHECK(move_loop_handler); + + enum { + kStarted, + kDragging, + kEnteredTarget, + kSnapped, + kDone + } test_step = kStarted; + + EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) { + EXPECT_TRUE(event->IsMouseEvent()); + switch (test_step) { + case kStarted: + EXPECT_EQ(ET_MOUSE_ENTERED, event->type()); + EXPECT_EQ(State::kDetached, drag_controller()->state()); + // Ensure PlatformScreen keeps consistent. + EXPECT_EQ(source_window->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({10, 10}, {})); + // Drag window a bit more. + SendDndMotion({50, 50}); + test_step = kDragging; + break; + case kEnteredTarget: + EXPECT_EQ(ET_MOUSE_ENTERED, event->type()); + EXPECT_EQ(State::kDetached, drag_controller()->state()); + // Ensure PlatformScreen keeps consistent. + EXPECT_EQ(target_window->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({10, 10}, {})); + + move_loop_handler->EndMoveLoop(); + test_step = kSnapped; + break; + default: + FAIL() << " event=" << event->GetName() + << " state=" << drag_controller()->state() + << " step=" << static_cast<int>(test_step); + return; + } + }); + + EXPECT_CALL(delegate_, OnBoundsChanged(_)) + .WillOnce([&](const gfx::Rect& bounds) { + EXPECT_EQ(State::kDetached, drag_controller()->state()); + EXPECT_EQ(kDragging, test_step); + EXPECT_EQ(gfx::Point(50, 50), bounds.origin()); + + // Exit |source_window| and enter the |target_window|. + SendDndLeave(); + SendDndEnter(target_window); + test_step = kEnteredTarget; + }); + + // RunMoveLoop() blocks until the dragging sessions ends, so resume test + // server's run loop until it returns. + server_.Resume(); + move_loop_handler->RunMoveLoop({}); + server_.Pause(); + + // Continue the dragging session after "snapping" the window. At this point, + // the DND session is expected to be still alive and responding normally to + // data object events. + EXPECT_EQ(State::kAttached, drag_controller()->state()); + EXPECT_EQ(kSnapped, test_step); + + // Drag the pointer a bit more within |target_window| and then releases the + // mouse button and ensures drag controller delivers the events properly and + // exit gracefully. + SendDndMotion({30, 30}); + SendDndMotion({30, 33}); + SendDndMotion({30, 36}); + SendDndMotion({30, 39}); + SendDndMotion({30, 42}); + EXPECT_CALL(delegate_, DispatchEvent(_)).Times(5); + Sync(); + + EXPECT_EQ(gfx::Point(30, 42), screen_->GetCursorScreenPoint()); + EXPECT_EQ(target_window->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({50, 50}, {})); + + SendDndDrop(); + EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) { + EXPECT_TRUE(event->IsMouseEvent()); + switch (test_step) { + case kSnapped: + EXPECT_EQ(ET_MOUSE_RELEASED, event->type()); + EXPECT_EQ(State::kDropped, drag_controller()->state()); + test_step = kDone; + break; + case kDone: + EXPECT_EQ(ET_MOUSE_EXITED, event->type()); + EXPECT_EQ(target_window->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({30, 42}, {})); + break; + default: + FAIL() << " event=" << event->GetName() + << " state=" << drag_controller()->state() + << " step=" << static_cast<int>(test_step); + return; + } + }); + Sync(); + + SendPointerEnter(target_window, &delegate_); + EXPECT_EQ(target_window, window_manager()->GetCurrentFocusedWindow()); + EXPECT_EQ(target_window->GetWidget(), + screen_->GetLocalProcessWidgetAtPoint({20, 20}, {})); +} + +INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, + WaylandWindowDragControllerTest, + ::testing::Values(kXdgShellStable)); + +INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test, + WaylandWindowDragControllerTest, + ::testing::Values(kXdgShellV6)); + +} // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_factory.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window_factory.cc index f74df68dbff..29d70dbdc39 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window_factory.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_factory.cc @@ -8,7 +8,7 @@ #include "ui/ozone/platform/wayland/host/wayland_connection.h" #include "ui/ozone/platform/wayland/host/wayland_popup.h" #include "ui/ozone/platform/wayland/host/wayland_subsurface.h" -#include "ui/ozone/platform/wayland/host/wayland_surface.h" +#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h" #include "ui/ozone/platform/wayland/host/wayland_window.h" namespace ui { @@ -26,7 +26,7 @@ std::unique_ptr<WaylandWindow> WaylandWindow::Create( // parent window to be set. Thus, create a normal window instead then. if (properties.parent_widget == gfx::kNullAcceleratedWidget && !connection->wayland_window_manager()->GetCurrentFocusedWindow()) { - window.reset(new WaylandSurface(delegate, connection)); + window.reset(new WaylandToplevelWindow(delegate, connection)); } else if (connection->IsDragInProgress()) { // We are in the process of drag and requested a popup. Most probably, // it is an arrow window. @@ -43,7 +43,7 @@ std::unique_ptr<WaylandWindow> WaylandWindow::Create( case PlatformWindowType::kDrag: // TODO(msisov): Figure out what kind of surface we need to create for // bubble and drag windows. - window.reset(new WaylandSurface(delegate, connection)); + window.reset(new WaylandToplevelWindow(delegate, connection)); break; default: NOTREACHED(); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.cc index bd7050813b2..51f07bbc27a 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.cc @@ -20,6 +20,11 @@ void WaylandWindowManager::RemoveObserver(WaylandWindowObserver* observer) { observers_.RemoveObserver(observer); } +void WaylandWindowManager::NotifyWindowConfigured(WaylandWindow* window) { + for (WaylandWindowObserver& observer : observers_) + observer.OnWindowConfigured(window); +} + void WaylandWindowManager::GrabLocatedEvents(WaylandWindow* window) { DCHECK_NE(located_events_grabber_, window); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.h b/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.h index 1873c907a68..1f4cad045ca 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_manager.h @@ -27,6 +27,10 @@ class WaylandWindowManager { void AddObserver(WaylandWindowObserver* observer); void RemoveObserver(WaylandWindowObserver* observer); + // Notifies observers that the Window has been ack configured and + // WaylandBufferManagerHost can start attaching buffers to the |surface_|. + void NotifyWindowConfigured(WaylandWindow* window); + // Stores the window that should grab the located events. void GrabLocatedEvents(WaylandWindow* event_grabber); diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.cc index d81f991e080..e289ea88e3d 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.cc @@ -12,4 +12,6 @@ void WaylandWindowObserver::OnWindowAdded(WaylandWindow* window) {} void WaylandWindowObserver::OnWindowRemoved(WaylandWindow* window) {} +void WaylandWindowObserver::OnWindowConfigured(WaylandWindow* window) {} + } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.h b/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.h index 0f81b8b5731..c461b03114e 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.h +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_observer.h @@ -20,6 +20,9 @@ class WaylandWindowObserver : public base::CheckedObserver { // Called when |window| has been removed. virtual void OnWindowRemoved(WaylandWindow* window); + // Called when |window| has been ack configured. + virtual void OnWindowConfigured(WaylandWindow* window); + protected: ~WaylandWindowObserver() override; }; diff --git a/chromium/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/chromium/ui/ozone/platform/wayland/host/wayland_window_unittest.cc index 9a1112a1bd8..d7c533edc09 100644 --- a/chromium/ui/ozone/platform/wayland/host/wayland_window_unittest.cc +++ b/chromium/ui/ozone/platform/wayland/host/wayland_window_unittest.cc @@ -98,25 +98,6 @@ class WaylandWindowTest : public WaylandTest { } protected: - void SendConfigureEvent(int width, - int height, - uint32_t serial, - struct wl_array* states) { - // In xdg_shell_v6+, both surfaces send serial configure event and toplevel - // surfaces send other data like states, heights and widths. - if (GetParam() == kXdgShellV6) { - zxdg_surface_v6_send_configure(xdg_surface_->resource(), serial); - ASSERT_TRUE(xdg_surface_->xdg_toplevel()); - zxdg_toplevel_v6_send_configure(xdg_surface_->xdg_toplevel()->resource(), - width, height, states); - } else { - xdg_surface_send_configure(xdg_surface_->resource(), serial); - ASSERT_TRUE(xdg_surface_->xdg_toplevel()); - xdg_toplevel_send_configure(xdg_surface_->xdg_toplevel()->resource(), - width, height, states); - } - } - void SendConfigureEventPopup(gfx::AcceleratedWidget menu_widget, const gfx::Rect bounds) { auto* popup = GetPopupByWidget(menu_widget); @@ -253,12 +234,20 @@ TEST_P(WaylandWindowTest, MaximizeAndRestore) { const auto kNormalBounds = gfx::Rect{0, 0, 500, 300}; const auto kMaximizedBounds = gfx::Rect{0, 0, 800, 600}; + uint32_t serial = 0; + // Make sure the window has normal state initially. EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds)); window_->SetBounds(kNormalBounds); EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState()); VerifyAndClearExpectations(); + // Deactivate the surface. + auto empty_state = MakeStateArray({}); + SendConfigureEvent(xdg_surface_, 0, 0, ++serial, empty_state.get()); + + Sync(); + auto active_maximized = MakeStateArray( {XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED}); EXPECT_CALL(*GetXdgToplevel(), SetMaximized()); @@ -268,7 +257,8 @@ TEST_P(WaylandWindowTest, MaximizeAndRestore) { EXPECT_CALL(delegate_, OnBoundsChanged(kMaximizedBounds)); EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0); window_->Maximize(); - SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 1, + SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), + kMaximizedBounds.height(), ++serial, active_maximized.get()); Sync(); VerifyAndClearExpectations(); @@ -278,7 +268,8 @@ TEST_P(WaylandWindowTest, MaximizeAndRestore) { kMaximizedBounds.height())); EXPECT_CALL(delegate_, OnActivationChanged(Eq(false))); EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0); - SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 2, + SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), + kMaximizedBounds.height(), ++serial, inactive_maximized.get()); Sync(); VerifyAndClearExpectations(); @@ -287,7 +278,8 @@ TEST_P(WaylandWindowTest, MaximizeAndRestore) { kMaximizedBounds.height())); EXPECT_CALL(delegate_, OnActivationChanged(Eq(true))); EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0); - SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 3, + SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), + kMaximizedBounds.height(), ++serial, active_maximized.get()); Sync(); VerifyAndClearExpectations(); @@ -301,7 +293,7 @@ TEST_P(WaylandWindowTest, MaximizeAndRestore) { window_->Restore(); // Reinitialize wl_array, which removes previous old states. auto active = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 4, active.get()); + SendConfigureEvent(xdg_surface_, 0, 0, ++serial, active.get()); Sync(); } @@ -310,7 +302,7 @@ TEST_P(WaylandWindowTest, Minimize) { // Make sure the window is initialized to normal state from the beginning. EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState()); - SendConfigureEvent(0, 0, 1, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 1, states.get()); Sync(); EXPECT_CALL(*GetXdgToplevel(), SetMinimized()); @@ -320,24 +312,24 @@ TEST_P(WaylandWindowTest, Minimize) { // Reinitialize wl_array, which removes previous old states. states = ScopedWlArray(); - SendConfigureEvent(0, 0, 2, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 2, states.get()); Sync(); // Wayland compositor doesn't notify clients about minimized state, but rather - // if a window is not activated. Thus, a WaylandSurface marks itself as being - // minimized and and sets state to minimized. Thus, the state mustn't change - // after the configuration event is sent. + // if a window is not activated. Thus, a WaylandToplevelWindow marks itself as + // being minimized and and sets state to minimized. Thus, the state mustn't + // change after the configuration event is sent. EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized); // Send one additional empty configuration event (which means the surface is // not maximized, fullscreen or activated) to ensure, WaylandWindow stays in // the same minimized state and doesn't notify its delegate. EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0); - SendConfigureEvent(0, 0, 3, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 3, states.get()); Sync(); // And one last time to ensure the behaviour. - SendConfigureEvent(0, 0, 4, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 4, states.get()); Sync(); } @@ -346,7 +338,7 @@ TEST_P(WaylandWindowTest, SetFullscreenAndRestore) { EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState()); ScopedWlArray states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 1, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 1, states.get()); Sync(); AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); @@ -358,7 +350,7 @@ TEST_P(WaylandWindowTest, SetFullscreenAndRestore) { // comment in the WaylandWindow::ToggleFullscreen. EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kFullScreen); - SendConfigureEvent(0, 0, 2, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 2, states.get()); Sync(); EXPECT_CALL(*GetXdgToplevel(), UnsetFullscreen()); @@ -367,7 +359,7 @@ TEST_P(WaylandWindowTest, SetFullscreenAndRestore) { EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal); // Reinitialize wl_array, which removes previous old states. states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 3, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 3, states.get()); Sync(); EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal); } @@ -408,7 +400,7 @@ TEST_P(WaylandWindowTest, StartWithFullscreen) { // Activate the surface. ScopedWlArray states = InitializeWlArrayWithActivatedState(); AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); - SendConfigureEvent(0, 0, 1, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 1, states.get()); Sync(); @@ -454,7 +446,7 @@ TEST_P(WaylandWindowTest, StartMaximized) { // Activate the surface. ScopedWlArray states = InitializeWlArrayWithActivatedState(); AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); - SendConfigureEvent(0, 0, 1, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 1, states.get()); Sync(); @@ -467,7 +459,7 @@ TEST_P(WaylandWindowTest, CompositorSideStateChanges) { ScopedWlArray states = InitializeWlArrayWithActivatedState(); AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); - SendConfigureEvent(2000, 2000, 1, states.get()); + SendConfigureEvent(xdg_surface_, 2000, 2000, 1, states.get()); EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kMaximized))) @@ -480,7 +472,7 @@ TEST_P(WaylandWindowTest, CompositorSideStateChanges) { // Unmaximize states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 2, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 2, states.get()); EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kNormal))) .Times(1); @@ -489,7 +481,7 @@ TEST_P(WaylandWindowTest, CompositorSideStateChanges) { // Now, set to fullscreen. AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); - SendConfigureEvent(2005, 2005, 3, states.get()); + SendConfigureEvent(xdg_surface_, 2005, 2005, 3, states.get()); EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kFullScreen))) .Times(1); @@ -499,7 +491,7 @@ TEST_P(WaylandWindowTest, CompositorSideStateChanges) { // Unfullscreen states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 4, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 4, states.get()); EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kNormal))) .Times(1); @@ -511,7 +503,7 @@ TEST_P(WaylandWindowTest, CompositorSideStateChanges) { // Now, maximize, fullscreen and restore. states = InitializeWlArrayWithActivatedState(); AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); - SendConfigureEvent(2000, 2000, 1, states.get()); + SendConfigureEvent(xdg_surface_, 2000, 2000, 1, states.get()); EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kMaximized))) @@ -521,7 +513,7 @@ TEST_P(WaylandWindowTest, CompositorSideStateChanges) { Sync(); AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); - SendConfigureEvent(2005, 2005, 1, states.get()); + SendConfigureEvent(xdg_surface_, 2005, 2005, 1, states.get()); EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kFullScreen))) @@ -530,7 +522,7 @@ TEST_P(WaylandWindowTest, CompositorSideStateChanges) { // Restore states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 4, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 4, states.get()); EXPECT_CALL(delegate_, OnWindowStateChanged(Eq(PlatformWindowState::kNormal))) .Times(1); @@ -544,12 +536,19 @@ TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) { const auto kNormalBounds = gfx::Rect{0, 0, 500, 300}; const auto kMaximizedBounds = gfx::Rect{0, 0, 800, 600}; + uint32_t serial = 0; + // Make sure the window has normal state initially. EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds)); window_->SetBounds(kNormalBounds); EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState()); VerifyAndClearExpectations(); + // Deactivate the surface. + ScopedWlArray empty_state; + SendConfigureEvent(xdg_surface_, 0, 0, ++serial, empty_state.get()); + Sync(); + auto active_maximized = MakeStateArray( {XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED}); EXPECT_CALL(*GetXdgToplevel(), SetMaximized()); @@ -561,7 +560,8 @@ TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) { window_->Maximize(); // State changes are synchronous. EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState()); - SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 2, + SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), + kMaximizedBounds.height(), ++serial, active_maximized.get()); Sync(); // Verify that the state has not been changed. @@ -578,7 +578,8 @@ TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) { EXPECT_EQ(PlatformWindowState::kFullScreen, window_->GetPlatformWindowState()); AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, active_maximized.get()); - SendConfigureEvent(kMaximizedBounds.width(), kMaximizedBounds.height(), 3, + SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), + kMaximizedBounds.height(), ++serial, active_maximized.get()); Sync(); // Verify that the state has not been changed. @@ -596,7 +597,7 @@ TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) { EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState()); // Reinitialize wl_array, which removes previous old states. auto active = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 4, active.get()); + SendConfigureEvent(xdg_surface_, 0, 0, ++serial, active.get()); Sync(); EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState()); } @@ -614,8 +615,8 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximize) { EXPECT_CALL(delegate_, OnBoundsChanged(Eq(maximized_bounds))); window_->Maximize(); AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); - SendConfigureEvent(maximized_bounds.width(), maximized_bounds.height(), 1, - states.get()); + SendConfigureEvent(xdg_surface_, maximized_bounds.width(), + maximized_bounds.height(), 1, states.get()); Sync(); restored_bounds = window_->GetRestoredBoundsInPixels(); EXPECT_EQ(bounds, restored_bounds); @@ -629,7 +630,7 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximize) { window_->Restore(); // Reinitialize wl_array, which removes previous old states. states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 2, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 2, states.get()); Sync(); bounds = window_->GetBounds(); EXPECT_EQ(bounds, restored_bounds); @@ -641,7 +642,7 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterFullscreen) { const gfx::Rect current_bounds = window_->GetBounds(); ScopedWlArray states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 1, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 1, states.get()); Sync(); gfx::Rect restored_bounds = window_->GetRestoredBoundsInPixels(); @@ -652,8 +653,8 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterFullscreen) { EXPECT_CALL(delegate_, OnBoundsChanged(Eq(fullscreen_bounds))); window_->ToggleFullscreen(); AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); - SendConfigureEvent(fullscreen_bounds.width(), fullscreen_bounds.height(), 2, - states.get()); + SendConfigureEvent(xdg_surface_, fullscreen_bounds.width(), + fullscreen_bounds.height(), 2, states.get()); Sync(); restored_bounds = window_->GetRestoredBoundsInPixels(); EXPECT_EQ(bounds, restored_bounds); @@ -667,7 +668,7 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterFullscreen) { window_->Restore(); // Reinitialize wl_array, which removes previous old states. states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 3, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 3, states.get()); Sync(); bounds = window_->GetBounds(); EXPECT_EQ(bounds, restored_bounds); @@ -688,8 +689,8 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximizeAndFullscreen) { EXPECT_CALL(delegate_, OnBoundsChanged(Eq(maximized_bounds))); window_->Maximize(); AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); - SendConfigureEvent(maximized_bounds.width(), maximized_bounds.height(), 1, - states.get()); + SendConfigureEvent(xdg_surface_, maximized_bounds.width(), + maximized_bounds.height(), 1, states.get()); Sync(); restored_bounds = window_->GetRestoredBoundsInPixels(); EXPECT_EQ(bounds, restored_bounds); @@ -698,8 +699,8 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximizeAndFullscreen) { EXPECT_CALL(delegate_, OnBoundsChanged(Eq(fullscreen_bounds))); window_->ToggleFullscreen(); AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); - SendConfigureEvent(fullscreen_bounds.width(), fullscreen_bounds.height(), 2, - states.get()); + SendConfigureEvent(xdg_surface_, fullscreen_bounds.width(), + fullscreen_bounds.height(), 2, states.get()); Sync(); gfx::Rect fullscreen_restore_bounds = window_->GetRestoredBoundsInPixels(); EXPECT_EQ(restored_bounds, fullscreen_restore_bounds); @@ -709,8 +710,8 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximizeAndFullscreen) { // Reinitialize wl_array, which removes previous old states. states = InitializeWlArrayWithActivatedState(); AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); - SendConfigureEvent(maximized_bounds.width(), maximized_bounds.height(), 3, - states.get()); + SendConfigureEvent(xdg_surface_, maximized_bounds.width(), + maximized_bounds.height(), 3, states.get()); Sync(); restored_bounds = window_->GetRestoredBoundsInPixels(); EXPECT_EQ(restored_bounds, fullscreen_restore_bounds); @@ -724,7 +725,7 @@ TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximizeAndFullscreen) { window_->Restore(); // Reinitialize wl_array, which removes previous old states. states = InitializeWlArrayWithActivatedState(); - SendConfigureEvent(0, 0, 4, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 4, states.get()); Sync(); bounds = window_->GetBounds(); EXPECT_EQ(bounds, restored_bounds); @@ -747,7 +748,7 @@ TEST_P(WaylandWindowTest, SendsBoundsOnRequest) { EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, new_bounds.width(), new_bounds.height())) .Times(2); - SendConfigureEvent(0, 0, 2, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 2, states.get()); Sync(); // Restored bounds should keep empty value. @@ -757,7 +758,7 @@ TEST_P(WaylandWindowTest, SendsBoundsOnRequest) { // Second case is when Wayland sends a configure event with 1, 1 height and // width. It looks more like a bug in Gnome Shell with Wayland as long as the // documentation says it must be set to 0, 0, when wayland requests bounds. - SendConfigureEvent(0, 0, 3, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 3, states.get()); Sync(); // Restored bounds should keep empty value. @@ -818,14 +819,14 @@ TEST_P(WaylandWindowTest, ConfigureEvent) { // xdg_toplevel in xdg_shell_v6 and by xdg_surface_ in xdg_shell_v5. EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, 1000, 1000)).Times(1); EXPECT_CALL(*xdg_surface_, AckConfigure(12)); - SendConfigureEvent(1000, 1000, 12, states.get()); + SendConfigureEvent(xdg_surface_, 1000, 1000, 12, states.get()); Sync(); EXPECT_CALL(delegate_, OnBoundsChanged(Eq(gfx::Rect(0, 0, 1500, 1000)))); EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, 1500, 1000)).Times(1); EXPECT_CALL(*xdg_surface_, AckConfigure(13)); - SendConfigureEvent(1500, 1000, 13, states.get()); + SendConfigureEvent(xdg_surface_, 1500, 1000, 13, states.get()); } TEST_P(WaylandWindowTest, ConfigureEventWithNulledSize) { @@ -834,7 +835,7 @@ TEST_P(WaylandWindowTest, ConfigureEventWithNulledSize) { // If Wayland sends configure event with 0 width and 0 size, client should // call back with desired sizes. In this case, that's the actual size of // the window. - SendConfigureEvent(0, 0, 14, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, 14, states.get()); // |xdg_surface_| must receive the following calls in both xdg_shell_v5 and // xdg_shell_v6. Other calls like SetTitle or SetMaximized are recieved by // xdg_toplevel in xdg_shell_v6 and by xdg_surface_ in xdg_shell_v5. @@ -843,16 +844,23 @@ TEST_P(WaylandWindowTest, ConfigureEventWithNulledSize) { } TEST_P(WaylandWindowTest, OnActivationChanged) { + uint32_t serial = 0; + + // Deactivate the surface. + ScopedWlArray empty_state; + SendConfigureEvent(xdg_surface_, 0, 0, ++serial, empty_state.get()); + Sync(); + { ScopedWlArray states = InitializeWlArrayWithActivatedState(); EXPECT_CALL(delegate_, OnActivationChanged(Eq(true))); - SendConfigureEvent(0, 0, 1, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, ++serial, states.get()); Sync(); } ScopedWlArray states; EXPECT_CALL(delegate_, OnActivationChanged(Eq(false))); - SendConfigureEvent(0, 0, 2, states.get()); + SendConfigureEvent(xdg_surface_, 0, 0, ++serial, states.get()); Sync(); } @@ -1275,7 +1283,8 @@ TEST_P(WaylandWindowTest, CanDispatchEvent) { TEST_P(WaylandWindowTest, DispatchWindowMove) { EXPECT_CALL(*GetXdgToplevel(), Move(_)); - ui::GetWmMoveResizeHandler(*window_)->DispatchHostWindowDragMovement(HTCAPTION, gfx::Point()); + ui::GetWmMoveResizeHandler(*window_)->DispatchHostWindowDragMovement( + HTCAPTION, gfx::Point()); } // Makes sure hit tests are converted into right edges. @@ -1482,7 +1491,7 @@ TEST_P(WaylandWindowTest, SetOpaqueRegion) { gfx::Rect new_bounds(0, 0, 500, 600); auto state_array = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED}); - SendConfigureEvent(new_bounds.width(), new_bounds.height(), 1, + SendConfigureEvent(xdg_surface_, new_bounds.width(), new_bounds.height(), 1, state_array.get()); SkIRect rect = @@ -1494,7 +1503,7 @@ TEST_P(WaylandWindowTest, SetOpaqueRegion) { VerifyAndClearExpectations(); new_bounds.set_size(gfx::Size(1000, 534)); - SendConfigureEvent(new_bounds.width(), new_bounds.height(), 2, + SendConfigureEvent(xdg_surface_, new_bounds.width(), new_bounds.height(), 2, state_array.get()); rect = SkIRect::MakeXYWH(0, 0, new_bounds.width(), new_bounds.height()); @@ -1922,8 +1931,8 @@ TEST_P(WaylandWindowTest, SetsPropertiesOnShow) { // We can't mock all those methods above as long as the xdg_toplevel is // created and destroyed on each show and hide call. However, it is the same - // WaylandSurface object that cached the values we set and must restore them - // on Show(). + // WaylandToplevelWindow object that cached the values we set and must restore + // them on Show(). EXPECT_EQ(mock_xdg_toplevel->min_size(), min_size.value()); EXPECT_EQ(mock_xdg_toplevel->max_size(), max_size.value()); EXPECT_EQ(std::string(kAppId), mock_xdg_toplevel->app_id()); @@ -2018,6 +2027,59 @@ TEST_P(WaylandWindowTest, CreatesPopupOnTouchDownSerial) { EXPECT_EQ(test_popup->grab_serial(), touch_down_serial); } +// Tests nested menu windows get the topmost window in the stack of windows +// within the same family/tree. +TEST_P(WaylandWindowTest, NestedPopupWindowsGetCorrectParent) { + VerifyAndClearExpectations(); + + gfx::Rect menu_window_bounds(gfx::Rect(10, 20, 20, 20)); + std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( + PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds, + &delegate_); + EXPECT_TRUE(menu_window); + + EXPECT_TRUE(menu_window->parent_window() == window_.get()); + + gfx::Rect menu_window_bounds2(gfx::Rect(20, 40, 30, 20)); + std::unique_ptr<WaylandWindow> menu_window2 = CreateWaylandWindowWithParams( + PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds2, + &delegate_); + EXPECT_TRUE(menu_window2); + + EXPECT_TRUE(menu_window2->parent_window() == menu_window.get()); + + gfx::Rect menu_window_bounds3(gfx::Rect(30, 40, 30, 20)); + std::unique_ptr<WaylandWindow> menu_window3 = CreateWaylandWindowWithParams( + PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds3, + &delegate_); + EXPECT_TRUE(menu_window3); + + EXPECT_TRUE(menu_window3->parent_window() == menu_window2.get()); + + gfx::Rect menu_window_bounds4(gfx::Rect(40, 40, 30, 20)); + std::unique_ptr<WaylandWindow> menu_window4 = CreateWaylandWindowWithParams( + PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds4, + &delegate_); + EXPECT_TRUE(menu_window4); + + EXPECT_TRUE(menu_window4->parent_window() == menu_window3.get()); +} + +TEST_P(WaylandWindowTest, DoesNotGrabPopupIfNoSeat) { + // Create a popup window and verify the grab serial is not set. + MockPlatformWindowDelegate delegate; + auto popup = CreateWaylandWindowWithParams( + PlatformWindowType::kMenu, window_->GetWidget(), gfx::Rect(0, 0, 50, 50), + &delegate); + ASSERT_TRUE(popup); + + Sync(); + + auto* test_popup = GetPopupByWidget(popup->GetWidget()); + ASSERT_TRUE(test_popup); + EXPECT_EQ(test_popup->grab_serial(), 0u); +} + INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, WaylandWindowTest, ::testing::Values(kXdgShellStable)); diff --git a/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc b/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc index 1e81f70b003..758ae1f222e 100644 --- a/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc +++ b/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.cc @@ -18,7 +18,7 @@ #include "ui/ozone/platform/wayland/host/wayland_event_source.h" #include "ui/ozone/platform/wayland/host/wayland_pointer.h" #include "ui/ozone/platform/wayland/host/wayland_popup.h" -#include "ui/ozone/platform/wayland/host/wayland_surface.h" +#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h" #include "ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.h" namespace ui { @@ -286,8 +286,8 @@ bool XDGPopupWrapperImpl::Initialize(WaylandConnection* connection, static_cast<XDGPopupWrapperImpl*>(wayland_popup->shell_popup()); parent_xdg_surface = popup->xdg_surface(); } else { - WaylandSurface* wayland_surface = - static_cast<WaylandSurface*>(wayland_window_->parent_window()); + WaylandToplevelWindow* wayland_surface = + static_cast<WaylandToplevelWindow*>(wayland_window_->parent_window()); parent_xdg_surface = static_cast<XDGSurfaceWrapperImpl*>(wayland_surface->shell_surface()); } @@ -324,41 +324,8 @@ bool XDGPopupWrapperImpl::InitializeStable( xdg_positioner_destroy(positioner); - // According to the spec, the grab call can only be done on a key press, mouse - // press or touch down. However, there is a problem with popup windows and - // touch events so long as Chromium creates them only on consequent touch up - // events. That results in Wayland compositors dismissing popups. To fix - // the issue, do not use grab with touch events. Please note that explicit - // grab means that a Wayland compositor dismisses a popup whenever the user - // clicks outside the created surfaces. If the explicit grab is not used, the - // popups are not dismissed in such cases. What is more, current non-ozone X11 - // implementation does the same. This means there is no functionality changes - // and we do things right. - // - // We cannot know what was the last event. Instead, we can check if the window - // has pointer or keyboard focus. If so, the popup will be explicitly grabbed. - // - // There is a bug in the gnome/mutter - if explicit grab is not used, - // unmapping of a wl_surface (aka destroying xdg_popup and surface) to hide a - // window results in weird behaviour. That is, a popup continues to be visible - // on a display and it results in a crash of the entire session. Thus, just - // continue to use grab here and avoid showing popups for touch events on - // gnome/mutter. That is better than crashing the entire system. Otherwise, - // Chromium has to change the way how it reacts on touch events - instead of - // creating a menu on touch up, it must do it on touch down events. - // https://gitlab.gnome.org/GNOME/mutter/issues/698#note_562601. - std::unique_ptr<base::Environment> env(base::Environment::Create()); - if ((base::nix::GetDesktopEnvironment(env.get()) == - base::nix::DESKTOP_ENVIRONMENT_GNOME) || - (wayland_window_->parent_window()->has_pointer_focus() || - wayland_window_->parent_window()->has_keyboard_focus())) { - // When drag process starts, as described the protocol - - // https://goo.gl/1Mskq3, the client must have an active implicit grab. If - // we try to create a popup and grab it, it will be immediately dismissed. - // Thus, do not take explicit grab during drag process. - if (!connection->IsDragInProgress()) - xdg_popup_grab(xdg_popup_.get(), connection->seat(), - connection->serial()); + if (CanGrabPopup(connection)) { + xdg_popup_grab(xdg_popup_.get(), connection->seat(), connection->serial()); } xdg_popup_add_listener(xdg_popup_.get(), &xdg_popup_listener, this); @@ -375,18 +342,11 @@ struct xdg_positioner* XDGPopupWrapperImpl::CreatePositionerStable( if (!positioner) return nullptr; - bool is_right_click_menu = - connection->event_source()->IsPointerButtonPressed(EF_RIGHT_MOUSE_BUTTON); + auto menu_type = GetMenuTypeForPositioner(connection, parent_window); - // Different types of menu require different anchors, constraint adjustments, - // gravity and etc. - MenuType menu_type = MenuType::TYPE_UNKNOWN; - if (is_right_click_menu) - menu_type = MenuType::TYPE_RIGHT_CLICK; - else if (wl::IsMenuType(parent_window->type())) - menu_type = MenuType::TYPE_3DOT_CHILD_MENU; - else - menu_type = MenuType::TYPE_3DOT_PARENT_MENU; + // The parent we got must be the topmost in the stack of the same family + // windows. + DCHECK_EQ(parent_window->GetTopMostChildWindow(), parent_window); // Place anchor to the end of the possible position. gfx::Rect anchor_rect = GetAnchorRect( @@ -426,34 +386,7 @@ bool XDGPopupWrapperImpl::InitializeV6( zxdg_positioner_v6_destroy(positioner); - // According to the spec, the grab call can only be done on a key press, mouse - // press or touch down. However, there is a problem with popup windows and - // touch events so long as Chromium creates them only on consequent touch up - // events. That results in Wayland compositors dismissing popups. To fix - // the issue, do not use grab with touch events. Please note that explicit - // grab means that a Wayland compositor dismisses a popup whenever the user - // clicks outside the created surfaces. If the explicit grab is not used, the - // popups are not dismissed in such cases. What is more, current non-ozone X11 - // implementation does the same. This means there is no functionality changes - // and we do things right. - // - // We cannot know what was the last event. Instead, we can check if the window - // has pointer or keyboard focus. If so, the popup will be explicitly grabbed. - // - // There is a bug in the gnome/mutter - if explicit grab is not used, - // unmapping of a wl_surface (aka destroying xdg_popup and surface) to hide a - // window results in weird behaviour. That is, a popup continues to be visible - // on a display and it results in a crash of the entire session. Thus, just - // continue to use grab here and avoid showing popups for touch events on - // gnome/mutter. That is better than crashing the entire system. Otherwise, - // Chromium has to change the way how it reacts on touch events - instead of - // creating a menu on touch up, it must do it on touch down events. - // https://gitlab.gnome.org/GNOME/mutter/issues/698#note_562601. - std::unique_ptr<base::Environment> env(base::Environment::Create()); - if ((base::nix::GetDesktopEnvironment(env.get()) == - base::nix::DESKTOP_ENVIRONMENT_GNOME) || - (wayland_window_->parent_window()->has_pointer_focus() || - wayland_window_->parent_window()->has_keyboard_focus())) { + if (CanGrabPopup(connection)) { zxdg_popup_v6_grab(zxdg_popup_v6_.get(), connection->seat(), connection->serial()); } @@ -473,18 +406,11 @@ zxdg_positioner_v6* XDGPopupWrapperImpl::CreatePositionerV6( if (!positioner) return nullptr; - bool is_right_click_menu = - connection->event_source()->IsPointerButtonPressed(EF_RIGHT_MOUSE_BUTTON); + auto menu_type = GetMenuTypeForPositioner(connection, parent_window); - // Different types of menu require different anchors, constraint adjustments, - // gravity and etc. - MenuType menu_type = MenuType::TYPE_UNKNOWN; - if (is_right_click_menu) - menu_type = MenuType::TYPE_RIGHT_CLICK; - else if (wl::IsMenuType(parent_window->type())) - menu_type = MenuType::TYPE_3DOT_CHILD_MENU; - else - menu_type = MenuType::TYPE_3DOT_PARENT_MENU; + // The parent we got must be the topmost in the stack of the same family + // windows. + DCHECK_EQ(parent_window->GetTopMostChildWindow(), parent_window); // Place anchor to the end of the possible position. gfx::Rect anchor_rect = GetAnchorRect( @@ -505,6 +431,61 @@ zxdg_positioner_v6* XDGPopupWrapperImpl::CreatePositionerV6( return positioner; } +MenuType XDGPopupWrapperImpl::GetMenuTypeForPositioner( + WaylandConnection* connection, + WaylandWindow* parent_window) const { + bool is_right_click_menu = + connection->event_source()->last_pointer_button_pressed() & + EF_RIGHT_MOUSE_BUTTON; + + // Different types of menu require different anchors, constraint adjustments, + // gravity and etc. + if (is_right_click_menu) + return MenuType::TYPE_RIGHT_CLICK; + else if (!wl::IsMenuType(parent_window->type())) + return MenuType::TYPE_3DOT_PARENT_MENU; + else + return MenuType::TYPE_3DOT_CHILD_MENU; +} + +bool XDGPopupWrapperImpl::CanGrabPopup(WaylandConnection* connection) const { + // When drag process starts, as described the protocol - + // https://goo.gl/1Mskq3, the client must have an active implicit grab. If + // we try to create a popup and grab it, it will be immediately dismissed. + // Thus, do not take explicit grab during drag process. + if (connection->IsDragInProgress() || !connection->seat()) + return false; + + // According to the spec, the grab call can only be done on a key press, mouse + // press or touch down. However, there is a problem with popup windows and + // touch events so long as Chromium creates them only on consequent touch up + // events. That results in Wayland compositors dismissing popups. To fix the + // issue, do not use grab with touch events. Please note that explicit grab + // means that a Wayland compositor dismisses a popup whenever the user clicks + // outside the created surfaces. If the explicit grab is not used, the popups + // are not dismissed in such cases. What is more, current non-ozone X11 + // implementation does the same. This means there is no functionality changes + // and we do things right. + // + // We cannot know what was the last event. Instead, we can check if the window + // has pointer or keyboard focus. If so, the popup will be explicitly grabbed. + // + // There is a bug in the gnome/mutter - if explicit grab is not used, + // unmapping of a wl_surface (aka destroying xdg_popup and surface) to hide a + // window results in weird behaviour. That is, a popup continues to be visible + // on a display and it results in a crash of the entire session. Thus, just + // continue to use grab here and avoid showing popups for touch events on + // gnome/mutter. That is better than crashing the entire system. Otherwise, + // Chromium has to change the way how it reacts on touch events - instead of + // creating a menu on touch up, it must do it on touch down events. + // https://gitlab.gnome.org/GNOME/mutter/issues/698#note_562601. + std::unique_ptr<base::Environment> env(base::Environment::Create()); + return (base::nix::GetDesktopEnvironment(env.get()) == + base::nix::DESKTOP_ENVIRONMENT_GNOME) || + (wayland_window_->parent_window()->has_pointer_focus() || + wayland_window_->parent_window()->has_keyboard_focus()); +} + void XDGPopupWrapperImpl::Configure(void* data, int32_t x, int32_t y, diff --git a/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h b/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h index c0479cfff7f..62f38a2dc6a 100644 --- a/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h +++ b/chromium/ui/ozone/platform/wayland/host/xdg_popup_wrapper_impl.h @@ -7,6 +7,7 @@ #include <memory> +#include "base/macros.h" #include "ui/ozone/platform/wayland/host/shell_popup_wrapper.h" namespace ui { @@ -41,6 +42,11 @@ class XDGPopupWrapperImpl : public ShellPopupWrapper { WaylandWindow* parent_window, const gfx::Rect& bounds); + MenuType GetMenuTypeForPositioner(WaylandConnection* connection, + WaylandWindow* parent_window) const; + + bool CanGrabPopup(WaylandConnection* connection) const; + // xdg_popup_listener static void Configure(void* data, int32_t x, diff --git a/chromium/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc b/chromium/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc index e7df772ae47..ae1ca159fd4 100644 --- a/chromium/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc +++ b/chromium/ui/ozone/platform/wayland/host/xdg_surface_wrapper_impl.cc @@ -119,6 +119,8 @@ void XDGSurfaceWrapperImpl::AckConfigure() { zxdg_surface_v6_ack_configure(zxdg_surface_v6_.get(), pending_configure_serial_); } + connection_->wayland_window_manager()->NotifyWindowConfigured( + wayland_window_); } void XDGSurfaceWrapperImpl::SetWindowGeometry(const gfx::Rect& bounds) { diff --git a/chromium/ui/ozone/platform/wayland/ozone_platform_wayland.cc b/chromium/ui/ozone/platform/wayland/ozone_platform_wayland.cc index 8ffb52c5213..894166db3f9 100644 --- a/chromium/ui/ozone/platform/wayland/ozone_platform_wayland.cc +++ b/chromium/ui/ozone/platform/wayland/ozone_platform_wayland.cc @@ -14,6 +14,7 @@ #include "base/message_loop/message_pump_type.h" #include "base/threading/sequenced_task_runner_handle.h" #include "ui/base/buildflags.h" +#include "ui/base/cursor/cursor_factory.h" #include "ui/base/cursor/ozone/bitmap_cursor_factory_ozone.h" #include "ui/base/ime/linux/input_method_auralinux.h" #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h" @@ -98,9 +99,7 @@ class OzonePlatformWayland : public OzonePlatform { return overlay_manager_.get(); } - CursorFactoryOzone* GetCursorFactoryOzone() override { - return cursor_factory_.get(); - } + CursorFactory* GetCursorFactory() override { return cursor_factory_.get(); } InputController* GetInputController() override { return input_controller_.get(); @@ -249,7 +248,7 @@ class OzonePlatformWayland : public OzonePlatform { std::unique_ptr<KeyboardLayoutEngine> keyboard_layout_engine_; std::unique_ptr<WaylandConnection> connection_; std::unique_ptr<WaylandSurfaceFactory> surface_factory_; - std::unique_ptr<BitmapCursorFactoryOzone> cursor_factory_; + std::unique_ptr<CursorFactory> cursor_factory_; std::unique_ptr<StubOverlayManager> overlay_manager_; std::unique_ptr<InputController> input_controller_; std::unique_ptr<GpuPlatformSupportHost> gpu_platform_support_host_; diff --git a/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.cc b/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.cc index d24c13658aa..cd87cb8aafc 100644 --- a/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.cc +++ b/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.cc @@ -36,6 +36,12 @@ MockWpPresentation::MockWpPresentation() MockWpPresentation::~MockWpPresentation() {} +wl_resource* MockWpPresentation::ReleasePresentationCallback() { + auto* presentation_callback = presentation_callback_; + presentation_callback_ = nullptr; + return presentation_callback; +} + void MockWpPresentation::SendPresentationCallback() { if (!presentation_callback_) return; @@ -50,4 +56,14 @@ void MockWpPresentation::SendPresentationCallback() { presentation_callback_ = nullptr; } +void MockWpPresentation::SendPresentationCallbackDiscarded() { + if (!presentation_callback_) + return; + + wp_presentation_feedback_send_discarded(presentation_callback_); + wl_client_flush(wl_resource_get_client(presentation_callback_)); + wl_resource_destroy(presentation_callback_); + presentation_callback_ = nullptr; +} + } // namespace wl diff --git a/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.h b/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.h index 616cd6e3fe1..64d09ab4f8e 100644 --- a/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.h +++ b/chromium/ui/ozone/platform/wayland/test/mock_wp_presentation.h @@ -7,7 +7,7 @@ #include <presentation-time-server-protocol.h> -#include "base/logging.h" +#include "base/check.h" #include "base/macros.h" #include "testing/gmock/include/gmock/gmock.h" #include "ui/ozone/platform/wayland/test/global_object.h" @@ -34,7 +34,10 @@ class MockWpPresentation : public GlobalObject { presentation_callback_ = callback_resource; } + wl_resource* ReleasePresentationCallback(); + void SendPresentationCallback(); + void SendPresentationCallbackDiscarded(); private: wl_resource* presentation_callback_ = nullptr; diff --git a/chromium/ui/ozone/platform/wayland/test/scoped_wl_array.cc b/chromium/ui/ozone/platform/wayland/test/scoped_wl_array.cc new file mode 100644 index 00000000000..9977ca1db17 --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/test/scoped_wl_array.cc @@ -0,0 +1,41 @@ +// Copyright 2020 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/test/scoped_wl_array.h" + +#include <wayland-server-core.h> + +namespace wl { + +ScopedWlArray::ScopedWlArray(const std::vector<int32_t> states) { + wl_array_init(&array_); + for (const auto& state : states) + AddStateToWlArray(state); +} + +ScopedWlArray::ScopedWlArray(ScopedWlArray&& rhs) { + array_ = rhs.array_; + // wl_array_init sets rhs.array_'s fields to nullptr, so that + // the free() in wl_array_release() is a no-op. + wl_array_init(&rhs.array_); +} + +ScopedWlArray& ScopedWlArray::operator=(ScopedWlArray&& rhs) { + wl_array_release(&array_); + array_ = rhs.array_; + // wl_array_init sets rhs.array_'s fields to nullptr, so that + // the free() in wl_array_release() is a no-op. + wl_array_init(&rhs.array_); + return *this; +} + +ScopedWlArray::~ScopedWlArray() { + wl_array_release(&array_); +} + +void ScopedWlArray::AddStateToWlArray(uint32_t state) { + *static_cast<uint32_t*>(wl_array_add(&array_, sizeof array_)) = state; +} + +} // namespace wl diff --git a/chromium/ui/ozone/platform/wayland/test/scoped_wl_array.h b/chromium/ui/ozone/platform/wayland/test/scoped_wl_array.h new file mode 100644 index 00000000000..d9c86f99265 --- /dev/null +++ b/chromium/ui/ozone/platform/wayland/test/scoped_wl_array.h @@ -0,0 +1,31 @@ +// Copyright 2020 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_TEST_SCOPED_WL_ARRAY_H_ +#define UI_OZONE_PLATFORM_WAYLAND_TEST_SCOPED_WL_ARRAY_H_ + +#include <wayland-server-core.h> + +#include <vector> + +namespace wl { + +class ScopedWlArray { + public: + explicit ScopedWlArray(const std::vector<int32_t> states); + ScopedWlArray(ScopedWlArray&& rhs); + ScopedWlArray& operator=(ScopedWlArray&& rhs); + ~ScopedWlArray(); + + wl_array* get() { return &array_; } + + void AddStateToWlArray(uint32_t state); + + private: + wl_array array_; +}; + +} // namespace wl + +#endif // UI_OZONE_PLATFORM_WAYLAND_TEST_SCOPED_WL_ARRAY_H_ diff --git a/chromium/ui/ozone/platform/wayland/test/test_data_device.cc b/chromium/ui/ozone/platform/wayland/test/test_data_device.cc index fcd71d6e9c5..6fc8a3e464d 100644 --- a/chromium/ui/ozone/platform/wayland/test/test_data_device.cc +++ b/chromium/ui/ozone/platform/wayland/test/test_data_device.cc @@ -6,8 +6,13 @@ #include <wayland-server-core.h> +#include <cstdint> + #include "base/notreached.h" +#include "ui/ozone/platform/wayland/test/mock_surface.h" +#include "ui/ozone/platform/wayland/test/server_object.h" #include "ui/ozone/platform/wayland/test/test_data_offer.h" +#include "ui/ozone/platform/wayland/test/test_data_source.h" namespace wl { @@ -19,7 +24,11 @@ void DataDeviceStartDrag(wl_client* client, wl_resource* origin, wl_resource* icon, uint32_t serial) { - NOTIMPLEMENTED(); + auto* data_source = GetUserDataAs<TestDataSource>(source); + auto* origin_surface = GetUserDataAs<MockSurface>(origin); + + GetUserDataAs<TestDataDevice>(resource)->StartDrag(data_source, + origin_surface, serial); } void DataDeviceSetSelection(wl_client* client, @@ -50,6 +59,16 @@ void TestDataDevice::SetSelection(TestDataSource* data_source, NOTIMPLEMENTED(); } +void TestDataDevice::StartDrag(TestDataSource* source, + MockSurface* origin, + uint32_t serial) { + DCHECK(source); + DCHECK(origin); + if (delegate_) + delegate_->StartDrag(source, origin, serial); + wl_client_flush(client_); +} + TestDataOffer* TestDataDevice::OnDataOffer() { wl_resource* data_offer_resource = CreateResourceWithImpl<TestDataOffer>( client_, &wl_data_offer_interface, wl_resource_get_version(resource()), diff --git a/chromium/ui/ozone/platform/wayland/test/test_data_device.h b/chromium/ui/ozone/platform/wayland/test/test_data_device.h index 9ac3357ed53..85ad4af518a 100644 --- a/chromium/ui/ozone/platform/wayland/test/test_data_device.h +++ b/chromium/ui/ozone/platform/wayland/test/test_data_device.h @@ -7,7 +7,10 @@ #include <wayland-server-protocol.h> +#include <cstdint> + #include "base/macros.h" +#include "ui/ozone/platform/wayland/test/mock_surface.h" #include "ui/ozone/platform/wayland/test/server_object.h" struct wl_client; @@ -22,10 +25,21 @@ class TestDataSource; class TestDataDevice : public ServerObject { public: + struct Delegate { + virtual void StartDrag(TestDataSource* source, + MockSurface* origin, + uint32_t serial) = 0; + }; + TestDataDevice(wl_resource* resource, wl_client* client); ~TestDataDevice() override; + void set_delegate(Delegate* delegate) { delegate_ = delegate; } + void SetSelection(TestDataSource* data_source, uint32_t serial); + void StartDrag(TestDataSource* data_source, + MockSurface* origin, + uint32_t serial); TestDataOffer* OnDataOffer(); void OnEnter(uint32_t serial, @@ -41,6 +55,7 @@ class TestDataDevice : public ServerObject { private: TestDataOffer* data_offer_; wl_client* client_ = nullptr; + Delegate* delegate_ = nullptr; DISALLOW_COPY_AND_ASSIGN(TestDataDevice); }; diff --git a/chromium/ui/ozone/platform/wayland/test/test_data_source.cc b/chromium/ui/ozone/platform/wayland/test/test_data_source.cc index 530a2e443c7..4d11ec8e9b1 100644 --- a/chromium/ui/ozone/platform/wayland/test/test_data_source.cc +++ b/chromium/ui/ozone/platform/wayland/test/test_data_source.cc @@ -5,6 +5,8 @@ #include "ui/ozone/platform/wayland/test/test_data_source.h" #include <wayland-server-core.h> + +#include <cstdint> #include <utility> #include "base/bind.h" @@ -51,16 +53,16 @@ void DataSourceDestroy(wl_client* client, wl_resource* resource) { wl_resource_destroy(resource); } -void SetActions(wl_client* client, - wl_resource* resource, - uint32_t dnd_actions) { - NOTIMPLEMENTED(); +void DataSourceSetActions(wl_client* client, + wl_resource* resource, + uint32_t dnd_actions) { + GetUserDataAs<TestDataSource>(resource)->SetActions(dnd_actions); } } // namespace const struct wl_data_source_interface kTestDataSourceImpl = { - DataSourceOffer, DataSourceDestroy, SetActions}; + DataSourceOffer, DataSourceDestroy, DataSourceSetActions}; TestDataSource::TestDataSource(wl_resource* resource) : ServerObject(resource), @@ -70,7 +72,11 @@ TestDataSource::TestDataSource(wl_resource* resource) TestDataSource::~TestDataSource() {} void TestDataSource::Offer(const std::string& mime_type) { - NOTIMPLEMENTED(); + mime_types_.push_back(mime_type); +} + +void TestDataSource::SetActions(uint32_t dnd_actions) { + actions_ |= dnd_actions; } void TestDataSource::ReadData(const std::string& mime_type, diff --git a/chromium/ui/ozone/platform/wayland/test/test_data_source.h b/chromium/ui/ozone/platform/wayland/test/test_data_source.h index 3cfdbff7a69..bcf66c3c26a 100644 --- a/chromium/ui/ozone/platform/wayland/test/test_data_source.h +++ b/chromium/ui/ozone/platform/wayland/test/test_data_source.h @@ -7,10 +7,12 @@ #include <wayland-server-protocol.h> +#include <cstdint> #include <memory> #include <string> #include <vector> +#include "base/callback_forward.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "ui/ozone/platform/wayland/test/server_object.h" @@ -31,15 +33,23 @@ class TestDataSource : public ServerObject { ~TestDataSource() override; void Offer(const std::string& mime_type); + void SetActions(uint32_t dnd_actions); using ReadDataCallback = base::OnceCallback<void(std::vector<uint8_t>&&)>; void ReadData(const std::string& mime_type, ReadDataCallback callback); void OnCancelled(); + std::vector<std::string> mime_types() const { return mime_types_; } + + uint32_t actions() const { return actions_; } + private: const scoped_refptr<base::SequencedTaskRunner> task_runner_; + std::vector<std::string> mime_types_; + uint32_t actions_ = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + DISALLOW_COPY_AND_ASSIGN(TestDataSource); }; diff --git a/chromium/ui/ozone/platform/wayland/test/test_subsurface.cc b/chromium/ui/ozone/platform/wayland/test/test_subsurface.cc index 1609bb2012a..73773c773b1 100644 --- a/chromium/ui/ozone/platform/wayland/test/test_subsurface.cc +++ b/chromium/ui/ozone/platform/wayland/test/test_subsurface.cc @@ -4,7 +4,7 @@ #include "ui/ozone/platform/wayland/test/mock_surface.h" -#include "base/logging.h" +#include "base/notreached.h" namespace wl { diff --git a/chromium/ui/ozone/platform/wayland/test/wayland_test.cc b/chromium/ui/ozone/platform/wayland/test/wayland_test.cc index 2866ab6a07f..67c8f2bbaf2 100644 --- a/chromium/ui/ozone/platform/wayland/test/wayland_test.cc +++ b/chromium/ui/ozone/platform/wayland/test/wayland_test.cc @@ -10,6 +10,7 @@ #include "ui/ozone/platform/wayland/host/wayland_output_manager.h" #include "ui/ozone/platform/wayland/host/wayland_screen.h" #include "ui/ozone/platform/wayland/test/mock_surface.h" +#include "ui/ozone/platform/wayland/test/scoped_wl_array.h" #include "ui/platform_window/platform_window_init_properties.h" #if BUILDFLAG(USE_XKBCOMMON) @@ -66,6 +67,11 @@ void WaylandTest::SetUp() { surface_ = server_.GetObject<wl::MockSurface>(widget_); ASSERT_TRUE(surface_); + // The surface must be activated before buffers are attached. + ActivateSurface(server_.GetObject<wl::MockSurface>(widget_)->xdg_surface()); + + Sync(); + initialized_ = true; } @@ -86,4 +92,33 @@ void WaylandTest::Sync() { server_.Pause(); } +void WaylandTest::SendConfigureEvent(wl::MockXdgSurface* xdg_surface, + int width, + int height, + uint32_t serial, + struct wl_array* states) { + // In xdg_shell_v6+, both surfaces send serial configure event and toplevel + // surfaces send other data like states, heights and widths. + // Please note that toplevel surfaces may not exist if the surface was created + // for the popup role. + if (GetParam() == kXdgShellV6) { + zxdg_surface_v6_send_configure(xdg_surface->resource(), serial); + if (xdg_surface->xdg_toplevel()) { + zxdg_toplevel_v6_send_configure(xdg_surface->xdg_toplevel()->resource(), + width, height, states); + } + } else { + xdg_surface_send_configure(xdg_surface->resource(), serial); + if (xdg_surface->xdg_toplevel()) { + xdg_toplevel_send_configure(xdg_surface->xdg_toplevel()->resource(), + width, height, states); + } + } +} + +void WaylandTest::ActivateSurface(wl::MockXdgSurface* xdg_surface) { + wl::ScopedWlArray state({XDG_TOPLEVEL_STATE_ACTIVATED}); + SendConfigureEvent(xdg_surface, 0, 0, 1, state.get()); +} + } // namespace ui diff --git a/chromium/ui/ozone/platform/wayland/test/wayland_test.h b/chromium/ui/ozone/platform/wayland/test/wayland_test.h index fc8161eb51d..97df3efbade 100644 --- a/chromium/ui/ozone/platform/wayland/test/wayland_test.h +++ b/chromium/ui/ozone/platform/wayland/test/wayland_test.h @@ -24,6 +24,7 @@ namespace wl { class MockSurface; +class MockXdgSurface; } // namespace wl namespace ui { @@ -47,6 +48,18 @@ class WaylandTest : public ::testing::TestWithParam<uint32_t> { void Sync(); protected: + // Sends configure event for the |xdg_surface|. + void SendConfigureEvent(wl::MockXdgSurface* xdg_surface, + int width, + int height, + uint32_t serial, + struct wl_array* states); + + // Sends XDG_TOPLEVEL_STATE_ACTIVATED to the |xdg_surface| with width and + // height set to 0, which results in asking the client to set the width and + // height of the surface. + void ActivateSurface(wl::MockXdgSurface* xdg_surface); + base::test::TaskEnvironment task_environment_; wl::TestWaylandServerThread server_; diff --git a/chromium/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc b/chromium/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc index de10370fe69..acf7594d5e2 100644 --- a/chromium/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc +++ b/chromium/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc @@ -125,7 +125,7 @@ class WaylandBufferManagerTest : public WaylandTest { } } - void CreateDmabufBasedBufferAndSetTerminateExpecation( + void CreateDmabufBasedBufferAndSetTerminateExpectation( bool fail, uint32_t buffer_id, base::ScopedFD fd = base::ScopedFD(), @@ -187,11 +187,14 @@ class WaylandBufferManagerTest : public WaylandTest { } } - std::unique_ptr<WaylandWindow> CreateWindow() { + std::unique_ptr<WaylandWindow> CreateWindow( + PlatformWindowType type = PlatformWindowType::kWindow, + gfx::AcceleratedWidget parent_widget = gfx::kNullAcceleratedWidget) { testing::Mock::VerifyAndClearExpectations(&delegate_); PlatformWindowInitProperties properties; properties.bounds = gfx::Rect(0, 0, 800, 600); - properties.type = PlatformWindowType::kWindow; + properties.type = type; + properties.parent_widget = parent_widget; auto new_window = WaylandWindow::Create(&delegate_, connection_.get(), std::move(properties)); EXPECT_TRUE(new_window); @@ -214,8 +217,8 @@ TEST_P(WaylandBufferManagerTest, CreateDmabufBasedBuffers) { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kDmabufBufferId); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kDmabufBufferId); DestroyBufferAndSetTerminateExpectation(gfx::kNullAcceleratedWidget, kDmabufBufferId, false /*fail*/); } @@ -255,7 +258,7 @@ TEST_P(WaylandBufferManagerTest, VerifyModifiers) { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation( + CreateDmabufBasedBufferAndSetTerminateExpectation( false /*fail*/, kDmabufBufferId, base::ScopedFD(), kDefaultSize, {1}, {2}, {kFormatModiferLinear}, kFourccFormatR8, 1); @@ -304,7 +307,7 @@ TEST_P(WaylandBufferManagerTest, ValidateDataFromGpu) { for (const auto& bad : kBadInputs) { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(0); base::ScopedFD dummy; - CreateDmabufBasedBufferAndSetTerminateExpecation( + CreateDmabufBasedBufferAndSetTerminateExpectation( true /*fail*/, bad.buffer_id, bad.has_file ? MakeFD() : std::move(dummy), bad.size, bad.strides, bad.offsets, bad.modifiers, bad.format, bad.planes_count); @@ -321,25 +324,27 @@ TEST_P(WaylandBufferManagerTest, CreateAndDestroyBuffer) { // id if they haven't been assigned to any surfaces yet. { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(2); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId2); // Can't create buffer with existing id. - CreateDmabufBasedBufferAndSetTerminateExpecation(true /*fail*/, kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(true /*fail*/, + kBufferId2); } // ... impossible to create buffers with the same id if one of them // has already been attached to a surface. { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId1); buffer_manager_gpu_->CommitBuffer(widget, kBufferId1, window_->GetBounds()); - CreateDmabufBasedBufferAndSetTerminateExpecation(true /*fail*/, kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(true /*fail*/, + kBufferId1); } // ... impossible to destroy non-existing buffer. @@ -356,8 +361,8 @@ TEST_P(WaylandBufferManagerTest, CreateAndDestroyBuffer) { // specified. { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId1); buffer_manager_gpu_->CommitBuffer(widget, kBufferId1, window_->GetBounds()); @@ -369,22 +374,22 @@ TEST_P(WaylandBufferManagerTest, CreateAndDestroyBuffer) { // widgets. { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId1); DestroyBufferAndSetTerminateExpectation(widget, kBufferId1, false /*fail*/); } // ... impossible to destroy buffers twice. { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(3); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId1); // Attach to a surface. buffer_manager_gpu_->CommitBuffer(widget, kBufferId1, window_->GetBounds()); // Created non-attached buffer as well. - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId2); DestroyBufferAndSetTerminateExpectation(widget, kBufferId1, false /*fail*/); // Can't destroy the buffer with non-existing id (the manager cleared the @@ -397,8 +402,8 @@ TEST_P(WaylandBufferManagerTest, CreateAndDestroyBuffer) { kBufferId2, true /*fail*/); // Create and destroy non-attached buffer twice. - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kBufferId2); DestroyBufferAndSetTerminateExpectation(gfx::kNullAcceleratedWidget, kBufferId2, false /*fail*/); DestroyBufferAndSetTerminateExpectation(gfx::kNullAcceleratedWidget, @@ -408,7 +413,7 @@ TEST_P(WaylandBufferManagerTest, CreateAndDestroyBuffer) { TEST_P(WaylandBufferManagerTest, CommitBufferNonExistingBufferId) { EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, 1u); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, 1u); // Can't commit for non-existing buffer id. SetTerminateCallbackExpectationAndDestroyChannel(&callback_, true /*fail*/); @@ -421,7 +426,7 @@ TEST_P(WaylandBufferManagerTest, CommitBufferNonExistingBufferId) { TEST_P(WaylandBufferManagerTest, CommitBufferNullWidget) { constexpr uint32_t kBufferId = 1; EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId); // Can't commit for non-existing widget. SetTerminateCallbackExpectationAndDestroyChannel(&callback_, true /*fail*/); @@ -443,8 +448,8 @@ TEST_P(WaylandBufferManagerTest, EnsureCorrectOrderOfCallbacks) { auto* linux_dmabuf = server_.zwp_linux_dmabuf_v1(); EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(2); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId2); Sync(); @@ -551,9 +556,9 @@ TEST_P(WaylandBufferManagerTest, auto* linux_dmabuf = server_.zwp_linux_dmabuf_v1(); EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(3); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId2); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId3); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId3); Sync(); @@ -650,7 +655,149 @@ TEST_P(WaylandBufferManagerTest, DestroyBufferAndSetTerminateExpectation(widget, kBufferId3, false /*fail*/); } -TEST_P(WaylandBufferManagerTest, MultiplePendingPresentationsForSameBuffer) {} +// This test ensures that a discarded presentation feedback sent prior receiving +// results for the previous presentation feedback does not make them +// automatically failed. +TEST_P(WaylandBufferManagerTest, + EnsureDiscardedPresentationDoesNotMakePreviousFeedbacksFailed) { + constexpr uint32_t kBufferId1 = 1; + constexpr uint32_t kBufferId2 = 2; + constexpr uint32_t kBufferId3 = 3; + + // Enable wp_presentation support. + auto* mock_wp_presentation = server_.EnsureWpPresentation(); + ASSERT_TRUE(mock_wp_presentation); + + const gfx::AcceleratedWidget widget = window_->GetWidget(); + const gfx::Rect bounds = gfx::Rect({0, 0}, kDefaultSize); + window_->SetBounds(bounds); + + MockSurfaceGpu mock_surface_gpu(buffer_manager_gpu_.get(), widget_); + + auto* linux_dmabuf = server_.zwp_linux_dmabuf_v1(); + EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(3); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId3); + + Sync(); + + ProcessCreatedBufferResourcesWithExpectation(3u /* expected size */, + false /* fail */); + + auto* mock_surface = server_.GetObject<wl::MockSurface>(widget); + + constexpr uint32_t kNumberOfCommits = 3; + EXPECT_CALL(*mock_surface, Attach(_, _, _)).Times(kNumberOfCommits); + EXPECT_CALL(*mock_surface, Frame(_)).Times(kNumberOfCommits); + EXPECT_CALL(*mock_surface, Commit()).Times(kNumberOfCommits); + + // All the other expectations must come in order. + ::testing::InSequence sequence; + EXPECT_CALL(mock_surface_gpu, + OnSubmission(kBufferId1, gfx::SwapResult::SWAP_ACK)) + .Times(1); + EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0); + + // Commit first buffer + buffer_manager_gpu_->CommitBuffer(widget, kBufferId1, bounds); + + Sync(); + + // Will be sent later. + auto* presentation_callback1 = + mock_wp_presentation->ReleasePresentationCallback(); + + mock_surface->SendFrameCallback(); + + Sync(); + + EXPECT_CALL(mock_surface_gpu, + OnSubmission(kBufferId2, gfx::SwapResult::SWAP_ACK)) + .Times(1); + EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0); + + // Commit second buffer + buffer_manager_gpu_->CommitBuffer(widget, kBufferId2, bounds); + + Sync(); + + // Will be sent later. + auto* presentation_callback2 = + mock_wp_presentation->ReleasePresentationCallback(); + + // Release previous buffer and commit third buffer. + mock_surface->ReleasePrevAttachedBuffer(); + mock_surface->SendFrameCallback(); + + Sync(); + + EXPECT_CALL(mock_surface_gpu, + OnSubmission(kBufferId3, gfx::SwapResult::SWAP_ACK)) + .Times(1); + EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0); + + // Commit third buffer + buffer_manager_gpu_->CommitBuffer(widget, kBufferId3, bounds); + + Sync(); + + mock_surface->ReleasePrevAttachedBuffer(); + + Sync(); + + // Even though WaylandBufferManagerHost stores the previous stores + // presentation feedbacks and waits for their value, the current last one + // mustn't result in the previous marked as failed. Thus, no feedback must be + // actually sent to the MockSurfaceGpu as it's required to send feedbacks in + // order. + EXPECT_CALL(mock_surface_gpu, OnPresentation(_, _)).Times(0); + + mock_wp_presentation->SendPresentationCallbackDiscarded(); + + Sync(); + + // Now, start to send all the previous callbacks. + EXPECT_CALL(mock_surface_gpu, + OnPresentation( + kBufferId1, + ::testing::Field( + &gfx::PresentationFeedback::flags, + ::testing::Eq(gfx::PresentationFeedback::Flags::kVSync)))) + .Times(1); + + mock_wp_presentation->set_presentation_callback(presentation_callback1); + mock_wp_presentation->SendPresentationCallback(); + + Sync(); + + // Now, send the second presentation feedback. It will send both second and + // third feedback that was discarded. + EXPECT_CALL(mock_surface_gpu, + OnPresentation( + kBufferId2, + ::testing::Field( + &gfx::PresentationFeedback::flags, + ::testing::Eq(gfx::PresentationFeedback::Flags::kVSync)))) + .Times(1); + EXPECT_CALL( + mock_surface_gpu, + OnPresentation( + kBufferId3, + ::testing::Field( + &gfx::PresentationFeedback::flags, + ::testing::Eq(gfx::PresentationFeedback::Flags::kFailure)))) + .Times(1); + + mock_wp_presentation->set_presentation_callback(presentation_callback2); + mock_wp_presentation->SendPresentationCallback(); + + Sync(); + + DestroyBufferAndSetTerminateExpectation(widget, kBufferId1, false /*fail*/); + DestroyBufferAndSetTerminateExpectation(widget, kBufferId2, false /*fail*/); + DestroyBufferAndSetTerminateExpectation(widget, kBufferId3, false /*fail*/); +} TEST_P(WaylandBufferManagerTest, TestCommitBufferConditions) { constexpr uint32_t kDmabufBufferId = 1; @@ -662,8 +809,8 @@ TEST_P(WaylandBufferManagerTest, TestCommitBufferConditions) { auto* linux_dmabuf = server_.zwp_linux_dmabuf_v1(); EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kDmabufBufferId); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kDmabufBufferId); // Part 1: the surface mustn't have a buffer attached until // zwp_linux_buffer_params_v1_send_created is called. Instead, the buffer must @@ -694,8 +841,8 @@ TEST_P(WaylandBufferManagerTest, TestCommitBufferConditions) { // sent by the server. EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, - kDmabufBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kDmabufBufferId2); ProcessCreatedBufferResourcesWithExpectation(1u /* expected size */, false /* fail */); @@ -726,6 +873,77 @@ TEST_P(WaylandBufferManagerTest, TestCommitBufferConditions) { false /*fail*/); } +// Tests the surface does not have buffers attached until it's configured at +// least once. +TEST_P(WaylandBufferManagerTest, TestCommitBufferConditionsAckConfigured) { + constexpr uint32_t kDmabufBufferId = 1; + + // Exercise three window types that create different windows - toplevel, popup + // and subsurface. + std::vector<PlatformWindowType> window_types{PlatformWindowType::kWindow, + PlatformWindowType::kPopup, + PlatformWindowType::kTooltip}; + + for (const auto& type : window_types) { + // If the type is not kWindow, provide default created window as parent of + // the newly created window. + auto temp_window = CreateWindow(type, type != PlatformWindowType::kWindow + ? widget_ + : gfx::kNullAcceleratedWidget); + auto widget = temp_window->GetWidget(); + + // Subsurface doesn't have an interface for sending configure events. + // Thus, WaylandSubsurface notifies the manager that the window is + // activated upon creation of the subsurface. Skip calling Show() and call + // later then. + if (type != PlatformWindowType::kTooltip) + temp_window->Show(false); + + Sync(); + + auto* mock_surface = server_.GetObject<wl::MockSurface>(widget); + + auto* linux_dmabuf = server_.zwp_linux_dmabuf_v1(); + EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(1); + + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, + kDmabufBufferId); + + Sync(); + + ProcessCreatedBufferResourcesWithExpectation(1u /* expected size */, + false /* fail */); + + EXPECT_CALL(*mock_surface, Attach(_, _, _)).Times(0); + EXPECT_CALL(*mock_surface, Frame(_)).Times(0); + EXPECT_CALL(*mock_surface, Commit()).Times(0); + + buffer_manager_gpu_->CommitBuffer(widget, kDmabufBufferId, + window_->GetBounds()); + Sync(); + + if (type != PlatformWindowType::kTooltip) { + DCHECK(mock_surface->xdg_surface()); + ActivateSurface(mock_surface->xdg_surface()); + } else { + // See the comment near Show() call above. + temp_window->Show(false); + } + + EXPECT_CALL(*mock_surface, Attach(_, _, _)).Times(1); + EXPECT_CALL(*mock_surface, Frame(_)).Times(1); + EXPECT_CALL(*mock_surface, Commit()).Times(1); + + Sync(); + + temp_window.reset(); + DestroyBufferAndSetTerminateExpectation(widget, kDmabufBufferId, + false /*fail*/); + + Sync(); + } +} + // The buffer that is not originally attached to any of the surfaces, // must be attached when a commit request comes. Also, it must setup a buffer // release listener and OnSubmission must be called for that buffer if it is @@ -743,7 +961,7 @@ TEST_P(WaylandBufferManagerTest, AnonymousBufferAttachedAndReleased) { auto* linux_dmabuf = server_.zwp_linux_dmabuf_v1(); EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId1); Sync(); @@ -776,7 +994,7 @@ TEST_P(WaylandBufferManagerTest, AnonymousBufferAttachedAndReleased) { // Now synchronously create a second buffer and commit it. The release // callback must be setup and OnSubmission must be called. EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId2); Sync(); @@ -804,7 +1022,7 @@ TEST_P(WaylandBufferManagerTest, AnonymousBufferAttachedAndReleased) { // released once the buffer is committed and processed (that is, it must be // able to setup a buffer release callback). EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId3); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId3); Sync(); @@ -845,7 +1063,7 @@ TEST_P(WaylandBufferManagerTest, DestroyBufferForDestroyedWindow) { auto widget = temp_window->GetWidget(); EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId); Sync(); @@ -866,7 +1084,7 @@ TEST_P(WaylandBufferManagerTest, DestroyedWindowNoSubmissionSingleBuffer) { auto bounds = temp_window->GetBounds(); EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId); ProcessCreatedBufferResourcesWithExpectation(1u /* expected size */, false /* fail */); @@ -894,11 +1112,22 @@ TEST_P(WaylandBufferManagerTest, DestroyedWindowNoSubmissionMultipleBuffers) { constexpr uint32_t kBufferId2 = 2; auto temp_window = CreateWindow(); + temp_window->Show(false); + + Sync(); + auto widget = temp_window->GetWidget(); auto bounds = temp_window->GetBounds(); + auto* mock_surface = server_.GetObject<wl::MockSurface>(widget); + ASSERT_TRUE(mock_surface); + + ActivateSurface(mock_surface->xdg_surface()); + + Sync(); + EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId1); ProcessCreatedBufferResourcesWithExpectation(1u /* expected size */, false /* fail */); @@ -915,13 +1144,12 @@ TEST_P(WaylandBufferManagerTest, DestroyedWindowNoSubmissionMultipleBuffers) { Sync(); - auto* mock_surface = server_.GetObject<wl::MockSurface>(widget); mock_surface->SendFrameCallback(); Sync(); EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId2); ProcessCreatedBufferResourcesWithExpectation(1u /* expected size */, false /* fail */); @@ -966,8 +1194,8 @@ TEST_P(WaylandBufferManagerTest, SubmitSameBufferMultipleTimes) { auto* linux_dmabuf = server_.zwp_linux_dmabuf_v1(); EXPECT_CALL(*linux_dmabuf, CreateParams(_, _, _)).Times(2); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId1); - CreateDmabufBasedBufferAndSetTerminateExpecation(false /*fail*/, kBufferId2); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId1); + CreateDmabufBasedBufferAndSetTerminateExpectation(false /*fail*/, kBufferId2); Sync(); |